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.