From b4a91d9e6d959599643e86c30d8bbbd06ea83c43 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 8 Oct 2026 17:29:44 +0200 Subject: [PATCH 1/7] add weak comment line in detections results file --- tests/_static/aplose_result.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/_static/aplose_result.csv b/tests/_static/aplose_result.csv index 6fe60a84..21e0c43d 100644 --- a/tests/_static/aplose_result.csv +++ b/tests/_static/aplose_result.csv @@ -9,3 +9,4 @@ great_tit,990694,586710,586655,1.412,3.651,2512.0,15660.0,2512.0,15660.0,bird,lo 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,, ,,,,,,,,0.0,24000.0,,,,2021-01-01T00:01:18.218+00:00,2021-01-01T00:01:38.218+00:00,,,,,,,,,,,,,,,,,,,,,, +great_tit,990694,,,,,,,,,,heartleap,,2021-01-01T00:00:00.000+00:00,2021-01-01T00:00:20.000+00:00,,,,,weak comment |- heartleap,,,,,,,,,,,,,,,,, From b300e96f174197adfa50359abec70a4d02e009f4 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 8 Oct 2026 17:56:10 +0200 Subject: [PATCH 2/7] skip lines that raise an error when parsing detection records --- src/osekit/core/detection.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/osekit/core/detection.py b/src/osekit/core/detection.py index e4b12065..1421af59 100644 --- a/src/osekit/core/detection.py +++ b/src/osekit/core/detection.py @@ -764,7 +764,13 @@ def _from_csv(cls, csv: Path, **kwargs: Any) -> list[Self]: } for record in records ] - return [cls.from_dict(record) for record in records] + parsed_records = [] + for idx, record in enumerate(records): + try: + parsed_records.append(cls.from_dict(record)) + except TypeError: # Most probably a weak comment on the spectrogram + pass + return parsed_records @classmethod def from_csv(cls, csv: Path | list[Path], **kwargs: Any) -> list[Self]: From 6ff04d75675f0873216cb6ee7b3c05e201156bd1 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 8 Oct 2026 17:58:07 +0200 Subject: [PATCH 3/7] log a warning if records lines fail to be parsed as detections --- src/osekit/core/detection.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/osekit/core/detection.py b/src/osekit/core/detection.py index 1421af59..f7b6a293 100644 --- a/src/osekit/core/detection.py +++ b/src/osekit/core/detection.py @@ -12,6 +12,7 @@ from matplotlib.transforms import TransformedBbox from pandas import Timedelta, Timestamp +from osekit.config import global_logging_context as glc from osekit.core.event import Event from osekit.utils.core import is_empty_dataclass @@ -765,11 +766,19 @@ def _from_csv(cls, csv: Path, **kwargs: Any) -> list[Self]: for record in records ] parsed_records = [] + failed_records = [] for idx, record in enumerate(records): try: parsed_records.append(cls.from_dict(record)) except TypeError: # Most probably a weak comment on the spectrogram - pass + failed_records.append(idx) + + if failed_records: + glc.logger.warning( + f"The following line(s) couldn't be parsed as detections: " + f"{failed_records}", + ) + return parsed_records @classmethod From 9300610af7dbe655566b90466dbdbc31d2002841 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Thu, 8 Oct 2026 18:02:44 +0200 Subject: [PATCH 4/7] add test case for warning logging --- tests/test_detection.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/test_detection.py b/tests/test_detection.py index a5f44636..63f149d9 100644 --- a/tests/test_detection.py +++ b/tests/test_detection.py @@ -1,9 +1,11 @@ +import logging from contextlib import AbstractContextManager, nullcontext from pathlib import Path from typing import Any, Literal import numpy as np import pytest +from _pytest.logging import LogCaptureFixture from matplotlib.axes import Axes from matplotlib.patches import Rectangle from pandas import DataFrame, Timedelta, Timestamp @@ -235,10 +237,14 @@ def test_confidence_indicator_from_relative_level_string( assert ci.maximum_level == e.maximum_level -def test_detections_from_csv() -> None: - detections = Detection.from_csv( - csv=Path(__file__).parent / "_static" / "aplose_result.csv", - ) +def test_detections_from_csv(caplog: LogCaptureFixture) -> None: + with caplog.at_level(logging.WARNING): + detections = Detection.from_csv( + csv=Path(__file__).parent / "_static" / "aplose_result.csv", + ) + + # Failed parsed line should be logged (weak comment at idx 10) + assert "[10]" in caplog.text minimal_detection = next(d for d in detections if d.label is None) detections = [d for d in detections if d is not minimal_detection] From f46b74e82419af558dfa8c312414b82096e7a2d8 Mon Sep 17 00:00:00 2001 From: Gautzilla <72027971+Gautzilla@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:58:03 +0200 Subject: [PATCH 5/7] update annotation column name to label --- src/osekit/core/detection.py | 3 ++- tests/_static/aplose_result.csv | 2 +- tests/_static/retro_compatibility_result.csv | 2 ++ tests/test_detection.py | 8 ++++++++ 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 tests/_static/retro_compatibility_result.csv diff --git a/src/osekit/core/detection.py b/src/osekit/core/detection.py index f7b6a293..8f618f5c 100644 --- a/src/osekit/core/detection.py +++ b/src/osekit/core/detection.py @@ -29,6 +29,7 @@ "min_frequency", "max_frequency", "annotation", + "label", "annotator", "annotator_expertise", "start_datetime", @@ -618,7 +619,7 @@ def from_dict(cls, row: dict) -> Self: return cls( metadata=metadata, - label=row.get("annotation"), + label=row.get("label") or row.get("annotation"), detector_info=detector_info, begin=Timestamp(row["start_datetime"]), end=Timestamp(row["end_datetime"]), diff --git a/tests/_static/aplose_result.csv b/tests/_static/aplose_result.csv index 21e0c43d..06b65307 100644 --- a/tests/_static/aplose_result.csv +++ b/tests/_static/aplose_result.csv @@ -1,4 +1,4 @@ -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 +dataset,filename,annotation_id,is_update_of_id,start_time,end_time,start_frequency,end_frequency,min_frequency,max_frequency,label,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 diff --git a/tests/_static/retro_compatibility_result.csv b/tests/_static/retro_compatibility_result.csv new file mode 100644 index 00000000..63e3f97b --- /dev/null +++ b/tests/_static/retro_compatibility_result.csv @@ -0,0 +1,2 @@ +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 diff --git a/tests/test_detection.py b/tests/test_detection.py index 63f149d9..07e2a790 100644 --- a/tests/test_detection.py +++ b/tests/test_detection.py @@ -399,6 +399,14 @@ def test_minimal_detector_result_file() -> None: assert len(detections) == 2 +def test_retro_compatibility() -> None: + detections = Detection.from_csv( + csv=Path(__file__).parent / "_static/retro_compatibility_result.csv", + ) + + assert detections[0].label == "bird" + + def test_detections_from_csv_list() -> None: detections = Detection.from_csv( [ From 3fafc0e1866ca7be5e9f414263d3fa7c9df2d1d0 Mon Sep 17 00:00:00 2001 From: Gautzilla <72027971+Gautzilla@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:14:54 +0200 Subject: [PATCH 6/7] raise an error if no Detection was successfully parsed in the csv(s) --- src/osekit/core/detection.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/osekit/core/detection.py b/src/osekit/core/detection.py index 8f618f5c..085ea03a 100644 --- a/src/osekit/core/detection.py +++ b/src/osekit/core/detection.py @@ -808,4 +808,11 @@ def from_csv(cls, csv: Path | list[Path], **kwargs: Any) -> list[Self]: for csv_file in csv: output += cls._from_csv(csv_file, **kwargs) + if not output: + msg = ( + "No valid Detection was found in the given csv file(s).\n" + "Tip: you can specify the used separator with the sep parameter." + ) + raise ValueError(msg) + return output From c121a436e35525577e4cfc4627f5bdb4ed964623 Mon Sep 17 00:00:00 2001 From: Gautzilla <72027971+Gautzilla@users.noreply.github.com> Date: Mon, 19 Oct 2026 17:13:54 +0200 Subject: [PATCH 7/7] add a test for the ValueError raising if no valid Detection is contained in the csv --- tests/test_detection.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_detection.py b/tests/test_detection.py index 07e2a790..3c5289ed 100644 --- a/tests/test_detection.py +++ b/tests/test_detection.py @@ -342,6 +342,21 @@ def test_detections_from_csv(caplog: LogCaptureFixture) -> None: assert len(minimal_detection.verifications) == 0 +def test_detections_from_csv_raises_if_no_detection( + monkeypatch: pytest.MonkeyPatch, +) -> None: + def mock_from_dict(*args, **kwargs) -> None: # noqa: ANN002, ANN003 + msg = r"Mock a TypeError while trying to parse a Detection object." + raise TypeError(msg) + + monkeypatch.setattr(Detection, "from_dict", mock_from_dict) + + with pytest.raises(ValueError, match="No valid Detection was found"): + Detection.from_csv( + csv=Path(__file__).parent / "_static/minimal_detector_result.csv", + ) + + def test_detections_to_dict(tmp_path: Path) -> None: for result_file in ("aplose_result.csv", "minimal_detector_result.csv"): detections = Detection.from_csv(