Compare commits

..

No commits in common. "e5a3d327e5bf37c1acac9ec72f5d0b0cad1fb927" and "9a304faa00b3aaddb79149845ec7e8a6af55946b" have entirely different histories.

6 changed files with 55 additions and 90 deletions

View File

@ -414,18 +414,12 @@ 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/recordings/qam64_35.png .. figure:: ../images/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:

View File

@ -1,5 +1,5 @@
Data Package (ria_toolkit_oss.data) Datatypes Package (ria_toolkit_oss.data)
======================================= =============================================
.. |br| raw:: html .. |br| raw:: html

View File

@ -3,12 +3,11 @@ 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, ticker from matplotlib import gridspec
from matplotlib.patches import Patch from matplotlib.patches import Patch
from PIL import Image, UnidentifiedImageError from PIL import Image
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
@ -186,7 +185,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] = 25, title_fontsize: Optional[int] = 35,
subtitle_fontsize: Optional[int] = 15, subtitle_fontsize: Optional[int] = 15,
) -> None: ) -> None:
""" """
@ -231,24 +230,11 @@ 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 = 3 * (plot_spectrogram) + 2 * (iq + frequency) + 3 * (constellation or metadata or logo) subplot_height = 2 * (plot_spectrogram + 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")
@ -266,8 +252,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 + 3, :]) spec_ax = plt.subplot(gs[plot_y_indx : plot_y_indx + 2, :])
plot_y_indx = plot_y_indx + 3 plot_y_indx = plot_y_indx + 2
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(
@ -294,12 +280,7 @@ def view_sig(
) )
set_spines(spec_ax, spines) set_spines(spec_ax, spines)
spec_ax.set_title("Spectrogram", loc="left", fontsize=subtitle_fontsize) spec_ax.set_title("Spectrogram", loc="center", 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, :])
@ -310,13 +291,12 @@ 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(True, alpha=0.2, linewidth=0.5) iq_ax.grid(False)
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", loc="left", fontsize=subtitle_fontsize) iq_ax.set_title("IQ Sample Plot", 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:
@ -330,12 +310,10 @@ 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) / 1e6 freqs = np.linspace(-sample_rate / 2, sample_rate / 2, len(complex_signal[:plot_length])) + center_frequency
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.grid(True, alpha=0.2, linewidth=0.5) freq_ax.set_title("Frequency Spectrum (Windowed FFT)", fontsize=subtitle_fontsize)
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:
@ -348,7 +326,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", loc="left", fontsize=subtitle_fontsize) const_ax.set_title("Constellation", fontsize=subtitle_fontsize)
const_ax.set_aspect("equal") const_ax.set_aspect("equal")
if not spines: if not spines:
@ -397,8 +375,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, UnidentifiedImageError, OSError) as exc: except FileNotFoundError:
print(f"Warning, could not load logo image: {logo_path}. Reason: {exc}") print(f"Warning, {logo_path} not found.")
fig.subplots_adjust( fig.subplots_adjust(
left=0.1, # Left margin left=0.1, # Left margin

View File

@ -119,19 +119,24 @@ 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 = 15 title_font = 12
label_font = 10 label_font = 10
matplotlib.rcParams.update( matplotlib.rcParams.update(
{ {
"figure.facecolor": "#161616", "figure.facecolor": "#0f172a",
"axes.facecolor": "#161616", "axes.facecolor": "#1e293b",
"savefig.facecolor": "#161616", "axes.edgecolor": COLORS["muted"],
"savefig.edgecolor": "#161616", "axes.labelcolor": COLORS["light"],
"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 + 4, "figure.titlesize": title_font + 2,
"legend.frameon": False, "legend.frameon": False,
"legend.facecolor": "none", "legend.facecolor": "none",
"xtick.labelsize": base_font, "xtick.labelsize": base_font,
@ -189,7 +194,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 Plot", title: Optional[str] = "Signal",
): ):
""" """
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.
@ -232,7 +237,7 @@ def view_simple_sig(
spec_signal = signal spec_signal = signal
if compact_mode: if compact_mode:
fig, (ax2, ax1) = plt.subplots(2, 1, figsize=(12, 6), gridspec_kw={"height_ratios": [5, 1]}) fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 6), gridspec_kw={"height_ratios": [1, 5]})
show_title = False show_title = False
show_labels = False show_labels = False
ax_constellation = ax_psd = None ax_constellation = ax_psd = None
@ -248,24 +253,25 @@ def view_simple_sig(
ax_psd = None ax_psd = None
else: else:
if constellation_mode: if constellation_mode:
fig, ((ax2, ax1), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12)) fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
ax_constellation, ax_psd = ax3, ax4 ax_constellation, ax_psd = ax3, ax4
else: else:
fig, (ax2, ax1) = plt.subplots(2, 1, figsize=(14, 10)) fig, (ax1, ax2) = 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=25) fig.suptitle(title, fontsize=16, color=COLORS["light"], y=0.96)
fig.patch.set_facecolor(matplotlib.rcParams["figure.facecolor"]) fig.patch.set_facecolor("#0f172a")
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.6, alpha=0.8, label="I") ax1.plot(t_s, display_signal.real, color=COLORS["purple"], linewidth=0.8, alpha=0.8, label="I")
ax1.plot(t_s, display_signal.imag, color=COLORS["magenta"], linewidth=0.6, alpha=0.8, label="Q") ax1.plot(t_s, display_signal.imag, color=COLORS["magenta"], linewidth=0.8, alpha=0.8, label="Q")
ax1.grid(True, alpha=0.2, linewidth=0.5) ax1.set_xlim(0, total_duration_s)
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)
@ -279,7 +285,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)
ax1.set_xlim(ax2.get_xlim()) ax2.set_xlim(0, total_duration_s)
if show_labels: if show_labels:
if horizontal_mode: if horizontal_mode:
@ -288,26 +294,20 @@ 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"IQ Sample Plot - {sdr} SDR", loc="left", pad=10, fontsize=15) ax1.set_title(f"Time Series - {sdr} SDR", loc="left", pad=10)
ax1.legend(loc="upper right", fontsize=10) ax1.legend(loc="upper right")
ax2.set_ylabel("Frequency (MHz)") ax2.set_ylabel("Frequency (Hz)")
ax2.set_title( ax2.set_title(
f"Spectrogram - {center_freq_hz / 1e6:.1f} MHz ± {sample_rate_hz / 2e6:.1f} MHz", loc="left", pad=10, fontsize=15 f"Spectrogram - {center_freq_hz / 1e6:.1f} MHz ± {sample_rate_hz / 2e6:.1f} MHz", loc="left", pad=10
)
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("IQ Sample Plot", loc="left", pad=10, fontsize=15) ax1.set_title("Time Series", loc="left", pad=10)
ax1.legend(loc="upper right", fontsize=10) ax1.legend(loc="upper right", fontsize=8)
ax2.set_xlabel("Time (s)") ax2.set_title("Spectrogram", loc="left", pad=10)
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", loc="left", fontsize=15) ax_constellation.set_title("Constellation")
ax_constellation.grid(True, alpha=0.2, linewidth=0.5) ax_constellation.grid(True, alpha=0.3)
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=0.8) ax_psd.plot(freqs / 1e6, spectrum_db, color=COLORS["accent"], linewidth=1.0)
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", loc="left", fontsize=15) ax_psd.set_title("Power Spectral Density")
ax_psd.grid(True, alpha=0.2, linewidth=0.5) ax_psd.grid(True, alpha=0.3)
if compact_mode: if compact_mode:
ax1.set_xticks([]) ax1.set_xticks([])
@ -367,20 +367,13 @@ def view_simple_sig(
else: else:
plt.tight_layout() plt.tight_layout()
if show_title: if show_title:
plt.subplots_adjust(top=0.9) plt.subplots_adjust(top=0.92)
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( plt.savefig(output_path, dpi=dpi_value, bbox_inches="tight", facecolor="#0f172a", edgecolor="none")
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