Skip to content

Commit 26085c1

Browse files
authored
move to two digit precision in results to improve accuracy when using overlap+audiospeed (#730)
* move to two digit precision in results to improve accuracy when using overlap+audiospeed * new ruff version * .
1 parent fa187eb commit 26085c1

8 files changed

Lines changed: 25 additions & 19 deletions

File tree

birdnet_analyzer/analyze/utils.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,7 @@ def combine_kaleidoscope_files(saved_results: list[str]):
365365
continue
366366

367367
# skip header and add to file
368-
for line in lines[1:]:
369-
f.write(line)
368+
f.writelines(lines[1:])
370369

371370
except Exception as ex:
372371
print(f"Error: Cannot combine results from {rfile}.\n", flush=True)
@@ -545,13 +544,12 @@ def iterate_audio_chunks(fpath: str, embeddings: bool = False):
545544
break
546545

547546
for chunk_index, chunk in enumerate(chunks):
547+
t_start = start + (chunk_index * (cfg.SIG_LENGTH - cfg.SIG_OVERLAP) * cfg.AUDIO_SPEED)
548+
end = min(t_start + cfg.SIG_LENGTH * cfg.AUDIO_SPEED, fileLengthSeconds)
549+
548550
# Add to batch
549551
samples.append(chunk)
550-
timestamps.append([round(start, 1), round(end, 1)])
551-
552-
# Advance start and end
553-
start += (cfg.SIG_LENGTH - cfg.SIG_OVERLAP) * cfg.AUDIO_SPEED
554-
end = min(start + cfg.SIG_LENGTH * cfg.AUDIO_SPEED, fileLengthSeconds)
552+
timestamps.append([round(t_start, 2), round(end, 2)])
555553

556554
# Check if batch is full or last chunk
557555
if len(samples) < cfg.BATCH_SIZE and chunk_index < len(chunks) - 1:
@@ -571,6 +569,8 @@ def iterate_audio_chunks(fpath: str, embeddings: bool = False):
571569
samples = []
572570
timestamps = []
573571

572+
start += len(chunks) * (cfg.SIG_LENGTH - cfg.SIG_OVERLAP) * cfg.AUDIO_SPEED
573+
574574

575575
def predict(samples):
576576
"""Predicts the classes for the given samples.

birdnet_analyzer/gui/evaluation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ def select_directory_on_empty(): # Nishant - Function modified for For Folder s
379379

380380
if folder:
381381
files = get_selection_tables(folder)
382-
files_to_display = files[:100] + [["..."]] if len(files) > 100 else files
382+
files_to_display = [*files[:100], ["..."]] if len(files) > 100 else files
383383
return [files, files_to_display, gr.update(visible=True), *on_select(files)]
384384

385385
return ["", [[loc.localize("eval-tab-no-files-found")]]]

birdnet_analyzer/gui/multi_file.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def select_directory_on_empty(): # Nishant - Function modified for For Folder s
119119
if folder:
120120
files_and_durations = gu.get_audio_files_and_durations(folder)
121121
if len(files_and_durations) > 100:
122-
return [folder, files_and_durations[:100] + [["..."]]] # hopefully fixes issue#272
122+
return [folder, *files_and_durations[:100], ["..."]] # hopefully fixes issue#272
123123
return [folder, files_and_durations]
124124

125125
return ["", [[loc.localize("multi-tab-samples-dataframe-no-files-found")]]]

birdnet_analyzer/model.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -872,8 +872,7 @@ def save_linear_classifier(classifier, model_path: str, labels: list[str], mode=
872872

873873
# Save labels
874874
with open(model_path.replace(".tflite", "_Labels.txt"), "w", encoding="utf-8") as f:
875-
for label in labels:
876-
f.write(label + "\n")
875+
f.writelines(label + "\n" for label in labels)
877876

878877
save_model_params(model_path.replace(".tflite", "_Params.csv"))
879878

birdnet_analyzer/species/utils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,4 @@ def run(output_path, lat, lon, week, threshold, sortby):
7070

7171
# Save species list
7272
with open(cfg.OUTPUT_PATH, "w") as f:
73-
for s in species_list:
74-
f.write(s + "\n")
73+
f.writelines(s + "\n" for s in species_list)

birdnet_analyzer/translate.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ def save_labels_file(labels: list[str], locale: str):
119119
cfg.TRANSLATED_LABELS_PATH, "{}_{}.txt".format(os.path.basename(cfg.LABELS_FILE).rsplit(".", 1)[0], locale)
120120
)
121121
with open(fpath, "w", encoding="utf-8") as f:
122-
for label in labels:
123-
f.write(label + "\n")
122+
f.writelines(label + "\n" for label in labels)
124123

125124

126125
if __name__ == "__main__":

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,5 +144,6 @@ ignore = [
144144
"PLR0915",
145145
"PLR0912",
146146
"PLC0206",
147+
"PLC0415",
147148
"RUF015",
148149
]

tests/analyze/test_analyze.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def test_analyze_with_custom_species_list(mock_analyze_file: MagicMock, mock_set
254254
_, kwargs = mock_set_params.call_args
255255
assert kwargs["slist"] == species_list
256256

257+
257258
@patch("birdnet_analyzer.utils.ensure_model_exists")
258259
def test_analyze_with_negative_speed(setup_test_environment):
259260
"""Test analyzing with negative speed."""
@@ -267,6 +268,7 @@ def test_analyze_with_negative_speed(setup_test_environment):
267268
with pytest.raises(ValueError, match="Audio speed must be a positive value."):
268269
analyze(soundscape_path, env["output_dir"], audio_speed=-1.0, top_n=1, min_conf=0)
269270

271+
270272
@patch("birdnet_analyzer.utils.ensure_model_exists")
271273
def test_analyze_with_zero_speed(setup_test_environment):
272274
"""Test analyzing with zero speed."""
@@ -280,6 +282,7 @@ def test_analyze_with_zero_speed(setup_test_environment):
280282
with pytest.raises(ValueError, match="Audio speed must be a positive value."):
281283
analyze(soundscape_path, env["output_dir"], audio_speed=0.0, top_n=1, min_conf=0)
282284

285+
283286
@patch("birdnet_analyzer.utils.ensure_model_exists")
284287
def test_analyze_with_invalid_audio_speed(setup_test_environment):
285288
"""Test analyzing with invalid audio speed."""
@@ -293,6 +296,7 @@ def test_analyze_with_invalid_audio_speed(setup_test_environment):
293296
with pytest.raises(ValueError, match="Audio speed must be a numeric value."):
294297
analyze(soundscape_path, env["output_dir"], audio_speed="fast", top_n=1, min_conf=0)
295298

299+
296300
@patch("birdnet_analyzer.utils.ensure_model_exists")
297301
def test_analyze_with_negative_overlap(setup_test_environment):
298302
"""Test analyzing with invalid overlap."""
@@ -306,6 +310,7 @@ def test_analyze_with_negative_overlap(setup_test_environment):
306310
with pytest.raises(ValueError, match="Overlap must be a non-negative value."):
307311
analyze(soundscape_path, env["output_dir"], audio_speed=1.0, top_n=1, overlap=-1)
308312

313+
309314
@patch("birdnet_analyzer.utils.ensure_model_exists")
310315
def test_analyze_with_invalid_overlap(setup_test_environment):
311316
"""Test analyzing with invalid overlap."""
@@ -319,6 +324,7 @@ def test_analyze_with_invalid_overlap(setup_test_environment):
319324
with pytest.raises(ValueError, match="Overlap must be a numeric value."):
320325
analyze(soundscape_path, env["output_dir"], audio_speed=1.0, top_n=1, overlap="high")
321326

327+
322328
@patch("birdnet_analyzer.utils.ensure_model_exists")
323329
def test_analyze_with_too_high_overlap(setup_test_environment):
324330
"""Test analyzing with too high overlap."""
@@ -332,9 +338,10 @@ def test_analyze_with_too_high_overlap(setup_test_environment):
332338
with pytest.raises(ValueError, match=f"Overlap must be less than {cfg.SIG_LENGTH} seconds."):
333339
analyze(soundscape_path, env["output_dir"], audio_speed=1.0, top_n=1, overlap=3.0)
334340

341+
335342
@pytest.mark.parametrize(
336343
("audio_speed", "overlap"),
337-
[(10, 1), (5, 2), (5, 0), (0.1, 1), (0.2, 0)],
344+
[(10, 1), (5, 2), (5, 0), (0.1, 1), (0.2, 0), (0.3, 0.7)],
338345
)
339346
def test_analyze_with_speed_up_and_overlap(setup_test_environment, audio_speed, overlap):
340347
"""Test analyzing with speed up."""
@@ -344,9 +351,10 @@ def test_analyze_with_speed_up_and_overlap(setup_test_environment, audio_speed,
344351

345352
assert os.path.exists(soundscape_path), "Soundscape file does not exist"
346353
file_length = 120
347-
step_size = round(3 * audio_speed - overlap * audio_speed, 1)
348-
expected_start_timestamps = [e / 10 for e in range(0, int(file_length * 10), int(step_size * 10))]
349-
expected_end_timestamps = [e / 10 for e in range(int(3 * audio_speed * 10), int(file_length) * 10 + 1, int(step_size * 10))]
354+
precision = 100
355+
step_size = round((3 - overlap) * audio_speed, precision // 10)
356+
expected_start_timestamps = [e / precision for e in range(0, int(file_length * precision), int(step_size * precision))]
357+
expected_end_timestamps = [e / precision for e in range(round(3 * audio_speed * precision), int(file_length * precision) + 1, int(step_size * precision))]
350358

351359
while len(expected_end_timestamps) < len(expected_start_timestamps):
352360
if file_length - expected_start_timestamps[-1] >= 1 * audio_speed:

0 commit comments

Comments
 (0)