Source code for crowsetta.annotation

"""A class to represent annotations for a single file."""
from __future__ import annotations

import reprlib
from pathlib import Path
from typing import Optional

import crowsetta

from .bbox import BBox
from .sequence import Sequence
from .typing import PathLike


[docs] class Annotation: """A class to represent annotations for a single file. The annotations can be one of two types: a single sequence, or a list of bounding boxes. Attributes ---------- annot_path : str, pathlib.Path Path to file from which annotations were loaded. notated_path : str, pathlib.Path Path to file that ``annot_path`` annotates. E.g., an audio file, or an array file that contains a spectrogram generated from audio. Optional, default is None. seq : crowsetta.Sequence, list A :class:`crowsetta.Sequence` instance, or a list of :class:`crowsetta.Sequence` instances. Each :class:`crowsetta.Sequence` instance represents a sequence of annotated segments, with a segment having an onset time, offset time, and label. bboxes : list List of annotated bounding boxes, each having an onset time, offset time, lowest frequency, highest frequency, and label. Each item in the list will be a :class:`crowsetta.BBox` instance. Notes ----- A :class:`crowsetta.Annotation` can have either a ``seq`` attribute or a ``bboxes`` attribute, but not both. Examples -------- A toy example of a sequence-like annotation. >>> import numpy as np >>> import crowsetta >>> onsets_s = np.array([1.0, 3.0, 5.0]) >>> offsets_s = np.array([2.0, 4.0, 6.0]) >>> labels = np.array(['a', 'a', 'b']) >>> seq = crowsetta.Sequence.from_keyword(labels=labels, onsets_s=onsets_s, offsets_s=offsets_s) >>> annot = crowsetta.Annotation(notated_path='bird1.wav', annot_path='bird1.csv', seq=seq) >>> print(annot) Annotation(annot_path=PosixPath('bird1.csv'), notated_path=PosixPath('bird1.wav'), seq=<Sequence with 3 segments>) A toy example of a bounding box-like annotation. >>> bbox1 = crowsetta.BBox(label='Pinacosaurus grangeri', onset=1.0, offset=2.0, low_freq=3e3, high_freq=1e4) >>> bbox2 = crowsetta.BBox(label='Pinacosaurus grangeri', onset=3.0, offset=4.0, low_freq=3.25e3, high_freq=1.25e4) >>> bboxes = [bbox1, bbox2] >>> annot = crowsetta.Annotation(notated_path='prebird1.wav', annot_path='prebird1.csv', bboxes=bboxes) >>> print(annot) Annotation(annot_path=PosixPath('prebird1.csv'), notated_path=PosixPath('prebird1.wav'), bboxes=[BBox(onset=1.0, offset=2.0, low_freq=3000.0, high_freq=10000.0, label='Pinacosaurus grangeri'), BBox(onset=3.0, offset=4.0, low_freq=3250.0, high_freq=12500.0, label='Pinacosaurus grangeri')]) """
[docs] def __init__( self, annot_path: PathLike, notated_path: Optional[PathLike] = None, seq: Optional[Sequence | list[Sequence]] = None, bboxes: Optional[list[BBox]] = None, ): if seq is None and bboxes is None: raise ValueError("an Annotation must have either a ``seq`` or ``bboxes``") if seq is not None and bboxes is not None: raise ValueError("an Annotation can have either a ``seq``" "or ``bboxes``, but not both.") if seq: if not ( isinstance(seq, crowsetta.Sequence) or (isinstance(seq, list) and all([isinstance(seq_, crowsetta.Sequence) for seq_ in seq])) ): raise TypeError(f"``seq`` should be a crowsetta.Sequence or list of Sequences but was: {type(seq)}") self.seq = seq if bboxes: if not isinstance(bboxes, list): raise ValueError("``bboxes`` should be a list") if not all([isinstance(bbox, BBox) for bbox in bboxes]): raise ValueError("``bboxes`` should be a list of ``crowsetta.BBox`` instances") self.bboxes = bboxes self.annot_path = Path(annot_path) if notated_path: self.notated_path = Path(notated_path) else: self.notated_path = notated_path
def __repr__(self): repr_ = f"Annotation(annot_path={repr(self.annot_path)}, notated_path={repr(self.notated_path)}, " if hasattr(self, "seq"): repr_ += f"seq={reprlib.repr(self.seq)})" elif hasattr(self, "bboxes"): repr_ += f"bboxes={reprlib.repr(self.bboxes)})" return repr_ def __eq__(self, other): is_annot_and_audio_eq = self.annot_path == other.annot_path and self.notated_path == other.notated_path if hasattr(self, "seq") and hasattr(other, "seq"): return is_annot_and_audio_eq and self.seq == other.seq elif hasattr(self, "bboxes") and hasattr(other, "bboxes"): return is_annot_and_audio_eq and self.bboxes == other.bboxes else: return False def __ne__(self, other): return not self == other