Compare commits
2 Commits
9a304faa00
...
e5a3d327e5
| Author | SHA1 | Date | |
|---|---|---|---|
| e5a3d327e5 | |||
| 4c94f6ae94 |
|
|
@ -414,12 +414,18 @@ Device selection (``--device``) is optional if only one device is detected. Exac
|
||||||
ria view capture.npy --show --no-save
|
ria view capture.npy --show --no-save
|
||||||
ria view old.npy --legacy --type simple
|
ria view old.npy --legacy --type simple
|
||||||
ria view recordings\qam64_35.npy --type simple
|
ria view recordings\qam64_35.npy --type simple
|
||||||
|
ria view recordings\qam64_35.npy --type full
|
||||||
|
|
||||||
.. figure:: ../images/qam64_35.png
|
.. figure:: ../images/recordings/qam64_35.png
|
||||||
:alt: Example output of ria view recordings\qam64_35.npy --type simple
|
:alt: Example output of ria view recordings\qam64_35.npy --type simple
|
||||||
|
|
||||||
Output of ``ria view recordings\qam64_35.npy --type simple``
|
Output of ``ria view recordings\qam64_35.npy --type simple``
|
||||||
|
|
||||||
|
.. figure:: ../images/recordings/qam64_35-full.png
|
||||||
|
:alt: Example output of ria view recordings\qam64_35.npy --type full
|
||||||
|
|
||||||
|
Output of ``ria view recordings\qam64_35.npy --type full``
|
||||||
|
|
||||||
|
|
||||||
.. _cmd-annotate:
|
.. _cmd-annotate:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
Datatypes Package (ria_toolkit_oss.data)
|
Data Package (ria_toolkit_oss.data)
|
||||||
=============================================
|
=======================================
|
||||||
|
|
||||||
.. |br| raw:: html
|
.. |br| raw:: html
|
||||||
|
|
||||||
|
|
@ -3,11 +3,12 @@ import os
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from matplotlib import gridspec
|
from matplotlib import gridspec, ticker
|
||||||
from matplotlib.patches import Patch
|
from matplotlib.patches import Patch
|
||||||
from PIL import Image
|
from PIL import Image, UnidentifiedImageError
|
||||||
from scipy.fft import fft, fftshift
|
from scipy.fft import fft, fftshift
|
||||||
from scipy.signal import spectrogram
|
from scipy.signal import spectrogram
|
||||||
from scipy.signal.windows import hann
|
from scipy.signal.windows import hann
|
||||||
|
|
@ -185,7 +186,7 @@ def view_sig(
|
||||||
logo: Optional[bool] = True,
|
logo: Optional[bool] = True,
|
||||||
dark: Optional[bool] = True,
|
dark: Optional[bool] = True,
|
||||||
spines: Optional[bool] = False,
|
spines: Optional[bool] = False,
|
||||||
title_fontsize: Optional[int] = 35,
|
title_fontsize: Optional[int] = 25,
|
||||||
subtitle_fontsize: Optional[int] = 15,
|
subtitle_fontsize: Optional[int] = 15,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -230,11 +231,24 @@ def view_sig(
|
||||||
complex_signal = recording.data[0]
|
complex_signal = recording.data[0]
|
||||||
sample_rate, center_frequency, _ = extract_metadata_fields(recording.metadata)
|
sample_rate, center_frequency, _ = extract_metadata_fields(recording.metadata)
|
||||||
|
|
||||||
subplot_height = 2 * (plot_spectrogram + iq + frequency) + 3 * (constellation or metadata or logo)
|
subplot_height = 3 * (plot_spectrogram) + 2 * (iq + frequency) + 3 * (constellation or metadata or logo)
|
||||||
subplot_width = max((constellation + metadata or 1), logo * 3)
|
subplot_width = max((constellation + metadata or 1), logo * 3)
|
||||||
|
|
||||||
if dark:
|
if dark:
|
||||||
plt.style.use("dark_background")
|
plt.style.use("dark_background")
|
||||||
|
matplotlib.rcParams.update({
|
||||||
|
"figure.facecolor": "#161616",
|
||||||
|
"axes.facecolor": "#161616",
|
||||||
|
"savefig.facecolor": "#161616",
|
||||||
|
"savefig.edgecolor": "#161616",
|
||||||
|
"font.size": 10,
|
||||||
|
"axes.titlesize": 15,
|
||||||
|
"axes.labelsize": 10,
|
||||||
|
"xtick.labelsize": 10,
|
||||||
|
"ytick.labelsize": 10,
|
||||||
|
"legend.frameon": False,
|
||||||
|
"legend.facecolor": "none",
|
||||||
|
})
|
||||||
logo_path = os.path.dirname(__file__) + "/graphics/Qoherent-logo-white-transparent.png"
|
logo_path = os.path.dirname(__file__) + "/graphics/Qoherent-logo-white-transparent.png"
|
||||||
else:
|
else:
|
||||||
plt.style.use("default")
|
plt.style.use("default")
|
||||||
|
|
@ -252,8 +266,8 @@ def view_sig(
|
||||||
plot_x_indx = 0
|
plot_x_indx = 0
|
||||||
|
|
||||||
if plot_spectrogram:
|
if plot_spectrogram:
|
||||||
spec_ax = plt.subplot(gs[plot_y_indx : plot_y_indx + 2, :])
|
spec_ax = plt.subplot(gs[plot_y_indx : plot_y_indx + 3, :])
|
||||||
plot_y_indx = plot_y_indx + 2
|
plot_y_indx = plot_y_indx + 3
|
||||||
fft_size = get_fft_size(plot_length=plot_length)
|
fft_size = get_fft_size(plot_length=plot_length)
|
||||||
|
|
||||||
_, t_spec, Sxx = spectrogram(
|
_, t_spec, Sxx = spectrogram(
|
||||||
|
|
@ -280,7 +294,12 @@ def view_sig(
|
||||||
)
|
)
|
||||||
|
|
||||||
set_spines(spec_ax, spines)
|
set_spines(spec_ax, spines)
|
||||||
spec_ax.set_title("Spectrogram", loc="center", fontsize=subtitle_fontsize)
|
spec_ax.set_title("Spectrogram", loc="left", fontsize=subtitle_fontsize)
|
||||||
|
spec_ax.set_xlabel("Time (s)")
|
||||||
|
spec_ax.set_ylabel("Frequency (MHz)")
|
||||||
|
spec_ax.yaxis.set_major_formatter(
|
||||||
|
ticker.FuncFormatter(lambda x, _: f"{x / 1e6:.1f}")
|
||||||
|
)
|
||||||
|
|
||||||
if iq:
|
if iq:
|
||||||
iq_ax = plt.subplot(gs[plot_y_indx : plot_y_indx + 2, :])
|
iq_ax = plt.subplot(gs[plot_y_indx : plot_y_indx + 2, :])
|
||||||
|
|
@ -291,12 +310,13 @@ def view_sig(
|
||||||
|
|
||||||
iq_ax.plot(t, plot_iq.real, color=COLORS["purple"], linewidth=0.6, alpha=0.8, label="I")
|
iq_ax.plot(t, plot_iq.real, color=COLORS["purple"], linewidth=0.6, alpha=0.8, label="I")
|
||||||
iq_ax.plot(t, plot_iq.imag, color=COLORS["magenta"], linewidth=0.6, alpha=0.8, label="Q")
|
iq_ax.plot(t, plot_iq.imag, color=COLORS["magenta"], linewidth=0.6, alpha=0.8, label="Q")
|
||||||
iq_ax.grid(False)
|
iq_ax.grid(True, alpha=0.2, linewidth=0.5)
|
||||||
|
|
||||||
iq_ax.set_ylabel("Amplitude")
|
iq_ax.set_ylabel("Amplitude")
|
||||||
iq_ax.set_xlim([min(t), max(t)])
|
iq_ax.set_xlim([min(t), max(t)])
|
||||||
iq_ax.set_xlabel("Time (s)")
|
iq_ax.set_xlabel("Time (s)")
|
||||||
iq_ax.set_title("IQ Sample Plot", fontsize=subtitle_fontsize)
|
iq_ax.set_title("IQ Sample Plot", loc="left", fontsize=subtitle_fontsize)
|
||||||
|
iq_ax.legend(loc="upper right", fontsize=10)
|
||||||
set_spines(iq_ax, spines)
|
set_spines(iq_ax, spines)
|
||||||
|
|
||||||
if frequency:
|
if frequency:
|
||||||
|
|
@ -310,10 +330,12 @@ def view_sig(
|
||||||
# Convert to dB
|
# Convert to dB
|
||||||
spectrum_db = 20 * np.log10(spectrum + 1e-12) # 20*log for magnitude
|
spectrum_db = 20 * np.log10(spectrum + 1e-12) # 20*log for magnitude
|
||||||
|
|
||||||
freqs = np.linspace(-sample_rate / 2, sample_rate / 2, len(complex_signal[:plot_length])) + center_frequency
|
freqs = (np.linspace(-sample_rate / 2, sample_rate / 2, len(complex_signal[:plot_length])) + center_frequency) / 1e6
|
||||||
freq_ax.plot(freqs, spectrum_db, color=COLORS["accent"], linewidth=0.8)
|
freq_ax.plot(freqs, spectrum_db, color=COLORS["accent"], linewidth=0.8)
|
||||||
|
freq_ax.set_xlabel("Frequency (MHz)")
|
||||||
freq_ax.set_ylabel("Magnitude (dB)")
|
freq_ax.set_ylabel("Magnitude (dB)")
|
||||||
freq_ax.set_title("Frequency Spectrum (Windowed FFT)", fontsize=subtitle_fontsize)
|
freq_ax.grid(True, alpha=0.2, linewidth=0.5)
|
||||||
|
freq_ax.set_title("Frequency Spectrum (Windowed FFT)", loc="left", fontsize=subtitle_fontsize)
|
||||||
set_spines(freq_ax, spines)
|
set_spines(freq_ax, spines)
|
||||||
|
|
||||||
if constellation:
|
if constellation:
|
||||||
|
|
@ -326,7 +348,7 @@ def view_sig(
|
||||||
const_ax.set_ylim([-1 * dimension, dimension])
|
const_ax.set_ylim([-1 * dimension, dimension])
|
||||||
const_ax.set_xlabel("In-phase (I)")
|
const_ax.set_xlabel("In-phase (I)")
|
||||||
const_ax.set_ylabel("Quadrature (Q)")
|
const_ax.set_ylabel("Quadrature (Q)")
|
||||||
const_ax.set_title("Constellation", fontsize=subtitle_fontsize)
|
const_ax.set_title("Constellation", loc="left", fontsize=subtitle_fontsize)
|
||||||
const_ax.set_aspect("equal")
|
const_ax.set_aspect("equal")
|
||||||
|
|
||||||
if not spines:
|
if not spines:
|
||||||
|
|
@ -375,8 +397,8 @@ def view_sig(
|
||||||
image = Image.open(logo_path) # Open the PNG image using PIL
|
image = Image.open(logo_path) # Open the PNG image using PIL
|
||||||
logo_ax.imshow(image)
|
logo_ax.imshow(image)
|
||||||
|
|
||||||
except FileNotFoundError:
|
except (FileNotFoundError, UnidentifiedImageError, OSError) as exc:
|
||||||
print(f"Warning, {logo_path} not found.")
|
print(f"Warning, could not load logo image: {logo_path}. Reason: {exc}")
|
||||||
|
|
||||||
fig.subplots_adjust(
|
fig.subplots_adjust(
|
||||||
left=0.1, # Left margin
|
left=0.1, # Left margin
|
||||||
|
|
|
||||||
|
|
@ -119,24 +119,19 @@ def setup_style(*, labels_mode: bool = False, compact_mode: bool = False) -> Non
|
||||||
label_font = 14
|
label_font = 14
|
||||||
else:
|
else:
|
||||||
base_font = 10
|
base_font = 10
|
||||||
title_font = 12
|
title_font = 15
|
||||||
label_font = 10
|
label_font = 10
|
||||||
|
|
||||||
matplotlib.rcParams.update(
|
matplotlib.rcParams.update(
|
||||||
{
|
{
|
||||||
"figure.facecolor": "#0f172a",
|
"figure.facecolor": "#161616",
|
||||||
"axes.facecolor": "#1e293b",
|
"axes.facecolor": "#161616",
|
||||||
"axes.edgecolor": COLORS["muted"],
|
"savefig.facecolor": "#161616",
|
||||||
"axes.labelcolor": COLORS["light"],
|
"savefig.edgecolor": "#161616",
|
||||||
"text.color": COLORS["light"],
|
|
||||||
"xtick.color": COLORS["muted"],
|
|
||||||
"ytick.color": COLORS["muted"],
|
|
||||||
"grid.color": COLORS["muted"],
|
|
||||||
"grid.alpha": 0.3,
|
|
||||||
"font.size": base_font,
|
"font.size": base_font,
|
||||||
"axes.titlesize": title_font,
|
"axes.titlesize": title_font,
|
||||||
"axes.labelsize": label_font,
|
"axes.labelsize": label_font,
|
||||||
"figure.titlesize": title_font + 2,
|
"figure.titlesize": title_font + 4,
|
||||||
"legend.frameon": False,
|
"legend.frameon": False,
|
||||||
"legend.facecolor": "none",
|
"legend.facecolor": "none",
|
||||||
"xtick.labelsize": base_font,
|
"xtick.labelsize": base_font,
|
||||||
|
|
@ -194,7 +189,7 @@ def view_simple_sig(
|
||||||
constellation_mode: Optional[bool] = False,
|
constellation_mode: Optional[bool] = False,
|
||||||
labels_mode: Optional[bool] = False,
|
labels_mode: Optional[bool] = False,
|
||||||
slice: Optional[tuple] = None,
|
slice: Optional[tuple] = None,
|
||||||
title: Optional[str] = "Signal",
|
title: Optional[str] = "Signal Plot",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create a simple plot of various signal visualizations as a png or svg image.
|
Create a simple plot of various signal visualizations as a png or svg image.
|
||||||
|
|
@ -237,7 +232,7 @@ def view_simple_sig(
|
||||||
spec_signal = signal
|
spec_signal = signal
|
||||||
|
|
||||||
if compact_mode:
|
if compact_mode:
|
||||||
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 6), gridspec_kw={"height_ratios": [1, 5]})
|
fig, (ax2, ax1) = plt.subplots(2, 1, figsize=(12, 6), gridspec_kw={"height_ratios": [5, 1]})
|
||||||
show_title = False
|
show_title = False
|
||||||
show_labels = False
|
show_labels = False
|
||||||
ax_constellation = ax_psd = None
|
ax_constellation = ax_psd = None
|
||||||
|
|
@ -253,25 +248,24 @@ def view_simple_sig(
|
||||||
ax_psd = None
|
ax_psd = None
|
||||||
else:
|
else:
|
||||||
if constellation_mode:
|
if constellation_mode:
|
||||||
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
|
fig, ((ax2, ax1), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
|
||||||
ax_constellation, ax_psd = ax3, ax4
|
ax_constellation, ax_psd = ax3, ax4
|
||||||
else:
|
else:
|
||||||
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10))
|
fig, (ax2, ax1) = plt.subplots(2, 1, figsize=(14, 10))
|
||||||
ax_constellation = ax_psd = None
|
ax_constellation = ax_psd = None
|
||||||
show_title = True
|
show_title = True
|
||||||
show_labels = labels_mode
|
show_labels = labels_mode
|
||||||
|
|
||||||
if show_title:
|
if show_title:
|
||||||
fig.suptitle(title, fontsize=16, color=COLORS["light"], y=0.96)
|
fig.suptitle(title, fontsize=25)
|
||||||
fig.patch.set_facecolor("#0f172a")
|
fig.patch.set_facecolor(matplotlib.rcParams["figure.facecolor"])
|
||||||
|
|
||||||
total_duration_s = len(signal) / sample_rate_hz if sample_rate_hz else 0.0
|
total_duration_s = len(signal) / sample_rate_hz if sample_rate_hz else 0.0
|
||||||
t_s = np.linspace(0, total_duration_s, len(display_signal)) if len(display_signal) else np.array([])
|
t_s = np.linspace(0, total_duration_s, len(display_signal)) if len(display_signal) else np.array([])
|
||||||
|
|
||||||
ax1.plot(t_s, display_signal.real, color=COLORS["purple"], linewidth=0.8, alpha=0.8, label="I")
|
ax1.plot(t_s, display_signal.real, color=COLORS["purple"], linewidth=0.6, alpha=0.8, label="I")
|
||||||
ax1.plot(t_s, display_signal.imag, color=COLORS["magenta"], linewidth=0.8, alpha=0.8, label="Q")
|
ax1.plot(t_s, display_signal.imag, color=COLORS["magenta"], linewidth=0.6, alpha=0.8, label="Q")
|
||||||
ax1.set_xlim(0, total_duration_s)
|
ax1.grid(True, alpha=0.2, linewidth=0.5)
|
||||||
ax1.grid(True, alpha=0.3)
|
|
||||||
|
|
||||||
nfft, overlap = _get_nfft_size(signal=signal, fast_mode=fast_mode)
|
nfft, overlap = _get_nfft_size(signal=signal, fast_mode=fast_mode)
|
||||||
|
|
||||||
|
|
@ -285,7 +279,7 @@ def view_simple_sig(
|
||||||
)
|
)
|
||||||
|
|
||||||
ax2.set_ylim(center_freq_hz - sample_rate_hz / 2, center_freq_hz + sample_rate_hz / 2)
|
ax2.set_ylim(center_freq_hz - sample_rate_hz / 2, center_freq_hz + sample_rate_hz / 2)
|
||||||
ax2.set_xlim(0, total_duration_s)
|
ax1.set_xlim(ax2.get_xlim())
|
||||||
|
|
||||||
if show_labels:
|
if show_labels:
|
||||||
if horizontal_mode:
|
if horizontal_mode:
|
||||||
|
|
@ -294,20 +288,26 @@ def view_simple_sig(
|
||||||
ax2.set_xlabel("Time (s)")
|
ax2.set_xlabel("Time (s)")
|
||||||
|
|
||||||
ax1.set_ylabel("Amplitude")
|
ax1.set_ylabel("Amplitude")
|
||||||
ax1.set_title(f"Time Series - {sdr} SDR", loc="left", pad=10)
|
ax1.set_title(f"IQ Sample Plot - {sdr} SDR", loc="left", pad=10, fontsize=15)
|
||||||
ax1.legend(loc="upper right")
|
ax1.legend(loc="upper right", fontsize=10)
|
||||||
|
|
||||||
ax2.set_ylabel("Frequency (Hz)")
|
ax2.set_ylabel("Frequency (MHz)")
|
||||||
ax2.set_title(
|
ax2.set_title(
|
||||||
f"Spectrogram - {center_freq_hz / 1e6:.1f} MHz ± {sample_rate_hz / 2e6:.1f} MHz", loc="left", pad=10
|
f"Spectrogram - {center_freq_hz / 1e6:.1f} MHz ± {sample_rate_hz / 2e6:.1f} MHz", loc="left", pad=10, fontsize=15
|
||||||
|
)
|
||||||
|
ax2.yaxis.set_major_formatter(
|
||||||
|
matplotlib.ticker.FuncFormatter(lambda x, _: f"{x / 1e6:.1f}")
|
||||||
)
|
)
|
||||||
yticks = ax2.get_yticks()
|
|
||||||
ax2.set_yticklabels([f"{y / 1e6:.1f}" for y in yticks])
|
|
||||||
elif not compact_mode:
|
elif not compact_mode:
|
||||||
ax1.set_title("Time Series", loc="left", pad=10)
|
ax1.set_title("IQ Sample Plot", loc="left", pad=10, fontsize=15)
|
||||||
ax1.legend(loc="upper right", fontsize=8)
|
ax1.legend(loc="upper right", fontsize=10)
|
||||||
|
|
||||||
ax2.set_title("Spectrogram", loc="left", pad=10)
|
ax2.set_xlabel("Time (s)")
|
||||||
|
ax2.set_ylabel("Frequency (MHz)")
|
||||||
|
ax2.set_title("Spectrogram", loc="left", pad=10, fontsize=15)
|
||||||
|
ax2.yaxis.set_major_formatter(
|
||||||
|
matplotlib.ticker.FuncFormatter(lambda x, _: f"{x / 1e6:.1f}")
|
||||||
|
)
|
||||||
|
|
||||||
_add_annotations(
|
_add_annotations(
|
||||||
annotations=annotations,
|
annotations=annotations,
|
||||||
|
|
@ -339,8 +339,8 @@ def view_simple_sig(
|
||||||
)
|
)
|
||||||
ax_constellation.set_xlabel("In-phase (I)")
|
ax_constellation.set_xlabel("In-phase (I)")
|
||||||
ax_constellation.set_ylabel("Quadrature (Q)")
|
ax_constellation.set_ylabel("Quadrature (Q)")
|
||||||
ax_constellation.set_title("Constellation")
|
ax_constellation.set_title("Constellation", loc="left", fontsize=15)
|
||||||
ax_constellation.grid(True, alpha=0.3)
|
ax_constellation.grid(True, alpha=0.2, linewidth=0.5)
|
||||||
ax_constellation.set_aspect("equal")
|
ax_constellation.set_aspect("equal")
|
||||||
|
|
||||||
if ax_psd is not None:
|
if ax_psd is not None:
|
||||||
|
|
@ -351,11 +351,11 @@ def view_simple_sig(
|
||||||
freqs = freqs + center_freq_hz
|
freqs = freqs + center_freq_hz
|
||||||
spectrum_db = 10 * np.log10(spectrum + 1e-12)
|
spectrum_db = 10 * np.log10(spectrum + 1e-12)
|
||||||
|
|
||||||
ax_psd.plot(freqs / 1e6, spectrum_db, color=COLORS["accent"], linewidth=1.0)
|
ax_psd.plot(freqs / 1e6, spectrum_db, color=COLORS["accent"], linewidth=0.8)
|
||||||
ax_psd.set_xlabel("Frequency (MHz)")
|
ax_psd.set_xlabel("Frequency (MHz)")
|
||||||
ax_psd.set_ylabel("Power (dB)")
|
ax_psd.set_ylabel("Power (dB)")
|
||||||
ax_psd.set_title("Power Spectral Density")
|
ax_psd.set_title("Power Spectral Density", loc="left", fontsize=15)
|
||||||
ax_psd.grid(True, alpha=0.3)
|
ax_psd.grid(True, alpha=0.2, linewidth=0.5)
|
||||||
|
|
||||||
if compact_mode:
|
if compact_mode:
|
||||||
ax1.set_xticks([])
|
ax1.set_xticks([])
|
||||||
|
|
@ -367,13 +367,20 @@ def view_simple_sig(
|
||||||
else:
|
else:
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
if show_title:
|
if show_title:
|
||||||
plt.subplots_adjust(top=0.92)
|
plt.subplots_adjust(top=0.9)
|
||||||
|
|
||||||
if saveplot:
|
if saveplot:
|
||||||
output_path, extension = set_path(output_path=output_path)
|
output_path, extension = set_path(output_path=output_path)
|
||||||
dpi_value = _set_dpi(fast_mode=fast_mode, labels_mode=labels_mode, extension=extension)
|
dpi_value = _set_dpi(fast_mode=fast_mode, labels_mode=labels_mode, extension=extension)
|
||||||
|
|
||||||
plt.savefig(output_path, dpi=dpi_value, bbox_inches="tight", facecolor="#0f172a", edgecolor="none")
|
plt.savefig(
|
||||||
|
output_path,
|
||||||
|
dpi=dpi_value,
|
||||||
|
bbox_inches="tight",
|
||||||
|
pad_inches=0.3,
|
||||||
|
facecolor=matplotlib.rcParams["savefig.facecolor"],
|
||||||
|
edgecolor=matplotlib.rcParams["savefig.edgecolor"],
|
||||||
|
)
|
||||||
print(f"Saved signal plot to {output_path}")
|
print(f"Saved signal plot to {output_path}")
|
||||||
return output_path
|
return output_path
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user