batdetect2/notebooks/plotting.py
2025-08-11 01:35:31 +01:00

274 lines
6.8 KiB
Python

import marimo
__generated_with = "0.14.16"
app = marimo.App(width="medium")
@app.cell
def _():
import marimo as mo
return
@app.cell
def _():
from batdetect2.data import load_dataset_config, load_dataset
from batdetect2.preprocess import load_preprocessing_config, build_preprocessor
from batdetect2 import api
from soundevent import data
from batdetect2.evaluate.types import MatchEvaluation
from batdetect2.types import Annotation
from batdetect2.compat import annotation_to_sound_event_prediction
from batdetect2.plotting import (
plot_clip,
plot_clip_annotation,
plot_clip_prediction,
plot_matches,
plot_false_positive_match,
plot_false_negative_match,
plot_true_positive_match,
plot_cross_trigger_match,
)
return (
MatchEvaluation,
annotation_to_sound_event_prediction,
api,
build_preprocessor,
data,
load_dataset,
load_dataset_config,
load_preprocessing_config,
plot_clip_annotation,
plot_clip_prediction,
plot_cross_trigger_match,
plot_false_negative_match,
plot_false_positive_match,
plot_matches,
plot_true_positive_match,
)
@app.cell
def _(build_preprocessor, load_dataset_config, load_preprocessing_config):
dataset_config = load_dataset_config(
path="example_data/config.yaml", field="datasets.train"
)
preprocessor_config = load_preprocessing_config(
path="example_data/config.yaml", field="preprocess"
)
preprocessor = build_preprocessor(preprocessor_config)
return dataset_config, preprocessor
@app.cell
def _(dataset_config, load_dataset):
dataset = load_dataset(dataset_config)
return (dataset,)
@app.cell
def _(dataset):
clip_annotation = dataset[1]
return (clip_annotation,)
@app.cell
def _(clip_annotation, plot_clip_annotation, preprocessor):
plot_clip_annotation(
clip_annotation, preprocessor=preprocessor, figsize=(15, 5)
)
return
@app.cell
def _(annotation_to_sound_event_prediction, api, clip_annotation, data):
audio = api.load_audio(clip_annotation.clip.recording.path)
detections, features, spec = api.process_audio(audio)
clip_prediction = data.ClipPrediction(
clip=clip_annotation.clip,
sound_events=[
annotation_to_sound_event_prediction(
prediction, clip_annotation.clip.recording
)
for prediction in detections
],
)
return (clip_prediction,)
@app.cell
def _(clip_prediction, plot_clip_prediction):
plot_clip_prediction(clip_prediction, figsize=(15, 5))
return
@app.cell
def _():
from batdetect2.evaluate import match_predictions_and_annotations
import random
return match_predictions_and_annotations, random
@app.cell
def _(data, random):
def add_noise(clip_annotation, time_buffer=0.003, freq_buffer=1000):
def _add_bbox_noise(bbox):
start_time, low_freq, end_time, high_freq = bbox.coordinates
return data.BoundingBox(
coordinates=[
start_time + random.uniform(-time_buffer, time_buffer),
low_freq + random.uniform(-freq_buffer, freq_buffer),
end_time + random.uniform(-time_buffer, time_buffer),
high_freq + random.uniform(-freq_buffer, freq_buffer),
]
)
def _add_noise(se):
return se.model_copy(
update=dict(
sound_event=se.sound_event.model_copy(
update=dict(
geometry=_add_bbox_noise(se.sound_event.geometry)
)
)
)
)
return clip_annotation.model_copy(
update=dict(
sound_events=[
_add_noise(se) for se in clip_annotation.sound_events
]
)
)
def drop_random(obj, p=0.5):
return obj.model_copy(
update=dict(
sound_events=[se for se in obj.sound_events if random.random() > p]
)
)
return add_noise, drop_random
@app.cell
def _(
add_noise,
clip_annotation,
clip_prediction,
drop_random,
match_predictions_and_annotations,
):
matches = match_predictions_and_annotations(
drop_random(add_noise(clip_annotation), p=0.2),
drop_random(clip_prediction),
)
return (matches,)
@app.cell
def _(clip_annotation, matches, plot_matches):
plot_matches(matches, clip_annotation.clip, figsize=(15, 5))
return
@app.cell
def _(matches):
true_positives = []
false_positives = []
false_negatives = []
for match in matches:
if match.source is None and match.target is not None:
false_negatives.append(match)
elif match.target is None and match.source is not None:
false_positives.append(match)
elif match.target is not None and match.source is not None:
true_positives.append(match)
else:
continue
return false_negatives, false_positives, true_positives
@app.cell
def _(MatchEvaluation, false_positives, plot_false_positive_match):
false_positive = false_positives[0]
false_positive_eval = MatchEvaluation(
match=false_positive,
gt_det=False,
gt_class=None,
pred_score=false_positive.source.score,
pred_class_scores={
"myomyo": 0.2
}
)
plot_false_positive_match(false_positive_eval)
return
@app.cell
def _(MatchEvaluation, false_negatives, plot_false_negative_match):
false_negative = false_negatives[0]
false_negative_eval = MatchEvaluation(
match=false_negative,
gt_det=True,
gt_class="myomyo",
pred_score=None,
pred_class_scores={}
)
plot_false_negative_match(false_negative_eval)
return
@app.cell
def _(MatchEvaluation, plot_true_positive_match, true_positives):
true_positive = true_positives[0]
true_positive_eval = MatchEvaluation(
match=true_positive,
gt_det=True,
gt_class="myomyo",
pred_score=0.87,
pred_class_scores={
"pyomyo": 0.84,
"pippip": 0.84,
}
)
plot_true_positive_match(true_positive_eval)
return (true_positive,)
@app.cell
def _(MatchEvaluation, plot_cross_trigger_match, true_positive):
cross_trigger_eval = MatchEvaluation(
match=true_positive,
gt_det=True,
gt_class="myomyo",
pred_score=0.87,
pred_class_scores={
"pippip": 0.84,
"myomyo": 0.84,
}
)
plot_cross_trigger_match(cross_trigger_eval)
return
@app.cell
def _():
return
if __name__ == "__main__":
app.run()