From 0ee6f5e63fcfa7e312747d752248e6d468bb3949 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 27 Jan 2026 12:44:27 -0500 Subject: [PATCH 1/2] recording performance improvements --- src/ria_toolkit_oss/viz/recording.py | 59 ++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/src/ria_toolkit_oss/viz/recording.py b/src/ria_toolkit_oss/viz/recording.py index 9c166a6..b31d6af 100644 --- a/src/ria_toolkit_oss/viz/recording.py +++ b/src/ria_toolkit_oss/viz/recording.py @@ -54,6 +54,18 @@ def spectrogram(rec: Recording, thumbnail: bool = False) -> Figure: frequencies_shifted = np.fft.fftshift(frequencies) Sxx_shifted = np.fft.fftshift(Sxx_log_norm, axes=0) + # Downsample heatmap for performance (max 500x500 = 250,000 points) + max_freq_bins = 500 + max_time_bins = 500 + + freq_step = max(1, len(frequencies_shifted) // max_freq_bins) + time_step = max(1, len(times) // max_time_bins) + + if freq_step > 1 or time_step > 1: + Sxx_shifted = Sxx_shifted[::freq_step, ::time_step] + frequencies_shifted = frequencies_shifted[::freq_step] + times = times[::time_step] + fig = go.Figure( data=go.Heatmap( z=Sxx_shifted, @@ -102,11 +114,19 @@ def iq_time_series(rec: Recording) -> Figure: complex_signal = rec.data[0] sample_rate = int(rec.metadata.get("sample_rate", 1)) plot_length = len(complex_signal) - t = np.arange(0, plot_length, 1) / sample_rate + # Downsample for performance (max 10,000 points) + max_points = 10000 + step = max(1, plot_length // max_points) + if step > 1: + complex_signal = complex_signal[::step] + plot_length = len(complex_signal) + + t = np.arange(0, plot_length, 1) * step / sample_rate fig = go.Figure() - fig.add_trace(go.Scatter(x=t, y=complex_signal.real, mode="lines", name="I (In-phase)", line=dict(width=0.6))) - fig.add_trace(go.Scatter(x=t, y=complex_signal.imag, mode="lines", name="Q (Quadrature)", line=dict(width=0.6))) + # Use Scattergl for WebGL-accelerated rendering + fig.add_trace(go.Scattergl(x=t, y=complex_signal.real, mode="lines", name="I (In-phase)", line=dict(width=0.6))) + fig.add_trace(go.Scattergl(x=t, y=complex_signal.imag, mode="lines", name="Q (Quadrature)", line=dict(width=0.6))) fig.update_layout( title="IQ Time Series", @@ -139,8 +159,15 @@ def frequency_spectrum(rec: Recording) -> Figure: log_spectrum = np.log10(spectrum + epsilon) scaled_log_spectrum = (log_spectrum - log_spectrum.min()) / (log_spectrum.max() - log_spectrum.min()) + # Downsample for performance (max 10,000 points) + max_points = 10000 + if len(freqs) > max_points: + step = len(freqs) // max_points + freqs = freqs[::step] + scaled_log_spectrum = scaled_log_spectrum[::step] + fig = go.Figure() - fig.add_trace(go.Scatter(x=freqs, y=scaled_log_spectrum, mode="lines", name="Spectrum", line=dict(width=0.4))) + fig.add_trace(go.Scattergl(x=freqs, y=scaled_log_spectrum, mode="lines", name="Spectrum", line=dict(width=0.4))) fig.update_layout( title="Frequency Spectrum", @@ -174,7 +201,7 @@ def constellation(rec: Recording) -> Figure: q_ds = complex_signal.imag[::step] fig = go.Figure() - fig.add_trace(go.Scatter(x=i_ds, y=q_ds, mode="lines", name="Constellation", line=dict(width=0.2))) + fig.add_trace(go.Scattergl(x=i_ds, y=q_ds, mode="lines", name="Constellation", line=dict(width=0.2))) fig.update_layout( title="Constellation", @@ -221,7 +248,7 @@ def power_spectral_density(rec: Recording) -> Figure: fig = go.Figure() fig.add_trace( - go.Scatter(x=frequencies_shifted, y=psd_db, mode="lines", name="PSD", line=dict(width=0.8, color="#00D9FF")) + go.Scattergl(x=frequencies_shifted, y=psd_db, mode="lines", name="PSD", line=dict(width=0.8, color="#00D9FF")) ) fig.update_layout( @@ -257,8 +284,14 @@ def fft_plot(rec: Recording) -> Figure: magnitude = np.abs(fft_result) magnitude_db = 20 * np.log10(magnitude + 1e-10) + max_points = 10000 + if len(freqs) > max_points: + step = len(freqs) // max_points + freqs = freqs[::step] + magnitude_db = magnitude_db[::step] + fig = go.Figure() - fig.add_trace(go.Scatter(x=freqs, y=magnitude_db, mode="lines", name="FFT", line=dict(width=0.6, color="#FF6B9D"))) + fig.add_trace(go.Scattergl(x=freqs, y=magnitude_db, mode="lines", name="FFT", line=dict(width=0.6, color="#FF6B9D"))) fig.update_layout( title="FFT Magnitude", @@ -314,6 +347,18 @@ def spectrogram_3d(rec: Recording) -> Figure: frequencies_shifted = np.fft.fftshift(frequencies) Sxx_shifted = np.fft.fftshift(Sxx_log, axes=0) + # Downsample to prevent browser memory issues (max ~40,000 total points) + max_freq_bins = 200 + max_time_bins = 200 + + freq_step = max(1, len(frequencies_shifted) // max_freq_bins) + time_step = max(1, len(times) // max_time_bins) + + if freq_step > 1 or time_step > 1: + Sxx_shifted = Sxx_shifted[::freq_step, ::time_step] + frequencies_shifted = frequencies_shifted[::freq_step] + times = times[::time_step] + fig = go.Figure( data=[ go.Surface( From 0178adcdb55e90e12ac010da1624622b41b8b8b5 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 27 Jan 2026 12:49:09 -0500 Subject: [PATCH 2/2] lint fix --- src/ria_toolkit_oss/viz/recording.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ria_toolkit_oss/viz/recording.py b/src/ria_toolkit_oss/viz/recording.py index b31d6af..82ad6a4 100644 --- a/src/ria_toolkit_oss/viz/recording.py +++ b/src/ria_toolkit_oss/viz/recording.py @@ -291,7 +291,9 @@ def fft_plot(rec: Recording) -> Figure: magnitude_db = magnitude_db[::step] fig = go.Figure() - fig.add_trace(go.Scattergl(x=freqs, y=magnitude_db, mode="lines", name="FFT", line=dict(width=0.6, color="#FF6B9D"))) + fig.add_trace( + go.Scattergl(x=freqs, y=magnitude_db, mode="lines", name="FFT", line=dict(width=0.6, color="#FF6B9D")) + ) fig.update_layout( title="FFT Magnitude",