Compare commits

..

No commits in common. "cfa1da9f4da2bc6ac0dfcdfafcd59abd64e6e858" and "5d909c4a22ab1787f9b559ce35a530dc3f8040da" have entirely different histories.

22 changed files with 1081 additions and 1243 deletions

2168
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
"""
The annotations package contains tools and utilities for creating, managing, and processing annotations.

View File

@ -167,10 +167,7 @@ def _estimate_spectral_bounds(signal_segment: np.ndarray, sample_rate: float) ->
else:
runs = _find_ranges(sig_indices, max_gap=max(1, spectral_smooth_bins // 2))
peak_idx = int(np.argmax(smoothed_fft))
lo_idx, hi_idx = min(
runs,
key=lambda run: 0 if run[0] <= peak_idx <= run[1] else min(abs(run[0] - peak_idx), abs(run[1] - peak_idx)),
)
lo_idx, hi_idx = min(runs, key=lambda run: 0 if run[0] <= peak_idx <= run[1] else min(abs(run[0] - peak_idx), abs(run[1] - peak_idx)))
# Prevent extremely narrow tone boxes from collapsing to just a few bins.
min_total_bw_hz = 20_000.0

View File

@ -956,10 +956,8 @@ def get_result_sizes( # noqa: C901 # TODO: Simplify function
# Check that each class that will be augmented does not already suffice target_size
for cls_name, target_size_value in zip(classes_to_augment, target_size):
if class_sizes[cls_name] >= target_size_value:
raise ValueError(
f"""target_size of {target_size_value} is already sufficed for current size of
{class_sizes[cls_name]} for class: {cls_name}"""
)
raise ValueError(f"""target_size of {target_size_value} is already sufficed for current size of
{class_sizes[cls_name]} for class: {cls_name}""")
for index, class_name in enumerate(classes_to_augment):
result_sizes[class_name] = target_size[index]

View File

@ -136,9 +136,9 @@ def from_npy(file: os.PathLike | str, legacy: bool = False) -> Recording:
annotations = []
except ModuleNotFoundError:
# File was pickled with utils.data.Annotation — remap to ria_toolkit_oss
import pickle
import sys
import types
import ria_toolkit_oss.datatypes.annotation as _ann_mod
utils_shim = types.ModuleType("utils")

View File

@ -474,10 +474,8 @@ class Blade(SDR):
if gain_mode == "relative":
if gain > 0:
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This sets \
the gain relative to the maximum possible gain."
)
raise SDRParameterError("When gain_mode = 'relative', gain must be < 0. This sets \
the gain relative to the maximum possible gain.")
else:
abs_gain = rx_gain_max + gain
else:
@ -550,10 +548,8 @@ class Blade(SDR):
if gain_mode == "relative":
if gain > 0:
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain."
)
raise SDRParameterError("When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain.")
else:
abs_gain = tx_gain_max + gain
else:

View File

@ -172,10 +172,8 @@ class HackRF(SDR):
tx_gain_max = 47
if gain_mode == "relative":
if gain > 0:
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This \
sets the gain relative to the maximum possible gain."
)
raise SDRParameterError("When gain_mode = 'relative', gain must be < 0. This \
sets the gain relative to the maximum possible gain.")
else:
abs_gain = tx_gain_max + gain
else:

View File

@ -274,20 +274,16 @@ class Pluto(SDR):
data = [self._convert_tx_samples(samples), self._convert_tx_samples(samples)]
else:
if len(recording) > 2:
warnings.warn(
"More recordings were provided than channels in the Pluto. \
Only the first two recordings will be used"
)
warnings.warn("More recordings were provided than channels in the Pluto. \
Only the first two recordings will be used")
sample0 = self._convert_tx_samples(recording.data[0])
sample1 = self._convert_tx_samples(recording.data[1])
data = [sample0, sample1]
elif isinstance(recording, list):
if len(recording) > 2:
warnings.warn(
"More recordings were provided than channels in the Pluto. \
Only the first two recordings will be used"
)
warnings.warn("More recordings were provided than channels in the Pluto. \
Only the first two recordings will be used")
if isinstance(recording[0], np.ndarray):
data = [self._convert_tx_samples(recording[0]), self._convert_tx_samples(recording[1])]
@ -427,10 +423,8 @@ class Pluto(SDR):
if gain_mode == "relative":
if gain > 0:
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This sets \
the gain relative to the maximum possible gain."
)
raise SDRParameterError("When gain_mode = 'relative', gain must be < 0. This sets \
the gain relative to the maximum possible gain.")
else:
abs_gain = rx_gain_max + gain
else:
@ -540,10 +534,8 @@ class Pluto(SDR):
if gain_mode == "relative":
if gain > 0:
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain."
)
raise SDRParameterError("When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain.")
else:
abs_gain = tx_gain_max + gain
else:

View File

@ -131,19 +131,15 @@ class RTLSDR(SDR):
if gain_mode == "relative":
if gain > 0:
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain."
)
raise SDRParameterError("When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain.")
target_gain = max_gain + gain
else:
target_gain = gain
if target_gain < min_gain or target_gain > max_gain:
print(
f"Requested gain {target_gain} dB out of range;\
clamping to valid span {min_gain}-{max_gain} dB."
)
print(f"Requested gain {target_gain} dB out of range;\
clamping to valid span {min_gain}-{max_gain} dB.")
target_gain = min(max(target_gain, min_gain), max_gain)
target_gain = min(available_gains, key=lambda g: abs(g - target_gain))

View File

@ -392,10 +392,8 @@ class ThinkRF(SDR):
actual_sample_rate = self.BASE_SAMPLE_RATE / decimation
if abs(actual_sample_rate - requested_sample_rate) > 1e3: # More than 1 kHz difference
print(
f"ThinkRF: Requested {requested_sample_rate/1e6:.2f} MS/s → \
Using decimation={decimation} ({actual_sample_rate/1e6:.2f} MS/s)"
)
print(f"ThinkRF: Requested {requested_sample_rate/1e6:.2f} MS/s → \
Using decimation={decimation} ({actual_sample_rate/1e6:.2f} MS/s)")
return decimation, actual_sample_rate

View File

@ -148,10 +148,8 @@ class USRP(SDR):
gain_range = self.usrp.get_rx_gain_range()
if gain_mode == "relative":
if gain > 0:
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain."
)
raise SDRParameterError("When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain.")
else:
# set gain relative to max
abs_gain = gain_range.stop() + gain
@ -356,10 +354,8 @@ class USRP(SDR):
gain_range = self.usrp.get_tx_gain_range()
if gain_mode == "relative":
if gain > 0:
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain."
)
raise SDRParameterError("When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain.")
else:
# set gain relative to max
abs_gain = gain_range.stop() + gain

View File

@ -37,10 +37,8 @@ class Add(RecordableBlock, ProcessBlock):
samples = block.get_samples(num_samples)
if len(samples) != num_samples:
raise ValueError(
f"Block {self.__class__.__name__} requested {num_samples} \
from block {block.__class__.__name__} but got {len(samples)}."
)
raise ValueError(f"Block {self.__class__.__name__} requested {num_samples} \
from block {block.__class__.__name__} but got {len(samples)}.")
return samples

View File

@ -1,5 +1,4 @@
import numpy as np
from ria_toolkit_oss.signal.block_generator.block import Block
from ria_toolkit_oss.signal.block_generator.data_types import DataType

View File

@ -23,11 +23,9 @@ class ProcessBlock(Block, ABC):
)
elif not all(isinstance(item, Block) for item in input):
raise ValueError(
f"Invalid input to block '{self.__class__.__name__}'. \
raise ValueError(f"Invalid input to block '{self.__class__.__name__}'. \
Expected a list of Block objects but got \
{'[' + ',' .join(f'{item.__class__.__name__}({repr(item)})' for item in input) + ']'}"
)
{'[' + ',' .join(f'{item.__class__.__name__}({repr(item)})' for item in input) + ']'}")
elif len(input) != len(self.input_type):
raise ValueError(

View File

@ -20,10 +20,8 @@ class RecordableBlock(Block, Recordable):
:raises ValueError: If the number of samples is incorrect."""
samples = self.get_samples(num_samples)
if len(samples) != num_samples:
raise ValueError(
f"Error in block {self.__class__.__name__} record(). \
Requested {num_samples} samples but got {len(samples)}"
)
raise ValueError(f"Error in block {self.__class__.__name__} record(). \
Requested {num_samples} samples but got {len(samples)}")
metadata = self._get_metadata()
return Recording(data=samples, metadata=metadata)

View File

@ -39,9 +39,7 @@ class RecordingSource(SourceBlock, RecordableBlock):
:raises ValueError: If num_samples is greater than the recording length.
"""
if num_samples - 1 >= self.recording.data.shape[1]:
raise ValueError(
f"{num_samples} samples requested from recording source with \
{self.recording.data.shape[1]} samples available."
)
raise ValueError(f"{num_samples} samples requested from recording source with \
{self.recording.data.shape[1]} samples available.")
return self.recording.data[0, 0:num_samples]

View File

@ -610,10 +610,8 @@ def cut_out( # noqa: C901 # TODO: Simplify function
raise ValueError("signal must be CxN complex.")
if fill_type not in {"zeros", "ones", "low-snr", "avg-snr", "high-snr"}:
raise UserWarning(
"""fill_type must be "zeros", "ones", "low-snr", "avg-snr", or "high-snr",
"ones" has been selected by default"""
)
raise UserWarning("""fill_type must be "zeros", "ones", "low-snr", "avg-snr", or "high-snr",
"ones" has been selected by default""")
if max_section_size < 1 or max_section_size >= n:
raise ValueError("max_section_size must be at least 1 and must be less than the length of signal.")

View File

@ -4,6 +4,7 @@ import textwrap
from typing import Optional
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
import numpy as np
from matplotlib import gridspec
from matplotlib.patches import Patch
@ -13,12 +14,7 @@ from scipy.signal import spectrogram
from scipy.signal.windows import hann
from ria_toolkit_oss.datatypes.recording import Recording
from ria_toolkit_oss.view.tools import (
COLORS,
decimate,
extract_metadata_fields,
set_path,
)
from ria_toolkit_oss.view.tools import COLORS, decimate, extract_metadata_fields, set_path
def get_fft_size(plot_length):
@ -63,6 +59,13 @@ def view_annotations(
annotations = recording.annotations
# 2. Setup Color Mapping
available_colors = [
COLORS.get("magenta", "magenta"),
COLORS.get("accent", "cyan"),
COLORS.get("light", "white"),
"lime",
]
palette = ["#2196F3", "#9C27B0", "#64B5F6", "#7B1FA2", "#5C6BC0", "#CE93D8", "#1565C0", "#7C4DFF"]
unique_labels = sorted(list(set(ann.label for ann in annotations if ann.label)))
label_to_color = {label: palette[i % len(palette)] for i, label in enumerate(unique_labels)}

View File

@ -13,12 +13,7 @@ from scipy.fft import fft, fftshift
from scipy.signal.windows import hann
from ria_toolkit_oss.datatypes.recording import Recording
from ria_toolkit_oss.view.tools import (
COLORS,
decimate,
extract_metadata_fields,
set_path,
)
from ria_toolkit_oss.view.tools import COLORS, decimate, extract_metadata_fields, set_path
def _add_annotations(annotations, compact_mode, show_labels, sample_rate_hz, center_freq_hz, ax2):

View File

@ -14,10 +14,7 @@ from ria_toolkit_oss.annotations import (
from ria_toolkit_oss.datatypes import Annotation
from ria_toolkit_oss.datatypes.recording import Recording
from ria_toolkit_oss.io import load_recording, to_blue, to_npy, to_sigmf, to_wav
from ria_toolkit_oss_cli.ria_toolkit_oss.common import (
format_frequency,
format_sample_count,
)
from ria_toolkit_oss_cli.ria_toolkit_oss.common import format_frequency, format_sample_count
def normalize_sigmf_path(filepath):
@ -661,12 +658,7 @@ def cusum(input, label, min_duration, window_size, tolerance, annotation_type, o
@click.argument("input", type=click.Path(exists=True))
@click.option("--threshold", type=float, required=True, help="Threshold (0.0-1.0, fraction of max magnitude)")
@click.option("--label", type=str, default=None, help="Annotation label")
@click.option(
"--window-size",
type=int,
default=None,
help="Smoothing window size in samples (default: 1ms at recording sample rate)",
)
@click.option("--window-size", type=int, default=None, help="Smoothing window size in samples (default: 1ms at recording sample rate)")
@click.option(
"--type",
"annotation_type",

View File

@ -34,12 +34,13 @@ VISUALIZATION_TYPES = {
"spines",
],
},
"annotations": {
"annotations": {
"function": view_annotations,
"description": "Annotation-focused spectrogram view",
"options": ["channel", "dark"],
},
"channels": {"function": view_channels, "description": "Multi-channel IQ and spectrogram view", "options": []},
"annotations": {"function": view_annotations, "description": "Annotated spectrogram view", "options": ["channel", "dark"]},
}