ria-toolkit-oss/src/ria_toolkit_oss/annotations/annotation_transforms.py
F fordg1 11d9532b5c Port annotation system from utils and fix ria package imports
Annotations package (new):
- Add threshold_qualifier with 3-pass hysteresis detector (Pass 1: strong
  bursts, Pass 2: weak residual bursts, Pass 3: macro-window faint burst
  detection), auto window_size scaled to 1ms, channel selection, and
  stable noise_floor baseline throughout
- Add energy_detector, cusum_annotator, parallel_signal_separator,
  qualify_slice, signal_isolation, annotation_transforms
- Add __init__.py exporting the four functions used by the CLI
- Fix all imports from utils.data → ria_toolkit_oss.datatypes

CLI annotate command (new):
- Port full annotate CLI from utils including list, add, remove, clear,
  energy, cusum, threshold, and separate subcommands
- Fix imports from utils.* → ria_toolkit_oss.* and utils_cli.* →
  ria_toolkit_oss_cli.*
- Safe overwrite logic: _annotated files always writable, originals
  protected; --overwrite writes in-place only on _annotated inputs

CLI view command:
- Add 'annotations' as a valid --type, wiring view_annotations from
  view_signal

view_signal.py:
- Add view_annotations function with blue/purple alternating palette and
  threshold %-sorted drawing order (lower % renders on top)

recording.py (datatypes):
- Fix lazy imports in to_wav() and to_blue() from utils.io → ria_toolkit_oss.io

io/recording.py:
- Add compatibility shim in from_npy to remap utils.data.annotation.Annotation
  to ria_toolkit_oss.datatypes.annotation.Annotation when loading .npy files
  pickled by the utils package
2026-03-31 13:34:00 -04:00

56 lines
1.7 KiB
Python

from ria_toolkit_oss.datatypes.annotation import Annotation
# TODO figure out how to transfer labels in the merge case
def remove_contained_boxes(annotations: list[Annotation]):
"""
Remove all annotations (bounding boxes) that are entirely contained within other boxes in the list.
:param annotations: A list of Annotation objects.
:type annotations: list[Annotation]
:returns: A new list of Annotation objects.
:rtype: list[Annotation]"""
output_boxes = []
for i in range(len(annotations)):
contained = False
for j in range(len(annotations)):
if i != j and is_annotation_contained(annotations[i], annotations[j]):
contained = True
break
if not contained:
output_boxes.append(annotations[i])
return output_boxes
def is_annotation_contained(inner: Annotation, outer: Annotation) -> bool:
"""
Check if an annotation box is entirely contained within another annotation bounding box.
:param inner: The inner box.
:type inner: Annotation.
:param outer: The outer box.
:type outer: Annotation.
:returns: True if inner is within outer, false otherwise.
:rtype: bool
"""
inner_sample_stop = inner.sample_start + inner.sample_count
outer_sample_stop = outer.sample_start + outer.sample_count
if inner.sample_start > outer.sample_start and inner_sample_stop < outer_sample_stop:
if inner.freq_lower_edge > outer.freq_lower_edge and inner.freq_upper_edge < outer.freq_upper_edge:
return True
return False
def merge_annotations(annotations: list[Annotation], overlap_threshold) -> list[Annotation]:
raise NotImplementedError