import textwrap import pytest import yaml from pydantic import TypeAdapter from soundevent import data from batdetect2.data.conditions import ( SoundEventConditionConfig, build_sound_event_condition, ) def build_condition_from_str(content): content = textwrap.dedent(content) content = yaml.safe_load(content) config = TypeAdapter(SoundEventConditionConfig).validate_python(content) return build_sound_event_condition(config) def test_has_tag(sound_event: data.SoundEvent): condition = build_condition_from_str(""" name: has_tag tag: key: species value: Myotis myotis """) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[data.Tag(key="species", value="Myotis myotis")], # type: ignore ) assert condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[data.Tag(key="species", value="Eptesicus fuscus")], # type: ignore ) assert not condition(sound_event_annotation) def test_has_all_tags(sound_event: data.SoundEvent): condition = build_condition_from_str(""" name: has_all_tags tags: - key: species value: Myotis myotis - key: event value: Echolocation """) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[data.Tag(key="species", value="Myotis myotis")], # type: ignore ) assert not condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[ data.Tag(key="species", value="Eptesicus fuscus"), # type: ignore data.Tag(key="event", value="Echolocation"), # type: ignore ], ) assert not condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[ data.Tag(key="species", value="Myotis myotis"), # type: ignore data.Tag(key="event", value="Echolocation"), # type: ignore ], ) assert condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[ data.Tag(key="species", value="Myotis myotis"), # type: ignore data.Tag(key="event", value="Echolocation"), # type: ignore data.Tag(key="sex", value="Female"), # type: ignore ], ) assert condition(sound_event_annotation) def test_has_any_tags(sound_event: data.SoundEvent): condition = build_condition_from_str(""" name: has_any_tag tags: - key: species value: Myotis myotis - key: event value: Echolocation """) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[data.Tag(key="species", value="Myotis myotis")], # type: ignore ) assert condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[ data.Tag(key="species", value="Eptesicus fuscus"), # type: ignore data.Tag(key="event", value="Echolocation"), # type: ignore ], ) assert condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[ data.Tag(key="species", value="Myotis myotis"), # type: ignore data.Tag(key="event", value="Echolocation"), # type: ignore ], ) assert condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[ data.Tag(key="species", value="Eptesicus fuscus"), # type: ignore data.Tag(key="event", value="Social"), # type: ignore ], ) assert not condition(sound_event_annotation) def test_not(sound_event: data.SoundEvent): condition = build_condition_from_str(""" name: not condition: name: has_tag tag: key: species value: Myotis myotis """) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[data.Tag(key="species", value="Myotis myotis")], # type: ignore ) assert not condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[data.Tag(key="species", value="Eptesicus fuscus")], # type: ignore ) assert condition(sound_event_annotation) sound_event_annotation = data.SoundEventAnnotation( sound_event=sound_event, tags=[ data.Tag(key="species", value="Myotis myotis"), # type: ignore data.Tag(key="event", value="Echolocation"), # type: ignore ], ) assert not condition(sound_event_annotation) def test_duration(recording: data.Recording): se1 = data.SoundEventAnnotation( sound_event=data.SoundEvent( recording=recording, geometry=data.TimeInterval(coordinates=[0, 1]) ), ) se2 = data.SoundEventAnnotation( sound_event=data.SoundEvent( recording=recording, geometry=data.TimeInterval(coordinates=[0, 2]) ), ) se3 = data.SoundEventAnnotation( sound_event=data.SoundEvent( recording=recording, geometry=data.TimeInterval(coordinates=[0, 3]) ), ) condition = build_condition_from_str(""" name: duration operator: lt seconds: 2 """) assert condition(se1) assert not condition(se2) assert not condition(se3) condition = build_condition_from_str(""" name: duration operator: lte seconds: 2 """) assert condition(se1) assert condition(se2) assert not condition(se3) condition = build_condition_from_str(""" name: duration operator: gt seconds: 2 """) assert not condition(se1) assert not condition(se2) assert condition(se3) condition = build_condition_from_str(""" name: duration operator: gte seconds: 2 """) assert not condition(se1) assert condition(se2) assert condition(se3) condition = build_condition_from_str(""" name: duration operator: eq seconds: 2 """) assert not condition(se1) assert condition(se2) assert not condition(se3) def test_frequency(recording: data.Recording): se12 = data.SoundEventAnnotation( sound_event=data.SoundEvent( recording=recording, geometry=data.BoundingBox(coordinates=[0, 100, 1, 200]), ), ) se13 = data.SoundEventAnnotation( sound_event=data.SoundEvent( recording=recording, geometry=data.BoundingBox(coordinates=[0, 100, 2, 300]), ), ) se14 = data.SoundEventAnnotation( sound_event=data.SoundEvent( recording=recording, geometry=data.BoundingBox(coordinates=[0, 100, 3, 400]), ), ) se24 = data.SoundEventAnnotation( sound_event=data.SoundEvent( recording=recording, geometry=data.BoundingBox(coordinates=[0, 200, 3, 400]), ), ) se34 = data.SoundEventAnnotation( sound_event=data.SoundEvent( recording=recording, geometry=data.BoundingBox(coordinates=[0, 300, 3, 400]), ), ) condition = build_condition_from_str(""" name: frequency boundary: high operator: lt hertz: 300 """) assert condition(se12) assert not condition(se13) assert not condition(se14) condition = build_condition_from_str(""" name: frequency boundary: high operator: lte hertz: 300 """) assert condition(se12) assert condition(se13) assert not condition(se14) condition = build_condition_from_str(""" name: frequency boundary: high operator: gt hertz: 300 """) assert not condition(se12) assert not condition(se13) assert condition(se14) condition = build_condition_from_str(""" name: frequency boundary: high operator: gte hertz: 300 """) assert not condition(se12) assert condition(se13) assert condition(se14) condition = build_condition_from_str(""" name: frequency boundary: high operator: eq hertz: 300 """) assert not condition(se12) assert condition(se13) assert not condition(se14) # LOW condition = build_condition_from_str(""" name: frequency boundary: low operator: lt hertz: 200 """) assert condition(se14) assert not condition(se24) assert not condition(se34) condition = build_condition_from_str(""" name: frequency boundary: low operator: lte hertz: 200 """) assert condition(se14) assert condition(se24) assert not condition(se34) condition = build_condition_from_str(""" name: frequency boundary: low operator: gt hertz: 200 """) assert not condition(se14) assert not condition(se24) assert condition(se34) condition = build_condition_from_str(""" name: frequency boundary: low operator: gte hertz: 200 """) assert not condition(se14) assert condition(se24) assert condition(se34) condition = build_condition_from_str(""" name: frequency boundary: low operator: eq hertz: 200 """) assert not condition(se14) assert condition(se24) assert not condition(se34) def test_frequency_is_false_for_temporal_geometries(recording: data.Recording): condition = build_condition_from_str(""" name: frequency boundary: low operator: eq hertz: 200 """) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeInterval(coordinates=[0, 3]), recording=recording, ) ) assert not condition(se) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeStamp(coordinates=3), recording=recording, ) ) assert not condition(se) def test_has_tags_fails_if_empty(): with pytest.raises(ValueError): build_condition_from_str(""" name: has_tags tags: [] """) def test_frequency_is_false_if_no_geometry(recording: data.Recording): condition = build_condition_from_str(""" name: frequency boundary: low operator: eq hertz: 200 """) se = data.SoundEventAnnotation( sound_event=data.SoundEvent(geometry=None, recording=recording) ) assert not condition(se) def test_duration_is_false_if_no_geometry(recording: data.Recording): condition = build_condition_from_str(""" name: duration operator: eq seconds: 1 """) se = data.SoundEventAnnotation( sound_event=data.SoundEvent(geometry=None, recording=recording) ) assert not condition(se) def test_all_of(recording: data.Recording): condition = build_condition_from_str(""" name: all_of conditions: - name: has_tag tag: key: species value: Myotis myotis - name: duration operator: lt seconds: 1 """) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeInterval(coordinates=[0, 0.5]), recording=recording, ), tags=[data.Tag(key="species", value="Myotis myotis")], # type: ignore ) assert condition(se) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeInterval(coordinates=[0, 2]), recording=recording, ), tags=[data.Tag(key="species", value="Myotis myotis")], # type: ignore ) assert not condition(se) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeInterval(coordinates=[0, 0.5]), recording=recording, ), tags=[data.Tag(key="species", value="Eptesicus fuscus")], # type: ignore ) assert not condition(se) def test_any_of(recording: data.Recording): condition = build_condition_from_str(""" name: any_of conditions: - name: has_tag tag: key: species value: Myotis myotis - name: duration operator: lt seconds: 1 """) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeInterval(coordinates=[0, 2]), recording=recording, ), tags=[data.Tag(key="species", value="Eptesicus fuscus")], # type: ignore ) assert not condition(se) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeInterval(coordinates=[0, 0.5]), recording=recording, ), tags=[data.Tag(key="species", value="Myotis myotis")], # type: ignore ) assert condition(se) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeInterval(coordinates=[0, 2]), recording=recording, ), tags=[data.Tag(key="species", value="Myotis myotis")], # type: ignore ) assert condition(se) se = data.SoundEventAnnotation( sound_event=data.SoundEvent( geometry=data.TimeInterval(coordinates=[0, 0.5]), recording=recording, ), tags=[data.Tag(key="species", value="Eptesicus fuscus")], # type: ignore ) assert condition(se)