Home > Blockchain >  What is causing my Python spectrogram maker to slow down?
What is causing my Python spectrogram maker to slow down?

Time:12-17

I have made a short python program to loop through around 9000 audio files, trim off the edges to make them around 2.5 seconds long, convert them into spectrograms, and save them as PNGs in their appropriate folders. It gets the paths of the audio (.wav) files from a .csv file.

The problem is the program starts out very quickly, taking around 0.5 seconds for each spectrogram, but begins slowing down quickly. I don't think this is caused by my computer thermal throttling, because it's constantly slowing down; it doesn't settle in at a certain amount of time per spectrogram. Each spectrogram takes longer than the last. Additionally, if I stop the program and then start it very quickly, it resets to around 0.5 seconds per spectrogram.

Here is my code:


import csv
import time
import wave
import matplotlib.pyplot as plt
import numpy as np

# Function to make a spectrogram from a given wav file, and save it as a png to a given location

def makeSpectrogram(soundFilePath, fileSavePath, counter, station, species):
soundFile = wave.open(soundFilePath, "r")

    # Get frame rate and number of frames
    frameRate = soundFile.getframerate()
    numFrames = soundFile.getnframes()
    
    # Extract the raw audio data
    rawAudio = soundFile.readframes(numFrames)
    
    # Convert the raw audio data to a NumPy array
    audio = np.frombuffer(rawAudio, dtype=np.int16)
    
    # Trim the audio
    reductionFactor = int((len(audio) - 500000) / 2)
    audio = audio[reductionFactor:-reductionFactor]
    
    # Create the spectrogram
    spectrogram = plt.specgram(audio, Fs=frameRate, NFFT=4096, noverlap=2048, cmap="Greys", )
    
    # Limit the y-axis to certain frequencies
    plt.ylim([15000, 90000])
    
    # Save the spectrogram as an image
    plt.savefig(f"{fileSavePath}spectrogram_station{station}_{species}_{counter}.png", dpi=250)
    
    # Close the file
    soundFile.close()

# Open CSV

csvFile = open("/Users/myname/Desktop/spreadsheet.csv", "r")
csv = csv.reader(csvFile)

# List from CSV

csvList = list(csv)

# Start timer

start = time.time()

# Loop through files in CSV, make spectrogram for each

for i in range(510, len(csvList)):
    row = csvList\[i\]
    wavPath = row\[0\]
    station = row\[4\]
    species = row\[1\]
    try:
        start2 = time.time()
        makeSpectrogram(wavPath, f"/Users/myname/Desktop/Spectrograms/Station {station}/uncropped/", i, station, species)
        print(f"Spectrogram complete: {wavPath}")
        end = time.time()
        print(f"Time Elapsed: {end - start} s")
        end2 = time.time()
        print(f"Render Time: {end2 - start2}")
        print(f"Estimated Time Remaining: {((end2 - start2) \* (len(csvList) - i)) / 3600} hrs")
        print(f"Spectrograms Completed: {i}\\n")
    except:
        print(f"File not found: {wavPath}")
        end = time.time()
        print(f"Time Elapsed: {end - start} s")
        print(f"Spectrograms Completed: {i}\\n")

csvFile.close()

I was initially missing soundFile.close() at the end of the makeSpectrogram function, so I though Python was leaving every file open and the program was slowing down due to a buildup of open files (I'm not actually sure if this is how Python handles files), but upon adding that line nothing changed. After making around 500 spectrograms, it's taking 10.8 seconds each, and still increasing.

CodePudding user response:

I didn't spot a problem. But assuming some resource is leaking the the background, you could create subprocesses to do the work and kill them off periodically. This is most easily done with a multiprocessing.Pool. This example leaves out the actual processing function and just focuses on the pieces that need to change. You should get a boost from multiprocessing anyway, and hopefully whatever is slowing you down is thwarted.

import multiprocessing as mp

def spectro_runner(params):
    """Multiprocessing worker for spectrogram generation"""
    wavPath, station, species = params
    try:
        start = time.time()
        makeSpectrogram(wavPath, f"/Users/myname/Desktop/Spectrograms/Station {station}/uncropped/", i, station, species)
        print(f"Render time {end-start)s - Spectrogram complete: {wavPath}")
    except OSError:
        print(f"File not found: {wavPath}")
    return wavPath, time.time() - start    
    
def make_spectrograms():
    with open("/Users/myname/Desktop/spreadsheet.csv", "r") as in_file:
        # dump first 509 lines
        for i in range(509):
            next(in_file)
        # get (wavPath, station, species)
        params = [(row[0], row[4], row[1]) for row in csv.reader(in_file)]
    # wild guess, use half the cpus
    cores = mp.cpu_count()/2
    start = time.time()
    with mp.Pool(cores) as pool:
        for wavPath, delta in pool.imap_unordered(spectro_runner, params, chunksize=4,
                maxtasksperchild=4):
            print(f"{time.time()-start}s: Render time {delta)s - Spectrogram complete: {wavPath}")

if __name__ == "__main__:
    make_spectrogram()

CodePudding user response:

Thanks to user mozway for this solution.

I needed to use plt.close(fig) after creating every spectrogram, or else the figures would remain open in the background, or at least that's how I understand it. Here is my slightly modified makeSpectrogram function:

def makeSpectrogram(soundFilePath, fileSavePath, counter, station, species):
    with wave.open(soundFilePath, "r") as soundFile:
        # Get frame rate and number of frames
        frameRate = soundFile.getframerate()
        numFrames = soundFile.getnframes()

        # Extract the raw audio data
        rawAudio = soundFile.readframes(numFrames)

        # Convert the raw audio data to a NumPy array
        audio = np.frombuffer(rawAudio, dtype=np.int16)

        # Trim the audio
        reductionFactor = int((len(audio) - 500000) / 2)
        audio = audio[reductionFactor:-reductionFactor]

        # Create the spectrogram
        spectrogram = plt.specgram(audio, Fs=frameRate, NFFT=4096, noverlap=2048, cmap="Greys", )

        # Limit the y-axis to certain frequencies
        plt.ylim([15000, 90000])

        # Save the spectrogram as an image
        plt.savefig(f"{fileSavePath}spectrogram_station{station}_{species}_{counter}.png", dpi=250)

        # Close the spectrogram
        plt.close()

Everything is working perfectly now. The current time estimation is 1 hour, rather than the previous 65 hours after around 1000 spectrogram generations. Thanks for all the helpful responses.

  • Related