BirdCallDetector
Detect bird calls in audio files using a trained YOLO model. Processes WAV, FLAC, OGG, and MP3 files through the PCEN spectrogram pipeline and returns timestamped song segments with species labels and confidence scores.
Import¶
from inference.detect_birds import BirdCallDetector, merge_detections, reconstruct_songs, find_audio_files
BirdCallDetector¶
The main entry point for all inference workflows. Instantiate once per model, then call detect() for any number of files.
Constructor¶
BirdCallDetector(
model_path,
species_mapping,
conf_threshold=0.001,
nms_iou_threshold=0.7,
song_gap_threshold=0.1,
num_workers=1,
verbose=True,
freq_iou_threshold=None,
merge_method=None,
phrase_gap=None,
)
| Parameter | Type / Default | Required? | Description |
|---|---|---|---|
model_path |
str / — |
Yes | Path to the trained YOLO model file (.pt, .onnx, .engine, etc.). |
species_mapping |
str / — |
Yes | 'Just-Bird' or 'Birds-With-Labels', the model the class IDs come from. It maps them to eBird codes and sets the default merge. Must match the model. See Species mapping. |
conf_threshold |
float / 0.001 |
No | Confidence threshold (0.0–1.0). Detections below this value are discarded. For direct field use, take 0.15 for Just-Bird and 0.2 for the other models. Use 0.001 to retain all raw detections for evaluation. |
nms_iou_threshold |
float / 0.7 |
No | IoU threshold for Non-Maximum Suppression applied per-clip and across overlapping time windows. |
song_gap_threshold |
float / 0.1 |
No | Maximum temporal gap in seconds between two detections of the same species that are still merged into one continuous song segment. |
num_workers |
int / 1 |
No | Number of CPU processes for PCEN, spectrogram rendering, and multi-file preprocess. YOLO inference stays in the parent and runs in batches. |
verbose |
bool / True |
No | If True, print per-file processing details and clip-level progress bars. If False, show a single file-level progress bar. The CLI passes False unless --verbose is set. |
freq_iou_threshold |
float / None |
No | reconstruct merge with Just-Bird only: frequency IoU gate (None = 0.6, negative = time-only). |
merge_method |
str / None |
No | 'fusion' (window ownership + weighted box fusion) or 'reconstruct' (song merge). None or 'auto' picks 'fusion' for Just-Bird and 'reconstruct' for Birds-With-Labels. See Window fusion. |
phrase_gap |
float / None |
No | Fusion only. Join same-band boxes closer than this many seconds into one phrase box. 0 keeps call-level boxes. None uses the model default: 0.05 s for Just-Bird, 0 for Birds-With-Labels. |
Memory Usage
Extra workers are render processes, not extra model copies. The parent holds one YOLO model. Workers use additional RAM for matplotlib/librosa, not another set of weights.
Methods¶
| Method | Returns | Description |
|---|---|---|
detect(audio_path, output_path, output_formats, no_merge) |
List[Dict] |
Detect in a file or directory. Main entry point. |
detect_single_file(audio_path, progress_callback, no_merge) |
List[Dict] |
Detect in a single audio file. |
detect_multiple_files(audio_paths, output_path, output_formats, no_merge) |
List[Dict] |
Detect across a list of audio files. |
save_results(detections, output_path, audio_path, output_formats, no_merge) |
None |
Write detections to one or more output formats. |
merge_overlapping_detections(detections, merge_mode) |
List[Dict] |
Merge raw clip detections. |
load_audio(audio_path) |
Tuple[np.ndarray, int] |
Load an audio file, returns (signal, sample_rate). |
detect¶
Detect bird calls in an audio file or directory. Automatically routes to detect_single_file() or detect_multiple_files() based on whether audio_path is a file or a directory.
| Parameter | Type / Default | Required? | Description |
|---|---|---|---|
audio_path |
str / — |
Yes | Path to a single audio file (WAV, FLAC, OGG, MP3) or a directory. Directories are searched recursively. |
output_path |
str / None |
No | Output directory for result files. If None, results are only returned, not written. |
output_formats |
List[str] / ['json-with-algorithm-metadata'] |
No | One or more output format keys. Accepts json-with-algorithm-metadata, simplified-csv, xeno-canto-annota-json, raven-selection-table, or all. Ignored when no_merge=True. |
no_merge |
bool / False |
No | Evaluation mode. Returns raw per-clip detections and writes only raw_detections.json. Use with conf_threshold=0.001 for F-beta sweep workflows. |
Returns: List[Dict] — merged song segments (or raw clip detections when no_merge=True).
detect_single_file¶
Detect bird calls in a single audio file. Use this method directly when you need a progress_callback (e.g. in a GUI or web app).
| Parameter | Type / Default | Required? | Description |
|---|---|---|---|
audio_path |
str / — |
Yes | Path to the audio file. |
progress_callback |
Callable[[int, int, str], None] / None |
No | Called as callback(current, total, message) after each clip. Useful for progress bars. |
no_merge |
bool / False |
No | If True, return raw clip-level detections without merging. |
Returns: List[Dict] — detections for this file.
save_results¶
detector.save_results(
detections,
output_path,
audio_path=None,
output_formats=None,
no_merge=False,
)
Write detections to one or more output formats under output_path.
| Parameter | Type / Default | Required? | Description |
|---|---|---|---|
detections |
List[Dict] / — |
Yes | Detections returned by detect() or detect_single_file(). |
output_path |
str / — |
Yes | Directory to write output files into. |
audio_path |
str / None |
No | Source audio path, used for metadata in JSON output. |
output_formats |
List[str] / ['json-with-algorithm-metadata'] |
No | One or more format keys (same values as detect()). |
no_merge |
bool / False |
No | If True, writes only raw_detections.json and ignores output_formats. |
merge_overlapping_detections¶
Merge raw clip detections into song segments or deduplicate with NMS.
| Parameter | Type / Default | Required? | Description |
|---|---|---|---|
detections |
List[Dict] / — |
Yes | Raw per-clip detections. |
merge_mode |
str / 'reconstruct' |
No | 'reconstruct' applies the detector's merge_method (window fusion or the song merge, default). 'nms' removes duplicates by IoU, keeping the highest-confidence box. |
file_end |
float / None |
No | End of the file's last window, used by window-fusion ownership. None takes the latest clip_end among the detections. |
Returns: List[Dict] — merged or deduplicated detections.
merge_detections¶
Standalone version of the detector's merge, for raw detections saved with --no-merge. Filter by confidence first, then merge. The evaluation scripts use it, so their scores match detection.
from inference.detect_birds import file_ends_from_raw, merge_detections, resolve_merge_settings
settings = resolve_merge_settings(raw_json["model_config"]) # fusion for the call-level models
filtered = [d for d in raw_json["detections"] if d["confidence"] >= 0.15]
merged = merge_detections(filtered, **settings, file_end=file_ends_from_raw(raw_json["detections"]))
| Parameter | Type / Default | Required? | Description |
|---|---|---|---|
detections |
List[Dict] / — |
Yes | Raw detections with time_start, time_end, freq_low_hz, freq_high_hz, confidence, species_id, species, and for fusion clip_start / clip_end. Optional filename for multi-file inputs. |
merge_method |
str / — |
Yes | 'fusion' or 'reconstruct'. |
song_gap_threshold |
float / — |
Yes | reconstruct only: max gap in seconds. |
freq_iou_threshold |
float / None |
No | reconstruct only: frequency IoU gate. |
phrase_gap |
float / None |
No | Fusion only: join same-band boxes closer than this into phrase boxes. None or 0 keeps call-level boxes (no model default at this level; resolve_merge_settings fills it in). |
file_end |
float, Dict[str, float] or None |
No | Fusion only: end of each file's last window. file_ends_from_raw builds the per-file dict from the unfiltered raw list, which keeps ownership identical to live detection. |
resolve_merge_settings(model_config, song_gap, freq_iou, merge_method, phrase_gap) returns these keyword arguments from a raw JSON's model_config. Explicit arguments win, then the JSON, then the model defaults (resolve_phrase_gap: 0.05 s for Just-Bird). A raw JSON written before the Just-Bird default changed has no phrase_gap and therefore gets 0.05 s; pass phrase_gap=0 to score it as before.
Returns: List[Dict] — merged segments in the format below.
reconstruct_songs¶
Standalone version of the song-merging logic. Use this outside a BirdCallDetector instance, for example in evaluation scripts that operate on previously saved raw detections.
from inference.detect_birds import reconstruct_songs
merged = reconstruct_songs(detections, song_gap_threshold)
merged = reconstruct_songs(detections, song_gap_threshold, freq_iou_threshold=0.6)
| Parameter | Type / Default | Required? | Description |
|---|---|---|---|
detections |
List[Dict] / — |
Yes | Raw detections, each with time_start, time_end, species_id, species, confidence, freq_low_hz, freq_high_hz. Optional filename key for multi-file inputs. |
song_gap_threshold |
float / — |
Yes | Max gap in seconds between two detections of the same species to merge them into one song segment. |
freq_iou_threshold |
float / None |
No | If set, also require this frequency IoU. None keeps the time-only merger. Just-Bird detection and evaluation pass 0.6 unless overridden. |
Returns: List[Dict] — merged song segments, sorted by (filename, time_start).
Each merged segment contains:
| Field | Type | Description |
|---|---|---|
species |
str |
eBird species code |
species_id |
int |
Model class ID |
time_start |
float |
Start time in seconds |
time_end |
float |
End time in seconds |
avg_confidence |
float |
Running average confidence across merged clips |
max_confidence |
float |
Highest confidence among merged clips |
detections_merged |
int |
Number of raw clips that were merged |
freq_low_hz |
int |
Lowest frequency of merged bounding boxes |
freq_high_hz |
int |
Highest frequency of merged bounding boxes |
filename |
str |
Source filename (multi-file inputs only) |
find_audio_files¶
Discover all supported audio files in a path.
| Parameter | Type / Default | Required? | Description |
|---|---|---|---|
audio_path |
str / — |
Yes | Path to a single audio file or a directory. Directories are searched recursively. |
verbose |
bool / True |
No | If True, print how many audio files were found in a directory. |
Returns: List[str] — sorted list of absolute file paths. Supported extensions: .wav, .flac, .ogg, .mp3.
Examples¶
The examples use Just-Bird, the recommended model. It detects bird vocalizations with a single bird class. For species-level output, use the Birds-With-Labels model with its species mapping, as in the directory batch example.
Basic detection¶
from inference.detect_birds import BirdCallDetector
detector = BirdCallDetector(
model_path="models/Just-Bird.pt",
species_mapping="Just-Bird",
conf_threshold=0.15,
)
detections = detector.detect(
"recording.wav",
output_path="results",
output_formats=["simplified-csv"],
)
print(f"Found {len(detections)} song segments")
Evaluation workflow (raw detections)¶
from inference.detect_birds import BirdCallDetector
detector = BirdCallDetector(
model_path="models/Just-Bird.pt",
species_mapping="Just-Bird",
conf_threshold=0.001,
)
raw_detections = detector.detect(
"data/test_audio/",
output_path="results",
no_merge=True,
)
This writes results/raw_detections.json. Feed it into FBetaScoreAnalyzer next.
With progress callback¶
from inference.detect_birds import BirdCallDetector
detector = BirdCallDetector(
model_path="models/Just-Bird.pt",
species_mapping="Just-Bird",
)
def on_progress(current, total, message):
print(f"[{current}/{total}] {message}")
detections = detector.detect_single_file(
"long_recording.flac",
progress_callback=on_progress,
)
Using reconstruct_songs independently¶
import json
from inference.detect_birds import reconstruct_songs
with open("results/raw_detections.json") as f:
data = json.load(f)
raw = data["detections"]
gap = data["model_config"]["song_gap_threshold"]
merged = reconstruct_songs(raw, song_gap_threshold=gap)
print(f"{len(raw)} raw detections → {len(merged)} song segments")
Parallel inference¶
from inference.detect_birds import BirdCallDetector
detector = BirdCallDetector(
model_path="models/Just-Bird.pt",
species_mapping="Just-Bird",
num_workers=4,
)
detections = detector.detect(
"long_recording.wav",
output_path="results",
output_formats=["simplified-csv"],
)
Lossy Audio Formats
The model was trained on lossless WAV files. When processing MP3 or OGG input, detection performance may degrade, especially for faint calls and high-frequency species. In a study of avian recordings, MP3 compression reduced the detections of human listeners and automated recognisers [19]. Use WAV or FLAC whenever possible. If you must use MP3, ensure a bitrate of ≥ 256 kbps.
References¶
[19] MacPhail, A. G., Yip, D. A., Knight, E. C., Hedley, R., Knaggs, M., Shonfield, J., Upham-Mills, E., & Bayne, E. M. (2024). "Audio data compression affects acoustic indices and reduces detections of birds by human listening and automated recognisers." Bioacoustics, 33(1):74–90.