From 435e96c7edc0c2410f292e0ab5928a25d5a4c8f4 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Thu, 26 Sep 2024 17:46:03 +0200 Subject: [PATCH 01/20] Chore: Refactor to pull out audio loading --- tmp.py | 24 +++++++++++++++ viewer.py | 90 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 72 insertions(+), 42 deletions(-) create mode 100644 tmp.py diff --git a/tmp.py b/tmp.py new file mode 100644 index 0000000..6a4a3e2 --- /dev/null +++ b/tmp.py @@ -0,0 +1,24 @@ +import wave + +import wave +import numpy as np + +# Open the WAV file +with wave.open('Audio_fs_48000_Chans_2_1725816438.wav', 'rb') as wav_file: + # Read parameters + num_channels = wav_file.getnchannels() + sample_width = wav_file.getsampwidth() + frame_rate = wav_file.getframerate() + num_frames = wav_file.getnframes() + + # Read audio data + audio_data = wav_file.readframes(num_frames) + + # Convert byte data to numpy array + audio_array = np.frombuffer(audio_data, dtype=np.int16) + +# Reshape if stereo +if num_channels == 2: + audio_array = audio_array.reshape(-1, 2) + +print(audio_array) diff --git a/viewer.py b/viewer.py index faee3f9..bd79293 100644 --- a/viewer.py +++ b/viewer.py @@ -113,45 +113,37 @@ def __init__(self, *args, **kwargs): def update_image(self): - # First check if path exists - if not(os.path.exists(self.file_path_label.text())): - self.show_popup("File Not Found") - return - - # Then load it - data, fs = librosa.load(self.file_path_label.text(),sr=None) - - f, t, self.Sxx = signal.spectrogram(data, - nperseg=int(self.fft_size.getInputText()), - noverlap=int(self.fft_overlap.getInputText()), - fs=fs, - mode="psd", - return_onesided=True - ) - - # Split the array into groups - tmp = np.abs(self.Sxx) - averaging_count = int(self.integration_count.getInputText()) - groups = np.array_split(self.Sxx, np.arange(averaging_count, tmp.shape[1], averaging_count), axis=1) - tmp =np.array([group.sum(axis=1) for group in groups]).T - - - self.Sxx_proc = tmp - - freq_max = fs/(2*1000) - time_max = len(data)*1/fs - - print(freq_max) - - # Plot the spectrogram - self.axes.clear() - self.axes.imshow(20*np.log10(self.Sxx_proc), origin='lower', aspect="auto", extent=[0,time_max, 0, freq_max]) - self.axes.set_xlabel('Time [s]') - self.axes.set_ylabel('Frequency [KHz]') - self.axes.set_title('Spectrogram of \n' + self.file_path_label.text()) - - # Update the canvas to display the plot - self.spectrogram_canvas.draw() + if not(self.load_audio_data()): + self.show_popup("File Not Found") + return False + + f, t, self.Sxx = signal.spectrogram(self.data[1,:], + nperseg=int(self.fft_size.getInputText()), + noverlap=int(self.fft_overlap.getInputText()), + fs=self.sample_rate_hz, + mode="psd", + return_onesided=True + ) + + # Split the array into groups + tmp = np.abs(self.Sxx) + averaging_count = int(self.integration_count.getInputText()) + groups = np.array_split(self.Sxx, np.arange(averaging_count, tmp.shape[1], averaging_count), axis=1) + tmp =np.array([group.sum(axis=1) for group in groups]).T + self.Sxx_proc = tmp + + self.freq_max_khz = self.sample_rate_hz/(2*1000) + time_max_s = len(self.data[1,:])*1/self.sample_rate_hz + + # Plot the spectrogram + self.axes.clear() + self.axes.imshow(20*np.log10(self.Sxx_proc), origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) + self.axes.set_xlabel('Time [s]') + self.axes.set_ylabel('Frequency [KHz]') + self.axes.set_title('Spectrogram of \n' + self.file_path_label.text()) + + # Update the canvas to display the plot + self.spectrogram_canvas.draw() def on_click(self, event): if event.inaxes == self.axes: # Ensure the click is inside the spectrogram plot @@ -160,14 +152,15 @@ def on_click(self, event): if self.Sxx is not None: # Get the column corresponding to the clicked x-coordinate (time index) column_data = 20 * np.log10(np.abs(self.Sxx_proc[:, x_click])) + freq_axis = np.linspace(1,len(column_data),len(column_data))*self.freq_max_khz/len(column_data) # Plot the column data (frequency vs. intensity) self.spectrum_axes.clear() - self.spectrum_axes.plot(column_data) - self.spectrum_axes.set_xlabel('Frequency [Hz]') + self.spectrum_axes.plot(freq_axis,column_data) + self.spectrum_axes.set_xlabel('Frequency [KHz]') self.spectrum_axes.set_ylabel('Intensity [Arb dB]') self.spectrum_axes.set_title(f'Spectrum Sample at Time of {x_click} Seconds of \n ' + self.file_path_label.text()) - + # Update the column plot canvas self.spectrum_canvas.draw() @@ -199,6 +192,19 @@ def save_spectrum_image(self): # Save the current figure as an image self.spectrum_figure.savefig(save_path) + def load_audio_data(self): + + # First check if path exists + if not(os.path.exists(self.file_path_label.text())): + return False + + # Then load it + self.data, self.sample_rate_hz \ + = librosa.load(self.file_path_label.text(),sr=None, mono=False) + + return True + + def play_audio(self): # First check if path exists From f6f245f179d3bed5e8a837bb3022bfdb2885efce Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 29 Sep 2024 10:15:27 +0200 Subject: [PATCH 02/20] Chore: Porting to new sft scipy --- viewer.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/viewer.py b/viewer.py index bd79293..a93ae18 100644 --- a/viewer.py +++ b/viewer.py @@ -76,11 +76,14 @@ def __init__(self, *args, **kwargs): # Inputs self.fft_size = HorizontalLabelInput("FFT Size", "256") - self.fft_overlap = HorizontalLabelInput("FFT Overlap","0") + self.fft_hop = HorizontalLabelInput("FFT Hop","256") self.integration_count = HorizontalLabelInput("Integration Count","1") + self.spectrum_mode = QComboBox() + self.spectrum_mode.addItem("") + input_layout.addWidget(self.fft_size) - input_layout.addWidget(self.fft_overlap) + input_layout.addWidget(self.fft_hop) input_layout.addWidget(self.integration_count) # Buttons @@ -117,13 +120,13 @@ def update_image(self): self.show_popup("File Not Found") return False - f, t, self.Sxx = signal.spectrogram(self.data[1,:], - nperseg=int(self.fft_size.getInputText()), - noverlap=int(self.fft_overlap.getInputText()), - fs=self.sample_rate_hz, - mode="psd", - return_onesided=True - ) + SFT = signal.ShortTimeFFT( win=np.ones(int(self.fft_size.getInputText())), + mfft=int(self.fft_size.getInputText()), + hop=int(self.fft_hop.getInputText()), + fs=self.sample_rate_hz, + fft_mode="onesided" + ) + self.Sxx = SFT.stft(self.data[1,:]) # Split the array into groups tmp = np.abs(self.Sxx) @@ -137,7 +140,7 @@ def update_image(self): # Plot the spectrogram self.axes.clear() - self.axes.imshow(20*np.log10(self.Sxx_proc), origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) + self.axes.imshow(20*np.log10(np.abs(self.Sxx_proc)), origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) self.axes.set_xlabel('Time [s]') self.axes.set_ylabel('Frequency [KHz]') self.axes.set_title('Spectrogram of \n' + self.file_path_label.text()) From 363e917b3f1de9bbd23e400bc461eda0b7a35fc8 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 29 Sep 2024 10:18:31 +0200 Subject: [PATCH 03/20] Chore: Moving spectrogram generation into its own function --- viewer.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/viewer.py b/viewer.py index a93ae18..d54bf29 100644 --- a/viewer.py +++ b/viewer.py @@ -120,14 +120,8 @@ def update_image(self): self.show_popup("File Not Found") return False - SFT = signal.ShortTimeFFT( win=np.ones(int(self.fft_size.getInputText())), - mfft=int(self.fft_size.getInputText()), - hop=int(self.fft_hop.getInputText()), - fs=self.sample_rate_hz, - fft_mode="onesided" - ) - self.Sxx = SFT.stft(self.data[1,:]) - + self.generate_spectrogram_data() + # Split the array into groups tmp = np.abs(self.Sxx) averaging_count = int(self.integration_count.getInputText()) @@ -228,6 +222,17 @@ def show_popup(self, text): msg.setStandardButtons(QMessageBox.StandardButton.Ok) msg.exec() + def generate_spectrogram_data(self): + + SFT = signal.ShortTimeFFT( win=np.ones(int(self.fft_size.getInputText())), + mfft=int(self.fft_size.getInputText()), + hop=int(self.fft_hop.getInputText()), + fs=self.sample_rate_hz, + fft_mode="onesided" + ) + + self.Sxx = SFT.stft(self.data[1,:]) + app = QApplication([]) window = MainWindow() app.exec() \ No newline at end of file From 95615f1e6d86774d5eb86e7bc0e5359c2a48cef9 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 29 Sep 2024 13:15:24 +0200 Subject: [PATCH 04/20] Feat: Adding window selection --- Components/HorizontalLabelComboBox.py | 21 ++++++++++++++ .../HorizontalLabelComboBox.cpython-312.pyc | Bin 0 -> 1431 bytes viewer.py | 26 +++++++++++++++--- 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 Components/HorizontalLabelComboBox.py create mode 100644 Components/__pycache__/HorizontalLabelComboBox.cpython-312.pyc diff --git a/Components/HorizontalLabelComboBox.py b/Components/HorizontalLabelComboBox.py new file mode 100644 index 0000000..82356ff --- /dev/null +++ b/Components/HorizontalLabelComboBox.py @@ -0,0 +1,21 @@ +from PyQt6.QtGui import * +from PyQt6.QtWidgets import * +from PyQt6.QtCore import * + + +class HorizontalLabelComboBox(QWidget): + def __init__(self, description, items, parent=None): + super().__init__(parent) + layout = QHBoxLayout(self) + + self.description_label = QLabel(description) + self.combo_box = QComboBox(self) + self.combo_box.addItems(items) + + layout.addWidget(self.description_label) + layout.addWidget(self.combo_box) + + self.setLayout(layout) + + def getInputText(self): + return self.combo_box.currentText() diff --git a/Components/__pycache__/HorizontalLabelComboBox.cpython-312.pyc b/Components/__pycache__/HorizontalLabelComboBox.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11175962a14f375f315bed31fc7fb427f4e8a351 GIT binary patch literal 1431 zcma)6&1(}u6o0eXO(#v#P(RzEZT&!96)U15BH|ZRL5e{|2!Ua{yOqREHqK0>DN%YT zn1Z4gX~lycmEvFG#nMR2kb|HXZ$eenlQY?D62yW7dGnk1@q2IQy~(>|vKQoHJC;j<2snS*nZikxf#F6E$^x|>3SFa z!lZw}pSJZbZbhwr>t_+bGqeD_ZJal%@K}CWHL@gaY9Zj^he8!X?R}m11a|3*LTwdD zI}eYy?d>ROk9M^AM2I_kKGne?s%jxWf~ubH5A~hB3VGZ)h3G$`{!u|!6y!IDT%EnD zcRVWa22Gf~!eHf+O9dt*SIlujL_B?2Uiww5;+MIIrkkx0{f^6Qnk(^~UnHLDj)gq?;n*Dv=KApYET*|WJ)GG4qC70ddekscfrDIuHQpqn$5|-UynvRqz zG$z?*MmKT@Z$b?TyC=oZS!m$oe9tl-Slm{}qmBL@i#JyKch{nyckO>2U)goMrq}Us zV`$H-OE1Q1`lG%zoOseV-?xfKmhs3dqmK7|#PT$t7`Chqjx7(4)$!4_j_&2~^Wi!^ z@G*#cdhY`sUYD7{ZcVbn@*TEj|D*&9a-#D}``5~ckFQI=)WX++s*f@?+9x*=x64#r z_zicOn}}|bI;QfC(g9^o$ktscmdZ{0%-)TztJ`afqSz|MEUcwAKY1|!U^Qhfr_7bq zq4)R@9h8=!f~Xp}qE&F^!1WSR@SU=!aDtG#Wy=dJ%|6i`s+MGH6l~Y?2$98ZSD93? z=bFV*_0F3@O$tj&@pBHoYjI=b#}G0O2dS0QglUNQwMv?wJd)<)<(x=`hhSKlNQ8=u zK6OP;I?y-C-#|d5?&mi?6O?_Hs=ql^3mcN+YXl+GfWZdrY{1aBXbK%(y!{>I){OlF D0VhKq literal 0 HcmV?d00001 diff --git a/viewer.py b/viewer.py index d54bf29..236ba96 100644 --- a/viewer.py +++ b/viewer.py @@ -16,6 +16,7 @@ import os from Components.HorizontalLabelInput import HorizontalLabelInput +from Components.HorizontalLabelComboBox import HorizontalLabelComboBox class MainWindow(QMainWindow): @@ -79,12 +80,12 @@ def __init__(self, *args, **kwargs): self.fft_hop = HorizontalLabelInput("FFT Hop","256") self.integration_count = HorizontalLabelInput("Integration Count","1") - self.spectrum_mode = QComboBox() - self.spectrum_mode.addItem("") + self.fft_window = HorizontalLabelComboBox("FFT Window", ["Rect", "Hanning", "Hamming", "Blackman"]) input_layout.addWidget(self.fft_size) input_layout.addWidget(self.fft_hop) input_layout.addWidget(self.integration_count) + input_layout.addWidget(self.fft_window) # Buttons @@ -121,7 +122,7 @@ def update_image(self): return False self.generate_spectrogram_data() - + # Split the array into groups tmp = np.abs(self.Sxx) averaging_count = int(self.integration_count.getInputText()) @@ -222,9 +223,26 @@ def show_popup(self, text): msg.setStandardButtons(QMessageBox.StandardButton.Ok) msg.exec() + def get_selected_window(self): + + selected_window = self.fft_window.getInputText() + window_length = int(self.fft_size.getInputText()) + window = np.ones(window_length) + + if selected_window == "Blackman": + window = np.blackman(window_length) + elif selected_window == "Hamming": + window = np.hamming(window_length) + elif selected_window == "Hanning": + window = np.hanning(window_length) + + return window + def generate_spectrogram_data(self): + + window = self.get_selected_window() - SFT = signal.ShortTimeFFT( win=np.ones(int(self.fft_size.getInputText())), + SFT = signal.ShortTimeFFT(win=window, mfft=int(self.fft_size.getInputText()), hop=int(self.fft_hop.getInputText()), fs=self.sample_rate_hz, From c76c81e1459427ed8c36994e48b3bff41673fb66 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 29 Sep 2024 13:22:00 +0200 Subject: [PATCH 05/20] Chore: refactor to plot spectrum with spectrogram --- viewer.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/viewer.py b/viewer.py index 236ba96..197d422 100644 --- a/viewer.py +++ b/viewer.py @@ -90,7 +90,7 @@ def __init__(self, *args, **kwargs): # Buttons self.plot_image_button = QPushButton("Plot Image") - self.plot_image_button.clicked.connect(lambda: self.update_image()) + self.plot_image_button.clicked.connect(lambda: self.update_images()) input_layout.addWidget(self.plot_image_button) self.save_image_button = QPushButton("Save Spectrogram") @@ -115,7 +115,7 @@ def __init__(self, *args, **kwargs): self.Sxx = None # Store spectrogram data self.show() - def update_image(self): + def update_images(self): if not(self.load_audio_data()): self.show_popup("File Not Found") @@ -130,6 +130,11 @@ def update_image(self): tmp =np.array([group.sum(axis=1) for group in groups]).T self.Sxx_proc = tmp + self.update_spectrogram_image() + self.update_spectrum_image(0) + + def update_spectrogram_image(self): + self.freq_max_khz = self.sample_rate_hz/(2*1000) time_max_s = len(self.data[1,:])*1/self.sample_rate_hz @@ -140,16 +145,13 @@ def update_image(self): self.axes.set_ylabel('Frequency [KHz]') self.axes.set_title('Spectrogram of \n' + self.file_path_label.text()) - # Update the canvas to display the plot self.spectrogram_canvas.draw() - - def on_click(self, event): - if event.inaxes == self.axes: # Ensure the click is inside the spectrogram plot - x_click = int(event.xdata) - - if self.Sxx is not None: + + def update_spectrum_image(self, spectrum_column_index): + + if self.Sxx is not None: # Get the column corresponding to the clicked x-coordinate (time index) - column_data = 20 * np.log10(np.abs(self.Sxx_proc[:, x_click])) + column_data = 20 * np.log10(np.abs(self.Sxx_proc[:, spectrum_column_index])) freq_axis = np.linspace(1,len(column_data),len(column_data))*self.freq_max_khz/len(column_data) # Plot the column data (frequency vs. intensity) @@ -157,10 +159,15 @@ def on_click(self, event): self.spectrum_axes.plot(freq_axis,column_data) self.spectrum_axes.set_xlabel('Frequency [KHz]') self.spectrum_axes.set_ylabel('Intensity [Arb dB]') - self.spectrum_axes.set_title(f'Spectrum Sample at Time of {x_click} Seconds of \n ' + self.file_path_label.text()) + self.spectrum_axes.set_title(f'Spectrum Sample at Time of {spectrum_column_index} Seconds of \n ' + self.file_path_label.text()) # Update the column plot canvas self.spectrum_canvas.draw() + + def on_click(self, event): + if event.inaxes == self.axes: # Ensure the click is inside the spectrogram plot + spectrum_column_index = int(event.xdata) + self.update_spectrum_image(spectrum_column_index) def load_file(self): # Open a file dialog to let the user select a file From f38c1f2cc93b5c0f823190cbcdcef2436516693c Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 29 Sep 2024 16:57:25 +0200 Subject: [PATCH 06/20] Fix: For selection of spectogram position --- viewer.py | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/viewer.py b/viewer.py index 197d422..528449b 100644 --- a/viewer.py +++ b/viewer.py @@ -123,13 +123,6 @@ def update_images(self): self.generate_spectrogram_data() - # Split the array into groups - tmp = np.abs(self.Sxx) - averaging_count = int(self.integration_count.getInputText()) - groups = np.array_split(self.Sxx, np.arange(averaging_count, tmp.shape[1], averaging_count), axis=1) - tmp =np.array([group.sum(axis=1) for group in groups]).T - self.Sxx_proc = tmp - self.update_spectrogram_image() self.update_spectrum_image(0) @@ -140,7 +133,7 @@ def update_spectrogram_image(self): # Plot the spectrogram self.axes.clear() - self.axes.imshow(20*np.log10(np.abs(self.Sxx_proc)), origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) + self.axes.imshow(self.Sxx, origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) self.axes.set_xlabel('Time [s]') self.axes.set_ylabel('Frequency [KHz]') self.axes.set_title('Spectrogram of \n' + self.file_path_label.text()) @@ -150,8 +143,9 @@ def update_spectrogram_image(self): def update_spectrum_image(self, spectrum_column_index): if self.Sxx is not None: + # Get the column corresponding to the clicked x-coordinate (time index) - column_data = 20 * np.log10(np.abs(self.Sxx_proc[:, spectrum_column_index])) + column_data = self.Sxx[:, spectrum_column_index] freq_axis = np.linspace(1,len(column_data),len(column_data))*self.freq_max_khz/len(column_data) # Plot the column data (frequency vs. intensity) @@ -166,7 +160,17 @@ def update_spectrum_image(self, spectrum_column_index): def on_click(self, event): if event.inaxes == self.axes: # Ensure the click is inside the spectrogram plot - spectrum_column_index = int(event.xdata) + + # Check where one clicked + x_axis_timestamp = int(event.xdata) + _, x_max = self.axes.get_xlim() + + # Convert the time stamp to the index in spectorgram + spectrogram_shape_tuple = np.shape(self.Sxx) + averaging_count = int(self.integration_count.getInputText()) + spectrum_column_index = int(np.floor(spectrogram_shape_tuple[1]*x_axis_timestamp/x_max)) + + # Plot that part of the spectrogram self.update_spectrum_image(spectrum_column_index) def load_file(self): @@ -247,17 +251,32 @@ def get_selected_window(self): def generate_spectrogram_data(self): + # Generate spectrogram game window = self.get_selected_window() - SFT = signal.ShortTimeFFT(win=window, mfft=int(self.fft_size.getInputText()), hop=int(self.fft_hop.getInputText()), fs=self.sample_rate_hz, fft_mode="onesided" ) - self.Sxx = SFT.stft(self.data[1,:]) + + # Process data further + self.apply_integration() + + # Convert to logrithm + self.Sxx = 20*np.log10(np.abs(self.Sxx)) + + print(np.shape(self.Sxx)) + def apply_integration(self): + + # Split the array into groups + tmp = np.abs(self.Sxx) + averaging_count = int(self.integration_count.getInputText()) + groups = np.array_split(self.Sxx, np.arange(averaging_count, tmp.shape[1], averaging_count), axis=1) + self.Sxx = np.array([group.sum(axis=1) for group in groups]).T + app = QApplication([]) window = MainWindow() app.exec() \ No newline at end of file From c70f0448bc8c8e9df8f0a5f108115e8e612495be Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 29 Sep 2024 20:14:58 +0200 Subject: [PATCH 07/20] Feat: Adding ability to plot amp and phase --- viewer.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/viewer.py b/viewer.py index 528449b..635d53c 100644 --- a/viewer.py +++ b/viewer.py @@ -81,11 +81,13 @@ def __init__(self, *args, **kwargs): self.integration_count = HorizontalLabelInput("Integration Count","1") self.fft_window = HorizontalLabelComboBox("FFT Window", ["Rect", "Hanning", "Hamming", "Blackman"]) + self.spectrum_mode = HorizontalLabelComboBox("Spectrum Mode", ["Amplitude [dB]", "Amplitude [lin]", "Phase [Rad]", "Phase [Deg]"]) input_layout.addWidget(self.fft_size) input_layout.addWidget(self.fft_hop) input_layout.addWidget(self.integration_count) input_layout.addWidget(self.fft_window) + input_layout.addWidget(self.spectrum_mode) # Buttons @@ -263,11 +265,8 @@ def generate_spectrogram_data(self): # Process data further self.apply_integration() + self.apply_spectrum_mode() - # Convert to logrithm - self.Sxx = 20*np.log10(np.abs(self.Sxx)) - - print(np.shape(self.Sxx)) def apply_integration(self): @@ -276,6 +275,19 @@ def apply_integration(self): averaging_count = int(self.integration_count.getInputText()) groups = np.array_split(self.Sxx, np.arange(averaging_count, tmp.shape[1], averaging_count), axis=1) self.Sxx = np.array([group.sum(axis=1) for group in groups]).T + + def apply_spectrum_mode(self): + + selected_spectrum_mode = self.spectrum_mode.getInputText() + + if selected_spectrum_mode == "Amplitude [dB]": + self.Sxx = 20*np.log10(np.abs(self.Sxx)) + elif selected_spectrum_mode == "Amplitude [lin]": + self.Sxx = np.abs(self.Sxx) + elif selected_spectrum_mode == "Phase [Rad]": + self.Sxx = np.angle(self.Sxx, deg=False) + elif selected_spectrum_mode == "Phase [Deg]": + self.Sxx = np.angle(self.Sxx, deg=True) app = QApplication([]) window = MainWindow() From 9e00d4a845e33d24075418483d71f4db7fc9c056 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 29 Sep 2024 20:23:55 +0200 Subject: [PATCH 08/20] Feat: Maximise on launch --- viewer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer.py b/viewer.py index 635d53c..a7d3c39 100644 --- a/viewer.py +++ b/viewer.py @@ -22,7 +22,7 @@ class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) - self.setWindowTitle("Main Window with Input Box") + self.setWindowTitle("Insert Funny and Quirky Comment") # Central widget and layout central_widget = QWidget() @@ -115,7 +115,7 @@ def __init__(self, *args, **kwargs): # Connect the canvas click event self.spectrogram_canvas.mpl_connect('button_press_event', self.on_click) self.Sxx = None # Store spectrogram data - self.show() + self.showMaximized() def update_images(self): From a67ef4f5881276182572aeb29ec01a86d585c789 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 29 Sep 2024 20:57:14 +0200 Subject: [PATCH 09/20] Feat: Partial implementation of multi channel support --- viewer.py | 51 ++++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/viewer.py b/viewer.py index a7d3c39..c3f53ab 100644 --- a/viewer.py +++ b/viewer.py @@ -135,7 +135,7 @@ def update_spectrogram_image(self): # Plot the spectrogram self.axes.clear() - self.axes.imshow(self.Sxx, origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) + self.axes.imshow(self.Sxx[0], origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) self.axes.set_xlabel('Time [s]') self.axes.set_ylabel('Frequency [KHz]') self.axes.set_title('Spectrogram of \n' + self.file_path_label.text()) @@ -147,7 +147,7 @@ def update_spectrum_image(self, spectrum_column_index): if self.Sxx is not None: # Get the column corresponding to the clicked x-coordinate (time index) - column_data = self.Sxx[:, spectrum_column_index] + column_data = self.Sxx[0][:, spectrum_column_index] freq_axis = np.linspace(1,len(column_data),len(column_data))*self.freq_max_khz/len(column_data) # Plot the column data (frequency vs. intensity) @@ -168,7 +168,7 @@ def on_click(self, event): _, x_max = self.axes.get_xlim() # Convert the time stamp to the index in spectorgram - spectrogram_shape_tuple = np.shape(self.Sxx) + spectrogram_shape_tuple = np.shape(self.Sxx[0]) averaging_count = int(self.integration_count.getInputText()) spectrum_column_index = int(np.floor(spectrogram_shape_tuple[1]*x_axis_timestamp/x_max)) @@ -213,6 +213,8 @@ def load_audio_data(self): self.data, self.sample_rate_hz \ = librosa.load(self.file_path_label.text(),sr=None, mono=False) + self.num_channels = np.shape(self.data)[0] + return True @@ -254,40 +256,43 @@ def get_selected_window(self): def generate_spectrogram_data(self): # Generate spectrogram game + self.Sxx = {} window = self.get_selected_window() - SFT = signal.ShortTimeFFT(win=window, - mfft=int(self.fft_size.getInputText()), - hop=int(self.fft_hop.getInputText()), - fs=self.sample_rate_hz, - fft_mode="onesided" - ) - self.Sxx = SFT.stft(self.data[1,:]) - - # Process data further - self.apply_integration() - self.apply_spectrum_mode() + + for i in range(self.num_channels): + SFT = signal.ShortTimeFFT(win=window, + mfft=int(self.fft_size.getInputText()), + hop=int(self.fft_hop.getInputText()), + fs=self.sample_rate_hz, + fft_mode="onesided" + ) + self.Sxx[i] = SFT.stft(self.data[i,:]) + + # Process data further + self.apply_integration(i) + self.apply_spectrum_mode(i) - def apply_integration(self): + def apply_integration(self, channel_index): # Split the array into groups - tmp = np.abs(self.Sxx) + tmp = np.abs(self.Sxx[channel_index]) averaging_count = int(self.integration_count.getInputText()) - groups = np.array_split(self.Sxx, np.arange(averaging_count, tmp.shape[1], averaging_count), axis=1) - self.Sxx = np.array([group.sum(axis=1) for group in groups]).T + groups = np.array_split(self.Sxx[channel_index], np.arange(averaging_count, tmp.shape[1], averaging_count), axis=1) + self.Sxx[channel_index] = np.array([group.sum(axis=1) for group in groups]).T - def apply_spectrum_mode(self): + def apply_spectrum_mode(self, channel_index): selected_spectrum_mode = self.spectrum_mode.getInputText() if selected_spectrum_mode == "Amplitude [dB]": - self.Sxx = 20*np.log10(np.abs(self.Sxx)) + self.Sxx[channel_index] = 20*np.log10(np.abs(self.Sxx[channel_index])) elif selected_spectrum_mode == "Amplitude [lin]": - self.Sxx = np.abs(self.Sxx) + self.Sxx[channel_index] = np.abs(self.Sxx[channel_index]) elif selected_spectrum_mode == "Phase [Rad]": - self.Sxx = np.angle(self.Sxx, deg=False) + self.Sxx[channel_index] = np.angle(self.Sxx[channel_index], deg=False) elif selected_spectrum_mode == "Phase [Deg]": - self.Sxx = np.angle(self.Sxx, deg=True) + self.Sxx[channel_index] = np.angle(self.Sxx[channel_index], deg=True) app = QApplication([]) window = MainWindow() From cd5da019859fc2d7b615467c8c8e46d7231967c5 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Mon, 30 Sep 2024 17:54:42 +0200 Subject: [PATCH 10/20] Feat: Adding loading button --- viewer.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/viewer.py b/viewer.py index c3f53ab..8a63f68 100644 --- a/viewer.py +++ b/viewer.py @@ -5,6 +5,8 @@ import librosa import librosa.display import pygame +import time + from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar import matplotlib.pyplot as plt @@ -123,10 +125,14 @@ def update_images(self): self.show_popup("File Not Found") return False + msg = self.show_popup("Loading", False) + self.generate_spectrogram_data() self.update_spectrogram_image() self.update_spectrum_image(0) + + msg.close() def update_spectrogram_image(self): @@ -231,12 +237,22 @@ def play_audio(self): pygame.mixer.music.play() - def show_popup(self, text): + def show_popup(self, text, use_button_continue = True): msg = QMessageBox() msg.setIcon(QMessageBox.Icon.Information) msg.setText(text) - msg.setStandardButtons(QMessageBox.StandardButton.Ok) - msg.exec() + + if use_button_continue: + msg.setStandardButtons(QMessageBox.StandardButton.Ok) + else: + msg.setStandardButtons(QMessageBox.StandardButton.NoButton) + + # Show the message box non-blocking + msg.show() + # Process pending events to ensure the text is displayed + QApplication.processEvents() + + return msg def get_selected_window(self): From 7d2b49d90a583781ed2fd439c33fe25476312ada Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Tue, 1 Oct 2024 18:34:05 +0200 Subject: [PATCH 11/20] Fix: For spectrum timestamp --- viewer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/viewer.py b/viewer.py index 8a63f68..a1fd1a4 100644 --- a/viewer.py +++ b/viewer.py @@ -155,13 +155,15 @@ def update_spectrum_image(self, spectrum_column_index): # Get the column corresponding to the clicked x-coordinate (time index) column_data = self.Sxx[0][:, spectrum_column_index] freq_axis = np.linspace(1,len(column_data),len(column_data))*self.freq_max_khz/len(column_data) + _, x_max = self.axes.get_xlim() + slice_time = np.round(x_max*spectrum_column_index/np.shape(self.Sxx[0])[1],1) # Plot the column data (frequency vs. intensity) self.spectrum_axes.clear() self.spectrum_axes.plot(freq_axis,column_data) self.spectrum_axes.set_xlabel('Frequency [KHz]') self.spectrum_axes.set_ylabel('Intensity [Arb dB]') - self.spectrum_axes.set_title(f'Spectrum Sample at Time of {spectrum_column_index} Seconds of \n ' + self.file_path_label.text()) + self.spectrum_axes.set_title(f'Spectrum Sample at Time of {slice_time} Seconds of \n ' + self.file_path_label.text()) # Update the column plot canvas self.spectrum_canvas.draw() From e92ff586d23b758edf47d871c155031ba24d7368 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Tue, 1 Oct 2024 18:44:27 +0200 Subject: [PATCH 12/20] Feat: Controlling axes for display modes --- viewer.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/viewer.py b/viewer.py index a1fd1a4..a63282f 100644 --- a/viewer.py +++ b/viewer.py @@ -165,9 +165,24 @@ def update_spectrum_image(self, spectrum_column_index): self.spectrum_axes.set_ylabel('Intensity [Arb dB]') self.spectrum_axes.set_title(f'Spectrum Sample at Time of {slice_time} Seconds of \n ' + self.file_path_label.text()) + self.set_spectrum_axes_limits() + # Update the column plot canvas self.spectrum_canvas.draw() + + def set_spectrum_axes_limits(self): + + selected_spectrum_mode = self.spectrum_mode.getInputText() + if selected_spectrum_mode == "Amplitude [dB]": + self.spectrum_axes.set_ylim([-60,60]) + elif selected_spectrum_mode == "Amplitude [lin]": + self.spectrum_axes.set_ylim([-1,50]) + elif selected_spectrum_mode == "Phase [Rad]": + self.spectrum_axes.set_ylim([-1.1*np.pi,1.1*np.pi]) + elif selected_spectrum_mode == "Phase [Deg]": + self.spectrum_axes.set_ylim([-185,185]) + def on_click(self, event): if event.inaxes == self.axes: # Ensure the click is inside the spectrogram plot From 5a527e3bb08f6c89fbe88d5afae323d8e3b310c2 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Tue, 1 Oct 2024 19:11:41 +0200 Subject: [PATCH 13/20] Fix: Adding provisional support for one channel of data --- viewer.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/viewer.py b/viewer.py index a63282f..16c7505 100644 --- a/viewer.py +++ b/viewer.py @@ -137,7 +137,7 @@ def update_images(self): def update_spectrogram_image(self): self.freq_max_khz = self.sample_rate_hz/(2*1000) - time_max_s = len(self.data[1,:])*1/self.sample_rate_hz + time_max_s = len(self.data[0,:])*1/self.sample_rate_hz # Plot the spectrogram self.axes.clear() @@ -192,7 +192,6 @@ def on_click(self, event): # Convert the time stamp to the index in spectorgram spectrogram_shape_tuple = np.shape(self.Sxx[0]) - averaging_count = int(self.integration_count.getInputText()) spectrum_column_index = int(np.floor(spectrogram_shape_tuple[1]*x_axis_timestamp/x_max)) # Plot that part of the spectrogram @@ -236,7 +235,17 @@ def load_audio_data(self): self.data, self.sample_rate_hz \ = librosa.load(self.file_path_label.text(),sr=None, mono=False) - self.num_channels = np.shape(self.data)[0] + # Lets determine the number of audio channels + if len(np.shape(self.data)) == 1: + self.num_channels = 1 + else: + self.num_channels = np.shape(self.data)[0] + + # Then reshape to what the program expects in the case of one channel + if self.num_channels == 1: + self.data = np.reshape(self.data, [1,-1]) + print(np.shape( self.data)) + return True From 75e0c4297a868340d317c525cca68b4fdd92eca7 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Tue, 1 Oct 2024 19:27:02 +0200 Subject: [PATCH 14/20] Chore: Removing save func as already in figure --- viewer.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/viewer.py b/viewer.py index 16c7505..494d89f 100644 --- a/viewer.py +++ b/viewer.py @@ -97,14 +97,6 @@ def __init__(self, *args, **kwargs): self.plot_image_button.clicked.connect(lambda: self.update_images()) input_layout.addWidget(self.plot_image_button) - self.save_image_button = QPushButton("Save Spectrogram") - self.save_image_button.clicked.connect(self.save_spectrogram_image) - input_layout.addWidget(self.save_image_button) - - self.save_image_button = QPushButton("Save Spectrum") - self.save_image_button.clicked.connect(self.save_spectrum_image) - input_layout.addWidget(self.save_image_button) - self.play_audio_button = QPushButton("Play Audio") self.play_audio_button.clicked.connect(self.play_audio) input_layout.addWidget(self.play_audio_button) @@ -209,22 +201,6 @@ def load_file(self): # Handle the case where no file was selected self.file_path_label.setText("No file selected") - def save_spectrogram_image(self): - # Open a file dialog to select where to save the image - save_path, _ = QFileDialog.getSaveFileName(self, "Save Image", "", "PNG Files (*.png);;JPEG Files (*.jpg)") - - if save_path: - # Save the current figure as an image - self.figure.savefig(save_path) - - def save_spectrum_image(self): - # Open a file dialog to select where to save the image - save_path, _ = QFileDialog.getSaveFileName(self, "Save Image", "", "PNG Files (*.png);;JPEG Files (*.jpg)") - - if save_path: - # Save the current figure as an image - self.spectrum_figure.savefig(save_path) - def load_audio_data(self): # First check if path exists @@ -245,7 +221,6 @@ def load_audio_data(self): if self.num_channels == 1: self.data = np.reshape(self.data, [1,-1]) print(np.shape( self.data)) - return True From 8a238e543900462e0b636d22c32621291386d179 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Tue, 1 Oct 2024 19:28:23 +0200 Subject: [PATCH 15/20] Chore: removing print --- viewer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/viewer.py b/viewer.py index 494d89f..9fe220e 100644 --- a/viewer.py +++ b/viewer.py @@ -220,11 +220,9 @@ def load_audio_data(self): # Then reshape to what the program expects in the case of one channel if self.num_channels == 1: self.data = np.reshape(self.data, [1,-1]) - print(np.shape( self.data)) return True - def play_audio(self): # First check if path exists From c653826ae51d5eb0527506c963e22f66e1ee3eae Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 6 Oct 2024 20:39:15 +0200 Subject: [PATCH 16/20] Feat: Initial implementation of multi channel --- viewer.py | 72 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/viewer.py b/viewer.py index 9fe220e..4b5ab5b 100644 --- a/viewer.py +++ b/viewer.py @@ -31,36 +31,9 @@ def __init__(self, *args, **kwargs): layout = QHBoxLayout() # Use QHBoxLayout for horizontal arrangement central_widget.setLayout(layout) - plots_layout = QVBoxLayout() - - ### Spectrogram ### - - # Plot area for the spectrogram - self.figure, self.axes = plt.subplots() - self.figure.subplots_adjust(left=0.1, right=0.95, top=0.85, bottom=0.15) - self.spectrogram_canvas = FigureCanvasQTAgg(self.figure) - self.axes.plot([],[]) - - # Toolbar and figure layout - plots_layout.addWidget(NavigationToolbar(self.spectrogram_canvas, self)) - plots_layout.addWidget(self.spectrogram_canvas) - - ### Spectrum ### - - # Add a second figure for plotting the column data - self.spectrum_figure, self.spectrum_axes = plt.subplots() - self.spectrum_figure.subplots_adjust(left=0.1, right=0.95, top=0.85, bottom=0.15) - self.spectrum_canvas = FigureCanvasQTAgg(self.spectrum_figure) - self.spectrum_axes.plot([],[]) - - # Toolbar and figure layout - plots_layout.addWidget(NavigationToolbar(self.spectrum_canvas, self)) - plots_layout.addWidget(self.spectrum_canvas) + self.channel_plots_tabs = QTabWidget() - # Create a new widget to hold the spectrogran_toolbar_layout - toolbar_widget = QWidget() - toolbar_widget.setLayout(plots_layout) - layout.addWidget(toolbar_widget) + layout.addWidget(self.channel_plots_tabs) ### @@ -107,7 +80,7 @@ def __init__(self, *args, **kwargs): self.setCentralWidget(central_widget) # Connect the canvas click event - self.spectrogram_canvas.mpl_connect('button_press_event', self.on_click) + # self.spectrogram_canvas.mpl_connect('button_press_event', self.on_click) self.Sxx = None # Store spectrogram data self.showMaximized() @@ -119,8 +92,10 @@ def update_images(self): msg = self.show_popup("Loading", False) - self.generate_spectrogram_data() + for i in range(self.num_channels): + self.add_plot_tab(i) + self.generate_spectrogram_data() self.update_spectrogram_image() self.update_spectrum_image(0) @@ -309,6 +284,41 @@ def apply_spectrum_mode(self, channel_index): elif selected_spectrum_mode == "Phase [Deg]": self.Sxx[channel_index] = np.angle(self.Sxx[channel_index], deg=True) + def add_plot_tab(self, channel_index): + + plots_layout = QVBoxLayout() + + ### Spectrogram ### + + # Plot area for the spectrogram + self.figure, self.axes = plt.subplots() + self.figure.subplots_adjust(left=0.1, right=0.95, top=0.85, bottom=0.15) + self.spectrogram_canvas = FigureCanvasQTAgg(self.figure) + self.axes.plot([],[]) + + # Toolbar and figure layout + plots_layout.addWidget(NavigationToolbar(self.spectrogram_canvas, self)) + plots_layout.addWidget(self.spectrogram_canvas) + + ### Spectrum ### + + # Add a second figure for plotting the column data + self.spectrum_figure, self.spectrum_axes = plt.subplots() + self.spectrum_figure.subplots_adjust(left=0.1, right=0.95, top=0.85, bottom=0.15) + self.spectrum_canvas = FigureCanvasQTAgg(self.spectrum_figure) + self.spectrum_axes.plot([],[]) + + # Toolbar and figure layout + plots_layout.addWidget(NavigationToolbar(self.spectrum_canvas, self)) + plots_layout.addWidget(self.spectrum_canvas) + + # Create a new widget to hold the spectrogran_toolbar_layout + toolbar_widget = QWidget() + toolbar_widget.setLayout(plots_layout) + + self.channel_plots_tabs.addTab(toolbar_widget,"Channel " + str(channel_index)) + + app = QApplication([]) window = MainWindow() app.exec() \ No newline at end of file From c4d7975d382d3e745e53074955c2bcc0dfa2a004 Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 6 Oct 2024 21:05:42 +0200 Subject: [PATCH 17/20] Feat: Partial support for multi channel plotting --- PlotConfig.py | 2 + __pycache__/PlotConfig.cpython-312.pyc | Bin 0 -> 310 bytes tmp.py | 24 --------- viewer.py | 71 ++++++++++++------------- 4 files changed, 37 insertions(+), 60 deletions(-) create mode 100644 PlotConfig.py create mode 100644 __pycache__/PlotConfig.cpython-312.pyc delete mode 100644 tmp.py diff --git a/PlotConfig.py b/PlotConfig.py new file mode 100644 index 0000000..876ac3c --- /dev/null +++ b/PlotConfig.py @@ -0,0 +1,2 @@ +class PlotConfig: + pass \ No newline at end of file diff --git a/__pycache__/PlotConfig.cpython-312.pyc b/__pycache__/PlotConfig.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2b697d0bc5c1422260e3b412c3ce54080b9302c GIT binary patch literal 310 zcmX@j%ge<81c&c3rAq_p#~=<2us|7~C4h|S3``8}3@HpP3@MDOnIJMz43$ip%*inI zAes@%`OE`kOlL@Ch+<4(h+?W_)MUEF6_Ar(;+&tCmYMFS$$X0=K0YroH#I)~7FT?H zZhlH>4v5VYA75CSm;;en$?zFu-Y?B$tC-N@)S}`T_oBqSk{FlN;_Q Date: Sun, 6 Oct 2024 23:15:56 +0200 Subject: [PATCH 18/20] Feat: Mostly supporting multi channel plotting except zoom --- viewer.py | 65 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/viewer.py b/viewer.py index c422d9e..f4f368c 100644 --- a/viewer.py +++ b/viewer.py @@ -96,43 +96,42 @@ def update_images(self): for i in range(self.num_channels): self.add_plot_tab(i) - - self.generate_spectrogram_data() - self.update_spectrogram_image() - self.update_spectrum_image(0) + self.generate_spectrogram_data(i) + self.update_spectrogram_image(i) + self.update_spectrum_image(i, 0) msg.close() - def update_spectrogram_image(self): + def update_spectrogram_image(self, channel_index): self.freq_max_khz = self.sample_rate_hz/(2*1000) time_max_s = len(self.data[0,:])*1/self.sample_rate_hz # Plot the spectrogram - self.channel_plots[0].axes.clear() - self.channel_plots[0].axes.imshow(self.Sxx[0], origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) - self.channel_plots[0].axes.set_xlabel('Time [s]') - self.channel_plots[0].axes.set_ylabel('Frequency [KHz]') - self.channel_plots[0].axes.set_title('Spectrogram of \n' + self.file_path_label.text()) + self.channel_plots[channel_index].axes.clear() + self.channel_plots[channel_index].axes.imshow(self.Sxx[channel_index], origin='lower', aspect="auto", extent=[0,time_max_s, 0, self.freq_max_khz]) + self.channel_plots[channel_index].axes.set_xlabel('Time [s]') + self.channel_plots[channel_index].axes.set_ylabel('Frequency [KHz]') + self.channel_plots[channel_index].axes.set_title('Spectrogram of \n' + self.file_path_label.text()) - self.channel_plots[0].spectrogram_canvas.draw() + self.channel_plots[channel_index].spectrogram_canvas.draw() - def update_spectrum_image(self, spectrum_column_index): + def update_spectrum_image(self, channel_index, spectrum_column_index): if self.Sxx is not None: # Get the column corresponding to the clicked x-coordinate (time index) - column_data = self.Sxx[0][:, spectrum_column_index] + column_data = self.Sxx[channel_index][:, spectrum_column_index] freq_axis = np.linspace(1,len(column_data),len(column_data))*self.freq_max_khz/len(column_data) - _, x_max = self.channel_plots[0].axes.get_xlim() - slice_time = np.round(x_max*spectrum_column_index/np.shape(self.Sxx[0])[1],1) + _, x_max = self.channel_plots[channel_index].axes.get_xlim() + slice_time = np.round(x_max*spectrum_column_index/np.shape(self.Sxx[channel_index])[1],1) # Plot the column data (frequency vs. intensity) - self.channel_plots[0].spectrum_axes.clear() - self.channel_plots[0].spectrum_axes.plot(freq_axis,column_data) - self.channel_plots[0].spectrum_axes.set_xlabel('Frequency [KHz]') - self.channel_plots[0].spectrum_axes.set_ylabel('Intensity [Arb dB]') - self.channel_plots[0].spectrum_axes.set_title(f'Spectrum Sample at Time of {slice_time} Seconds of \n ' + self.file_path_label.text()) + self.channel_plots[channel_index].spectrum_axes.clear() + self.channel_plots[channel_index].spectrum_axes.plot(freq_axis,column_data) + self.channel_plots[channel_index].spectrum_axes.set_xlabel('Frequency [KHz]') + self.channel_plots[channel_index].spectrum_axes.set_ylabel('Intensity [Arb dB]') + self.channel_plots[channel_index].spectrum_axes.set_title(f'Spectrum Sample at Time of {slice_time} Seconds of \n ' + self.file_path_label.text()) self.set_spectrum_axes_limits() @@ -245,24 +244,24 @@ def get_selected_window(self): return window - def generate_spectrogram_data(self): + def generate_spectrogram_data(self, channel_index): # Generate spectrogram game self.Sxx = {} window = self.get_selected_window() - for i in range(self.num_channels): - SFT = signal.ShortTimeFFT(win=window, - mfft=int(self.fft_size.getInputText()), - hop=int(self.fft_hop.getInputText()), - fs=self.sample_rate_hz, - fft_mode="onesided" - ) - self.Sxx[i] = SFT.stft(self.data[i,:]) - - # Process data further - self.apply_integration(i) - self.apply_spectrum_mode(i) + SFT = signal.ShortTimeFFT(win=window, + mfft=int(self.fft_size.getInputText()), + hop=int(self.fft_hop.getInputText()), + fs=self.sample_rate_hz, + fft_mode="onesided" + ) + + self.Sxx[channel_index] = SFT.stft(self.data[channel_index,:]) + + # Process data further + self.apply_integration(channel_index) + self.apply_spectrum_mode(channel_index) def apply_integration(self, channel_index): From cd79ccf4c79386ddd67684727bc115f259aa997d Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Sun, 6 Oct 2024 23:51:08 +0200 Subject: [PATCH 19/20] Feat: Fully supporting multi channel pending cleanup --- viewer.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/viewer.py b/viewer.py index f4f368c..9ce3ecc 100644 --- a/viewer.py +++ b/viewer.py @@ -82,8 +82,6 @@ def __init__(self, *args, **kwargs): self.setCentralWidget(central_widget) # Connect the canvas click event - # self.spectrogram_canvas.mpl_connect('button_press_event', self.on_click) - self.Sxx = None # Store spectrogram data self.showMaximized() def update_images(self): @@ -94,6 +92,7 @@ def update_images(self): msg = self.show_popup("Loading", False) + self.Sxx = {} for i in range(self.num_channels): self.add_plot_tab(i) self.generate_spectrogram_data(i) @@ -136,7 +135,7 @@ def update_spectrum_image(self, channel_index, spectrum_column_index): self.set_spectrum_axes_limits() # Update the column plot canvas - self.channel_plots[0].spectrum_canvas.draw() + self.channel_plots[channel_index].spectrum_canvas.draw() def set_spectrum_axes_limits(self): @@ -152,18 +151,21 @@ def set_spectrum_axes_limits(self): self.channel_plots[0].spectrum_axes.set_ylim([-185,185]) def on_click(self, event): - if event.inaxes == self.axes: # Ensure the click is inside the spectrogram plot + + for i in range(self.num_channels): + + if event.inaxes == self.channel_plots[i].axes: # Ensure the click is inside the spectrogram plot - # Check where one clicked - x_axis_timestamp = int(event.xdata) - _, x_max = self.axes.get_xlim() + # Check where one clicked + x_axis_timestamp = int(event.xdata) + _, x_max = self.channel_plots[i].axes.get_xlim() - # Convert the time stamp to the index in spectorgram - spectrogram_shape_tuple = np.shape(self.Sxx[0]) - spectrum_column_index = int(np.floor(spectrogram_shape_tuple[1]*x_axis_timestamp/x_max)) + # Convert the time stamp to the index in spectorgram + spectrogram_shape_tuple = np.shape(self.Sxx[i]) + spectrum_column_index = int(np.floor(spectrogram_shape_tuple[1]*x_axis_timestamp/x_max)) - # Plot that part of the spectrogram - self.update_spectrum_image(spectrum_column_index) + # Plot that part of the spectrogram + self.update_spectrum_image(i, spectrum_column_index) def load_file(self): # Open a file dialog to let the user select a file @@ -247,7 +249,6 @@ def get_selected_window(self): def generate_spectrogram_data(self, channel_index): # Generate spectrogram game - self.Sxx = {} window = self.get_selected_window() SFT = signal.ShortTimeFFT(win=window, @@ -315,6 +316,7 @@ def add_plot_tab(self, channel_index): toolbar_widget.setLayout(self.channel_plots[channel_index].plots_layout) self.channel_plots_tabs.addTab(toolbar_widget,"Channel " + str(channel_index)) + self.channel_plots[channel_index].spectrogram_canvas.mpl_connect('button_press_event', self.on_click) app = QApplication([]) From 0e6e7ea14e006cd20a73b4fb62d34a6a5986c90e Mon Sep 17 00:00:00 2001 From: Grabt234 Date: Mon, 7 Oct 2024 17:18:56 +0200 Subject: [PATCH 20/20] Feat: Adding auto tab removal --- viewer.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/viewer.py b/viewer.py index 9ce3ecc..55c8556 100644 --- a/viewer.py +++ b/viewer.py @@ -34,6 +34,7 @@ def __init__(self, *args, **kwargs): self.channel_plots_tabs = QTabWidget() self.channel_plots = {} + self.num_channels = 0 layout.addWidget(self.channel_plots_tabs) @@ -168,6 +169,12 @@ def on_click(self, event): self.update_spectrum_image(i, spectrum_column_index) def load_file(self): + + # Clear current tabs if they exist + if self.num_channels: + for i in range(self.num_channels): + self.remove_plot_tab(i) + # Open a file dialog to let the user select a file file_path, _ = QFileDialog.getOpenFileName(self, "Select File", "", "*.wav") @@ -178,7 +185,9 @@ def load_file(self): else: # Handle the case where no file was selected self.file_path_label.setText("No file selected") - + return + + def load_audio_data(self): # First check if path exists @@ -286,6 +295,10 @@ def apply_spectrum_mode(self, channel_index): elif selected_spectrum_mode == "Phase [Deg]": self.Sxx[channel_index] = np.angle(self.Sxx[channel_index], deg=True) + def remove_plot_tab(self, channel_index): + self.channel_plots[channel_index] = PlotConfig() + self.channel_plots_tabs.removeTab(0) + def add_plot_tab(self, channel_index): self.channel_plots[channel_index] = PlotConfig()