annotationsfix #19

Merged
madrigal merged 23 commits from annotationsfix into main 2026-04-20 15:57:23 -04:00
15 changed files with 26 additions and 328 deletions
Showing only changes of commit 5cfced8855 - Show all commits

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
"""
The annotations package contains tools and utilities for creating, managing, and processing annotations.
@ -54,9 +53,3 @@ from .parallel_signal_separator import (
from .qualify_slice import qualify_slice_from_annotations
from .signal_isolation import isolate_signal
from .threshold_qualifier import threshold_qualifier
=======
from .cusum_annotator import annotate_with_cusum
from .energy_detector import detect_signals_energy
from .parallel_signal_separator import split_recording_annotations
from .threshold_qualifier import threshold_qualifier
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4

View File

@ -1,8 +1,4 @@
<<<<<<< HEAD
from utils.data.annotation import Annotation
=======
from ria_toolkit_oss.datatypes.annotation import Annotation
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
# TODO figure out how to transfer labels in the merge case

View File

@ -3,11 +3,7 @@ from typing import Optional
import numpy as np
<<<<<<< HEAD
from utils.data import Annotation, Recording
=======
from ria_toolkit_oss.datatypes import Annotation, Recording
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
def annotate_with_cusum(
@ -28,11 +24,7 @@ def annotate_with_cusum(
changes between a low and high amplitude.
:param recording: A ``Recording`` object to annotate.
<<<<<<< HEAD
:type recording: ``utils.data.Recording``
=======
:type recording: ``ria_toolkit_oss.datatypes.Recording``
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
:param label: Label for the detected segments.
:type label: str
:param window_size: The length (in samples) of the moving average window.

View File

@ -11,11 +11,7 @@ from typing import Tuple
import numpy as np
from scipy.signal import filtfilt
<<<<<<< HEAD
from utils.data import Annotation, Recording
=======
from ria_toolkit_oss.datatypes import Annotation, Recording
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
def detect_signals_energy(
@ -77,13 +73,8 @@ def detect_signals_energy(
**Example**::
<<<<<<< HEAD
>>> from utils.io import load_recording
>>> from utils.annotations import detect_signals_energy
=======
>>> from ria.io import load_recording
>>> from ria_toolkit_oss.annotations import detect_signals_energy
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
>>> recording = load_recording("capture.sigmf")
>>> # Detect with NBW frequency bounds (default, best for real signals)
@ -239,7 +230,7 @@ def calculate_nominal_bandwidth(
**Example**::
>>> from utils.annotations import calculate_nominal_bandwidth
>>> from ria_toolkit_oss.annotations import calculate_nominal_bandwidth
>>> nbw, center = calculate_nominal_bandwidth(signal, sampling_rate=10e6)
>>> print(f"NBW: {nbw/1e6:.3f} MHz, Center: {center/1e6:.3f} MHz")
"""
@ -356,11 +347,7 @@ def annotate_with_obw(
**Example**::
<<<<<<< HEAD
>>> from utils.annotations import annotate_with_obw
=======
>>> from ria_toolkit_oss.annotations import annotate_with_obw
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
>>> annotated = annotate_with_obw(recording, label="signal_obw")
"""
signal = recording.data[0]

View File

@ -38,11 +38,7 @@ sub-annotations.
Example:
Two WiFi channels captured simultaneously:
<<<<<<< HEAD
>>> from utils.annotations import find_spectral_components
=======
>>> from ria_toolkit_oss.annotations import find_spectral_components
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
>>> # Detect the two distinct channels (returns relative frequencies)
>>> components = find_spectral_components(signal, sampling_rate=20e6)
>>> print(f"Found {len(components)} components")
@ -59,11 +55,7 @@ import numpy as np
from scipy import ndimage
from scipy import signal as scipy_signal
<<<<<<< HEAD
from utils.data import Annotation, Recording
=======
from ria_toolkit_oss.datatypes import Annotation, Recording
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
def find_spectral_components(
@ -119,13 +111,8 @@ def find_spectral_components(
**Example**::
<<<<<<< HEAD
>>> from utils.io import load_recording
>>> from utils.annotations import find_spectral_components
=======
>>> from ria.io import load_recording
>>> from ria_toolkit_oss.annotations import find_spectral_components
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
>>> recording = load_recording("capture.sigmf")
>>> segment = recording.data[0][start:end]
>>> # Components in relative (baseband) frequency
@ -254,13 +241,8 @@ def split_annotation_by_components(
**Example**::
<<<<<<< HEAD
>>> from utils.io import load_recording
>>> from utils.annotations import split_annotation_by_components
=======
>>> from ria.io import load_recording
>>> from ria_toolkit_oss.annotations import split_annotation_by_components
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
>>> recording = load_recording("capture.sigmf")
>>> # Original annotation spans multiple channels
>>> original = recording.annotations[0]
@ -387,13 +369,8 @@ def split_recording_annotations(
**Example**::
<<<<<<< HEAD
>>> from utils.io import load_recording
>>> from utils.annotations import split_recording_annotations
=======
>>> from ria.io import load_recording
>>> from ria_toolkit_oss.annotations import split_recording_annotations
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
>>> recording = load_recording("capture.sigmf")
>>> # Split all annotations
>>> split_rec = split_recording_annotations(recording)

View File

@ -1,10 +1,6 @@
import numpy as np
<<<<<<< HEAD
from utils.data import Recording
=======
from ria_toolkit_oss.datatypes import Recording
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
def qualify_slice_from_annotations(recording: Recording, slice_length: int):

View File

@ -1,13 +1,8 @@
import numpy as np
from scipy.signal import butter, lfilter
<<<<<<< HEAD
from utils.data.annotation import Annotation
from utils.data.recording import Recording
=======
from ria_toolkit_oss.datatypes.annotation import Annotation
from ria_toolkit_oss.datatypes.recording import Recording
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
def isolate_signal(recording: Recording, annotation: Annotation) -> Recording:

View File

@ -46,29 +46,17 @@ from typing import Optional
import numpy as np
<<<<<<< HEAD
from utils.data import Annotation, Recording
def _find_ranges(indices, window_size):
=======
from ria_toolkit_oss.datatypes import Annotation, Recording
def _find_ranges(indices, max_gap):
M

One of the pytests failed because the starting point for an annotation wasn't within the expected range. I checked what the annotation looked like (image attached), and the range does look to be too wide. I think we need to take another look at the threshold qualifier and how it determines the range

One of the pytests failed because the starting point for an annotation wasn't within the expected range. I checked what the annotation looked like (image attached), and the range does look to be too wide. I think we need to take another look at the threshold qualifier and how it determines the range
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
Groups individual indices into continuous temporal ranges.
Args:
indices: Array of indices where the signal exceeded a threshold.
<<<<<<< HEAD
window_size: Maximum gap allowed between indices to consider them part
of the same range.
=======
max_gap: Maximum gap allowed between indices to consider them part
of the same range.
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
Returns:
A list of (start, stop) tuples representing detected signal segments.
@ -77,30 +65,6 @@ def _find_ranges(indices, max_gap):
if len(indices) == 0:
return []
<<<<<<< HEAD
ranges = []
start = indices[0]
in_range = False
for i in range(1, len(indices)):
# If the gap between current and previous index is within window_size,
# keep the range alive.
if indices[i] - indices[i - 1] <= window_size:
if not in_range:
# Start a new range
start = indices[i - 1]
in_range = True
else:
# Gap is too large; close the current range if one was active.
if in_range:
ranges.append((start, indices[i - 1]))
in_range = False
# Ensure the final segment is captured if the loop ends while in_range.
if in_range:
ranges.append((start, indices[-1]))
=======
start = indices[0]
prev = indices[0]
ranges = []
@ -112,19 +76,10 @@ def _find_ranges(indices, max_gap):
prev = indices[i]
ranges.append((start, prev))
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
return ranges
<<<<<<< HEAD
def threshold_qualifier(
recording: Recording,
threshold: float,
window_size: Optional[int] = 1024,
label: Optional[str] = None,
annotation_type: Optional[str] = "standalone",
=======
def _expand_and_filter_ranges(
smoothed_power: np.ndarray,
initial_ranges: list[tuple[int, int]],
@ -231,7 +186,6 @@ def threshold_qualifier(
label: Optional[str] = None,
annotation_type: Optional[str] = "standalone",
channel: int = 0,
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
) -> Recording:
"""
Annotate a recording with bounding boxes for regions above a threshold.
@ -249,27 +203,15 @@ def threshold_qualifier(
Args:
recording: The Recording object containing IQ or real signal data.
threshold: Sensitivity multiplier (0.0 to 1.0) applied to max power.
<<<<<<< HEAD
window_size: Size of the smoothing filter and max gap for merging hits.
label: Custom string label for annotations.
annotation_type: Metadata string for the 'type' field in the annotation.
=======
window_size: Size of the smoothing filter in samples. Defaults to 1ms worth of samples.
label: Custom string label for annotations.
annotation_type: Metadata string for the 'type' field in the annotation.
channel: Index of the channel to annotate. Defaults to 0.
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
Returns:
A new Recording object populated with detected Annotations.
"""
# Extract signal and metadata
<<<<<<< HEAD
sample_data = recording.data[0]
sample_rate = recording.metadata["sample_rate"]
center_frequency = recording.metadata.get("center_frequency", 0)
=======
sample_data = recording.data[channel]
sample_rate = recording.metadata["sample_rate"]
center_frequency = recording.metadata.get("center_frequency", 0)
@ -277,69 +219,11 @@ def threshold_qualifier(
if window_size is None:
window_size = max(64, int(sample_rate * 0.001))
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
# --- 1. SIGNAL CONDITIONING ---
# Convert to power (Magnitude squared)
power_data = np.abs(sample_data) ** 2
smoothing_window = np.ones(window_size) / window_size
smoothed_power = np.convolve(power_data, smoothing_window, mode="same")
<<<<<<< HEAD
# Define thresholds based on the global peak of the smoothed signal
max_power = np.max(smoothed_power)
trigger_val = threshold * max_power # High threshold to trigger detection
boundary_val = (threshold / 2) * max_power # Low threshold to define signal edges
# --- 2. INITIAL DETECTION ---
# Identify indices that strictly exceed the high trigger
indices = np.where(smoothed_power > trigger_val)[0]
initial_ranges = _find_ranges(indices=indices, window_size=window_size)
annotations = []
threshold_base = min(sample_rate, len(sample_data))
for start, stop in initial_ranges:
if (stop - start) < (threshold_base * 0.01):
continue
# --- 3. HYSTERESIS (Boundary Expansion) ---
# Search backward from 'start' until power drops below the low boundary_val
true_start = start
while true_start > 0 and smoothed_power[true_start] > boundary_val:
true_start -= 1
# Search forward from 'stop' until power drops below the low boundary_val
true_stop = stop
while true_stop < len(smoothed_power) - 1 and smoothed_power[true_stop] > boundary_val:
true_stop += 1
# --- 4. SPECTRAL ANALYSIS (Frequency Detection) ---
signal_segment = sample_data[true_start:true_stop]
if len(signal_segment) > 0:
fft_data = np.abs(np.fft.fftshift(np.fft.fft(signal_segment)))
fft_freqs = np.fft.fftshift(np.fft.fftfreq(len(signal_segment), 1 / sample_rate))
# Determine frequency bounds where spectral energy is > 15% of segment peak
spectral_thresh = np.max(fft_data) * 0.15
sig_indices = np.where(fft_data > spectral_thresh)[0]
# Ensure the signal has some spectral width before annotating
if len(sig_indices) < 5:
continue
if len(sig_indices) > 0:
f_min, f_max = fft_freqs[sig_indices[0]], fft_freqs[sig_indices[-1]]
else:
# Default to middle half of bandwidth if no clear peaks found
f_min, f_max = -sample_rate / 4, sample_rate / 4
else:
f_min, f_max = -sample_rate / 4, sample_rate / 4
# --- 5. ANNOTATION GENERATION ---
if label is None:
label = f"{int(threshold*100)}%"
=======
group_gap_samples = _estimate_group_gap(sample_rate)
# Define thresholds using peak relative to baseline.
@ -442,7 +326,6 @@ def threshold_qualifier(
# --- 5. ANNOTATION GENERATION ---
ann_label = label if label is not None else f"{int(threshold*100)}%"
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
# Pack metadata for the UI/Downstream processing
comment_data = {
@ -459,11 +342,7 @@ def threshold_qualifier(
sample_count=true_stop - true_start,
freq_lower_edge=center_frequency + f_min,
freq_upper_edge=center_frequency + f_max,
<<<<<<< HEAD
label=label,
=======
label=ann_label,
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
comment=json.dumps(comment_data),
detail={"generator": "hysteresis_qualifier"},
)

View File

@ -12,7 +12,7 @@ from typing import Any, Iterator, Optional
import numpy as np
from numpy.typing import ArrayLike
from utils.data.annotation import Annotation
from ria_toolkit_oss.datatypes.annotation import Annotation
PROTECTED_KEYS = ["rec_id", "timestamp"]
@ -66,7 +66,7 @@ class Recording:
**Examples:**
>>> import numpy
>>> from utils.data import Recording, Annotation
>>> from ria_toolkit_oss.datatypes import Recording, Annotation
>>> # Create an array of complex samples, just 1s in this case.
>>> samples = numpy.ones(10000, dtype=numpy.complex64)
@ -305,7 +305,7 @@ class Recording:
Create a recording and add metadata:
>>> import numpy
>>> from utils.data import Recording
>>> from ria_toolkit_oss.datatypes import Recording
>>>
>>> samples = numpy.ones(10000, dtype=numpy.complex64)
>>> metadata = {
@ -360,7 +360,7 @@ class Recording:
Create a recording and update metadata:
>>> import numpy
>>> from utils.data import Recording
>>> from ria_toolkit_oss.datatypes import Recording
>>> samples = numpy.ones(10000, dtype=numpy.complex64)
>>> metadata = {
@ -414,7 +414,7 @@ class Recording:
Create a recording and add metadata:
>>> import numpy
>>> from utils.data import Recording
>>> from ria_toolkit_oss.datatypes import Recording
>>> samples = numpy.ones(10000, dtype=numpy.complex64)
>>> metadata = {
@ -455,7 +455,7 @@ class Recording:
Create a recording and view it as a plot in a .png image:
>>> import numpy
>>> from utils.data import Recording
>>> from ria_toolkit_oss.datatypes import Recording
>>> samples = numpy.ones(10000, dtype=numpy.complex64)
>>> metadata = {
@ -466,14 +466,14 @@ class Recording:
>>> recording = Recording(data=samples, metadata=metadata)
>>> recording.view()
"""
from utils.view import view_sig
from ria_toolkit_oss.view import view_sig
view_sig(recording=self, output_path=output_path, **kwargs)
def simple_view(self, **kwargs) -> None:
"""Create a plot of various signal visualizations as a PNG or SVG image.
:param kwargs: Keyword arguments passed on to utils.view.view_signal_simple.create_plots.
:param kwargs: Keyword arguments passed on to ria_toolkit_oss.view.view_signal_simple.create_plots.
:type: dict of keyword arguments
**Examples:**
@ -481,7 +481,7 @@ class Recording:
Create a recording and view it as a plot in a .png image:
>>> import numpy
>>> from utils.data import Recording
>>> from ria_toolkit_oss.datatypes import Recording
>>> samples = numpy.ones(10000, dtype=numpy.complex64)
>>> metadata = {
@ -492,7 +492,7 @@ class Recording:
>>> recording = Recording(data=samples, metadata=metadata)
>>> recording.simple_view()
"""
from utils.view.view_signal_simple import view_simple_sig
from ria_toolkit_oss.view.view_signal_simple import view_simple_sig
view_simple_sig(recording=self, **kwargs)
@ -530,7 +530,7 @@ class Recording:
>>> recording = Recording(data=samples, metadata=metadata)
>>> recording.view()
"""
from utils.io.recording import to_sigmf
from ria_toolkit_oss.io.recording import to_sigmf
to_sigmf(filename=filename, path=path, recording=self, overwrite=overwrite)
@ -565,7 +565,7 @@ class Recording:
>>> recording = Recording(data=samples, metadata=metadata)
>>> recording.to_npy()
"""
from utils.io.recording import to_npy
from ria_toolkit_oss.io.recording import to_npy
to_npy(recording=self, filename=filename, path=path, overwrite=overwrite)
@ -611,7 +611,7 @@ class Recording:
>>> recording = Recording(data=samples, metadata=metadata)
>>> recording.to_wav()
"""
from utils.io.recording import to_wav
from ria_toolkit_oss.io.recording import to_wav
return to_wav(
recording=self,
@ -661,7 +661,7 @@ class Recording:
>>> recording = Recording(data=samples, metadata=metadata)
>>> recording.to_blue()
"""
from utils.io.recording import to_blue
from ria_toolkit_oss.io.recording import to_blue
return to_blue(recording=self, filename=filename, path=path, data_format=data_format, overwrite=overwrite)

View File

@ -1,6 +1,6 @@
import numpy as np
from utils.signal.block_generator.block import Block
from utils.signal.block_generator.data_types import DataType
from ria_toolkit_oss.signal.block_generator.block import Block
from ria_toolkit_oss.signal.block_generator.data_types import DataType
class FrequencyUpConversion(Block):

View File

@ -11,7 +11,7 @@ def spectrogram(rec: Recording, thumbnail: bool = False) -> Figure:
"""Create a spectrogram for the recording.
:param rec: Signal to plot.
:type rec: utils.data.Recording
:type rec: ria_toolkit_oss.datatypes.Recording
:param thumbnail: Whether to return a small thumbnail version or full plot.
:type thumbnail: bool
@ -95,7 +95,7 @@ def iq_time_series(rec: Recording) -> Figure:
"""Create a time series plot of the real and imaginary parts of signal.
:param rec: Signal to plot.
:type rec: utils.data.Recording
:type rec: ria_toolkit_oss.datatypes.Recording
:return: Time series plot as a Plotly figure.
"""
@ -125,7 +125,7 @@ def frequency_spectrum(rec: Recording) -> Figure:
"""Create a frequency spectrum plot from the recording.
:param rec: Input signal to plot.
:type rec: utils.data.Recording
:type rec: ria_toolkit_oss.datatypes.Recording
:return: Frequency spectrum as a Plotly figure.
"""
@ -160,7 +160,7 @@ def constellation(rec: Recording) -> Figure:
"""Create a constellation plot from the recording.
:param rec: Input signal to plot.
:type rec: utils.data.Recording
:type rec: ria_toolkit_oss.datatypes.Recording
:return: Constellation as a Plotly figure.
"""

View File

@ -13,8 +13,8 @@ from scipy.fft import fft, fftshift
from scipy.signal import spectrogram
from scipy.signal.windows import hann
from utils.data.recording import Recording
from utils.view.tools import COLORS, decimate, extract_metadata_fields, set_path
from ria_toolkit_oss.datatypes.recording import Recording
from ria_toolkit_oss.view.tools import COLORS, decimate, extract_metadata_fields, set_path
def get_fft_size(plot_length):
@ -58,17 +58,6 @@ def view_annotations(
sample_rate, center_frequency, _ = extract_metadata_fields(recording.metadata)
annotations = recording.annotations
<<<<<<< HEAD
# 2. Setup Color Mapping (No more hardcoded yellow fallback!)
# available_colors = [
# COLORS.get("magenta", "magenta"),
# COLORS.get("accent", "cyan"),
# COLORS.get("light", "white"),
# "lime",
# ]
palette = ["#FF00FF", "#00FF00", "#00FFFF", "#FFFF00", "#FF8000"]
=======
# 2. Setup Color Mapping
available_colors = [
COLORS.get("magenta", "magenta"),
@ -78,7 +67,6 @@ def view_annotations(
]
palette = ["#2196F3", "#9C27B0", "#64B5F6", "#7B1FA2", "#5C6BC0", "#CE93D8", "#1565C0", "#7C4DFF"]
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
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)}
@ -87,11 +75,6 @@ def view_annotations(
complex_signal, NFFT=256, Fs=sample_rate, Fc=center_frequency, noverlap=128, cmap="twilight"
)
<<<<<<< HEAD
# 4. Draw Annotations
for annotation in annotations:
# --- DEFINING VARIABLES FIRST ---
=======
# 4. Draw Annotations (highest threshold % first so lower % renders on top)
def _threshold_sort_key(ann):
try:
@ -100,21 +83,13 @@ def view_annotations(
return 0
for annotation in sorted(annotations, key=_threshold_sort_key, reverse=True):
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
t_start = annotation.sample_start / sample_rate
t_width = annotation.sample_count / sample_rate
f_start = annotation.freq_lower_edge
f_height = annotation.freq_upper_edge - annotation.freq_lower_edge
<<<<<<< HEAD
# Look up the color for this specific label
ann_color = label_to_color.get(annotation.label, "gray")
# Draw the Rectangle
=======
ann_color = label_to_color.get(annotation.label, "gray")
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
rect = plt.Rectangle(
(t_start, f_start), t_width, f_height, linewidth=1.5, edgecolor=ann_color, facecolor="none", alpha=0.8
)
@ -130,11 +105,7 @@ def view_annotations(
ax.set_title(title, fontsize=title_fontsize, pad=20)
ax.set_xlabel("Time (s)", fontsize=12)
ax.set_ylabel("Frequency (MHz)", fontsize=12)
<<<<<<< HEAD
ax.grid(alpha=0.1) # Add faint grid
=======
ax.grid(alpha=0.1)
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
output_path, _ = set_path(output_path=output_path)
plt.savefig(output_path, dpi=dpi, bbox_inches="tight")

View File

@ -12,8 +12,8 @@ import numpy as np
from scipy.fft import fft, fftshift
from scipy.signal.windows import hann
from utils.data.recording import Recording
from utils.view.tools import COLORS, decimate, extract_metadata_fields, set_path
from ria_toolkit_oss.datatypes.recording import Recording
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

@ -11,13 +11,8 @@ from ria_toolkit_oss.annotations import (
split_recording_annotations,
threshold_qualifier,
)
<<<<<<< HEAD
from ria_toolkit_oss.data import Annotation
from ria_toolkit_oss.data.recording import Recording
=======
from ria_toolkit_oss.datatypes import Annotation
from ria_toolkit_oss.datatypes.recording import Recording
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
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
@ -55,15 +50,6 @@ def detect_input_format(filepath):
def determine_output_path(input_path, output_path, fmt, quiet, overwrite):
input_path = Path(input_path)
<<<<<<< HEAD
if output_path:
target = Path(output_path)
final_path = target
else:
annotated_name = f"{input_path.stem}_annotated"
target = input_path.with_name(f"{annotated_name}{input_path.suffix}")
=======
input_is_annotated = input_path.stem.endswith("_annotated")
if output_path:
@ -73,7 +59,6 @@ def determine_output_path(input_path, output_path, fmt, quiet, overwrite):
target = input_path
else:
target = input_path.with_name(f"{input_path.stem}_annotated{input_path.suffix}")
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
if fmt == "sigmf":
final_path = normalize_sigmf_path(target)
@ -84,15 +69,10 @@ def determine_output_path(input_path, output_path, fmt, quiet, overwrite):
if not quiet:
click.echo(f"Saving to: {final_path}")
<<<<<<< HEAD
if final_path.exists() and not overwrite and final_path != input_path:
click.echo(f"Error: {final_path} already exists. Use --overwrite to replace it.", err=True)
=======
# Always allow writing to _annotated files; guard against overwriting originals
target_is_annotated = final_path.stem.endswith("_annotated")
if final_path.exists() and not target_is_annotated and final_path != input_path:
click.echo(f"Error: {final_path} is not an annotated file and cannot be overwritten.", err=True)
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
return None
return final_path
@ -250,13 +230,8 @@ def list(input, verbose):
\b
Examples:
<<<<<<< HEAD
utils annotate list recording.sigmf-data
utils annotate list signal.npy --verbose
=======
ria annotate list recording.sigmf-data
ria annotate list signal.npy --verbose
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
try:
recording = load_recording(input)
@ -324,13 +299,8 @@ def add(input, start, count, label, freq_lower, freq_upper, comment, annotation_
\b
Examples:
<<<<<<< HEAD
utils annotate add file.npy --start 1000 --count 500 --label wifi
utils annotate add signal.sigmf-data --start 0 --count 1000 --label burst --comment "Strong signal"
=======
ria annotate add file.npy --start 1000 --count 500 --label wifi
ria annotate add signal.sigmf-data --start 0 --count 1000 --label burst --comment "Strong signal"
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
try:
recording = load_recording(input)
@ -412,21 +382,12 @@ def add(input, start, count, label, freq_lower, freq_upper, comment, annotation_
def remove(input, index, output, overwrite, quiet):
"""Remove annotation by index.
<<<<<<< HEAD
Use 'utils annotate list' to see annotation indices.
\b
Examples:
utils annotate remove signal.sigmf-data 2
utils annotate remove file.npy 0
=======
Use 'ria annotate list' to see annotation indices.
\b
Examples:
ria annotate remove signal.sigmf-data 2
ria annotate remove file.npy 0
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
try:
recording = load_recording(input)
@ -475,13 +436,8 @@ def clear(input, output, overwrite, force, quiet):
\b
Examples:
<<<<<<< HEAD
utils annotate clear signal.sigmf-data
utils annotate clear file.npy --force
=======
ria annotate clear signal.sigmf-data
ria annotate clear file.npy --force
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
try:
recording = load_recording(input)
@ -576,17 +532,10 @@ def energy(
\b
Examples:
<<<<<<< HEAD
utils annotate energy capture.sigmf-data --label burst
utils annotate energy signal.npy --threshold 1.5 --min-distance 10000
utils annotate energy signal.sigmf-data --freq-method obw
utils annotate energy signal.sigmf-data --freq-method full-detected
=======
ria annotate energy capture.sigmf-data --label burst
ria annotate energy signal.npy --threshold 1.5 --min-distance 10000
ria annotate energy signal.sigmf-data --freq-method obw
ria annotate energy signal.sigmf-data --freq-method full-detected
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
try:
@ -662,13 +611,8 @@ def cusum(input, label, min_duration, window_size, tolerance, annotation_type, o
\b
Examples:
<<<<<<< HEAD
utils annotate cusum signal.sigmf-data --min-duration 5.0
utils annotate cusum data.npy --min-duration 10.0 --label state
=======
ria annotate cusum signal.sigmf-data --min-duration 5.0
ria annotate cusum data.npy --min-duration 10.0 --label state
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
try:
recording = load_recording(input)
@ -714,11 +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")
<<<<<<< HEAD
@click.option("--window-size", type=int, default=1024, help="Smoothing window size")
=======
@click.option("--window-size", type=int, default=None, help="Smoothing window size in samples (default: 1ms at recording sample rate)")
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
@click.option(
"--type",
"annotation_type",
@ -726,18 +666,11 @@ def cusum(input, label, min_duration, window_size, tolerance, annotation_type, o
default="standalone",
help="Annotation type",
)
<<<<<<< HEAD
@click.option("--output", "-o", type=click.Path(), help="Output file path")
@click.option("--overwrite", is_flag=True, help="Overwrite input file (non-SigMF only)")
@click.option("--quiet", is_flag=True, help="Quiet mode")
def threshold(input, threshold, label, window_size, annotation_type, output, overwrite, quiet):
=======
@click.option("--channel", type=int, default=0, help="Channel index to annotate (default: 0)")
@click.option("--output", "-o", type=click.Path(), help="Output file path")
@click.option("--overwrite", is_flag=True, help="Overwrite input file (non-SigMF only)")
@click.option("--quiet", is_flag=True, help="Quiet mode")
def threshold(input, threshold, label, window_size, annotation_type, channel, output, overwrite, quiet):
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""Auto-detect signals using threshold method.
Detects samples above a percentage of maximum magnitude. Best for simple
@ -745,13 +678,8 @@ def threshold(input, threshold, label, window_size, annotation_type, channel, ou
\b
Examples:
<<<<<<< HEAD
utils annotate threshold signal.sigmf-data --threshold 0.7 --label wifi
utils annotate threshold data.npy --threshold 0.5 --window-size 2048
=======
ria annotate threshold signal.sigmf-data --threshold 0.7 --label wifi
ria annotate threshold data.npy --threshold 0.5 --window-size 2048
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
if not (0.0 <= threshold <= 1.0):
raise click.ClickException(f"--threshold must be between 0.0 and 1.0, got {threshold}")
@ -766,12 +694,8 @@ def threshold(input, threshold, label, window_size, annotation_type, channel, ou
if not quiet:
click.echo("\nDetecting signals using threshold qualifier...")
click.echo(f" Threshold: {threshold * 100:.1f}% of max magnitude")
<<<<<<< HEAD
click.echo(f" Window size: {window_size} samples")
=======
click.echo(f" Window size: {'auto (1ms)' if window_size is None else f'{window_size} samples'}")
click.echo(f" Channel: {channel}")
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
try:
initial_count = len(recording.annotations)
@ -781,10 +705,7 @@ def threshold(input, threshold, label, window_size, annotation_type, channel, ou
window_size=window_size,
label=label,
annotation_type=annotation_type,
<<<<<<< HEAD
=======
channel=channel,
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
)
added = len(recording.annotations) - initial_count
@ -833,17 +754,10 @@ def separate(input, indices, nfft, noise_threshold_db, min_component_bw, output,
\b
Examples:
<<<<<<< HEAD
utils annotate separate capture.sigmf-data
utils annotate separate signal.npy --indices 0,1,2
utils annotate separate data.sigmf-data --noise-threshold-db -70
utils annotate separate signal.npy --min-component-bw 100000
=======
ria annotate separate capture.sigmf-data
ria annotate separate signal.npy --indices 0,1,2
ria annotate separate data.sigmf-data --noise-threshold-db -70
ria annotate separate signal.npy --min-component-bw 100000
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
"""
try:

View File

@ -2,10 +2,8 @@
"""
This module contains all the CLI bindings for the ria package.
"""
<<<<<<< HEAD
=======
>>>>>>> 2bb2d9d5a780dbc17172135a5a1f10eba14b1af4
from .annotate import annotate
from .capture import capture
from .combine import combine