From d0902423c15d9292cede04c194e9fc34b2e69a3a Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 20 May 2026 17:58:05 +0200 Subject: [PATCH 01/36] add the osekit.core.annotation.Annotation class --- src/osekit/core/annotation.py | 62 +++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/osekit/core/annotation.py diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py new file mode 100644 index 00000000..439e0498 --- /dev/null +++ b/src/osekit/core/annotation.py @@ -0,0 +1,62 @@ +"""The Annotation class represents an annotation made on APLOSE.""" + +from pandas import Timestamp + +from osekit.core.event import Event + + +class Annotation(Event): + """Class represents an annotation made on APLOSE.""" + + def __init__( # noqa: PLR0913 + self, + project: str, + begin: Timestamp, + end: Timestamp, + min_frequency: int, + max_frequency: int, + annotation: str, + annotator: str, + confidence_indicator_label: str, + confidence_indicator_level: str, + *, + is_box: bool, + ) -> None: + """Initialize an Annotation object. + + Parameters + ---------- + project: str + Name of the project in which the annotation was made. + begin: Timestamp + Begin timestamp of the annotation. + end: Timestamp + End timestamp of the annotation. + min_frequency: int + Minimum frequency of the annotation. + max_frequency: int + Maximum frequency of the annotation. + annotation: str + Label of the annotation. + annotator: str + Name of the annotator or detector. + confidence_indicator_label: str + Name of the level of confidence. + confidence_indicator_level: str + Level of confidence relative to the maximum level available. + Should be formatted as ``n/m``, where ``n`` is the level of confidence + of the annotation and ``m`` is the maximum level available in the project. + is_box: bool + If ``True``, the annotation is a box. + If ``False``, the annotation is a weak annotation. + + """ + super().__init__(begin=begin, end=end) + self.project = project + self.min_frequency = min_frequency + self.max_frequency = max_frequency + self.annotation = annotation + self.annotator = annotator + self.confidence_indicator_label = confidence_indicator_label + self.confidence_indicator_level = confidence_indicator_level + self.is_box = is_box From 721525b29d2471d994553d18961d1f53c01f9121 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 27 May 2026 18:05:50 +0200 Subject: [PATCH 02/36] add Annotation deserialization from csv --- src/osekit/core/annotation.py | 313 ++++++++++++++++++++++++++++++---- 1 file changed, 277 insertions(+), 36 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 439e0498..a9748a36 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -1,62 +1,303 @@ """The Annotation class represents an annotation made on APLOSE.""" +from dataclasses import dataclass +from pathlib import Path +from typing import Literal, Self + +import pandas as pd from pandas import Timestamp from osekit.core.event import Event +KNOWN_KEYS = { + "dataset", + "analysis", + "filename", + "annotation_id", + "is_update_of_id", + "start_time", + "end_time", + "start_frequency", + "end_frequency", + "min_frequency", + "max_frequency", + "annotation", + "annotator", + "annotator_expertise", + "start_datetime", + "end_datetime", + "is_box", + "type", + "confidence_indicator_label", + "confidence_indicator_level", + "comments", + "signal_quantity", + "signal_is_intensity_too_low", + "signal_does_overlap_other_signals", + "signal_start_frequency", + "signal_end_frequency", + "signal_relative_min_frequency_count", + "signal_relative_max_frequency_count", + "signal_steps_count", + "signal_has_harmonics", + "signal_trend", + "signal_sidebands", + "signal_subharmonics", + "signal_frequency_jumps", + "signal_deterministic_chaos", + "created_at_phase", +} + + +@dataclass +class FrequencyBounds: + """Class representing the frequency bounds of an annotation. + + Parameters + ---------- + min: int + Lower frequency bound. + max: int + Upper frequency bound. + + """ + + min: int + max: int + + @property + def bandwidth(self) -> int: + """Bandwidth of the annotation.""" + return self.max - self.min + + +@dataclass +class AnnotatorInfo: + """Class representing an annotator info.""" + + annotator: str + annotator_expertise: Literal["NOVICE", "AVERAGE", "EXPERT"] + + +@dataclass +class SignalParameters: + """Class representing parameters of an annoted signal.""" + + is_itensity_too_low: bool + does_overlap_other_signals: bool + min_frequency: int + max_frequency: int + nb_relative_mins: int + nb_relative_maxes: int + nb_steps: int + trend: Literal["FLAT", "ASCENDING", "DESCENDING", "MODULATED"] + frequency_jumps: bool | int + has_harmonics: bool + has_sidebands: bool + has_subharmonics: bool + has_deterministic_chaos: bool + + +@dataclass +class ConfidenceIndicator: + """Class that represents an annotation confidence indicator. + + Parameters + ---------- + confidence_indicator_label: str + Name of the level of confidence. + confidence_indicator_level: str + Level of confidence relative to the maximum level available. + Should be formatted as ``n/m``, where ``n`` is the level of confidence + of the annotation and ``m`` is the maximum level available in the project. + + """ + + confidence_indicator_label: str + confidence_indicator_level: str + + +@dataclass +class AnnotationMetaData: + """Class that represents the metadata of an annotation. + + Parameters + ---------- + project: str + Name of the project in which the annotation was made. + output: str + Name of the output ``SpectroDataset`` this annotation was made on. + filename: str + Name of the file this annotation was made on. + annotation_id: int + ID of the annotation. + base_id: int + ID of the base annotation. + May differ from ``id`` if the annotation is an update/correction. + + """ + + project: str + output: str + filename: str + annotation_id: int + base_id: int + + +@dataclass +class Verification: + """Class that represents a verification of an annotation.""" + + verificator: str + is_validated: bool + class Annotation(Event): - """Class represents an annotation made on APLOSE.""" + """Class that represents an annotation made on APLOSE.""" def __init__( # noqa: PLR0913 self, - project: str, + metadata: AnnotationMetaData, begin: Timestamp, end: Timestamp, - min_frequency: int, - max_frequency: int, - annotation: str, - annotator: str, - confidence_indicator_label: str, - confidence_indicator_level: str, - *, - is_box: bool, + frequency_bounds: FrequencyBounds, + label: str, + annotator_info: AnnotatorInfo, + annotation_type: Literal["WEAK", "POINT", "BOX"], + confidence_indicator: ConfidenceIndicator, + comments: str, + phase: Literal["ANNOTATION", "VERIFICATION"], + signal_quantity: Literal["SINGLE", "MULTIPLE"], + signal_parameters: SignalParameters | None, + verifications: list[Verification], ) -> None: """Initialize an Annotation object. Parameters ---------- - project: str - Name of the project in which the annotation was made. + metadata: AnnotationMetaData + Metadata on the annotation. begin: Timestamp Begin timestamp of the annotation. end: Timestamp End timestamp of the annotation. - min_frequency: int - Minimum frequency of the annotation. - max_frequency: int - Maximum frequency of the annotation. - annotation: str + frequency_bounds: FrequencyBounds + Frequency bounds of the annotation. + label: str Label of the annotation. - annotator: str - Name of the annotator or detector. - confidence_indicator_label: str - Name of the level of confidence. - confidence_indicator_level: str - Level of confidence relative to the maximum level available. - Should be formatted as ``n/m``, where ``n`` is the level of confidence - of the annotation and ``m`` is the maximum level available in the project. - is_box: bool - If ``True``, the annotation is a box. - If ``False``, the annotation is a weak annotation. + annotator_info: AnnotatorInfo + Information on the annotator or detector. + annotation_type: Literal["WEAK", "POINT", "BOX"] + Type of the annotation. + ``WEAK``: Annotation made on the whole spectrogram. + ``POINT``: Annotation made on one pixel of the spectrogram. + ``BOX``: Annotation made on one box within the spectrogram. + confidence_indicator: ConfidenceIndicator + Indicator of the confidence of the annotator. + comments: str + Comments left by the annotator. + phase: Literal["ANNOTATION", "VERIFICATION"] + Phase during which the annotation was created. + signal_quantity: Literal["SINGLE","MULTIPLE"] + Whether there is only one signal in the annotation or more. + signal_parameters: SignalParameters | None + Parameters of the annotated signal. + ```None`` if ``signal_quantity`` is ``MULTIPLE``. + verifications: list[Verification] + Verifications made on this annotation. """ + self.metadata = metadata + self.label = label + self.annotator_info = annotator_info + self.frequency_bounds = frequency_bounds + self.type = annotation_type + self.confidence_indicator = confidence_indicator + self.comments = comments + self.phase = phase + self.signal_quantity = signal_quantity + self.signal_parameters = signal_parameters + self.verifications = verifications + super().__init__(begin=begin, end=end) - self.project = project - self.min_frequency = min_frequency - self.max_frequency = max_frequency - self.annotation = annotation - self.annotator = annotator - self.confidence_indicator_label = confidence_indicator_label - self.confidence_indicator_level = confidence_indicator_level - self.is_box = is_box + + def __repr__(self) -> str: + """Override the string representation of the annotation.""" + return str(self.metadata.annotation_id) + + @classmethod + def from_dict(cls, row: dict) -> Self: + """Deserialize an Annotation object.""" + metadata = AnnotationMetaData( + project=row["project"] if "project" in row else row["dataset"], + output=row["output"] if "output" in row else row["analysis"], + filename=row["filename"], + annotation_id=row["annotation_id"], + base_id=row["is_update_of_id"], + ) + annotator_info = AnnotatorInfo( + annotator=row["annotator"], + annotator_expertise=row["annotator_expertise"], + ) + frequency_bounds = FrequencyBounds( + min=row["min_frequency"], + max=row["max_frequency"], + ) + confidence_indicator = ConfidenceIndicator( + confidence_indicator_label=row["confidence_indicator_label"], + confidence_indicator_level=row["confidence_indicator_level"], + ) + + signal_quantity = row["signal_quantity"] + signal_parameters = ( + SignalParameters( + does_overlap_other_signals=row["signal_is_intensity_too_low"], + frequency_jumps=row["signal_frequency_jumps"], + has_deterministic_chaos=row["signal_deterministic_chaos"], + has_harmonics=row["signal_has_harmonics"], + has_sidebands=row["signal_sidebands"], + has_subharmonics=row["signal_subharmonics"], + is_itensity_too_low=row["signal_is_intensity_too_low"], + max_frequency=row["signal_end_frequency"], + min_frequency=row["signal_start_frequency"], + nb_relative_maxes=row["signal_relative_max_frequency_count"], + nb_relative_mins=row["signal_relative_min_frequency_count"], + nb_steps=row["signal_steps_count"], + trend=row["signal_trend"], + ) + if signal_quantity == "SINGLE" + else None + ) + + verifications = [ + Verification( + verificator=key, + is_validated=value, + ) + for key, value in row.items() + if key not in KNOWN_KEYS + ] + + return cls( + metadata=metadata, + label=row["annotation"], + annotator_info=annotator_info, + begin=Timestamp(row["start_datetime"]), + end=Timestamp(row["end_datetime"]), + frequency_bounds=frequency_bounds, + annotation_type=row["type"], + confidence_indicator=confidence_indicator, + comments=row["comments"], + phase=row["created_at_phase"], + signal_quantity=row["signal_quantity"], + signal_parameters=signal_parameters, + verifications=verifications, + ) + + @classmethod + def from_csv(cls, csv: Path) -> list[Self]: + """Deserialize a list of Annotation from an annotations csv file.""" + return [ + cls.from_dict(record) + for record in pd.read_csv(filepath_or_buffer=csv).to_dict(orient="records") + ] From 425691fc5f33550f5d86d82f62e54f9f3086642b Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 28 May 2026 12:21:54 +0200 Subject: [PATCH 03/36] move comments and phase to the AnnotationMetadata class --- src/osekit/core/annotation.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index a9748a36..92b8af60 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -134,6 +134,10 @@ class AnnotationMetaData: base_id: int ID of the base annotation. May differ from ``id`` if the annotation is an update/correction. + comments: str + Comments left by the annotator. + phase: Literal["ANNOTATION", "VERIFICATION"] + Phase during which the annotation was created. """ @@ -142,6 +146,8 @@ class AnnotationMetaData: filename: str annotation_id: int base_id: int + comments: str + phase: Literal["ANNOTATION", "VERIFICATION"] @dataclass @@ -165,8 +171,6 @@ def __init__( # noqa: PLR0913 annotator_info: AnnotatorInfo, annotation_type: Literal["WEAK", "POINT", "BOX"], confidence_indicator: ConfidenceIndicator, - comments: str, - phase: Literal["ANNOTATION", "VERIFICATION"], signal_quantity: Literal["SINGLE", "MULTIPLE"], signal_parameters: SignalParameters | None, verifications: list[Verification], @@ -194,10 +198,6 @@ def __init__( # noqa: PLR0913 ``BOX``: Annotation made on one box within the spectrogram. confidence_indicator: ConfidenceIndicator Indicator of the confidence of the annotator. - comments: str - Comments left by the annotator. - phase: Literal["ANNOTATION", "VERIFICATION"] - Phase during which the annotation was created. signal_quantity: Literal["SINGLE","MULTIPLE"] Whether there is only one signal in the annotation or more. signal_parameters: SignalParameters | None @@ -213,8 +213,6 @@ def __init__( # noqa: PLR0913 self.frequency_bounds = frequency_bounds self.type = annotation_type self.confidence_indicator = confidence_indicator - self.comments = comments - self.phase = phase self.signal_quantity = signal_quantity self.signal_parameters = signal_parameters self.verifications = verifications @@ -234,6 +232,8 @@ def from_dict(cls, row: dict) -> Self: filename=row["filename"], annotation_id=row["annotation_id"], base_id=row["is_update_of_id"], + comments=row["comments"], + phase=row["created_at_phase"], ) annotator_info = AnnotatorInfo( annotator=row["annotator"], @@ -287,8 +287,6 @@ def from_dict(cls, row: dict) -> Self: frequency_bounds=frequency_bounds, annotation_type=row["type"], confidence_indicator=confidence_indicator, - comments=row["comments"], - phase=row["created_at_phase"], signal_quantity=row["signal_quantity"], signal_parameters=signal_parameters, verifications=verifications, From 5fe8a61c6c97cd87236c5204d1066d2ad074a5fc Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 2 Jun 2026 11:05:58 +0200 Subject: [PATCH 04/36] filter na in annotation read_csv() --- src/osekit/core/annotation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 92b8af60..86502c67 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -297,5 +297,7 @@ def from_csv(cls, csv: Path) -> list[Self]: """Deserialize a list of Annotation from an annotations csv file.""" return [ cls.from_dict(record) - for record in pd.read_csv(filepath_or_buffer=csv).to_dict(orient="records") + for record in pd.read_csv(filepath_or_buffer=csv, na_filter=False).to_dict( + orient="records", + ) ] From f077bdc83d298aa45f155451ab58ede42953c3d6 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 2 Jun 2026 15:30:30 +0200 Subject: [PATCH 05/36] add FrequencyBounds post_init validity check --- src/osekit/core/annotation.py | 20 ++++++++++++++++++++ tests/test_annotation.py | 0 2 files changed, 20 insertions(+) create mode 100644 tests/test_annotation.py diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 86502c67..2b4d0b2c 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -65,6 +65,26 @@ class FrequencyBounds: min: int max: int + def __post_init__(self) -> None: + """Check the validity of the frequency bounds.""" + error_msgs = [] + if self.min < 0: + error_msgs.append( + f"Min frequency must be greater than or equal to 0, got {self.min}.", + ) + if self.max < 0: + error_msgs.append( + f"Max frequency must be greater than or equal to 0, got {self.max}.", + ) + if self.min > self.max: + error_msgs.append( + f"Max frequency must be greater than min frequency, " + f"got ({self.min},{self.max}).", + ) + if error_msgs: + msg = "\n".join(error_msgs) + raise ValueError(msg) + @property def bandwidth(self) -> int: """Bandwidth of the annotation.""" diff --git a/tests/test_annotation.py b/tests/test_annotation.py new file mode 100644 index 00000000..e69de29b From a3abd305e1b4e8b6d1b1cf776d66747d366d4893 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 2 Jun 2026 15:30:44 +0200 Subject: [PATCH 06/36] add FrequencyBounds tests --- tests/test_annotation.py | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index e69de29b..824c61cf 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -0,0 +1,67 @@ +from contextlib import AbstractContextManager, nullcontext + +import pytest + +from osekit.core.annotation import ( + FrequencyBounds, +) + + +@pytest.mark.parametrize( + ("min_frequency", "max_frequency", "expectation"), + [ + pytest.param( + 0, + 1000, + nullcontext(1000), + id="box_from_bottom", + ), + pytest.param( + 300, + 1000, + nullcontext(700), + id="box_bandwidth_from_higher_than_0", + ), + pytest.param( + -10, + 1000, + pytest.raises(ValueError, match=r"Min frequency.*-10"), + id="negative_min_frequency_raises", + ), + pytest.param( + 0, + -5, + pytest.raises(ValueError, match=r"Max frequency.*-5"), + id="negative_max_frequency_raises", + ), + pytest.param( + 80, + 50, + pytest.raises( + ValueError, + match=r"Max frequency.*greater.*min frequency.*\(80,50\)", + ), + id="min_greater_than_max_raises", + ), + pytest.param( + -20, + -30, + pytest.raises( + ValueError, + match=r"(?s)" # Activates the DOTALL mode: includes \n in regex .* + r"(?=.*Min frequency.*got -20)" + r"(?=.*Max frequency.*got -30)" + r"(?=.*Max frequency.*greater.*min frequency.*\(-20,-30\))", + ), + id="errors_concatenation", + ), + ], +) +def test_frequency_bounds( + min_frequency: int, + max_frequency: int, + expectation: AbstractContextManager, +) -> None: + with expectation as e: + frequency_bounds = FrequencyBounds(min=min_frequency, max=max_frequency) + assert frequency_bounds.bandwidth == e From 448ce25b65b417f35c892d24999ce0d4d1f05aa0 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 2 Jun 2026 17:27:15 +0200 Subject: [PATCH 07/36] parse missing cell values as None --- src/osekit/core/annotation.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 2b4d0b2c..a52d0e0d 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -1,5 +1,6 @@ """The Annotation class represents an annotation made on APLOSE.""" +import math from dataclasses import dataclass from pathlib import Path from typing import Literal, Self @@ -96,7 +97,7 @@ class AnnotatorInfo: """Class representing an annotator info.""" annotator: str - annotator_expertise: Literal["NOVICE", "AVERAGE", "EXPERT"] + annotator_expertise: Literal["NOVICE", "AVERAGE", "EXPERT"] | None = None @dataclass @@ -259,10 +260,14 @@ def from_dict(cls, row: dict) -> Self: annotator=row["annotator"], annotator_expertise=row["annotator_expertise"], ) - frequency_bounds = FrequencyBounds( - min=row["min_frequency"], - max=row["max_frequency"], + + min_frequency, max_frequency = row["min_frequency"], row["max_frequency"] + frequency_bounds = ( + FrequencyBounds(min=min_frequency, max=max_frequency) + if not any(m is None for m in (min_frequency, max_frequency)) + else None ) + confidence_indicator = ConfidenceIndicator( confidence_indicator_label=row["confidence_indicator_label"], confidence_indicator_level=row["confidence_indicator_level"], @@ -315,9 +320,14 @@ def from_dict(cls, row: dict) -> Self: @classmethod def from_csv(cls, csv: Path) -> list[Self]: """Deserialize a list of Annotation from an annotations csv file.""" - return [ - cls.from_dict(record) - for record in pd.read_csv(filepath_or_buffer=csv, na_filter=False).to_dict( - orient="records", - ) + records = pd.read_csv(filepath_or_buffer=csv).to_dict( + orient="records", + ) + records = [ + { + key: None if type(value) is float and math.isnan(value) else value + for key, value in record.items() + } + for record in records ] + return [cls.from_dict(record) for record in records] From 7ffd859616d3d2ecfb842dc68ed15eb0f00505de Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 2 Jun 2026 17:31:37 +0200 Subject: [PATCH 08/36] remove analysis column from result csv --- src/osekit/core/annotation.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index a52d0e0d..21b1d09d 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -12,7 +12,7 @@ KNOWN_KEYS = { "dataset", - "analysis", + "project", "filename", "annotation_id", "is_update_of_id", @@ -146,8 +146,6 @@ class AnnotationMetaData: ---------- project: str Name of the project in which the annotation was made. - output: str - Name of the output ``SpectroDataset`` this annotation was made on. filename: str Name of the file this annotation was made on. annotation_id: int @@ -163,7 +161,6 @@ class AnnotationMetaData: """ project: str - output: str filename: str annotation_id: int base_id: int @@ -249,7 +246,6 @@ def from_dict(cls, row: dict) -> Self: """Deserialize an Annotation object.""" metadata = AnnotationMetaData( project=row["project"] if "project" in row else row["dataset"], - output=row["output"] if "output" in row else row["analysis"], filename=row["filename"], annotation_id=row["annotation_id"], base_id=row["is_update_of_id"], From 97935d9ce3901405d523fbf3d9e497bb79adac59 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 2 Jun 2026 17:57:07 +0200 Subject: [PATCH 09/36] fix docstring parameter name --- src/osekit/core/annotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 21b1d09d..3db968b9 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -152,7 +152,7 @@ class AnnotationMetaData: ID of the annotation. base_id: int ID of the base annotation. - May differ from ``id`` if the annotation is an update/correction. + May differ from ``annotation_id`` if the annotation is an update/correction. comments: str Comments left by the annotator. phase: Literal["ANNOTATION", "VERIFICATION"] From 34197dc081a5bb27dad92ceaef5dbba4fa4d4732 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 2 Jun 2026 18:14:39 +0200 Subject: [PATCH 10/36] add AnnotatorInfo hash() method --- src/osekit/core/annotation.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 3db968b9..80f679a8 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -99,6 +99,10 @@ class AnnotatorInfo: annotator: str annotator_expertise: Literal["NOVICE", "AVERAGE", "EXPERT"] | None = None + def __hash__(self) -> int: + """Return a hash for the annotator.""" + return hash((self.annotator, self.annotator_expertise)) + @dataclass class SignalParameters: From 521f96e234dd2699c21a7ec528446bdf40eb5343 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 2 Jun 2026 18:16:04 +0200 Subject: [PATCH 11/36] add AnnotatorInfo hash test --- tests/test_annotation.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index 824c61cf..dbfea31f 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -3,6 +3,7 @@ import pytest from osekit.core.annotation import ( + AnnotatorInfo, FrequencyBounds, ) @@ -65,3 +66,19 @@ def test_frequency_bounds( with expectation as e: frequency_bounds = FrequencyBounds(min=min_frequency, max=max_frequency) assert frequency_bounds.bandwidth == e + + +def test_annotator_info() -> None: + annotators = [ + AnnotatorInfo(annotator="ruby", annotator_expertise="NOVICE"), + AnnotatorInfo(annotator="ruby", annotator_expertise="NOVICE"), + AnnotatorInfo(annotator="haunt", annotator_expertise="EXPERT"), + AnnotatorInfo(annotator="haunt", annotator_expertise="EXPERT"), + AnnotatorInfo(annotator="nevada", annotator_expertise="EXPERT"), + AnnotatorInfo(annotator="nevada", annotator_expertise="EXPERT"), + AnnotatorInfo(annotator="haunt", annotator_expertise=None), + ] + + nb_unique_annotators = 4 + + assert sum(1 for _ in set(annotators)) == nb_unique_annotators From d4b54ed360aac7cbcb47b91060bcfc3814862216 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 3 Jun 2026 10:33:19 +0200 Subject: [PATCH 12/36] add default None value for SignalParameters parameters --- src/osekit/core/annotation.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 80f679a8..e3c799e2 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -108,19 +108,19 @@ def __hash__(self) -> int: class SignalParameters: """Class representing parameters of an annoted signal.""" - is_itensity_too_low: bool - does_overlap_other_signals: bool - min_frequency: int - max_frequency: int - nb_relative_mins: int - nb_relative_maxes: int - nb_steps: int - trend: Literal["FLAT", "ASCENDING", "DESCENDING", "MODULATED"] - frequency_jumps: bool | int - has_harmonics: bool - has_sidebands: bool - has_subharmonics: bool - has_deterministic_chaos: bool + is_itensity_too_low: bool | None = None + does_overlap_other_signals: bool | None = None + min_frequency: int | None = None + max_frequency: int | None = None + nb_relative_mins: int | None = None + nb_relative_maxes: int | None = None + nb_steps: int | None = None + trend: Literal["FLAT", "ASCENDING", "DESCENDING", "MODULATED"] | None = None + frequency_jumps: bool | int | None = None + has_harmonics: bool | None = None + has_sidebands: bool | None = None + has_subharmonics: bool | None = None + has_deterministic_chaos: bool | None = None @dataclass From 2f8d6200593d7302261648acec7b405af714c843 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 3 Jun 2026 10:40:07 +0200 Subject: [PATCH 13/36] add ConfidenceIndicator level parsing in post_init() --- src/osekit/core/annotation.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index e3c799e2..df4bf92f 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -29,8 +29,8 @@ "end_datetime", "is_box", "type", - "confidence_indicator_label", - "confidence_indicator_level", + "label", + "level", "comments", "signal_quantity", "signal_is_intensity_too_low", @@ -129,17 +129,21 @@ class ConfidenceIndicator: Parameters ---------- - confidence_indicator_label: str + label: str Name of the level of confidence. - confidence_indicator_level: str + level: str Level of confidence relative to the maximum level available. Should be formatted as ``n/m``, where ``n`` is the level of confidence of the annotation and ``m`` is the maximum level available in the project. """ - confidence_indicator_label: str - confidence_indicator_level: str + label: str + level: str + + def __post_init__(self) -> None: + """Parse the level of confidence of the annotation.""" + self.level, self.maximum_level = map(int, self.level.split("/")) @dataclass @@ -269,8 +273,8 @@ def from_dict(cls, row: dict) -> Self: ) confidence_indicator = ConfidenceIndicator( - confidence_indicator_label=row["confidence_indicator_label"], - confidence_indicator_level=row["confidence_indicator_level"], + label=row["label"], + level=row["level"], ) signal_quantity = row["signal_quantity"] From f69ddefc0c638260158756afd8bcb52fd537714f Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 3 Jun 2026 10:56:35 +0200 Subject: [PATCH 14/36] clarify ConfidenceIndicator parameters --- src/osekit/core/annotation.py | 47 ++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index df4bf92f..1f817cfb 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -131,19 +131,48 @@ class ConfidenceIndicator: ---------- label: str Name of the level of confidence. - level: str - Level of confidence relative to the maximum level available. - Should be formatted as ``n/m``, where ``n`` is the level of confidence - of the annotation and ``m`` is the maximum level available in the project. + level: int + Level of confidence of the annotation. + maximum_level: int + Maximum level of confidence authorized in the project. """ label: str - level: str + level: int + maximum_level: int def __post_init__(self) -> None: - """Parse the level of confidence of the annotation.""" - self.level, self.maximum_level = map(int, self.level.split("/")) + """Check the validity of the level and maximum level values.""" + if self.level > self.maximum_level: + msg = ( + f"Confidence level {self.level} is higher than " + f"maximum level {self.maximum_level} authorized in the project." + ) + raise ValueError(msg) + + @classmethod + def from_relative_level_string(cls, label: str, relative_level_string: str) -> Self: + """Return a ``ConfidenceIndicator`` from a string representing its level. + + Parameters + ---------- + label: str + Name of the level of confidence. + relative_level_string: str + Level of confidence relative to the maximum level available. + Should be formatted as ``n/m``, where ``n`` is the level of confidence + of the annotation and ``m`` is the maximum level available in the project. + + Returns + ------- + ConfidenceIndicator + The confidence indicator parsed from the input string. + + """ + level, maximum_level = map(int, relative_level_string.split("/")) + + return cls(label=label, level=level, maximum_level=maximum_level) @dataclass @@ -272,9 +301,9 @@ def from_dict(cls, row: dict) -> Self: else None ) - confidence_indicator = ConfidenceIndicator( + confidence_indicator = ConfidenceIndicator.from_relative_level_string( label=row["label"], - level=row["level"], + relative_level_string=row["level"], ) signal_quantity = row["signal_quantity"] From e8a2976ace6a20fb261ac8c9dc85d1f761d6fa52 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 3 Jun 2026 11:06:11 +0200 Subject: [PATCH 15/36] add ConfidenceIndicator post_init() tests --- tests/test_annotation.py | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index dbfea31f..b29bcd52 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -4,6 +4,7 @@ from osekit.core.annotation import ( AnnotatorInfo, + ConfidenceIndicator, FrequencyBounds, ) @@ -82,3 +83,50 @@ def test_annotator_info() -> None: nb_unique_annotators = 4 assert sum(1 for _ in set(annotators)) == nb_unique_annotators + + +@pytest.mark.parametrize( + ("label", "level", "max_level", "expectation"), + [ + pytest.param( + "Sure", + 1, + 1, + nullcontext(), + id="max_level_is_ok", + ), + pytest.param( + "Not sure", + 0, + 1, + nullcontext(), + id="level_0_is_ok", + ), + pytest.param( + "Moderate", + 1, + 2, + nullcontext(), + id="between_0_and_max_is_ok", + ), + pytest.param( + "Moderate", + 3, + 2, + pytest.raises(ValueError, match=r"level 3.*higher.*maximum level 2"), + id="higher_than_max_raises", + ), + ], +) +def test_confidence_indicator_value_check( + label: str, + level: int, + max_level: int, + expectation: AbstractContextManager, +) -> None: + with expectation: + ConfidenceIndicator( + label=label, + level=level, + maximum_level=max_level, + ) From d06fffcc62570e51a272497754d7dc2aba0e728c Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 3 Jun 2026 11:13:03 +0200 Subject: [PATCH 16/36] add ConfidenceIndicator.from_relative_level_string() test --- tests/test_annotation.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index b29bcd52..5bb08f81 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -130,3 +130,42 @@ def test_confidence_indicator_value_check( level=level, maximum_level=max_level, ) + + +@pytest.mark.parametrize( + ("label", "relative_level_string", "expectation"), + [ + pytest.param( + "cool", + "1/6", + nullcontext( + ConfidenceIndicator( + label="cool", + level=1, + maximum_level=6, + ), + ), + id="correct_levels", + ), + pytest.param( + "cool", + "4/2", + pytest.raises(ValueError, match=r"level 4.*higher.*maximum level 2"), + id="incorrect_levels_should_raise", + ), + ], +) +def test_confidence_indicator_from_relative_level_string( + label: str, + relative_level_string: str, + expectation: AbstractContextManager, +) -> None: + with expectation as e: + ci = ConfidenceIndicator.from_relative_level_string( + label=label, + relative_level_string=relative_level_string, + ) + + assert ci.label == e.label + assert ci.level == e.level + assert ci.maximum_level == e.maximum_level From a08a1f641e423c426dce75542debdb7a4a6e1cb2 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 3 Jun 2026 14:15:16 +0200 Subject: [PATCH 17/36] reverse index to the correct row name --- src/osekit/core/annotation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 1f817cfb..1f906090 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -190,7 +190,7 @@ class AnnotationMetaData: base_id: int ID of the base annotation. May differ from ``annotation_id`` if the annotation is an update/correction. - comments: str + comments: str | None Comments left by the annotator. phase: Literal["ANNOTATION", "VERIFICATION"] Phase during which the annotation was created. @@ -200,8 +200,8 @@ class AnnotationMetaData: project: str filename: str annotation_id: int - base_id: int - comments: str + base_id: int | None + comments: str | None phase: Literal["ANNOTATION", "VERIFICATION"] @@ -302,8 +302,8 @@ def from_dict(cls, row: dict) -> Self: ) confidence_indicator = ConfidenceIndicator.from_relative_level_string( - label=row["label"], - relative_level_string=row["level"], + label=row["confidence_indicator_label"], + relative_level_string=row["confidence_indicator_level"], ) signal_quantity = row["signal_quantity"] From 0826e4709f728dc160f5906add8267f92161fadf Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 3 Jun 2026 16:43:10 +0200 Subject: [PATCH 18/36] add annotation from csv integration test --- src/osekit/core/annotation.py | 28 +++++++++-- tests/_static/aplose_result.csv | 9 ++++ tests/test_annotation.py | 83 +++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 tests/_static/aplose_result.csv diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 1f906090..af290f3c 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -29,8 +29,8 @@ "end_datetime", "is_box", "type", - "label", - "level", + "confidence_indicator_label", + "confidence_indicator_level", "comments", "signal_quantity", "signal_is_intensity_too_low", @@ -103,6 +103,13 @@ def __hash__(self) -> int: """Return a hash for the annotator.""" return hash((self.annotator, self.annotator_expertise)) + def __eq__(self, other: Self) -> bool: + """Return whether two annotators are equal.""" + return ( + self.annotator == other.annotator + and self.annotator_expertise == other.annotator_expertise + ) + @dataclass class SignalParameters: @@ -212,6 +219,17 @@ class Verification: verificator: str is_validated: bool + def __hash__(self) -> int: + """Return a hash of the verification.""" + return hash((self.verificator, self.is_validated)) + + def __eq__(self, other: Self) -> bool: + """Return whether the two verifications are equal.""" + return ( + self.verificator == other.verificator + and self.is_validated == other.is_validated + ) + class Annotation(Event): """Class that represents an annotation made on APLOSE.""" @@ -283,7 +301,7 @@ def from_dict(cls, row: dict) -> Self: """Deserialize an Annotation object.""" metadata = AnnotationMetaData( project=row["project"] if "project" in row else row["dataset"], - filename=row["filename"], + filename=str(row["filename"]), annotation_id=row["annotation_id"], base_id=row["is_update_of_id"], comments=row["comments"], @@ -327,14 +345,14 @@ def from_dict(cls, row: dict) -> Self: else None ) - verifications = [ + verifications = { Verification( verificator=key, is_validated=value, ) for key, value in row.items() if key not in KNOWN_KEYS - ] + } return cls( metadata=metadata, diff --git a/tests/_static/aplose_result.csv b/tests/_static/aplose_result.csv new file mode 100644 index 00000000..dd321bfe --- /dev/null +++ b/tests/_static/aplose_result.csv @@ -0,0 +1,9 @@ +dataset,filename,annotation_id,is_update_of_id,start_time,end_time,start_frequency,end_frequency,min_frequency,max_frequency,annotation,annotator,annotator_expertise,start_datetime,end_datetime,is_box,type,confidence_indicator_label,confidence_indicator_level,comments,signal_quantity,signal_is_intensity_too_low,signal_does_overlap_other_signals,signal_start_frequency,signal_end_frequency,signal_relative_min_frequency_count,signal_relative_max_frequency_count,signal_steps_count,signal_has_harmonics,signal_trend,signal_sidebands,signal_subharmonics,signal_frequency_jumps,signal_deterministic_chaos,created_at_phase,lookaftering,bunyan +great_tit,990694,586654,,0.0,20.0,0.0,24000.0,0.0,24000.0,bird,vashti,NOVICE,2021-01-01T00:00:00.000+00:00,2021-01-01T00:00:20.000+00:00,0,WEAK,Sure,1/1,great tits |- vashti,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,False +great_tit,990694,586655,,1.412,3.651,2512.0,15661.0,2512.0,15661.0,bird,vashti,NOVICE,2021-01-01T00:00:01.412+00:00,2021-01-01T00:00:03.651+00:00,1,BOX,Not sure,0/1,fluffy-backed tit-babbler |- vashti,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False,False +great_tit,990694,586656,,0.0,20.0,0.0,24000.0,0.0,24000.0,rain,heartleap,,2021-01-01T00:00:00.000+00:00,2021-01-01T00:00:20.000+00:00,0,WEAK,Sure,1/1,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +great_tit,990694,586657,,3.53,4.71,11137.0,13997.0,11137.0,13997.0,rain,heartleap,,2021-01-01T00:00:03.530+00:00,2021-01-01T00:00:04.710+00:00,1,BOX,Sure,1/1,,SINGLE,,True,12000.0,13000.0,3,2,4,True,MOD,True,,True,True,ANNOTATION,True,True +great_tit,990694,586669,586655,1.412,3.651,2512.0,15660.0,2512.0,15660.0,bird,bunyan,EXPERT,2021-01-01T00:00:01.412+00:00,2021-01-01T00:00:03.651+00:00,1,BOX,Not sure,0/1,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, +great_tit,990694,586710,586655,1.412,3.651,2512.0,15660.0,2512.0,15660.0,bird,lookaftering,EXPERT,2021-01-01T00:00:01.412+00:00,2021-01-01T00:00:03.651+00:00,1,BOX,Not sure,0/1,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, +great_tit,994410,586671,,0.0,20.0,0.0,24000.0,0.0,24000.0,car,bunyan,EXPERT,2021-01-01T00:01:18.218+00:00,2021-01-01T00:01:38.218+00:00,0,WEAK,Sure,1/1,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, +great_tit,994410,586672,,0.0,20.0,0.0,24000.0,0.0,24000.0,bird,bunyan,EXPERT,2021-01-01T00:01:18.218+00:00,2021-01-01T00:01:38.218+00:00,0,WEAK,Sure,1/1,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, diff --git a/tests/test_annotation.py b/tests/test_annotation.py index 5bb08f81..e7cc7626 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -1,11 +1,15 @@ from contextlib import AbstractContextManager, nullcontext +from pathlib import Path +import numpy as np import pytest from osekit.core.annotation import ( + Annotation, AnnotatorInfo, ConfidenceIndicator, FrequencyBounds, + Verification, ) @@ -169,3 +173,82 @@ def test_confidence_indicator_from_relative_level_string( assert ci.label == e.label assert ci.level == e.level assert ci.maximum_level == e.maximum_level + + +def test_annotations_from_csv() -> None: + annotations = Annotation.from_csv( + csv=Path(r"_static/aplose_result.csv"), + ) + + # All records should be loaded + assert len(annotations) == 8 + assert all(a.metadata.project == "great_tit" for a in annotations) + + # Two distinct annotated files + filenames = {a.metadata.filename for a in annotations} + assert filenames == {"990694", "994410"} + + # Types + types = {a.type for a in annotations} + assert types == {"WEAK", "BOX"} + + # Phases + phases = {a.metadata.phase for a in annotations} + assert phases == {"ANNOTATION", "VERIFICATION"} + + # Single signal parameters + single = next(a for a in annotations if a.metadata.annotation_id == 586657) + assert single.signal_quantity == "SINGLE" + assert single.signal_parameters is not None + assert not single.signal_parameters.is_itensity_too_low + assert not single.signal_parameters.does_overlap_other_signals + assert single.signal_parameters.min_frequency == 12000 + assert single.signal_parameters.max_frequency == 13000 + assert single.signal_parameters.nb_relative_mins == 3 + assert single.signal_parameters.nb_relative_maxes == 2 + assert single.signal_parameters.nb_steps == 4 + assert single.signal_parameters.trend == "MOD" + assert single.signal_parameters.frequency_jumps + assert single.signal_parameters.has_harmonics + assert single.signal_parameters.has_sidebands + assert not single.signal_parameters.has_subharmonics + assert single.signal_parameters.has_deterministic_chaos + + # Multiple signal quantity: parameters should be None + multiple = next(a for a in annotations if a.metadata.annotation_id == 586654) + assert multiple.signal_quantity == "MULTIPLE" + assert multiple.signal_parameters is None + + # Annotation update + update = next(a for a in annotations if a.metadata.annotation_id == 586669) + assert update.metadata.base_id == 586655 + + # Annotation without base + base = next(a for a in annotations if a.metadata.annotation_id == 586655) + assert base.metadata.base_id is None + + # Annotator parsing + annotators = { + AnnotatorInfo(annotator="vashti", annotator_expertise="NOVICE"), + AnnotatorInfo(annotator="heartleap", annotator_expertise=None), + AnnotatorInfo(annotator="bunyan", annotator_expertise="EXPERT"), + AnnotatorInfo(annotator="lookaftering", annotator_expertise="EXPERT"), + } + assert np.array_equal( + annotators, + {a.annotator_info for a in annotations}, + ) + + # Verification parsing + verificated = next(a for a in annotations if a.metadata.annotation_id == 586654) + verification = { + Verification( + verificator="lookaftering", + is_validated=True, + ), + Verification( + verificator="bunyan", + is_validated=False, + ), + } + assert np.array_equal(verification, verificated.verifications) From 562b2ff98be1062c6618c6d23980d4d0e8081e53 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 3 Jun 2026 17:37:38 +0200 Subject: [PATCH 19/36] fix relative path to sample csv file in tests --- tests/test_annotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index e7cc7626..bf681f30 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -177,7 +177,7 @@ def test_confidence_indicator_from_relative_level_string( def test_annotations_from_csv() -> None: annotations = Annotation.from_csv( - csv=Path(r"_static/aplose_result.csv"), + csv=Path(__file__).parent / "_static" / "aplose_result.csv", ) # All records should be loaded From a9c9efd4f87454b386b73c2a469e0e879a4b2ba7 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 4 Jun 2026 11:50:46 +0200 Subject: [PATCH 20/36] add Annotation.to_rectangle() method --- src/osekit/core/annotation.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index af290f3c..df282fb0 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -6,6 +6,7 @@ from typing import Literal, Self import pandas as pd +from matplotlib.patches import Rectangle from pandas import Timestamp from osekit.core.event import Event @@ -246,7 +247,7 @@ def __init__( # noqa: PLR0913 confidence_indicator: ConfidenceIndicator, signal_quantity: Literal["SINGLE", "MULTIPLE"], signal_parameters: SignalParameters | None, - verifications: list[Verification], + verifications: set[Verification], ) -> None: """Initialize an Annotation object. @@ -276,7 +277,7 @@ def __init__( # noqa: PLR0913 signal_parameters: SignalParameters | None Parameters of the annotated signal. ```None`` if ``signal_quantity`` is ``MULTIPLE``. - verifications: list[Verification] + verifications: set[Verification] Verifications made on this annotation. """ @@ -368,6 +369,25 @@ def from_dict(cls, row: dict) -> Self: verifications=verifications, ) + def to_rectangle(self) -> Rectangle: + """Return a matplotlib Rectangle representing the annotation. + + Returns + ------- + matplotlib.patches.Rectangle + Rectangle representing the annotation. + The coordinates of the rectangle are in time x frequency. + + """ + return Rectangle( + xy=( # type: ignore[arg-type] + self.begin, + self.frequency_bounds.min, + ), + width=self.duration, # type: ignore[arg-type] + height=self.frequency_bounds.bandwidth, + ) + @classmethod def from_csv(cls, csv: Path) -> list[Self]: """Deserialize a list of Annotation from an annotations csv file.""" From 485a4140b5b17671f7a56368fa561cc4df8e53f8 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 4 Jun 2026 15:02:32 +0200 Subject: [PATCH 21/36] add None parameter in localize_timestamp --- src/osekit/utils/timestamp.py | 7 ++++--- tests/test_timestamp_utils.py | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/osekit/utils/timestamp.py b/src/osekit/utils/timestamp.py index 9eb69ad4..560ee4fc 100644 --- a/src/osekit/utils/timestamp.py +++ b/src/osekit/utils/timestamp.py @@ -90,7 +90,7 @@ def normalize_datetime(datetime: tuple[str], template: str) -> tuple[str, str]: def localize_timestamp( timestamp: Timestamp, - timezone: str | pytz.timezone, + timezone: str | pytz.timezone | None, ) -> Timestamp: """Localize a timestamp in the given timezone. @@ -98,8 +98,9 @@ def localize_timestamp( ---------- timestamp: pandas.Timestamp The timestamp to localize. - timezone: str | pytz.timezone + timezone: str | pytz.timezone | None The timezone in which the timestamp is localized. + If None, the output timestamp is naive. Returns ------- @@ -109,7 +110,7 @@ def localize_timestamp( to the new timezone. """ - if not timestamp.tz: + if not timestamp.tz or timezone is None: return timestamp.tz_localize(timezone) if timestamp.utcoffset() != timestamp.tz_convert(timezone).utcoffset(): diff --git a/tests/test_timestamp_utils.py b/tests/test_timestamp_utils.py index 674ba46f..1ee533b9 100644 --- a/tests/test_timestamp_utils.py +++ b/tests/test_timestamp_utils.py @@ -583,6 +583,12 @@ def test_reformat_timestamp( Timestamp("2024-10-17T10:14:11.000+0000", tz="UTC"), id="negative_zero_UTC_offset_timezone", ), + pytest.param( + Timestamp("2024-10-17 10:14:11+0200"), + None, + Timestamp("2024-10-17T10:14:11"), + id="aware_to_naive", + ), ], ) def test_localize_timestamp( From bea61bdfbc1ac9c917545989e3cebfeb4207983a Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 4 Jun 2026 15:03:15 +0200 Subject: [PATCH 22/36] add Event.localize() method --- src/osekit/core/event.py | 19 +++++++++++++++++ tests/test_event.py | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/osekit/core/event.py b/src/osekit/core/event.py index 575599df..60e1d5e3 100644 --- a/src/osekit/core/event.py +++ b/src/osekit/core/event.py @@ -7,6 +7,8 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, TypeVar +from osekit.utils.timestamp import localize_timestamp + if TYPE_CHECKING: from pandas import Timedelta, Timestamp @@ -63,6 +65,23 @@ def __repr__(self) -> str: """Overwrite repr.""" return f"{self.begin} - {self.end}" + def localize(self, timezone: str | None) -> None: + """Localize the event begin and end in a timezone. + + If the event is already tz-aware, it will be converted + to the target timezone. + + Parameters + ---------- + timezone: str | None + Target timezone + + """ + # We use the private fields here because we can't compare + # naive and aware timestamps in the begin and end setters + self._begin = localize_timestamp(timestamp=self._begin, timezone=timezone) + self._end = localize_timestamp(timestamp=self._end, timezone=timezone) + def overlaps(self, other: type[Event] | Event) -> bool: """Return ``True`` if the other event shares time with the current event. diff --git a/tests/test_event.py b/tests/test_event.py index e9d21bdb..a5c02632 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -446,3 +446,49 @@ def test_repr() -> None: ) == "1990-09-12 12:00:00 - 1990-09-12 12:00:10" ) + + +@pytest.mark.parametrize( + ("event", "timezone", "expected"), + [ + pytest.param( + Event( + begin=Timestamp("18-02-1954 00:00:00"), + end=Timestamp("26-05-2022 00:00:00"), + ), + "UTC+0100", + Event( + begin=Timestamp("18-02-1954 00:00:00+0100"), + end=Timestamp("26-05-2022 00:00:00+0100"), + ), + id="naive_to_aware", + ), + pytest.param( + Event( + begin=Timestamp("18-02-1954 00:00:00+0100"), + end=Timestamp("26-05-2022 00:00:00+0100"), + ), + None, + Event( + begin=Timestamp("18-02-1954 00:00:00"), + end=Timestamp("26-05-2022 00:00:00"), + ), + id="aware_to_naive", + ), + pytest.param( + Event( + begin=Timestamp("18-02-1954 00:00:00+0100"), + end=Timestamp("26-05-2022 00:00:00+0100"), + ), + "UTC+0300", + Event( + begin=Timestamp("18-02-1954 02:00:00+0300"), + end=Timestamp("26-05-2022 02:00:00+0300"), + ), + id="aware_to_aware_converts_timezones", + ), + ], +) +def test_localize(event: Event, timezone: str | None, expected: Event) -> None: + event.localize(timezone) + assert event == expected From e313e8534cf991ae85f41fc48c6c442b2efcad96 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 4 Jun 2026 17:23:38 +0200 Subject: [PATCH 23/36] pass kwargs through Annotation.to_rectangle() --- src/osekit/core/annotation.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index df282fb0..22dfae84 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -369,15 +369,22 @@ def from_dict(cls, row: dict) -> Self: verifications=verifications, ) - def to_rectangle(self) -> Rectangle: + def to_rectangle(self, **kwargs) -> Rectangle: """Return a matplotlib Rectangle representing the annotation. + Parameters + ---------- + kwargs: + Additional keyword arguments + Returns ------- matplotlib.patches.Rectangle Rectangle representing the annotation. The coordinates of the rectangle are in time x frequency. + + """ return Rectangle( xy=( # type: ignore[arg-type] @@ -386,6 +393,7 @@ def to_rectangle(self) -> Rectangle: ), width=self.duration, # type: ignore[arg-type] height=self.frequency_bounds.bandwidth, + **kwargs, ) @classmethod From d11ec1ed0b31b5d14826e60a85504c0af6f5cdcf Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 4 Jun 2026 17:26:57 +0200 Subject: [PATCH 24/36] make SpetroData.plot() return the Axes on which the spetro has been plotted --- src/osekit/core/spectro_data.py | 10 ++++++++-- tests/test_spectro.py | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/osekit/core/spectro_data.py b/src/osekit/core/spectro_data.py index 127b122e..bf5d8c15 100644 --- a/src/osekit/core/spectro_data.py +++ b/src/osekit/core/spectro_data.py @@ -404,12 +404,12 @@ def plot( ax: plt.Axes | None = None, sx: np.ndarray | None = None, scale: Scale | None = None, - ) -> None: + ) -> plt.Axes: """Plot the spectrogram on a specific ``Axes``. Parameters ---------- - ax: plt.axes | None + ax: plt.Axes | None ``Axes`` on which the spectrogram should be plotted. Defaulted to ``osekit.utils.plot.get_default_axes()``. sx: np.ndarray | None @@ -417,6 +417,11 @@ def plot( scale: osekit.core.frequecy_scale.Scale Custom frequency scale to use for plotting the spectrogram. + Returns + ------- + plt.Axes + The ``Axes`` on which the spectrogram has been plotted. + """ ax = ax if ax is not None else get_default_axes() sx = self.get_value() if sx is None else sx @@ -439,6 +444,7 @@ def plot( interpolation="none", extent=(date2num(time[0]), date2num(time[-1]), freq[0], freq[-1]), ) + return ax def get_db_value(self, sx: np.ndarray | None = None) -> np.ndarray: """Return the ``Sx`` spectrum of the spectrogram expressed in ``dB``. diff --git a/tests/test_spectro.py b/tests/test_spectro.py index 9fc38ae3..4e4e309f 100644 --- a/tests/test_spectro.py +++ b/tests/test_spectro.py @@ -1425,7 +1425,8 @@ def mock_imshow( monkeypatch.setattr(plt.Axes, "imshow", mock_imshow) - sd.plot() + _, ax = plt.subplots() + sd_ax = sd.plot(ax=ax) assert (plot_kwargs["vmin"], plot_kwargs["vmax"]) == sd.v_lim assert plot_kwargs["cmap"] == sd.colormap @@ -1441,6 +1442,8 @@ def mock_imshow( assert f1 == sd.fft.f[0] assert f2 == sd.fft.f[-1] + assert sd_ax == ax + def test_spectro_default_v_lim(audio_files: pytest.fixture) -> None: files, _ = audio_files From 7ec806691d5a0faacb6829a03476132e339884aa Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 4 Jun 2026 17:40:01 +0200 Subject: [PATCH 25/36] add Annotation.__repr__() test --- tests/test_annotation.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index bf681f30..4946aa67 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -252,3 +252,7 @@ def test_annotations_from_csv() -> None: ), } assert np.array_equal(verification, verificated.verifications) + + # Repr should be the annotation ID + annotation = annotations[0] + assert str(annotation) == str(annotation.metadata.annotation_id) From 4897136d200a95567f903d5d361a148503450721 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Mon, 8 Jun 2026 10:55:19 +0200 Subject: [PATCH 26/36] add sample Annotation fixture --- tests/test_annotation.py | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index 4946aa67..dcc61067 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -3,16 +3,72 @@ import numpy as np import pytest +from pandas import Timestamp from osekit.core.annotation import ( Annotation, + AnnotationMetaData, AnnotatorInfo, ConfidenceIndicator, FrequencyBounds, + SignalParameters, Verification, ) +@pytest.fixture +def sample_annotation() -> Annotation: + return Annotation( + metadata=AnnotationMetaData( + annotation_id=35173, + base_id=None, + comments="He's a sneaky, sneaky dog friend", + filename="its_teasy", + phase="ANNOTATION", + project="mockasin", + ), + begin=Timestamp("2013-11-05 00:00:00"), + end=Timestamp("2013-11-05 00:00:10"), + frequency_bounds=FrequencyBounds( + min=1_000, + max=3_000, + ), + label="Connan", + annotator_info=AnnotatorInfo( + annotator="Mockasin", + annotator_expertise="EXPERT", + ), + annotation_type="BOX", + confidence_indicator=ConfidenceIndicator( + label="Sure", + level=2, + maximum_level=2, + ), + signal_quantity="SINGLE", + signal_parameters=SignalParameters( + does_overlap_other_signals=False, + frequency_jumps=True, + has_deterministic_chaos=True, + has_harmonics=True, + has_sidebands=True, + has_subharmonics=False, + is_itensity_too_low=False, + max_frequency=2_800, + min_frequency=1_300, + nb_relative_maxes=2, + nb_relative_mins=3, + nb_steps=4, + trend="MODULATED", + ), + verifications={ + Verification( + verificator="soft_hair", + is_validated=True, + ), + }, + ) + + @pytest.mark.parametrize( ("min_frequency", "max_frequency", "expectation"), [ From bedd7d74d29bb6483d91efb6e8e9ec2acd39ef08 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Mon, 8 Jun 2026 10:59:30 +0200 Subject: [PATCH 27/36] add Annotation.to_rectangle() test --- src/osekit/core/annotation.py | 4 ++-- tests/test_annotation.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 22dfae84..02b087dd 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -3,7 +3,7 @@ import math from dataclasses import dataclass from pathlib import Path -from typing import Literal, Self +from typing import Any, Literal, Self import pandas as pd from matplotlib.patches import Rectangle @@ -369,7 +369,7 @@ def from_dict(cls, row: dict) -> Self: verifications=verifications, ) - def to_rectangle(self, **kwargs) -> Rectangle: + def to_rectangle(self, **kwargs: Any) -> Rectangle: """Return a matplotlib Rectangle representing the annotation. Parameters diff --git a/tests/test_annotation.py b/tests/test_annotation.py index dcc61067..9b6691ab 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -312,3 +312,20 @@ def test_annotations_from_csv() -> None: # Repr should be the annotation ID annotation = annotations[0] assert str(annotation) == str(annotation.metadata.annotation_id) + + +def test_annotation_to_rectangle(sample_annotation: Annotation) -> None: + rectangle = sample_annotation.to_rectangle() + + t1, t2 = sample_annotation.begin, sample_annotation.end + + f_box = sample_annotation.frequency_bounds + f1, f2 = f_box.min, f_box.max + + x, y = rectangle.xy + + assert x == t1 + assert y == f1 + + assert x + rectangle.get_width() == t2 + assert y + rectangle.get_height() == f2 From 4566eaefd27f8b171e9a3b8c0cc0cbf1748f4eba Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Mon, 8 Jun 2026 11:39:46 +0200 Subject: [PATCH 28/36] rename AnnotatorInfo fields --- src/osekit/core/annotation.py | 15 ++++++--------- tests/test_annotation.py | 26 +++++++++++++------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/osekit/core/annotation.py b/src/osekit/core/annotation.py index 02b087dd..d96e26ea 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/annotation.py @@ -97,19 +97,16 @@ def bandwidth(self) -> int: class AnnotatorInfo: """Class representing an annotator info.""" - annotator: str - annotator_expertise: Literal["NOVICE", "AVERAGE", "EXPERT"] | None = None + name: str + expertise: Literal["NOVICE", "AVERAGE", "EXPERT"] | None = None def __hash__(self) -> int: """Return a hash for the annotator.""" - return hash((self.annotator, self.annotator_expertise)) + return hash((self.name, self.expertise)) def __eq__(self, other: Self) -> bool: """Return whether two annotators are equal.""" - return ( - self.annotator == other.annotator - and self.annotator_expertise == other.annotator_expertise - ) + return self.name == other.name and self.expertise == other.expertise @dataclass @@ -309,8 +306,8 @@ def from_dict(cls, row: dict) -> Self: phase=row["created_at_phase"], ) annotator_info = AnnotatorInfo( - annotator=row["annotator"], - annotator_expertise=row["annotator_expertise"], + name=row["annotator"], + expertise=row["annotator_expertise"], ) min_frequency, max_frequency = row["min_frequency"], row["max_frequency"] diff --git a/tests/test_annotation.py b/tests/test_annotation.py index 9b6691ab..0460453a 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -35,8 +35,8 @@ def sample_annotation() -> Annotation: ), label="Connan", annotator_info=AnnotatorInfo( - annotator="Mockasin", - annotator_expertise="EXPERT", + name="Mockasin", + expertise="EXPERT", ), annotation_type="BOX", confidence_indicator=ConfidenceIndicator( @@ -131,13 +131,13 @@ def test_frequency_bounds( def test_annotator_info() -> None: annotators = [ - AnnotatorInfo(annotator="ruby", annotator_expertise="NOVICE"), - AnnotatorInfo(annotator="ruby", annotator_expertise="NOVICE"), - AnnotatorInfo(annotator="haunt", annotator_expertise="EXPERT"), - AnnotatorInfo(annotator="haunt", annotator_expertise="EXPERT"), - AnnotatorInfo(annotator="nevada", annotator_expertise="EXPERT"), - AnnotatorInfo(annotator="nevada", annotator_expertise="EXPERT"), - AnnotatorInfo(annotator="haunt", annotator_expertise=None), + AnnotatorInfo(name="ruby", expertise="NOVICE"), + AnnotatorInfo(name="ruby", expertise="NOVICE"), + AnnotatorInfo(name="haunt", expertise="EXPERT"), + AnnotatorInfo(name="haunt", expertise="EXPERT"), + AnnotatorInfo(name="nevada", expertise="EXPERT"), + AnnotatorInfo(name="nevada", expertise="EXPERT"), + AnnotatorInfo(name="haunt", expertise=None), ] nb_unique_annotators = 4 @@ -285,10 +285,10 @@ def test_annotations_from_csv() -> None: # Annotator parsing annotators = { - AnnotatorInfo(annotator="vashti", annotator_expertise="NOVICE"), - AnnotatorInfo(annotator="heartleap", annotator_expertise=None), - AnnotatorInfo(annotator="bunyan", annotator_expertise="EXPERT"), - AnnotatorInfo(annotator="lookaftering", annotator_expertise="EXPERT"), + AnnotatorInfo(name="vashti", expertise="NOVICE"), + AnnotatorInfo(name="heartleap", expertise=None), + AnnotatorInfo(name="bunyan", expertise="EXPERT"), + AnnotatorInfo(name="lookaftering", expertise="EXPERT"), } assert np.array_equal( annotators, From 238a3fb48fda945d085fbe776b1a145d6ef2ccdd Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 9 Jun 2026 16:15:07 +0200 Subject: [PATCH 29/36] add aplose resutlts entry in doc --- .../_static/annotations/aplose_results.csv | 49 +++++++++++++++++++ docs/source/annotation.rst | 25 ++++++++++ docs/source/aplose.rst | 7 +++ docs/source/coreapi.rst | 1 + docs/source/usage.rst | 1 + 5 files changed, 83 insertions(+) create mode 100644 docs/source/_static/annotations/aplose_results.csv create mode 100644 docs/source/annotation.rst create mode 100644 docs/source/aplose.rst diff --git a/docs/source/_static/annotations/aplose_results.csv b/docs/source/_static/annotations/aplose_results.csv new file mode 100644 index 00000000..7a1e9e15 --- /dev/null +++ b/docs/source/_static/annotations/aplose_results.csv @@ -0,0 +1,49 @@ +dataset,filename,annotation_id,is_update_of_id,start_time,end_time,start_frequency,end_frequency,min_frequency,max_frequency,annotation,annotator,annotator_expertise,start_datetime,end_datetime,is_box,type,confidence_indicator_label,confidence_indicator_level,comments,signal_quantity,signal_is_intensity_too_low,signal_does_overlap_other_signals,signal_start_frequency,signal_end_frequency,signal_relative_min_frequency_count,signal_relative_max_frequency_count,signal_steps_count,signal_has_harmonics,signal_trend,signal_sidebands,signal_subharmonics,signal_frequency_jumps,signal_deterministic_chaos,created_at_phase,jbeesa +doc_osekit,2022_09_25_22_35_15_000000,593717,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:35:15.000+00:00,2022-09-25T22:35:22.000+00:00,0,WEAK,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_15_000000,593718,,4.931,6.703,6665.0,22181.0,6665.0,22181.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:35:19.931+00:00,2022-09-25T22:35:21.703+00:00,1,BOX,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_15_000000,593727,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,efouin,,2022-09-25T22:35:15.000+00:00,2022-09-25T22:35:22.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_15_000000,593728,,4.967,5.631,8025.0,15197.0,8025.0,15197.0,Odontocete whistle,efouin,,2022-09-25T22:35:19.967+00:00,2022-09-25T22:35:20.631+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_15_000000,593729,,6.16,6.658,7463.0,22135.0,7463.0,22135.0,Odontocete whistle,efouin,,2022-09-25T22:35:21.160+00:00,2022-09-25T22:35:21.658+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_15_000000,593737,,5.658,6.122,8447.0,20400.0,8447.0,20400.0,Odontocete whistle,efouin,,2022-09-25T22:35:20.658+00:00,2022-09-25T22:35:21.122+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_15_000000,593772,,5.936,5.936,20399.0,20399.0,20399.0,20399.0,Odontocete whistle,jbeesa,EXPERT,2022-09-25T22:35:20.936+00:00,2022-09-25T22:35:20.936+00:00,1,POINT,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION, +doc_osekit,2022_09_25_22_35_15_000000,593773,,5.659,6.681,7260.0,20448.0,7260.0,20448.0,Odontocete whistle,jbeesa,EXPERT,2022-09-25T22:35:20.659+00:00,2022-09-25T22:35:21.681+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION, +doc_osekit,2022_09_25_22_35_22_000000,593719,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:35:22.000+00:00,2022-09-25T22:35:29.000+00:00,0,WEAK,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_22_000000,593720,,4.239,5.39,5728.0,20540.0,5728.0,20540.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:35:26.239+00:00,2022-09-25T22:35:27.390+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_22_000000,593738,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,efouin,,2022-09-25T22:35:22.000+00:00,2022-09-25T22:35:29.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_22_000000,593739,,4.855,5.299,6525.0,15947.0,6525.0,15947.0,Odontocete whistle,efouin,,2022-09-25T22:35:26.855+00:00,2022-09-25T22:35:27.299+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_22_000000,593740,,4.164,4.77,9479.0,19182.0,9479.0,19182.0,Odontocete whistle,efouin,,2022-09-25T22:35:26.164+00:00,2022-09-25T22:35:26.770+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False +doc_osekit,2022_09_25_22_35_29_000000,593721,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_29_000000,593722,,6.498,7.0,9571.0,23868.0,9571.0,23868.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:35:35.498+00:00,2022-09-25T22:35:36.000+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_29_000000,593741,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,efouin,,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_29_000000,593742,,6.485,6.971,1322.0,24000.0,1322.0,24000.0,Odontocete click,efouin,,2022-09-25T22:35:35.485+00:00,2022-09-25T22:35:35.971+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_29_000000,593743,,0.0,7.0,0.0,24000.0,0.0,24000.0,Boat,efouin,,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_35_29_000000,593744,,3.431,5.786,1979.0,5447.0,1979.0,5447.0,Boat,efouin,,2022-09-25T22:35:32.431+00:00,2022-09-25T22:35:34.786+00:00,1,BOX,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,False +doc_osekit,2022_09_25_22_36_04_000000,593723,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_04_000000,593724,,5.042,7.0,8493.0,21337.0,8493.0,21337.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:36:09.042+00:00,2022-09-25T22:36:11.000+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_04_000000,593747,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,efouin,,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_04_000000,593748,,5.913,6.589,12104.0,16135.0,12104.0,16135.0,Odontocete whistle,efouin,,2022-09-25T22:36:09.913+00:00,2022-09-25T22:36:10.589+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_04_000000,593767,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ncaron,AVERAGE,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_04_000000,593768,,4.975,6.724,11400.0,19041.0,11400.0,19041.0,Odontocete whistle,ncaron,AVERAGE,2022-09-25T22:36:08.975+00:00,2022-09-25T22:36:10.724+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_11_000000,593725,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_11_000000,593726,,5.008,7.0,12337.0,23868.0,12337.0,23868.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:36:16.008+00:00,2022-09-25T22:36:18.000+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_11_000000,593749,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,efouin,,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_11_000000,593750,,5.048,6.971,11729.0,23916.0,11729.0,23916.0,Odontocete click,efouin,,2022-09-25T22:36:16.048+00:00,2022-09-25T22:36:17.971+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_11_000000,593751,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete buzz,efouin,,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_11_000000,593752,,3.616,3.774,4229.0,23447.0,4229.0,23447.0,Odontocete buzz,efouin,,2022-09-25T22:36:14.616+00:00,2022-09-25T22:36:14.774+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False +doc_osekit,2022_09_25_22_36_11_000000,593769,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_11_000000,593770,,0.0,1.245,13041.0,24000.0,13041.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:12.245+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_11_000000,593771,,5.18,6.986,10040.0,24000.0,10040.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:16.180+00:00,2022-09-25T22:36:17.986+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593730,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593731,,1.977,3.533,5071.0,20399.0,5071.0,20399.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:36:19.977+00:00,2022-09-25T22:36:21.533+00:00,1,BOX,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False +doc_osekit,2022_09_25_22_36_18_000000,593732,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593733,,0.0,0.9,12665.0,24000.0,12665.0,24000.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:18.900+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593734,,0.0,7.0,0.0,24000.0,0.0,24000.0,Boat,mdupont,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593735,,6.178,6.405,3524.0,24000.0,3524.0,24000.0,Boat,mdupont,EXPERT,2022-09-25T22:36:24.178+00:00,2022-09-25T22:36:24.405+00:00,1,BOX,Quite sure,2/3,,,,,,,,,,,,,,,,ANNOTATION,False +doc_osekit,2022_09_25_22_36_18_000000,593736,,1.737,3.722,4086.0,11539.0,4086.0,11539.0,Boat,mdupont,EXPERT,2022-09-25T22:36:19.737+00:00,2022-09-25T22:36:21.722+00:00,1,BOX,100% sure!,3/3,,,,,,,,,,,,,,,,ANNOTATION,False +doc_osekit,2022_09_25_22_36_18_000000,593753,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,efouin,,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593754,,1.689,4.095,2869.0,22744.0,2869.0,22744.0,Odontocete whistle,efouin,,2022-09-25T22:36:19.689+00:00,2022-09-25T22:36:22.095+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False +doc_osekit,2022_09_25_22_36_18_000000,593774,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593775,,4.234,6.423,2822.0,24000.0,2822.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:22.234+00:00,2022-09-25T22:36:24.423+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593776,,0.0,0.797,9244.0,24000.0,9244.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:18.797+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593777,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ncaron,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True +doc_osekit,2022_09_25_22_36_18_000000,593778,,2.079,3.384,6104.0,20213.0,6104.0,20213.0,Odontocete whistle,ncaron,AVERAGE,2022-09-25T22:36:20.079+00:00,2022-09-25T22:36:21.384+00:00,1,BOX,Not sure at all,1/3,,SINGLE,,,11494.0,6666.0,1,1,,,MOD,,,,,ANNOTATION,False diff --git a/docs/source/annotation.rst b/docs/source/annotation.rst new file mode 100644 index 00000000..c1f5bb4b --- /dev/null +++ b/docs/source/annotation.rst @@ -0,0 +1,25 @@ +.. _annotation: + +Annotation +---------- + +.. autoclass:: osekit.core.annotation.Annotation + :members: + +.. autoclass:: osekit.core.annotation.FrequencyBounds + :members: + +.. autoclass:: osekit.core.annotation.AnnotatorInfo + :members: + +.. autoclass:: osekit.core.annotation.SignalParameters + :members: + +.. autoclass:: osekit.core.annotation.ConfidenceIndicator + :members: + +.. autoclass:: osekit.core.annotation.AnnotationMetaData + :members: + +.. autoclass:: osekit.core.annotation.Verification + :members: diff --git a/docs/source/aplose.rst b/docs/source/aplose.rst new file mode 100644 index 00000000..1bbb5892 --- /dev/null +++ b/docs/source/aplose.rst @@ -0,0 +1,7 @@ +Working with APLOSE results +--------------------------- + +`APLOSE `_ is **OSmOSE**'s web-based annotation platform. + +**APLOSE** campaigns `results `_ are provided as csv files +that can be parsed in **OSEkit** as :class:`osekit.core.annotation.Annotation` instances. diff --git a/docs/source/coreapi.rst b/docs/source/coreapi.rst index 20d96e7f..f9e838f3 100644 --- a/docs/source/coreapi.rst +++ b/docs/source/coreapi.rst @@ -23,3 +23,4 @@ Core ltasdata audiofilemanager frequencyscale + annotation diff --git a/docs/source/usage.rst b/docs/source/usage.rst index cf1769f6..68aa8f97 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -27,3 +27,4 @@ The package combines two APIs: coreapi_home multiprocessing jobs + aplose From 2888739d6bec566ca4d5f8eede8498f33bf8251d Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Tue, 9 Jun 2026 17:09:18 +0200 Subject: [PATCH 30/36] complete annotation results file --- .../_static/annotations/aplose_results.csv | 102 +++++++++--------- docs/source/aplose.rst | 5 + docs/source/example_aplose_result.ipynb | 74 +++++++++++++ 3 files changed, 132 insertions(+), 49 deletions(-) create mode 100644 docs/source/example_aplose_result.ipynb diff --git a/docs/source/_static/annotations/aplose_results.csv b/docs/source/_static/annotations/aplose_results.csv index 7a1e9e15..6d067338 100644 --- a/docs/source/_static/annotations/aplose_results.csv +++ b/docs/source/_static/annotations/aplose_results.csv @@ -1,49 +1,53 @@ -dataset,filename,annotation_id,is_update_of_id,start_time,end_time,start_frequency,end_frequency,min_frequency,max_frequency,annotation,annotator,annotator_expertise,start_datetime,end_datetime,is_box,type,confidence_indicator_label,confidence_indicator_level,comments,signal_quantity,signal_is_intensity_too_low,signal_does_overlap_other_signals,signal_start_frequency,signal_end_frequency,signal_relative_min_frequency_count,signal_relative_max_frequency_count,signal_steps_count,signal_has_harmonics,signal_trend,signal_sidebands,signal_subharmonics,signal_frequency_jumps,signal_deterministic_chaos,created_at_phase,jbeesa -doc_osekit,2022_09_25_22_35_15_000000,593717,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:35:15.000+00:00,2022-09-25T22:35:22.000+00:00,0,WEAK,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_15_000000,593718,,4.931,6.703,6665.0,22181.0,6665.0,22181.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:35:19.931+00:00,2022-09-25T22:35:21.703+00:00,1,BOX,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_15_000000,593727,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,efouin,,2022-09-25T22:35:15.000+00:00,2022-09-25T22:35:22.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_15_000000,593728,,4.967,5.631,8025.0,15197.0,8025.0,15197.0,Odontocete whistle,efouin,,2022-09-25T22:35:19.967+00:00,2022-09-25T22:35:20.631+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_15_000000,593729,,6.16,6.658,7463.0,22135.0,7463.0,22135.0,Odontocete whistle,efouin,,2022-09-25T22:35:21.160+00:00,2022-09-25T22:35:21.658+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_15_000000,593737,,5.658,6.122,8447.0,20400.0,8447.0,20400.0,Odontocete whistle,efouin,,2022-09-25T22:35:20.658+00:00,2022-09-25T22:35:21.122+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_15_000000,593772,,5.936,5.936,20399.0,20399.0,20399.0,20399.0,Odontocete whistle,jbeesa,EXPERT,2022-09-25T22:35:20.936+00:00,2022-09-25T22:35:20.936+00:00,1,POINT,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION, -doc_osekit,2022_09_25_22_35_15_000000,593773,,5.659,6.681,7260.0,20448.0,7260.0,20448.0,Odontocete whistle,jbeesa,EXPERT,2022-09-25T22:35:20.659+00:00,2022-09-25T22:35:21.681+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION, -doc_osekit,2022_09_25_22_35_22_000000,593719,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:35:22.000+00:00,2022-09-25T22:35:29.000+00:00,0,WEAK,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_22_000000,593720,,4.239,5.39,5728.0,20540.0,5728.0,20540.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:35:26.239+00:00,2022-09-25T22:35:27.390+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_22_000000,593738,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,efouin,,2022-09-25T22:35:22.000+00:00,2022-09-25T22:35:29.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_22_000000,593739,,4.855,5.299,6525.0,15947.0,6525.0,15947.0,Odontocete whistle,efouin,,2022-09-25T22:35:26.855+00:00,2022-09-25T22:35:27.299+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_22_000000,593740,,4.164,4.77,9479.0,19182.0,9479.0,19182.0,Odontocete whistle,efouin,,2022-09-25T22:35:26.164+00:00,2022-09-25T22:35:26.770+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False -doc_osekit,2022_09_25_22_35_29_000000,593721,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_29_000000,593722,,6.498,7.0,9571.0,23868.0,9571.0,23868.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:35:35.498+00:00,2022-09-25T22:35:36.000+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_29_000000,593741,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,efouin,,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_29_000000,593742,,6.485,6.971,1322.0,24000.0,1322.0,24000.0,Odontocete click,efouin,,2022-09-25T22:35:35.485+00:00,2022-09-25T22:35:35.971+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_29_000000,593743,,0.0,7.0,0.0,24000.0,0.0,24000.0,Boat,efouin,,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_35_29_000000,593744,,3.431,5.786,1979.0,5447.0,1979.0,5447.0,Boat,efouin,,2022-09-25T22:35:32.431+00:00,2022-09-25T22:35:34.786+00:00,1,BOX,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,False -doc_osekit,2022_09_25_22_36_04_000000,593723,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_04_000000,593724,,5.042,7.0,8493.0,21337.0,8493.0,21337.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:36:09.042+00:00,2022-09-25T22:36:11.000+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_04_000000,593747,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,efouin,,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_04_000000,593748,,5.913,6.589,12104.0,16135.0,12104.0,16135.0,Odontocete whistle,efouin,,2022-09-25T22:36:09.913+00:00,2022-09-25T22:36:10.589+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_04_000000,593767,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ncaron,AVERAGE,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_04_000000,593768,,4.975,6.724,11400.0,19041.0,11400.0,19041.0,Odontocete whistle,ncaron,AVERAGE,2022-09-25T22:36:08.975+00:00,2022-09-25T22:36:10.724+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_11_000000,593725,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_11_000000,593726,,5.008,7.0,12337.0,23868.0,12337.0,23868.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:36:16.008+00:00,2022-09-25T22:36:18.000+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_11_000000,593749,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,efouin,,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_11_000000,593750,,5.048,6.971,11729.0,23916.0,11729.0,23916.0,Odontocete click,efouin,,2022-09-25T22:36:16.048+00:00,2022-09-25T22:36:17.971+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_11_000000,593751,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete buzz,efouin,,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_11_000000,593752,,3.616,3.774,4229.0,23447.0,4229.0,23447.0,Odontocete buzz,efouin,,2022-09-25T22:36:14.616+00:00,2022-09-25T22:36:14.774+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False -doc_osekit,2022_09_25_22_36_11_000000,593769,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_11_000000,593770,,0.0,1.245,13041.0,24000.0,13041.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:12.245+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_11_000000,593771,,5.18,6.986,10040.0,24000.0,10040.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:16.180+00:00,2022-09-25T22:36:17.986+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593730,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593731,,1.977,3.533,5071.0,20399.0,5071.0,20399.0,Odontocete whistle,mdupont,EXPERT,2022-09-25T22:36:19.977+00:00,2022-09-25T22:36:21.533+00:00,1,BOX,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False -doc_osekit,2022_09_25_22_36_18_000000,593732,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593733,,0.0,0.9,12665.0,24000.0,12665.0,24000.0,Odontocete click,mdupont,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:18.900+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593734,,0.0,7.0,0.0,24000.0,0.0,24000.0,Boat,mdupont,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593735,,6.178,6.405,3524.0,24000.0,3524.0,24000.0,Boat,mdupont,EXPERT,2022-09-25T22:36:24.178+00:00,2022-09-25T22:36:24.405+00:00,1,BOX,Quite sure,2/3,,,,,,,,,,,,,,,,ANNOTATION,False -doc_osekit,2022_09_25_22_36_18_000000,593736,,1.737,3.722,4086.0,11539.0,4086.0,11539.0,Boat,mdupont,EXPERT,2022-09-25T22:36:19.737+00:00,2022-09-25T22:36:21.722+00:00,1,BOX,100% sure!,3/3,,,,,,,,,,,,,,,,ANNOTATION,False -doc_osekit,2022_09_25_22_36_18_000000,593753,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,efouin,,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593754,,1.689,4.095,2869.0,22744.0,2869.0,22744.0,Odontocete whistle,efouin,,2022-09-25T22:36:19.689+00:00,2022-09-25T22:36:22.095+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False -doc_osekit,2022_09_25_22_36_18_000000,593774,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593775,,4.234,6.423,2822.0,24000.0,2822.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:22.234+00:00,2022-09-25T22:36:24.423+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593776,,0.0,0.797,9244.0,24000.0,9244.0,24000.0,Odontocete click,ncaron,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:18.797+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593777,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ncaron,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True -doc_osekit,2022_09_25_22_36_18_000000,593778,,2.079,3.384,6104.0,20213.0,6104.0,20213.0,Odontocete whistle,ncaron,AVERAGE,2022-09-25T22:36:20.079+00:00,2022-09-25T22:36:21.384+00:00,1,BOX,Not sure at all,1/3,,SINGLE,,,11494.0,6666.0,1,1,,,MOD,,,,,ANNOTATION,False +project,filename,annotation_id,is_update_of_id,start_time,end_time,start_frequency,end_frequency,min_frequency,max_frequency,annotation,annotator,annotator_expertise,start_datetime,end_datetime,is_box,type,confidence_indicator_label,confidence_indicator_level,comments,signal_quantity,signal_is_intensity_too_low,signal_does_overlap_other_signals,signal_start_frequency,signal_end_frequency,signal_relative_min_frequency_count,signal_relative_max_frequency_count,signal_steps_count,signal_has_harmonics,signal_trend,signal_sidebands,signal_subharmonics,signal_frequency_jumps,signal_deterministic_chaos,created_at_phase,ben,leslie +doc_osekit,2022_09_25_22_35_15_000000,593717,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ron,EXPERT,2022-09-25T22:35:15.000+00:00,2022-09-25T22:35:22.000+00:00,0,WEAK,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_15_000000,593718,,4.931,6.703,6665.0,22181.0,6665.0,22181.0,Odontocete whistle,ron,EXPERT,2022-09-25T22:35:19.931+00:00,2022-09-25T22:35:21.703+00:00,1,BOX,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_15_000000,593727,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,april,,2022-09-25T22:35:15.000+00:00,2022-09-25T22:35:22.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_15_000000,593728,,4.967,5.631,8025.0,15197.0,8025.0,15197.0,Odontocete whistle,april,,2022-09-25T22:35:19.967+00:00,2022-09-25T22:35:20.631+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_15_000000,593729,,6.16,6.658,7463.0,22135.0,7463.0,22135.0,Odontocete whistle,april,,2022-09-25T22:35:21.160+00:00,2022-09-25T22:35:21.658+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_15_000000,593737,,5.658,6.122,8447.0,20400.0,8447.0,20400.0,Odontocete whistle,april,,2022-09-25T22:35:20.658+00:00,2022-09-25T22:35:21.122+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_15_000000,593772,,5.936,5.936,20399.0,20399.0,20399.0,20399.0,Odontocete whistle,ben,EXPERT,2022-09-25T22:35:20.936+00:00,2022-09-25T22:35:20.936+00:00,1,POINT,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, +doc_osekit,2022_09_25_22_35_15_000000,593773,,5.659,6.681,7260.0,20448.0,7260.0,20448.0,Odontocete whistle,ben,EXPERT,2022-09-25T22:35:20.659+00:00,2022-09-25T22:35:21.681+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, +doc_osekit,2022_09_25_22_35_22_000000,593719,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ron,EXPERT,2022-09-25T22:35:22.000+00:00,2022-09-25T22:35:29.000+00:00,0,WEAK,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_22_000000,593720,,4.239,5.39,5728.0,20540.0,5728.0,20540.0,Odontocete whistle,ron,EXPERT,2022-09-25T22:35:26.239+00:00,2022-09-25T22:35:27.390+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_22_000000,593738,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,april,,2022-09-25T22:35:22.000+00:00,2022-09-25T22:35:29.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_22_000000,593739,,4.855,5.299,6525.0,15947.0,6525.0,15947.0,Odontocete whistle,april,,2022-09-25T22:35:26.855+00:00,2022-09-25T22:35:27.299+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_22_000000,593740,,4.164,4.77,9479.0,19182.0,9479.0,19182.0,Odontocete whistle,april,,2022-09-25T22:35:26.164+00:00,2022-09-25T22:35:26.770+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False,True +doc_osekit,2022_09_25_22_35_29_000000,593721,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ron,EXPERT,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_29_000000,593722,,6.498,7.0,9571.0,23868.0,9571.0,23868.0,Odontocete click,ron,EXPERT,2022-09-25T22:35:35.498+00:00,2022-09-25T22:35:36.000+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_29_000000,593741,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,april,,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_29_000000,593742,,6.485,6.971,1322.0,24000.0,1322.0,24000.0,Odontocete click,april,,2022-09-25T22:35:35.485+00:00,2022-09-25T22:35:35.971+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_35_29_000000,593743,,0.0,7.0,0.0,24000.0,0.0,24000.0,Boat,april,,2022-09-25T22:35:29.000+00:00,2022-09-25T22:35:36.000+00:00,0,WEAK,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,True,False +doc_osekit,2022_09_25_22_35_29_000000,593744,,3.431,5.786,1979.0,5447.0,1979.0,5447.0,Boat,april,,2022-09-25T22:35:32.431+00:00,2022-09-25T22:35:34.786+00:00,1,BOX,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,False,False +doc_osekit,2022_09_25_22_36_04_000000,593723,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ron,EXPERT,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_04_000000,593724,,5.042,7.0,8493.0,21337.0,8493.0,21337.0,Odontocete whistle,ron,EXPERT,2022-09-25T22:36:09.042+00:00,2022-09-25T22:36:11.000+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,False +doc_osekit,2022_09_25_22_36_04_000000,593747,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,april,,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_04_000000,593748,,5.913,6.589,12104.0,16135.0,12104.0,16135.0,Odontocete whistle,april,,2022-09-25T22:36:09.913+00:00,2022-09-25T22:36:10.589+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_04_000000,593767,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ann,AVERAGE,2022-09-25T22:36:04.000+00:00,2022-09-25T22:36:11.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_04_000000,593768,,4.975,6.724,11400.0,19041.0,11400.0,19041.0,Odontocete whistle,ann,AVERAGE,2022-09-25T22:36:08.975+00:00,2022-09-25T22:36:10.724+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,False +doc_osekit,2022_09_25_22_36_04_000000,593805,593724,5.918468342784749,6.571428453600085,13133.0,21336.0,13133.0,21336.0,Odontocete whistle,leslie,EXPERT,2022-09-25T22:36:09.918+00:00,2022-09-25T22:36:10.571+00:00,1,BOX,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, +doc_osekit,2022_09_25_22_36_04_000000,593806,593768,5.913256081835184,6.596589371964738,12806.0,19040.0,12806.0,19040.0,Odontocete whistle,leslie,EXPERT,2022-09-25T22:36:09.913+00:00,2022-09-25T22:36:10.596+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, +doc_osekit,2022_09_25_22_36_11_000000,593725,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ron,EXPERT,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Quite sure,2/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_11_000000,593726,,5.008,7.0,12337.0,23868.0,12337.0,23868.0,Odontocete click,ron,EXPERT,2022-09-25T22:36:16.008+00:00,2022-09-25T22:36:18.000+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_11_000000,593749,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,april,,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_11_000000,593750,,5.048,6.971,11729.0,23916.0,11729.0,23916.0,Odontocete click,april,,2022-09-25T22:36:16.048+00:00,2022-09-25T22:36:17.971+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_11_000000,593751,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete buzz,april,,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,False +doc_osekit,2022_09_25_22_36_11_000000,593752,,3.616,3.774,4229.0,23447.0,4229.0,23447.0,Odontocete buzz,april,,2022-09-25T22:36:14.616+00:00,2022-09-25T22:36:14.774+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False,False +doc_osekit,2022_09_25_22_36_11_000000,593769,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ann,AVERAGE,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:18.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_11_000000,593770,,0.0,1.245,13041.0,24000.0,13041.0,24000.0,Odontocete click,ann,AVERAGE,2022-09-25T22:36:11.000+00:00,2022-09-25T22:36:12.245+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_11_000000,593771,,5.18,6.986,10040.0,24000.0,10040.0,24000.0,Odontocete click,ann,AVERAGE,2022-09-25T22:36:16.180+00:00,2022-09-25T22:36:17.986+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_11_000000,593807,,3.857,3.857,6337.0,6337.0,6337.0,6337.0,Odontocete buzz,leslie,EXPERT,2022-09-25T22:36:14.857+00:00,2022-09-25T22:36:14.857+00:00,1,POINT,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, +doc_osekit,2022_09_25_22_36_18_000000,593730,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ron,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_18_000000,593731,,1.977,3.533,5071.0,20399.0,5071.0,20399.0,Odontocete whistle,ron,EXPERT,2022-09-25T22:36:19.977+00:00,2022-09-25T22:36:21.533+00:00,1,BOX,100% sure!,3/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False,True +doc_osekit,2022_09_25_22_36_18_000000,593732,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ron,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_18_000000,593733,,0.0,0.9,12665.0,24000.0,12665.0,24000.0,Odontocete click,ron,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:18.900+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_18_000000,593734,,0.0,7.0,0.0,24000.0,0.0,24000.0,Boat,ron,EXPERT,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,,,,,,,,,,,,,,,ANNOTATION,True,False +doc_osekit,2022_09_25_22_36_18_000000,593735,,6.178,6.405,3524.0,24000.0,3524.0,24000.0,Boat,ron,EXPERT,2022-09-25T22:36:24.178+00:00,2022-09-25T22:36:24.405+00:00,1,BOX,Quite sure,2/3,,,,,,,,,,,,,,,,ANNOTATION,False,False +doc_osekit,2022_09_25_22_36_18_000000,593736,,1.737,3.722,4086.0,11539.0,4086.0,11539.0,Boat,ron,EXPERT,2022-09-25T22:36:19.737+00:00,2022-09-25T22:36:21.722+00:00,1,BOX,100% sure!,3/3,,,,,,,,,,,,,,,,ANNOTATION,False,False +doc_osekit,2022_09_25_22_36_18_000000,593753,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,april,,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_18_000000,593754,,1.689,4.095,2869.0,22744.0,2869.0,22744.0,Odontocete whistle,april,,2022-09-25T22:36:19.689+00:00,2022-09-25T22:36:22.095+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False,False +doc_osekit,2022_09_25_22_36_18_000000,593774,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete click,ann,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_18_000000,593775,,4.234,6.423,2822.0,24000.0,2822.0,24000.0,Odontocete click,ann,AVERAGE,2022-09-25T22:36:22.234+00:00,2022-09-25T22:36:24.423+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_18_000000,593776,,0.0,0.797,9244.0,24000.0,9244.0,24000.0,Odontocete click,ann,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:18.797+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_18_000000,593777,,0.0,7.0,0.0,24000.0,0.0,24000.0,Odontocete whistle,ann,AVERAGE,2022-09-25T22:36:18.000+00:00,2022-09-25T22:36:25.000+00:00,0,WEAK,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True +doc_osekit,2022_09_25_22_36_18_000000,593778,,2.079,3.384,6104.0,20213.0,6104.0,20213.0,Odontocete whistle,ann,AVERAGE,2022-09-25T22:36:20.079+00:00,2022-09-25T22:36:21.384+00:00,1,BOX,Not sure at all,1/3,,SINGLE,,,11494.0,6666.0,1,1,,,MOD,,,,,ANNOTATION,False,True +doc_osekit,2022_09_25_22_36_18_000000,593808,593754,2.1098456069769544,3.164478905872949,6290.0,20493.0,6290.0,20493.0,Odontocete whistle,leslie,EXPERT,2022-09-25T22:36:20.109+00:00,2022-09-25T22:36:21.164+00:00,1,BOX,Not sure at all,1/3,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, diff --git a/docs/source/aplose.rst b/docs/source/aplose.rst index 1bbb5892..f703739f 100644 --- a/docs/source/aplose.rst +++ b/docs/source/aplose.rst @@ -1,3 +1,5 @@ +.. _aplose: + Working with APLOSE results --------------------------- @@ -5,3 +7,6 @@ Working with APLOSE results **APLOSE** campaigns `results `_ are provided as csv files that can be parsed in **OSEkit** as :class:`osekit.core.annotation.Annotation` instances. + +Loading an APLOSE results file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/example_aplose_result.ipynb b/docs/source/example_aplose_result.ipynb new file mode 100644 index 00000000..bb1c3222 --- /dev/null +++ b/docs/source/example_aplose_result.ipynb @@ -0,0 +1,74 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "initial_id", + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Executing this cell will disable all TQDM outputs in stdout.\n", + "import os\n", + "\n", + "os.environ[\"DISABLE_TQDM\"] = \"True\"" + ] + }, + { + "cell_type": "markdown", + "id": "8c395a2079d86493", + "metadata": {}, + "source": [ + "# Using APLOSE annotation results [^download]\n", + "\n", + "[^download]: This notebook can be downloaded as **{nb-download}`example_aplose_result.ipynb`**." + ] + }, + { + "cell_type": "markdown", + "id": "5f23b72a5303ce5e", + "metadata": {}, + "source": "Parse the annotation result csv file thanks to the `Annotation` class:" + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1948b260fcaf03ab", + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-10T07:51:57.297976500Z", + "start_time": "2026-06-10T07:51:56.702618600Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from osekit.core.annotation import Annotation\n", + "\n", + "annotations = Annotation.from_csv(csv=Path(r\"_static/annotations/aplose_results.csv\"))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 5050ddf49063e47ba4bbfe5cf7f42794971e6cbf Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 10 Jun 2026 12:15:01 +0200 Subject: [PATCH 31/36] add APLOSE results notebook --- docs/source/example_aplose_result.ipynb | 247 +++++++++++++++++++++++- 1 file changed, 238 insertions(+), 9 deletions(-) diff --git a/docs/source/example_aplose_result.ipynb b/docs/source/example_aplose_result.ipynb index bb1c3222..12947349 100644 --- a/docs/source/example_aplose_result.ipynb +++ b/docs/source/example_aplose_result.ipynb @@ -9,10 +9,107 @@ }, "outputs": [], "source": [ - "# Executing this cell will disable all TQDM outputs in stdout.\n", + "# Executing this cell will:\n", + "\n", + "# Disable all TQDM outputs in stdout.\n", "import os\n", "\n", - "os.environ[\"DISABLE_TQDM\"] = \"True\"" + "os.environ[\"DISABLE_TQDM\"] = \"True\"\n", + "\n", + "# Setup the python logger for the Public API\n", + "from osekit import setup_logging\n", + "\n", + "setup_logging() # Overwrites the default logger to" + ] + }, + { + "cell_type": "markdown", + "id": "567a8c1c1e6bffa", + "metadata": {}, + "source": [ + "# Creating the Public Project\n", + "\n", + "[APLOSE](https://osmose.ifremer.fr/doc/)-compatible projects are build thanks to OSEkit's `Public API`.\n", + "\n", + "First, we will build a project and run a transform that would be uploaded and annotated on APLOSE (see the [Public API documentation](https://project-osmose.github.io/OSEkit/publicapi_usage.html) for more info).\n", + "\n", + "The `_static/annotations/aplose_results.csv` file used in this notebook simulates the results of this annotation campaign." + ] + }, + { + "cell_type": "markdown", + "id": "967313a80c4c1c24", + "metadata": {}, + "source": [ + "## Build the Project\n", + "\n", + "First, we have to build the project from the raw audio files:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6db74ea7fc7da450", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from osekit.public.project import Project\n", + "from osekit.core.instrument import Instrument\n", + "\n", + "folder = Path(r\"_static/sample_audio/timestamped\")\n", + "strptime_format = r\"%y%m%d_%H%M%S\"\n", + "\n", + "project = Project(\n", + " folder=folder,\n", + " strptime_format=strptime_format,\n", + " instrument=Instrument(end_to_end_db=150.0),\n", + " timezone=\"UTC\",\n", + ")\n", + "\n", + "project.build()" + ] + }, + { + "cell_type": "markdown", + "id": "b4678b4caa387fd3", + "metadata": {}, + "source": [ + "## Declare & Run the Transform\n", + "\n", + "Then we **declare** and **run** a `Transform` which would export the spectrograms to be annotated:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "270b08f7327ca5a7", + "metadata": {}, + "outputs": [], + "source": [ + "from osekit.public.transform import Transform, OutputType\n", + "from osekit.utils.audio import Normalization\n", + "from pandas import Timestamp, Timedelta\n", + "from scipy.signal import ShortTimeFFT\n", + "from scipy.signal.windows import hamming\n", + "\n", + "transform = Transform(\n", + " output_type=OutputType.SPECTROGRAM,\n", + " begin=Timestamp(\"2022-09-25 22:35:15+0000\"),\n", + " end=Timestamp(\"2022-09-25 22:36:25+0000\"),\n", + " data_duration=Timedelta(seconds=7.5),\n", + " normalization=Normalization.DC_REJECT,\n", + " fft=ShortTimeFFT(win=hamming(1024), hop=128, fs=project.origin_dataset.sample_rate),\n", + " v_lim=(50.0, 120.0), # Boundaries of the spectrograms\n", + " colormap=\"viridis\", # Default value\n", + " name=\"example_transform\",\n", + ")\n", + "\n", + "# We remove all spectrograms that contain silent parts\n", + "ads = project.prepare_audio(transform=transform)\n", + "ads.remove_empty_data(threshold=0.99)\n", + "\n", + "project.run(transform=transform, audio_dataset=ads)" ] }, { @@ -33,14 +130,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "1948b260fcaf03ab", - "metadata": { - "ExecuteTime": { - "end_time": "2026-06-10T07:51:57.297976500Z", - "start_time": "2026-06-10T07:51:56.702618600Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", @@ -48,6 +140,143 @@ "\n", "annotations = Annotation.from_csv(csv=Path(r\"_static/annotations/aplose_results.csv\"))" ] + }, + { + "cell_type": "markdown", + "id": "3630993dfeb0b359", + "metadata": {}, + "source": [ + "## Filtering the annotations\n", + "\n", + "We can use basic python filtering to access specific annotations.\n", + "\n", + "Here, we will:\n", + "- keep only `Odontocete whistle` annotations of type `BOX` with a strong **confidence level**\n", + "- Filter the `SpectroDataset` to keep only the files in which such annotations were made\n", + "- Plot the annotations as `Rectangle`s on the spectrograms." + ] + }, + { + "cell_type": "markdown", + "id": "ab4b0325c81590d0", + "metadata": {}, + "source": [ + "### Keeping specific annotations\n", + "\n", + "Filtering out unwanted annotations can be done with a basic list comprehension:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45892d179652235b", + "metadata": {}, + "outputs": [], + "source": [ + "def does_satisfy_constraints(annotation: Annotation) -> bool:\n", + " # Keeping only odontocete whistles\n", + " if annotation.label != \"Odontocete whistle\":\n", + " return False\n", + "\n", + " # Keeping only BOX annotations\n", + " if annotation.type != \"BOX\":\n", + " return False\n", + "\n", + " # Keeping only maximum confidence level annotations\n", + " if (\n", + " annotation.confidence_indicator.level\n", + " < annotation.confidence_indicator.maximum_level\n", + " ):\n", + " return False\n", + "\n", + " return True\n", + "\n", + "\n", + "filtered_annotations = [\n", + " annotation for annotation in annotations if does_satisfy_constraints(annotation)\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "e6599f8129aff523", + "metadata": {}, + "source": [ + "## Filtering the SpectroDataset\n", + "\n", + "`Annotation`s inherit from the `Event` class, which allows for an easy filtering of the `SpectroData`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "237d399b7de6ffd5", + "metadata": {}, + "outputs": [], + "source": [ + "# Recover the transform output (SpectroDataset)\n", + "sds = project.get_output(output_name=\"example_transform\")\n", + "\n", + "# Keeping only SpectroDatas that contain filtered annotations\n", + "sds.data = [\n", + " sd\n", + " for sd in sds.data\n", + " if any(annotation.overlaps(sd) for annotation in filtered_annotations)\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "10f7f194ff60a9f9", + "metadata": {}, + "source": [ + "## Plotting the Spectrograms along with annotations\n", + "\n", + "We then want to plot annotation boxes directly the spectrograms:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0d242240e791509", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# Create a figure with one spectrogram per row\n", + "fig, axs = plt.subplots(nrows=len(sds.data), ncols=1)\n", + "\n", + "# Plot spectrograms\n", + "for idx, sd in enumerate(sds.data):\n", + " # We want to plot each spectrogram in a specific ax\n", + " ax = axs[idx]\n", + "\n", + " sd.plot(ax=ax)\n", + "\n", + " # We want to plot all annotations related to this spectrogram\n", + " for annotation in filtered_annotations:\n", + " if not annotation.overlaps(sd):\n", + " continue\n", + "\n", + " # Annotations are plotted as matplotlib Rectangles\n", + " rectangle = annotation.to_rectangle(fill=False)\n", + " ax.add_patch(rectangle)\n", + "\n", + "# Let's take a look at the output figure\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "449dc442b4a5df75", + "metadata": {}, + "outputs": [], + "source": [ + "# Reset the project to get all files back to place.\n", + "project.reset()" + ] } ], "metadata": { From 1c4271b6cfc962fc8c112a2ec7e313a2a618166f Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 10 Jun 2026 12:22:02 +0200 Subject: [PATCH 32/36] add aplose notebook in toctree --- docs/source/example_aplose_result.ipynb | 5 ++++- docs/source/examples.rst | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/source/example_aplose_result.ipynb b/docs/source/example_aplose_result.ipynb index 12947349..e6c3e78a 100644 --- a/docs/source/example_aplose_result.ipynb +++ b/docs/source/example_aplose_result.ipynb @@ -5,7 +5,10 @@ "execution_count": null, "id": "initial_id", "metadata": { - "collapsed": true + "collapsed": true, + "tags": [ + "remove-cell" + ] }, "outputs": [], "source": [ diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 7f6bb68f..c6c72083 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -55,6 +55,12 @@ In the ``docs/source/_static/sample_audio/timestamped`` folder, files are just n Compute, plot and export a **L**\ ong-\ **T**\ erm **A**\ verage **S**\ pectrum (**LTAS**). +=========== + +.. topic:: :doc:`Use APLOSE results ` + + Parse `APLOSE `_ results csv files in OSEkit, and use the annotations to filter the audio or spectrograms from the project. + .. toctree :: :hidden: @@ -64,3 +70,4 @@ In the ``docs/source/_static/sample_audio/timestamped`` folder, files are just n example_multiple_spectrograms example_multiple_spectrograms_id example_ltas + example_aplose_result From 95b50a552cca78ab88d97ead68713f2894a0207e Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 10 Jun 2026 14:47:30 +0200 Subject: [PATCH 33/36] fix cell order --- docs/source/example_aplose_result.ipynb | 56 ++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/source/example_aplose_result.ipynb b/docs/source/example_aplose_result.ipynb index e6c3e78a..7b35b1ae 100644 --- a/docs/source/example_aplose_result.ipynb +++ b/docs/source/example_aplose_result.ipynb @@ -27,8 +27,17 @@ }, { "cell_type": "markdown", - "id": "567a8c1c1e6bffa", + "id": "8c395a2079d86493", + "metadata": {}, + "source": [ + "# Using APLOSE annotation results [^download]\n", + "\n", + "[^download]: This notebook can be downloaded as **{nb-download}`example_aplose_result.ipynb`**." + ] + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ "# Creating the Public Project\n", "\n", @@ -37,24 +46,24 @@ "First, we will build a project and run a transform that would be uploaded and annotated on APLOSE (see the [Public API documentation](https://project-osmose.github.io/OSEkit/publicapi_usage.html) for more info).\n", "\n", "The `_static/annotations/aplose_results.csv` file used in this notebook simulates the results of this annotation campaign." - ] + ], + "id": "90049102bdc38599" }, { - "cell_type": "markdown", - "id": "967313a80c4c1c24", "metadata": {}, + "cell_type": "markdown", "source": [ "## Build the Project\n", "\n", "First, we have to build the project from the raw audio files:" - ] + ], + "id": "e2d5321198880205" }, { - "cell_type": "code", - "execution_count": null, - "id": "6db74ea7fc7da450", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from pathlib import Path\n", "from osekit.public.project import Project\n", @@ -71,24 +80,24 @@ ")\n", "\n", "project.build()" - ] + ], + "id": "3ab3cb447c59a857" }, { - "cell_type": "markdown", - "id": "b4678b4caa387fd3", "metadata": {}, + "cell_type": "markdown", "source": [ "## Declare & Run the Transform\n", "\n", "Then we **declare** and **run** a `Transform` which would export the spectrograms to be annotated:" - ] + ], + "id": "2f5510c9c396ee2f" }, { - "cell_type": "code", - "execution_count": null, - "id": "270b08f7327ca5a7", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from osekit.public.transform import Transform, OutputType\n", "from osekit.utils.audio import Normalization\n", @@ -113,17 +122,8 @@ "ads.remove_empty_data(threshold=0.99)\n", "\n", "project.run(transform=transform, audio_dataset=ads)" - ] - }, - { - "cell_type": "markdown", - "id": "8c395a2079d86493", - "metadata": {}, - "source": [ - "# Using APLOSE annotation results [^download]\n", - "\n", - "[^download]: This notebook can be downloaded as **{nb-download}`example_aplose_result.ipynb`**." - ] + ], + "id": "d993991e8c23a2c0" }, { "cell_type": "markdown", @@ -154,9 +154,9 @@ "We can use basic python filtering to access specific annotations.\n", "\n", "Here, we will:\n", - "- keep only `Odontocete whistle` annotations of type `BOX` with a strong **confidence level**\n", + "- Keep only `Odontocete whistle` annotations of type `BOX` with a strong **confidence level**\n", "- Filter the `SpectroDataset` to keep only the files in which such annotations were made\n", - "- Plot the annotations as `Rectangle`s on the spectrograms." + "- Plot the annotations as `Rectangle`s on the spectrograms" ] }, { From 358f0365c5a106b70f2b24768af6c4410b39e4f2 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 10 Jun 2026 16:48:52 +0200 Subject: [PATCH 34/36] rename annotation as detection --- .../aplose_results.csv | 0 docs/source/annotation.rst | 25 ---- docs/source/aplose.rst | 2 +- docs/source/coreapi.rst | 2 +- docs/source/detection.rst | 25 ++++ docs/source/example_aplose_result.ipynb | 62 +++++----- docs/source/examples.rst | 2 +- .../core/{annotation.py => detection.py} | 110 +++++++++--------- tests/test_annotation.py | 86 +++++++------- 9 files changed, 157 insertions(+), 157 deletions(-) rename docs/source/_static/{annotations => detections}/aplose_results.csv (100%) delete mode 100644 docs/source/annotation.rst create mode 100644 docs/source/detection.rst rename src/osekit/core/{annotation.py => detection.py} (78%) diff --git a/docs/source/_static/annotations/aplose_results.csv b/docs/source/_static/detections/aplose_results.csv similarity index 100% rename from docs/source/_static/annotations/aplose_results.csv rename to docs/source/_static/detections/aplose_results.csv diff --git a/docs/source/annotation.rst b/docs/source/annotation.rst deleted file mode 100644 index c1f5bb4b..00000000 --- a/docs/source/annotation.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. _annotation: - -Annotation ----------- - -.. autoclass:: osekit.core.annotation.Annotation - :members: - -.. autoclass:: osekit.core.annotation.FrequencyBounds - :members: - -.. autoclass:: osekit.core.annotation.AnnotatorInfo - :members: - -.. autoclass:: osekit.core.annotation.SignalParameters - :members: - -.. autoclass:: osekit.core.annotation.ConfidenceIndicator - :members: - -.. autoclass:: osekit.core.annotation.AnnotationMetaData - :members: - -.. autoclass:: osekit.core.annotation.Verification - :members: diff --git a/docs/source/aplose.rst b/docs/source/aplose.rst index f703739f..7d1dd27b 100644 --- a/docs/source/aplose.rst +++ b/docs/source/aplose.rst @@ -6,7 +6,7 @@ Working with APLOSE results `APLOSE `_ is **OSmOSE**'s web-based annotation platform. **APLOSE** campaigns `results `_ are provided as csv files -that can be parsed in **OSEkit** as :class:`osekit.core.annotation.Annotation` instances. +that can be parsed in **OSEkit** as :class:`osekit.core.detection.Detection` instances. Loading an APLOSE results file ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/coreapi.rst b/docs/source/coreapi.rst index f9e838f3..2c27fe69 100644 --- a/docs/source/coreapi.rst +++ b/docs/source/coreapi.rst @@ -23,4 +23,4 @@ Core ltasdata audiofilemanager frequencyscale - annotation + detection diff --git a/docs/source/detection.rst b/docs/source/detection.rst new file mode 100644 index 00000000..46362280 --- /dev/null +++ b/docs/source/detection.rst @@ -0,0 +1,25 @@ +.. _detection: + +Detection +---------- + +.. autoclass:: osekit.core.detection.Detection + :members: + +.. autoclass:: osekit.core.detection.FrequencyBounds + :members: + +.. autoclass:: osekit.core.detection.DetectorInfo + :members: + +.. autoclass:: osekit.core.detection.SignalParameters + :members: + +.. autoclass:: osekit.core.detection.ConfidenceIndicator + :members: + +.. autoclass:: osekit.core.detection.DetectionMetaData + :members: + +.. autoclass:: osekit.core.detection.Verification + :members: diff --git a/docs/source/example_aplose_result.ipynb b/docs/source/example_aplose_result.ipynb index 7b35b1ae..d9f5d912 100644 --- a/docs/source/example_aplose_result.ipynb +++ b/docs/source/example_aplose_result.ipynb @@ -30,7 +30,7 @@ "id": "8c395a2079d86493", "metadata": {}, "source": [ - "# Using APLOSE annotation results [^download]\n", + "# Using APLOSE detection results [^download]\n", "\n", "[^download]: This notebook can be downloaded as **{nb-download}`example_aplose_result.ipynb`**." ] @@ -45,7 +45,7 @@ "\n", "First, we will build a project and run a transform that would be uploaded and annotated on APLOSE (see the [Public API documentation](https://project-osmose.github.io/OSEkit/publicapi_usage.html) for more info).\n", "\n", - "The `_static/annotations/aplose_results.csv` file used in this notebook simulates the results of this annotation campaign." + "The `_static/detections/aplose_results.csv` file used in this notebook simulates the results of this annotation campaign." ], "id": "90049102bdc38599" }, @@ -129,7 +129,7 @@ "cell_type": "markdown", "id": "5f23b72a5303ce5e", "metadata": {}, - "source": "Parse the annotation result csv file thanks to the `Annotation` class:" + "source": "Parse the detection result csv file thanks to the `Detection` class:" }, { "cell_type": "code", @@ -139,9 +139,9 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from osekit.core.annotation import Annotation\n", + "from osekit.core.detection import Detection\n", "\n", - "annotations = Annotation.from_csv(csv=Path(r\"_static/annotations/aplose_results.csv\"))" + "detections = Detection.from_csv(csv=Path(r\"_static/detections/aplose_results.csv\"))" ] }, { @@ -149,14 +149,14 @@ "id": "3630993dfeb0b359", "metadata": {}, "source": [ - "## Filtering the annotations\n", + "## Filtering the detections\n", "\n", - "We can use basic python filtering to access specific annotations.\n", + "We can use basic python filtering to access specific detections.\n", "\n", "Here, we will:\n", - "- Keep only `Odontocete whistle` annotations of type `BOX` with a strong **confidence level**\n", - "- Filter the `SpectroDataset` to keep only the files in which such annotations were made\n", - "- Plot the annotations as `Rectangle`s on the spectrograms" + "- Keep only `Odontocete whistle` detections of type `BOX` with a strong **confidence level**\n", + "- Filter the `SpectroDataset` to keep only the files in which such detections were made\n", + "- Plot the detections as `Rectangle`s on the spectrograms" ] }, { @@ -164,9 +164,9 @@ "id": "ab4b0325c81590d0", "metadata": {}, "source": [ - "### Keeping specific annotations\n", + "### Keeping specific detections\n", "\n", - "Filtering out unwanted annotations can be done with a basic list comprehension:" + "Filtering out unwanted detections can be done with a basic list comprehension:" ] }, { @@ -176,27 +176,27 @@ "metadata": {}, "outputs": [], "source": [ - "def does_satisfy_constraints(annotation: Annotation) -> bool:\n", + "def does_satisfy_constraints(detection: Detection) -> bool:\n", " # Keeping only odontocete whistles\n", - " if annotation.label != \"Odontocete whistle\":\n", + " if detection.label != \"Odontocete whistle\":\n", " return False\n", "\n", - " # Keeping only BOX annotations\n", - " if annotation.type != \"BOX\":\n", + " # Keeping only BOX detections\n", + " if detection.type != \"BOX\":\n", " return False\n", "\n", - " # Keeping only maximum confidence level annotations\n", + " # Keeping only maximum confidence level detections\n", " if (\n", - " annotation.confidence_indicator.level\n", - " < annotation.confidence_indicator.maximum_level\n", + " detection.confidence_indicator.level\n", + " < detection.confidence_indicator.maximum_level\n", " ):\n", " return False\n", "\n", " return True\n", "\n", "\n", - "filtered_annotations = [\n", - " annotation for annotation in annotations if does_satisfy_constraints(annotation)\n", + "filtered_detections = [\n", + " detection for detection in detections if does_satisfy_constraints(detection)\n", "]" ] }, @@ -207,7 +207,7 @@ "source": [ "## Filtering the SpectroDataset\n", "\n", - "`Annotation`s inherit from the `Event` class, which allows for an easy filtering of the `SpectroData`:" + "`Detection`s inherit from the `Event` class, which allows for an easy filtering of the `SpectroData`:" ] }, { @@ -220,11 +220,11 @@ "# Recover the transform output (SpectroDataset)\n", "sds = project.get_output(output_name=\"example_transform\")\n", "\n", - "# Keeping only SpectroDatas that contain filtered annotations\n", + "# Keeping only SpectroDatas that contain filtered detections\n", "sds.data = [\n", " sd\n", " for sd in sds.data\n", - " if any(annotation.overlaps(sd) for annotation in filtered_annotations)\n", + " if any(detection.overlaps(sd) for detection in filtered_detections)\n", "]" ] }, @@ -233,9 +233,9 @@ "id": "10f7f194ff60a9f9", "metadata": {}, "source": [ - "## Plotting the Spectrograms along with annotations\n", + "## Plotting the Spectrograms along with detections\n", "\n", - "We then want to plot annotation boxes directly the spectrograms:" + "We then want to plot detection boxes directly the spectrograms:" ] }, { @@ -257,13 +257,13 @@ "\n", " sd.plot(ax=ax)\n", "\n", - " # We want to plot all annotations related to this spectrogram\n", - " for annotation in filtered_annotations:\n", - " if not annotation.overlaps(sd):\n", + " # We want to plot all detections related to this spectrogram\n", + " for detection in filtered_detections:\n", + " if not detection.overlaps(sd):\n", " continue\n", "\n", - " # Annotations are plotted as matplotlib Rectangles\n", - " rectangle = annotation.to_rectangle(fill=False)\n", + " # Detections are plotted as matplotlib Rectangles\n", + " rectangle = detection.to_rectangle(fill=False)\n", " ax.add_patch(rectangle)\n", "\n", "# Let's take a look at the output figure\n", diff --git a/docs/source/examples.rst b/docs/source/examples.rst index c6c72083..37dbf83a 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -59,7 +59,7 @@ In the ``docs/source/_static/sample_audio/timestamped`` folder, files are just n .. topic:: :doc:`Use APLOSE results ` - Parse `APLOSE `_ results csv files in OSEkit, and use the annotations to filter the audio or spectrograms from the project. + Parse `APLOSE `_ results csv files in OSEkit, and use the detections to filter the audio or spectrograms from the project. .. toctree :: :hidden: diff --git a/src/osekit/core/annotation.py b/src/osekit/core/detection.py similarity index 78% rename from src/osekit/core/annotation.py rename to src/osekit/core/detection.py index d96e26ea..c08bd7c9 100644 --- a/src/osekit/core/annotation.py +++ b/src/osekit/core/detection.py @@ -1,4 +1,4 @@ -"""The Annotation class represents an annotation made on APLOSE.""" +"""The Detection class represents a detection made on APLOSE.""" import math from dataclasses import dataclass @@ -53,7 +53,7 @@ @dataclass class FrequencyBounds: - """Class representing the frequency bounds of an annotation. + """Class representing the frequency bounds of a detection. Parameters ---------- @@ -89,29 +89,29 @@ def __post_init__(self) -> None: @property def bandwidth(self) -> int: - """Bandwidth of the annotation.""" + """Bandwidth of the detection.""" return self.max - self.min @dataclass -class AnnotatorInfo: - """Class representing an annotator info.""" +class DetectorInfo: + """Class representing a detector info.""" name: str expertise: Literal["NOVICE", "AVERAGE", "EXPERT"] | None = None def __hash__(self) -> int: - """Return a hash for the annotator.""" + """Return a hash for the detector.""" return hash((self.name, self.expertise)) def __eq__(self, other: Self) -> bool: - """Return whether two annotators are equal.""" + """Return whether two detectors are equal.""" return self.name == other.name and self.expertise == other.expertise @dataclass class SignalParameters: - """Class representing parameters of an annoted signal.""" + """Class representing parameters of detection signal.""" is_itensity_too_low: bool | None = None does_overlap_other_signals: bool | None = None @@ -130,14 +130,14 @@ class SignalParameters: @dataclass class ConfidenceIndicator: - """Class that represents an annotation confidence indicator. + """Class that represents a detection confidence indicator. Parameters ---------- label: str Name of the level of confidence. level: int - Level of confidence of the annotation. + Level of confidence of the detection. maximum_level: int Maximum level of confidence authorized in the project. @@ -167,7 +167,7 @@ def from_relative_level_string(cls, label: str, relative_level_string: str) -> S relative_level_string: str Level of confidence relative to the maximum level available. Should be formatted as ``n/m``, where ``n`` is the level of confidence - of the annotation and ``m`` is the maximum level available in the project. + of the detection and ``m`` is the maximum level available in the project. Returns ------- @@ -181,30 +181,30 @@ def from_relative_level_string(cls, label: str, relative_level_string: str) -> S @dataclass -class AnnotationMetaData: - """Class that represents the metadata of an annotation. +class DetectionMetaData: + """Class that represents the metadata of a detection. Parameters ---------- project: str - Name of the project in which the annotation was made. + Name of the project in which the detection was made. filename: str - Name of the file this annotation was made on. - annotation_id: int - ID of the annotation. + Name of the file this detection was made on. + detection_id: int + ID of the detection. base_id: int - ID of the base annotation. - May differ from ``annotation_id`` if the annotation is an update/correction. + ID of the base detection. + May differ from ``detection_id`` if the detection is an update/correction. comments: str | None Comments left by the annotator. phase: Literal["ANNOTATION", "VERIFICATION"] - Phase during which the annotation was created. + Phase during which the detection was created. """ project: str filename: str - annotation_id: int + detection_id: int base_id: int | None comments: str | None phase: Literal["ANNOTATION", "VERIFICATION"] @@ -212,7 +212,7 @@ class AnnotationMetaData: @dataclass class Verification: - """Class that represents a verification of an annotation.""" + """Class that represents a verification of a detection.""" verificator: str is_validated: bool @@ -229,60 +229,60 @@ def __eq__(self, other: Self) -> bool: ) -class Annotation(Event): - """Class that represents an annotation made on APLOSE.""" +class Detection(Event): + """Class that represents a detection made on APLOSE.""" def __init__( # noqa: PLR0913 self, - metadata: AnnotationMetaData, + metadata: DetectionMetaData, begin: Timestamp, end: Timestamp, frequency_bounds: FrequencyBounds, label: str, - annotator_info: AnnotatorInfo, - annotation_type: Literal["WEAK", "POINT", "BOX"], + detector_info: DetectorInfo, + detection_type: Literal["WEAK", "POINT", "BOX"], confidence_indicator: ConfidenceIndicator, signal_quantity: Literal["SINGLE", "MULTIPLE"], signal_parameters: SignalParameters | None, verifications: set[Verification], ) -> None: - """Initialize an Annotation object. + """Initialize a Detection object. Parameters ---------- - metadata: AnnotationMetaData - Metadata on the annotation. + metadata: DetectionMetaData + Metadata on the detection. begin: Timestamp - Begin timestamp of the annotation. + Begin timestamp of the detection. end: Timestamp - End timestamp of the annotation. + End timestamp of the detection. frequency_bounds: FrequencyBounds - Frequency bounds of the annotation. + Frequency bounds of the detection. label: str - Label of the annotation. - annotator_info: AnnotatorInfo + Label of the detection. + detector_info: DetectorInfo Information on the annotator or detector. - annotation_type: Literal["WEAK", "POINT", "BOX"] - Type of the annotation. - ``WEAK``: Annotation made on the whole spectrogram. - ``POINT``: Annotation made on one pixel of the spectrogram. - ``BOX``: Annotation made on one box within the spectrogram. + detection_type: Literal["WEAK", "POINT", "BOX"] + Type of the detection. + ``WEAK``: Detection made on the whole spectrogram. + ``POINT``: Detection made on one pixel of the spectrogram. + ``BOX``: Detection made on one box within the spectrogram. confidence_indicator: ConfidenceIndicator Indicator of the confidence of the annotator. signal_quantity: Literal["SINGLE","MULTIPLE"] - Whether there is only one signal in the annotation or more. + Whether there is only one signal in the detection or more. signal_parameters: SignalParameters | None Parameters of the annotated signal. ```None`` if ``signal_quantity`` is ``MULTIPLE``. verifications: set[Verification] - Verifications made on this annotation. + Verifications made on this detection. """ self.metadata = metadata self.label = label - self.annotator_info = annotator_info + self.detector_info = detector_info self.frequency_bounds = frequency_bounds - self.type = annotation_type + self.type = detection_type self.confidence_indicator = confidence_indicator self.signal_quantity = signal_quantity self.signal_parameters = signal_parameters @@ -291,21 +291,21 @@ def __init__( # noqa: PLR0913 super().__init__(begin=begin, end=end) def __repr__(self) -> str: - """Override the string representation of the annotation.""" - return str(self.metadata.annotation_id) + """Override the string representation of the detection.""" + return str(self.metadata.detection_id) @classmethod def from_dict(cls, row: dict) -> Self: - """Deserialize an Annotation object.""" - metadata = AnnotationMetaData( + """Deserialize a Detection object.""" + metadata = DetectionMetaData( project=row["project"] if "project" in row else row["dataset"], filename=str(row["filename"]), - annotation_id=row["annotation_id"], + detection_id=row["annotation_id"], base_id=row["is_update_of_id"], comments=row["comments"], phase=row["created_at_phase"], ) - annotator_info = AnnotatorInfo( + detector_info = DetectorInfo( name=row["annotator"], expertise=row["annotator_expertise"], ) @@ -355,11 +355,11 @@ def from_dict(cls, row: dict) -> Self: return cls( metadata=metadata, label=row["annotation"], - annotator_info=annotator_info, + detector_info=detector_info, begin=Timestamp(row["start_datetime"]), end=Timestamp(row["end_datetime"]), frequency_bounds=frequency_bounds, - annotation_type=row["type"], + detection_type=row["type"], confidence_indicator=confidence_indicator, signal_quantity=row["signal_quantity"], signal_parameters=signal_parameters, @@ -367,7 +367,7 @@ def from_dict(cls, row: dict) -> Self: ) def to_rectangle(self, **kwargs: Any) -> Rectangle: - """Return a matplotlib Rectangle representing the annotation. + """Return a matplotlib Rectangle representing the detection. Parameters ---------- @@ -377,7 +377,7 @@ def to_rectangle(self, **kwargs: Any) -> Rectangle: Returns ------- matplotlib.patches.Rectangle - Rectangle representing the annotation. + Rectangle representing the detection. The coordinates of the rectangle are in time x frequency. @@ -395,7 +395,7 @@ def to_rectangle(self, **kwargs: Any) -> Rectangle: @classmethod def from_csv(cls, csv: Path) -> list[Self]: - """Deserialize a list of Annotation from an annotations csv file.""" + """Deserialize a list of Detection from a detections csv file.""" records = pd.read_csv(filepath_or_buffer=csv).to_dict( orient="records", ) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index 0460453a..1d1db2f4 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -5,11 +5,11 @@ import pytest from pandas import Timestamp -from osekit.core.annotation import ( - Annotation, - AnnotationMetaData, - AnnotatorInfo, +from osekit.core.detection import ( ConfidenceIndicator, + Detection, + DetectionMetaData, + DetectorInfo, FrequencyBounds, SignalParameters, Verification, @@ -17,10 +17,10 @@ @pytest.fixture -def sample_annotation() -> Annotation: - return Annotation( - metadata=AnnotationMetaData( - annotation_id=35173, +def sample_detection() -> Detection: + return Detection( + metadata=DetectionMetaData( + detection_id=35173, base_id=None, comments="He's a sneaky, sneaky dog friend", filename="its_teasy", @@ -34,11 +34,11 @@ def sample_annotation() -> Annotation: max=3_000, ), label="Connan", - annotator_info=AnnotatorInfo( + detector_info=DetectorInfo( name="Mockasin", expertise="EXPERT", ), - annotation_type="BOX", + detection_type="BOX", confidence_indicator=ConfidenceIndicator( label="Sure", level=2, @@ -131,13 +131,13 @@ def test_frequency_bounds( def test_annotator_info() -> None: annotators = [ - AnnotatorInfo(name="ruby", expertise="NOVICE"), - AnnotatorInfo(name="ruby", expertise="NOVICE"), - AnnotatorInfo(name="haunt", expertise="EXPERT"), - AnnotatorInfo(name="haunt", expertise="EXPERT"), - AnnotatorInfo(name="nevada", expertise="EXPERT"), - AnnotatorInfo(name="nevada", expertise="EXPERT"), - AnnotatorInfo(name="haunt", expertise=None), + DetectorInfo(name="ruby", expertise="NOVICE"), + DetectorInfo(name="ruby", expertise="NOVICE"), + DetectorInfo(name="haunt", expertise="EXPERT"), + DetectorInfo(name="haunt", expertise="EXPERT"), + DetectorInfo(name="nevada", expertise="EXPERT"), + DetectorInfo(name="nevada", expertise="EXPERT"), + DetectorInfo(name="haunt", expertise=None), ] nb_unique_annotators = 4 @@ -231,29 +231,29 @@ def test_confidence_indicator_from_relative_level_string( assert ci.maximum_level == e.maximum_level -def test_annotations_from_csv() -> None: - annotations = Annotation.from_csv( +def test_detections_from_csv() -> None: + detections = Detection.from_csv( csv=Path(__file__).parent / "_static" / "aplose_result.csv", ) # All records should be loaded - assert len(annotations) == 8 - assert all(a.metadata.project == "great_tit" for a in annotations) + assert len(detections) == 8 + assert all(a.metadata.project == "great_tit" for a in detections) # Two distinct annotated files - filenames = {a.metadata.filename for a in annotations} + filenames = {a.metadata.filename for a in detections} assert filenames == {"990694", "994410"} # Types - types = {a.type for a in annotations} + types = {a.type for a in detections} assert types == {"WEAK", "BOX"} # Phases - phases = {a.metadata.phase for a in annotations} + phases = {a.metadata.phase for a in detections} assert phases == {"ANNOTATION", "VERIFICATION"} # Single signal parameters - single = next(a for a in annotations if a.metadata.annotation_id == 586657) + single = next(a for a in detections if a.metadata.detection_id == 586657) assert single.signal_quantity == "SINGLE" assert single.signal_parameters is not None assert not single.signal_parameters.is_itensity_too_low @@ -271,32 +271,32 @@ def test_annotations_from_csv() -> None: assert single.signal_parameters.has_deterministic_chaos # Multiple signal quantity: parameters should be None - multiple = next(a for a in annotations if a.metadata.annotation_id == 586654) + multiple = next(a for a in detections if a.metadata.detection_id == 586654) assert multiple.signal_quantity == "MULTIPLE" assert multiple.signal_parameters is None - # Annotation update - update = next(a for a in annotations if a.metadata.annotation_id == 586669) + # Detection update + update = next(a for a in detections if a.metadata.detection_id == 586669) assert update.metadata.base_id == 586655 - # Annotation without base - base = next(a for a in annotations if a.metadata.annotation_id == 586655) + # Detection without base + base = next(a for a in detections if a.metadata.detection_id == 586655) assert base.metadata.base_id is None # Annotator parsing annotators = { - AnnotatorInfo(name="vashti", expertise="NOVICE"), - AnnotatorInfo(name="heartleap", expertise=None), - AnnotatorInfo(name="bunyan", expertise="EXPERT"), - AnnotatorInfo(name="lookaftering", expertise="EXPERT"), + DetectorInfo(name="vashti", expertise="NOVICE"), + DetectorInfo(name="heartleap", expertise=None), + DetectorInfo(name="bunyan", expertise="EXPERT"), + DetectorInfo(name="lookaftering", expertise="EXPERT"), } assert np.array_equal( annotators, - {a.annotator_info for a in annotations}, + {a.detector_info for a in detections}, ) # Verification parsing - verificated = next(a for a in annotations if a.metadata.annotation_id == 586654) + verificated = next(a for a in detections if a.metadata.detection_id == 586654) verification = { Verification( verificator="lookaftering", @@ -309,17 +309,17 @@ def test_annotations_from_csv() -> None: } assert np.array_equal(verification, verificated.verifications) - # Repr should be the annotation ID - annotation = annotations[0] - assert str(annotation) == str(annotation.metadata.annotation_id) + # Repr should be the detection ID + detection = detections[0] + assert str(detection) == str(detection.metadata.detection_id) -def test_annotation_to_rectangle(sample_annotation: Annotation) -> None: - rectangle = sample_annotation.to_rectangle() +def test_detection_to_rectangle(sample_detection: Detection) -> None: + rectangle = sample_detection.to_rectangle() - t1, t2 = sample_annotation.begin, sample_annotation.end + t1, t2 = sample_detection.begin, sample_detection.end - f_box = sample_annotation.frequency_bounds + f_box = sample_detection.frequency_bounds f1, f2 = f_box.min, f_box.max x, y = rectangle.xy From b305bca6e3643e629407a2f6fb40a931572a747a Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 10 Jun 2026 17:03:02 +0200 Subject: [PATCH 35/36] add aplose doc entry --- docs/source/aplose.rst | 52 +++++++++++++++++++++++++ docs/source/example_aplose_result.ipynb | 48 +++++++++++------------ 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/docs/source/aplose.rst b/docs/source/aplose.rst index 7d1dd27b..646f6018 100644 --- a/docs/source/aplose.rst +++ b/docs/source/aplose.rst @@ -10,3 +10,55 @@ that can be parsed in **OSEkit** as :class:`osekit.core.detection.Detection` ins Loading an APLOSE results file ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``Detections`` can be extracted from **APLOSE** results files thanks to the :meth:`osekit.core.detection.Detection.from_csv` method: + +.. code-block:: python + + from pathlib import Path + from osekit.core.detection import Detection + + detections = Detection.from_csv(csv=Path(r"_static/detections/aplose_results.csv")) + +Detection / Audio interaction +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The :class:`osekit.core.detection.Detection` class inherits from the :class:`osekit.core.event.Event` class: detections can easily be used to filter audio and spectro data: + +.. code-block:: python + + from osekit.core.spectro_dataset import SpectroDataset + + detection = Detection(...) # Generally Detection.from_csv(...)[i] + spectro_dataset = SpectroDataset(...) + + # Find all SpectroData in which detection appear: + positive_spectrograms = SpectroDataset([sd for sd in spectro_dataset.data if sd.overlaps(detection)]) + +Plotting a detection +^^^^^^^^^^^^^^^^^^^^ + +Detection boxes can be plotted on spectrograms thanks to the :method:`osekit.core.detection.Detection.to_rectangle` method: + +.. code-block:: python + + import matplotlib.pyplot as plt + from osekit.core.spectro_data import SpectroData + from osekit.core.detection import Detection + + sd = SpectroData(...) + detection = Detection(...) + + fig, axs = plt.subplots() + + # Plot the spectrogram + sd.plot(ax=ax) + + # Get a rectangle from the detection + rectangle = detection.to_rectangle(fill = False) + + # Draw the detection + ax.add_patch(rectangle) + + # Show the spectrogram + plt.show() diff --git a/docs/source/example_aplose_result.ipynb b/docs/source/example_aplose_result.ipynb index d9f5d912..e8b27107 100644 --- a/docs/source/example_aplose_result.ipynb +++ b/docs/source/example_aplose_result.ipynb @@ -2,7 +2,6 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "id": "initial_id", "metadata": { "collapsed": true, @@ -10,7 +9,6 @@ "remove-cell" ] }, - "outputs": [], "source": [ "# Executing this cell will:\n", "\n", @@ -23,7 +21,9 @@ "from osekit import setup_logging\n", "\n", "setup_logging() # Overwrites the default logger to" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -62,8 +62,6 @@ { "metadata": {}, "cell_type": "code", - "outputs": [], - "execution_count": null, "source": [ "from pathlib import Path\n", "from osekit.public.project import Project\n", @@ -81,7 +79,9 @@ "\n", "project.build()" ], - "id": "3ab3cb447c59a857" + "id": "3ab3cb447c59a857", + "outputs": [], + "execution_count": null }, { "metadata": {}, @@ -96,8 +96,6 @@ { "metadata": {}, "cell_type": "code", - "outputs": [], - "execution_count": null, "source": [ "from osekit.public.transform import Transform, OutputType\n", "from osekit.utils.audio import Normalization\n", @@ -123,7 +121,9 @@ "\n", "project.run(transform=transform, audio_dataset=ads)" ], - "id": "d993991e8c23a2c0" + "id": "d993991e8c23a2c0", + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -133,16 +133,16 @@ }, { "cell_type": "code", - "execution_count": null, "id": "1948b260fcaf03ab", "metadata": {}, - "outputs": [], "source": [ "from pathlib import Path\n", "from osekit.core.detection import Detection\n", "\n", "detections = Detection.from_csv(csv=Path(r\"_static/detections/aplose_results.csv\"))" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -171,10 +171,8 @@ }, { "cell_type": "code", - "execution_count": null, "id": "45892d179652235b", "metadata": {}, - "outputs": [], "source": [ "def does_satisfy_constraints(detection: Detection) -> bool:\n", " # Keeping only odontocete whistles\n", @@ -198,7 +196,9 @@ "filtered_detections = [\n", " detection for detection in detections if does_satisfy_constraints(detection)\n", "]" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -212,10 +212,8 @@ }, { "cell_type": "code", - "execution_count": null, "id": "237d399b7de6ffd5", "metadata": {}, - "outputs": [], "source": [ "# Recover the transform output (SpectroDataset)\n", "sds = project.get_output(output_name=\"example_transform\")\n", @@ -226,7 +224,9 @@ " for sd in sds.data\n", " if any(detection.overlaps(sd) for detection in filtered_detections)\n", "]" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -240,10 +240,8 @@ }, { "cell_type": "code", - "execution_count": null, "id": "d0d242240e791509", "metadata": {}, - "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", @@ -268,18 +266,20 @@ "\n", "# Let's take a look at the output figure\n", "plt.show()" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "execution_count": null, "id": "449dc442b4a5df75", "metadata": {}, - "outputs": [], "source": [ "# Reset the project to get all files back to place.\n", "project.reset()" - ] + ], + "outputs": [], + "execution_count": null } ], "metadata": { From fa85f40d50bd5cd3cf9fd092a0c3d84a44bd9d73 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Mon, 15 Jun 2026 18:05:51 +0200 Subject: [PATCH 36/36] make confidence indicator optionnal --- src/osekit/core/detection.py | 14 +++++++++----- tests/_static/aplose_result.csv | 1 + tests/test_annotation.py | 6 +++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/osekit/core/detection.py b/src/osekit/core/detection.py index c08bd7c9..4e850529 100644 --- a/src/osekit/core/detection.py +++ b/src/osekit/core/detection.py @@ -241,7 +241,7 @@ def __init__( # noqa: PLR0913 label: str, detector_info: DetectorInfo, detection_type: Literal["WEAK", "POINT", "BOX"], - confidence_indicator: ConfidenceIndicator, + confidence_indicator: ConfidenceIndicator | None, signal_quantity: Literal["SINGLE", "MULTIPLE"], signal_parameters: SignalParameters | None, verifications: set[Verification], @@ -267,7 +267,7 @@ def __init__( # noqa: PLR0913 ``WEAK``: Detection made on the whole spectrogram. ``POINT``: Detection made on one pixel of the spectrogram. ``BOX``: Detection made on one box within the spectrogram. - confidence_indicator: ConfidenceIndicator + confidence_indicator: ConfidenceIndicator | None Indicator of the confidence of the annotator. signal_quantity: Literal["SINGLE","MULTIPLE"] Whether there is only one signal in the detection or more. @@ -317,9 +317,13 @@ def from_dict(cls, row: dict) -> Self: else None ) - confidence_indicator = ConfidenceIndicator.from_relative_level_string( - label=row["confidence_indicator_label"], - relative_level_string=row["confidence_indicator_level"], + confidence_indicator = ( + ConfidenceIndicator.from_relative_level_string( + label=row["confidence_indicator_label"], + relative_level_string=row["confidence_indicator_level"], + ) + if row["confidence_indicator_label"] + else None ) signal_quantity = row["signal_quantity"] diff --git a/tests/_static/aplose_result.csv b/tests/_static/aplose_result.csv index dd321bfe..ecacfa98 100644 --- a/tests/_static/aplose_result.csv +++ b/tests/_static/aplose_result.csv @@ -1,6 +1,7 @@ dataset,filename,annotation_id,is_update_of_id,start_time,end_time,start_frequency,end_frequency,min_frequency,max_frequency,annotation,annotator,annotator_expertise,start_datetime,end_datetime,is_box,type,confidence_indicator_label,confidence_indicator_level,comments,signal_quantity,signal_is_intensity_too_low,signal_does_overlap_other_signals,signal_start_frequency,signal_end_frequency,signal_relative_min_frequency_count,signal_relative_max_frequency_count,signal_steps_count,signal_has_harmonics,signal_trend,signal_sidebands,signal_subharmonics,signal_frequency_jumps,signal_deterministic_chaos,created_at_phase,lookaftering,bunyan great_tit,990694,586654,,0.0,20.0,0.0,24000.0,0.0,24000.0,bird,vashti,NOVICE,2021-01-01T00:00:00.000+00:00,2021-01-01T00:00:20.000+00:00,0,WEAK,Sure,1/1,great tits |- vashti,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,False great_tit,990694,586655,,1.412,3.651,2512.0,15661.0,2512.0,15661.0,bird,vashti,NOVICE,2021-01-01T00:00:01.412+00:00,2021-01-01T00:00:03.651+00:00,1,BOX,Not sure,0/1,fluffy-backed tit-babbler |- vashti,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,False,False +great_tit,990694,586673,,10.0,12.0,12000.0,13000.0,12000.0,13000.0,rain,heartleap,,2021-01-01T00:00:00.000+00:00,2021-01-01T00:00:20.000+00:00,0,BOX,,,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True great_tit,990694,586656,,0.0,20.0,0.0,24000.0,0.0,24000.0,rain,heartleap,,2021-01-01T00:00:00.000+00:00,2021-01-01T00:00:20.000+00:00,0,WEAK,Sure,1/1,,MULTIPLE,,,,,,,,,,,,,,ANNOTATION,True,True great_tit,990694,586657,,3.53,4.71,11137.0,13997.0,11137.0,13997.0,rain,heartleap,,2021-01-01T00:00:03.530+00:00,2021-01-01T00:00:04.710+00:00,1,BOX,Sure,1/1,,SINGLE,,True,12000.0,13000.0,3,2,4,True,MOD,True,,True,True,ANNOTATION,True,True great_tit,990694,586669,586655,1.412,3.651,2512.0,15660.0,2512.0,15660.0,bird,bunyan,EXPERT,2021-01-01T00:00:01.412+00:00,2021-01-01T00:00:03.651+00:00,1,BOX,Not sure,0/1,,MULTIPLE,,,,,,,,,,,,,,VERIFICATION,, diff --git a/tests/test_annotation.py b/tests/test_annotation.py index 1d1db2f4..caff5e7d 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -237,7 +237,7 @@ def test_detections_from_csv() -> None: ) # All records should be loaded - assert len(detections) == 8 + assert len(detections) == 9 assert all(a.metadata.project == "great_tit" for a in detections) # Two distinct annotated files @@ -313,6 +313,10 @@ def test_detections_from_csv() -> None: detection = detections[0] assert str(detection) == str(detection.metadata.detection_id) + # Non-specified confidence indicator should be None + detection = next(d for d in detections if d.metadata.detection_id == 586673) + assert detection.confidence_indicator is None + def test_detection_to_rectangle(sample_detection: Detection) -> None: rectangle = sample_detection.to_rectangle()