annotationsfix #19
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
>>>>>>> 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"},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user