birdnet-analyzer 2.0.1__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- birdnet_analyzer/analyze/__init__.py +14 -0
- birdnet_analyzer/analyze/cli.py +5 -0
- birdnet_analyzer/analyze/core.py +6 -1
- birdnet_analyzer/analyze/utils.py +42 -40
- birdnet_analyzer/audio.py +2 -2
- birdnet_analyzer/cli.py +41 -18
- birdnet_analyzer/config.py +4 -3
- birdnet_analyzer/eBird_taxonomy_codes_2024E.json +13046 -0
- birdnet_analyzer/embeddings/core.py +2 -1
- birdnet_analyzer/embeddings/utils.py +42 -1
- birdnet_analyzer/evaluation/__init__.py +6 -13
- birdnet_analyzer/evaluation/assessment/performance_assessor.py +12 -57
- birdnet_analyzer/evaluation/assessment/plotting.py +61 -62
- birdnet_analyzer/evaluation/preprocessing/data_processor.py +1 -1
- birdnet_analyzer/gui/analysis.py +5 -1
- birdnet_analyzer/gui/assets/gui.css +8 -0
- birdnet_analyzer/gui/embeddings.py +37 -18
- birdnet_analyzer/gui/evaluation.py +14 -8
- birdnet_analyzer/gui/multi_file.py +25 -5
- birdnet_analyzer/gui/review.py +16 -63
- birdnet_analyzer/gui/settings.py +25 -4
- birdnet_analyzer/gui/single_file.py +14 -17
- birdnet_analyzer/gui/train.py +7 -16
- birdnet_analyzer/gui/utils.py +42 -55
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ca.txt +1 -1
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_pl.txt +1 -1
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sr.txt +108 -108
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_zh.txt +1 -1
- birdnet_analyzer/lang/de.json +7 -0
- birdnet_analyzer/lang/en.json +7 -0
- birdnet_analyzer/lang/fi.json +7 -0
- birdnet_analyzer/lang/fr.json +7 -0
- birdnet_analyzer/lang/id.json +7 -0
- birdnet_analyzer/lang/pt-br.json +7 -0
- birdnet_analyzer/lang/ru.json +36 -29
- birdnet_analyzer/lang/se.json +7 -0
- birdnet_analyzer/lang/tlh.json +7 -0
- birdnet_analyzer/lang/zh_TW.json +7 -0
- birdnet_analyzer/model.py +21 -21
- birdnet_analyzer/search/core.py +1 -1
- birdnet_analyzer/utils.py +3 -4
- {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/METADATA +18 -9
- {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/RECORD +47 -47
- {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/WHEEL +1 -1
- birdnet_analyzer/eBird_taxonomy_codes_2021E.json +0 -25280
- {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/entry_points.txt +0 -0
- {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,7 @@ from birdnet_analyzer.embeddings.core import get_database as get_embeddings_data
|
|
10
10
|
from birdnet_analyzer.search.core import get_database as get_search_database
|
11
11
|
|
12
12
|
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
|
13
|
-
PAGE_SIZE =
|
13
|
+
PAGE_SIZE = 6
|
14
14
|
|
15
15
|
|
16
16
|
def play_audio(audio_infos):
|
@@ -37,7 +37,7 @@ def update_export_state(audio_infos, checkbox_value, export_state: dict):
|
|
37
37
|
return export_state
|
38
38
|
|
39
39
|
|
40
|
-
def
|
40
|
+
def run_embeddings_with_tqdm_tracking(
|
41
41
|
input_path,
|
42
42
|
db_directory,
|
43
43
|
db_name,
|
@@ -47,6 +47,7 @@ def rum_embeddings_with_tqdm_tracking(
|
|
47
47
|
audio_speed,
|
48
48
|
fmin,
|
49
49
|
fmax,
|
50
|
+
file_output,
|
50
51
|
progress=gr.Progress(track_tqdm=True),
|
51
52
|
):
|
52
53
|
return run_embeddings(
|
@@ -59,6 +60,7 @@ def rum_embeddings_with_tqdm_tracking(
|
|
59
60
|
audio_speed,
|
60
61
|
fmin,
|
61
62
|
fmax,
|
63
|
+
file_output,
|
62
64
|
progress,
|
63
65
|
)
|
64
66
|
|
@@ -74,6 +76,7 @@ def run_embeddings(
|
|
74
76
|
audio_speed,
|
75
77
|
fmin,
|
76
78
|
fmax,
|
79
|
+
file_output,
|
77
80
|
progress,
|
78
81
|
):
|
79
82
|
from birdnet_analyzer.embeddings.utils import run
|
@@ -97,6 +100,7 @@ def run_embeddings(
|
|
97
100
|
settings["BANDPASS_FMAX"],
|
98
101
|
threads,
|
99
102
|
batch_size,
|
103
|
+
file_output,
|
100
104
|
)
|
101
105
|
except Exception as e:
|
102
106
|
db.db.close()
|
@@ -106,11 +110,11 @@ def run_embeddings(
|
|
106
110
|
if fmin is None or fmax is None or fmin < cfg.SIG_FMIN or fmax > cfg.SIG_FMAX or fmin > fmax:
|
107
111
|
raise gr.Error(f"{loc.localize('validation-no-valid-frequency')} [{cfg.SIG_FMIN}, {cfg.SIG_FMAX}]") from e
|
108
112
|
|
109
|
-
run(input_path, db_path, overlap, audio_speed, fmin, fmax, threads, batch_size)
|
113
|
+
run(input_path, db_path, overlap, audio_speed, fmin, fmax, threads, batch_size, file_output)
|
110
114
|
|
111
115
|
gr.Info(f"{loc.localize('embeddings-tab-finish-info')} {db_path}")
|
112
116
|
|
113
|
-
return gr.Plot(), gr.Slider(
|
117
|
+
return gr.Plot(), gr.Slider(interactive=False), gr.Number(interactive=False), gr.Number(interactive=False)
|
114
118
|
|
115
119
|
|
116
120
|
@gu.gui_runtime_error_handler
|
@@ -275,7 +279,7 @@ def _build_extract_tab():
|
|
275
279
|
gr.Number(interactive=True),
|
276
280
|
)
|
277
281
|
|
278
|
-
return None,
|
282
|
+
return None, gr.Textbox(visible=False), gr.Slider(interactive=True), gr.Number(interactive=True), gr.Number(interactive=True)
|
279
283
|
|
280
284
|
select_db_directory_btn.click(
|
281
285
|
select_directory_and_update_tb,
|
@@ -284,6 +288,32 @@ def _build_extract_tab():
|
|
284
288
|
show_progress=False,
|
285
289
|
)
|
286
290
|
|
291
|
+
with gr.Accordion(loc.localize("embedding-file-output-accordion-label"), open=False):
|
292
|
+
with gr.Row():
|
293
|
+
select_file_output_directory_btn = gr.Button(loc.localize("embeddings-select-file-output-directory-button-label"))
|
294
|
+
|
295
|
+
with gr.Row():
|
296
|
+
file_output_tb = gr.Textbox(
|
297
|
+
value=None,
|
298
|
+
placeholder=loc.localize("embeddings-tab-file-output-directory-textbox-placeholder"),
|
299
|
+
interactive=True,
|
300
|
+
label=loc.localize("embeddings-tab-file-output-directory-textbox-label"),
|
301
|
+
)
|
302
|
+
|
303
|
+
def select_file_output_directory_and_update_tb():
|
304
|
+
dir_name = gu.select_directory(state_key="embeddings-file-output-dir", collect_files=False)
|
305
|
+
|
306
|
+
if dir_name:
|
307
|
+
return dir_name
|
308
|
+
|
309
|
+
return None
|
310
|
+
|
311
|
+
select_file_output_directory_btn.click(
|
312
|
+
select_file_output_directory_and_update_tb,
|
313
|
+
inputs=[],
|
314
|
+
outputs=[file_output_tb],
|
315
|
+
)
|
316
|
+
|
287
317
|
def check_settings(dir_name, db_name):
|
288
318
|
db_path = os.path.join(dir_name, db_name)
|
289
319
|
|
@@ -316,7 +346,7 @@ def _build_extract_tab():
|
|
316
346
|
start_btn = gr.Button(loc.localize("embeddings-tab-start-button-label"), variant="huggingface")
|
317
347
|
|
318
348
|
start_btn.click(
|
319
|
-
|
349
|
+
run_embeddings_with_tqdm_tracking,
|
320
350
|
inputs=[
|
321
351
|
input_directory_state,
|
322
352
|
db_directory_state,
|
@@ -327,10 +357,10 @@ def _build_extract_tab():
|
|
327
357
|
audio_speed_slider,
|
328
358
|
fmin_number,
|
329
359
|
fmax_number,
|
360
|
+
file_output_tb,
|
330
361
|
],
|
331
362
|
outputs=[progress_plot, audio_speed_slider, fmin_number, fmax_number],
|
332
363
|
show_progress_on=progress_plot,
|
333
|
-
show_progress=True,
|
334
364
|
)
|
335
365
|
|
336
366
|
|
@@ -409,17 +439,6 @@ def _build_search_tab():
|
|
409
439
|
value="cosine",
|
410
440
|
interactive=True,
|
411
441
|
)
|
412
|
-
max_samples_number = gr.Number(
|
413
|
-
label=loc.localize("embeddings-search-max-samples-number-label"),
|
414
|
-
value=10,
|
415
|
-
interactive=True,
|
416
|
-
)
|
417
|
-
score_fn_select = gr.Radio(
|
418
|
-
label=loc.localize("embeddings-search-score-fn-select-label"),
|
419
|
-
choices=["cosine", "dot", "euclidean"],
|
420
|
-
value="cosine",
|
421
|
-
interactive=True,
|
422
|
-
)
|
423
442
|
search_btn = gr.Button(loc.localize("embeddings-search-start-button-label"), variant="huggingface")
|
424
443
|
|
425
444
|
with gr.Column():
|
@@ -339,6 +339,7 @@ def build_evaluation_tab():
|
|
339
339
|
labels_state = gr.State()
|
340
340
|
annotation_files_state = gr.State()
|
341
341
|
prediction_files_state = gr.State()
|
342
|
+
plot_name_state = gr.State()
|
342
343
|
|
343
344
|
def get_selection_tables(directory):
|
344
345
|
from pathlib import Path
|
@@ -530,7 +531,10 @@ def build_evaluation_tab():
|
|
530
531
|
)
|
531
532
|
download_data_button.click(fn=download_data_table, inputs=[processor_state])
|
532
533
|
metric_table = gr.Dataframe(show_label=False, type="pandas", visible=False, interactive=False)
|
533
|
-
|
534
|
+
|
535
|
+
with gr.Group(visible=False) as plot_group:
|
536
|
+
plot_output = gr.Plot(show_label=False)
|
537
|
+
plot_output_dl_btn = gr.Button("Download plot", size="sm")
|
534
538
|
|
535
539
|
# Update available selections (classes and recordings) and the processor state when files or mapping file change.
|
536
540
|
# Also pass the current selection values so that user selections are preserved.
|
@@ -604,7 +608,7 @@ def build_evaluation_tab():
|
|
604
608
|
"average precision (ap)": "ap",
|
605
609
|
"auroc": "auroc",
|
606
610
|
}
|
607
|
-
metrics = tuple(
|
611
|
+
metrics = tuple(valid_metrics[m] for m in selected_metrics if m in valid_metrics)
|
608
612
|
|
609
613
|
# Fall back to available classes from processor state if none selected.
|
610
614
|
if not selected_classes_list and proc_state and proc_state.processor:
|
@@ -717,14 +721,14 @@ def build_evaluation_tab():
|
|
717
721
|
fig = pa.plot_metrics(predictions, labels, per_class_metrics=class_wise_value)
|
718
722
|
plt.close(fig)
|
719
723
|
|
720
|
-
return gr.update(visible=True, value=fig)
|
724
|
+
return gr.update(visible=True), gr.update(value=fig), "metrics"
|
721
725
|
except Exception as e:
|
722
726
|
raise gr.Error(f"{loc.localize('eval-tab-error-plotting-metrics')}: {e}") from e
|
723
727
|
|
724
728
|
plot_metrics_button.click(
|
725
729
|
plot_metrics,
|
726
730
|
inputs=[pa_state, predictions_state, labels_state, class_wise],
|
727
|
-
outputs=[plot_output],
|
731
|
+
outputs=[plot_group, plot_output, plot_name_state],
|
728
732
|
)
|
729
733
|
|
730
734
|
def plot_confusion_matrix(pa: PerformanceAssessor, predictions, labels):
|
@@ -734,14 +738,14 @@ def build_evaluation_tab():
|
|
734
738
|
fig = pa.plot_confusion_matrix(predictions, labels)
|
735
739
|
plt.close(fig)
|
736
740
|
|
737
|
-
return gr.update(visible=True,
|
741
|
+
return gr.update(visible=True), fig, "confusion_matrix"
|
738
742
|
except Exception as e:
|
739
743
|
raise gr.Error(f"{loc.localize('eval-tab-error-plotting-confusion-matrix')}: {e}") from e
|
740
744
|
|
741
745
|
plot_confusion_button.click(
|
742
746
|
plot_confusion_matrix,
|
743
747
|
inputs=[pa_state, predictions_state, labels_state],
|
744
|
-
outputs=[plot_output],
|
748
|
+
outputs=[plot_group, plot_output, plot_name_state],
|
745
749
|
)
|
746
750
|
|
747
751
|
annotation_select_directory_btn.click(
|
@@ -780,16 +784,18 @@ def build_evaluation_tab():
|
|
780
784
|
fig = pa.plot_metrics_all_thresholds(predictions, labels, per_class_metrics=class_wise_value)
|
781
785
|
plt.close(fig)
|
782
786
|
|
783
|
-
return gr.update(visible=True, value=fig)
|
787
|
+
return gr.update(visible=True), gr.update(value=fig), "metrics_all_thresholds"
|
784
788
|
except Exception as e:
|
785
789
|
raise gr.Error(f"{loc.localize('eval-tab-error-plotting-metrics-all-thresholds')}: {e}") from e
|
786
790
|
|
787
791
|
plot_metrics_all_thresholds_button.click(
|
788
792
|
plot_metrics_all_thresholds,
|
789
793
|
inputs=[pa_state, predictions_state, labels_state, class_wise],
|
790
|
-
outputs=[plot_output],
|
794
|
+
outputs=[plot_group, plot_output, plot_name_state],
|
791
795
|
)
|
792
796
|
|
797
|
+
plot_output_dl_btn.click(gu.download_plot, inputs=[plot_output, plot_name_state])
|
798
|
+
|
793
799
|
|
794
800
|
if __name__ == "__main__":
|
795
801
|
gu.open_window(build_evaluation_tab)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# ruff: noqa: I001
|
2
2
|
import gradio as gr
|
3
3
|
|
4
4
|
import birdnet_analyzer.config as cfg
|
@@ -11,7 +11,16 @@ OUTPUT_TYPE_MAP = {
|
|
11
11
|
"CSV": "csv",
|
12
12
|
"Kaleidoscope": "kaleidoscope",
|
13
13
|
}
|
14
|
-
|
14
|
+
ADDITIONAL_COLUMNS_MAP = {
|
15
|
+
"Latitude": "lat",
|
16
|
+
"Longitude": "lon",
|
17
|
+
"Week": "week",
|
18
|
+
"Overlap": "overlap",
|
19
|
+
"Sensitivity": "sensitivity",
|
20
|
+
"Minimum confidence": "min_conf",
|
21
|
+
"Species list file": "species_list",
|
22
|
+
"Model file": "model",
|
23
|
+
}
|
15
24
|
|
16
25
|
@gu.gui_runtime_error_handler
|
17
26
|
def run_batch_analysis(
|
@@ -34,6 +43,7 @@ def run_batch_analysis(
|
|
34
43
|
sf_thresh,
|
35
44
|
custom_classifier_file,
|
36
45
|
output_type,
|
46
|
+
additional_columns,
|
37
47
|
combine_tables,
|
38
48
|
locale,
|
39
49
|
batch_size,
|
@@ -75,6 +85,7 @@ def run_batch_analysis(
|
|
75
85
|
sf_thresh,
|
76
86
|
custom_classifier_file,
|
77
87
|
output_type,
|
88
|
+
additional_columns,
|
78
89
|
combine_tables,
|
79
90
|
locale if locale else "en",
|
80
91
|
batch_size if batch_size and batch_size > 0 else 1,
|
@@ -113,9 +124,7 @@ def build_multi_analysis_tab():
|
|
113
124
|
|
114
125
|
return ["", [[loc.localize("multi-tab-samples-dataframe-no-files-found")]]]
|
115
126
|
|
116
|
-
select_directory_btn.click(
|
117
|
-
select_directory_on_empty, outputs=[input_directory_state, directory_input], show_progress=True
|
118
|
-
)
|
127
|
+
select_directory_btn.click(select_directory_on_empty, outputs=[input_directory_state, directory_input], show_progress=True)
|
119
128
|
|
120
129
|
with gr.Column():
|
121
130
|
select_out_directory_btn = gr.Button(loc.localize("multi-tab-output-selection-button-label"))
|
@@ -166,6 +175,12 @@ def build_multi_analysis_tab():
|
|
166
175
|
label=loc.localize("multi-tab-output-radio-label"),
|
167
176
|
info=loc.localize("multi-tab-output-radio-info"),
|
168
177
|
)
|
178
|
+
additional_columns_ = gr.CheckboxGroup(
|
179
|
+
list(ADDITIONAL_COLUMNS_MAP.items()),
|
180
|
+
visible=False,
|
181
|
+
label=loc.localize("multi-tab-additional-columns-checkbox-label"),
|
182
|
+
info=loc.localize("multi-tab-additional-columns-checkbox-info"),
|
183
|
+
)
|
169
184
|
|
170
185
|
with gr.Row():
|
171
186
|
combine_tables_checkbox = gr.Checkbox(
|
@@ -228,6 +243,7 @@ def build_multi_analysis_tab():
|
|
228
243
|
sf_thresh_number,
|
229
244
|
selected_classifier_state,
|
230
245
|
output_type_radio,
|
246
|
+
additional_columns_,
|
231
247
|
combine_tables_checkbox,
|
232
248
|
locale_radio,
|
233
249
|
batch_size_number,
|
@@ -236,7 +252,11 @@ def build_multi_analysis_tab():
|
|
236
252
|
skip_existing_checkbox,
|
237
253
|
]
|
238
254
|
|
255
|
+
def show_additional_columns(values):
|
256
|
+
return gr.update(visible="csv" in values)
|
257
|
+
|
239
258
|
start_batch_analysis_btn.click(run_batch_analysis, inputs=inputs, outputs=result_grid)
|
259
|
+
output_type_radio.change(show_additional_columns, inputs=output_type_radio, outputs=additional_columns_)
|
240
260
|
|
241
261
|
return lat_number, lon_number, map_plot
|
242
262
|
|
birdnet_analyzer/gui/review.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
import base64
|
2
|
-
import io
|
3
1
|
import os
|
4
2
|
import random
|
5
3
|
from functools import partial
|
@@ -13,6 +11,7 @@ from birdnet_analyzer import utils
|
|
13
11
|
|
14
12
|
POSITIVE_LABEL_DIR = "Positive"
|
15
13
|
NEGATIVE_LABEL_DIR = "Negative"
|
14
|
+
MATPLOTLIB_FIGURE_ID = "review-tab-spectrogram-plot"
|
16
15
|
|
17
16
|
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
|
18
17
|
|
@@ -23,11 +22,7 @@ def build_review_tab():
|
|
23
22
|
[
|
24
23
|
entry.path
|
25
24
|
for entry in os.scandir(directory)
|
26
|
-
if (
|
27
|
-
entry.is_file()
|
28
|
-
and not entry.name.startswith(".")
|
29
|
-
and entry.name.rsplit(".", 1)[-1] in cfg.ALLOWED_FILETYPES
|
30
|
-
)
|
25
|
+
if (entry.is_file() and not entry.name.startswith(".") and entry.name.rsplit(".", 1)[-1] in cfg.ALLOWED_FILETYPES)
|
31
26
|
]
|
32
27
|
if os.path.isdir(directory)
|
33
28
|
else []
|
@@ -55,16 +50,14 @@ def build_review_tab():
|
|
55
50
|
matplotlib.use("agg")
|
56
51
|
|
57
52
|
f = plt.figure(fig_num, figsize=(12, 6))
|
58
|
-
f.
|
53
|
+
f.clear()
|
59
54
|
f.tight_layout(pad=0)
|
60
55
|
f.set_dpi(300)
|
61
56
|
|
62
57
|
ax = f.add_subplot(111)
|
63
58
|
ax.set_xlim(0, 1)
|
64
59
|
ax.set_yticks([0, 1])
|
65
|
-
ax.set_ylabel(
|
66
|
-
f"{loc.localize('review-tab-regression-plot-y-label-false')}/{loc.localize('review-tab-regression-plot-y-label-true')}"
|
67
|
-
)
|
60
|
+
ax.set_ylabel(f"{loc.localize('review-tab-regression-plot-y-label-false')}/{loc.localize('review-tab-regression-plot-y-label-true')}")
|
68
61
|
ax.set_xlabel(loc.localize("review-tab-regression-plot-x-label"))
|
69
62
|
|
70
63
|
x_vals = []
|
@@ -88,10 +81,7 @@ def build_review_tab():
|
|
88
81
|
Xs = np.linspace(0, 10, 200)
|
89
82
|
Ys = expit(Xs * log_model.coef_ + log_model.intercept_).ravel()
|
90
83
|
target_ps = [0.85, 0.9, 0.95, 0.99]
|
91
|
-
thresholds = [
|
92
|
-
(np.log(target_p / (1 - target_p)) - log_model.intercept_[0]) / log_model.coef_[0][0]
|
93
|
-
for target_p in target_ps
|
94
|
-
]
|
84
|
+
thresholds = [(np.log(target_p / (1 - target_p)) - log_model.intercept_[0]) / log_model.coef_[0][0] for target_p in target_ps]
|
95
85
|
p_colors = ["blue", "purple", "orange", "green"]
|
96
86
|
|
97
87
|
for target_p, p_color, threshold in zip(target_ps, p_colors, thresholds, strict=True):
|
@@ -152,9 +142,7 @@ def build_review_tab():
|
|
152
142
|
|
153
143
|
with gr.Column() as review_item_col, gr.Row():
|
154
144
|
with gr.Column(), gr.Group():
|
155
|
-
spectrogram_image = gr.Plot(
|
156
|
-
label=loc.localize("review-tab-spectrogram-plot-label"), show_label=False
|
157
|
-
)
|
145
|
+
spectrogram_image = gr.Plot(label=loc.localize("review-tab-spectrogram-plot-label"), show_label=False)
|
158
146
|
spectrogram_dl_btn = gr.Button("Download spectrogram", size="sm")
|
159
147
|
|
160
148
|
with gr.Column():
|
@@ -197,16 +185,14 @@ def build_review_tab():
|
|
197
185
|
|
198
186
|
if not skip_plot:
|
199
187
|
update_dict |= {
|
200
|
-
species_regression_plot: create_log_plot(
|
201
|
-
next_review_state[POSITIVE_LABEL_DIR], next_review_state[NEGATIVE_LABEL_DIR], 2
|
202
|
-
),
|
188
|
+
species_regression_plot: create_log_plot(next_review_state[POSITIVE_LABEL_DIR], next_review_state[NEGATIVE_LABEL_DIR], 2),
|
203
189
|
}
|
204
190
|
|
205
191
|
if next_review_state["files"]:
|
206
192
|
next_file = next_review_state["files"][0]
|
207
193
|
update_dict |= {
|
208
194
|
review_audio: gr.Audio(next_file, label=os.path.basename(next_file)),
|
209
|
-
spectrogram_image: utils.spectrogram_from_file(next_file, fig_size=(8, 4)),
|
195
|
+
spectrogram_image: utils.spectrogram_from_file(next_file, fig_num=MATPLOTLIB_FIGURE_ID, fig_size=(8, 4)),
|
210
196
|
}
|
211
197
|
|
212
198
|
update_dict |= {
|
@@ -224,9 +210,7 @@ def build_review_tab():
|
|
224
210
|
no_samles_label: gr.Label(visible=not bool(next_review_state["files"])),
|
225
211
|
review_item_col: gr.Column(visible=bool(next_review_state["files"])),
|
226
212
|
regression_dl_btn: gr.Button(
|
227
|
-
visible=update_dict[species_regression_plot].constructor_args["visible"]
|
228
|
-
if species_regression_plot in update_dict
|
229
|
-
else False
|
213
|
+
visible=update_dict[species_regression_plot].constructor_args["visible"] if species_regression_plot in update_dict else False
|
230
214
|
),
|
231
215
|
}
|
232
216
|
|
@@ -278,9 +262,7 @@ def build_review_tab():
|
|
278
262
|
if dir_name:
|
279
263
|
next_review_state["input_directory"] = dir_name
|
280
264
|
specieslist = [
|
281
|
-
e.name
|
282
|
-
for e in os.scandir(next_review_state["input_directory"])
|
283
|
-
if e.is_dir() and e.name not in (POSITIVE_LABEL_DIR, NEGATIVE_LABEL_DIR)
|
265
|
+
e.name for e in os.scandir(next_review_state["input_directory"]) if e.is_dir() and e.name not in (POSITIVE_LABEL_DIR, NEGATIVE_LABEL_DIR)
|
284
266
|
]
|
285
267
|
|
286
268
|
next_review_state["species_list"] = specieslist
|
@@ -307,9 +289,7 @@ def build_review_tab():
|
|
307
289
|
if selected_species:
|
308
290
|
next_review_state["current_species"] = selected_species
|
309
291
|
else:
|
310
|
-
next_review_state["current_species"] =
|
311
|
-
next_review_state["species_list"][0] if next_review_state["species_list"] else None
|
312
|
-
)
|
292
|
+
next_review_state["current_species"] = next_review_state["species_list"][0] if next_review_state["species_list"] else None
|
313
293
|
|
314
294
|
todo_files, positives, negatives = collect_files(
|
315
295
|
os.path.join(next_review_state["input_directory"], next_review_state["current_species"])
|
@@ -339,9 +319,7 @@ def build_review_tab():
|
|
339
319
|
len(next_review_state[NEGATIVE_LABEL_DIR]),
|
340
320
|
],
|
341
321
|
],
|
342
|
-
species_regression_plot: create_log_plot(
|
343
|
-
next_review_state[POSITIVE_LABEL_DIR], next_review_state[NEGATIVE_LABEL_DIR], 2
|
344
|
-
),
|
322
|
+
species_regression_plot: create_log_plot(next_review_state[POSITIVE_LABEL_DIR], next_review_state[NEGATIVE_LABEL_DIR], 2),
|
345
323
|
}
|
346
324
|
|
347
325
|
if not selected_species:
|
@@ -360,15 +338,13 @@ def build_review_tab():
|
|
360
338
|
update_dict |= {
|
361
339
|
review_item_col: gr.Column(visible=True),
|
362
340
|
review_audio: gr.Audio(value=todo_files[0], label=os.path.basename(todo_files[0])),
|
363
|
-
spectrogram_image: utils.spectrogram_from_file(todo_files[0], fig_size=(8, 4)),
|
341
|
+
spectrogram_image: utils.spectrogram_from_file(todo_files[0], fig_num=MATPLOTLIB_FIGURE_ID, fig_size=(8, 4)),
|
364
342
|
no_samles_label: gr.Label(visible=False),
|
365
343
|
}
|
366
344
|
else:
|
367
345
|
update_dict |= {review_item_col: gr.Column(visible=False), no_samles_label: gr.Label(visible=True)}
|
368
346
|
|
369
|
-
update_dict[regression_dl_btn] = gr.Button(
|
370
|
-
visible=update_dict[species_regression_plot].constructor_args["visible"]
|
371
|
-
)
|
347
|
+
update_dict[regression_dl_btn] = gr.Button(visible=update_dict[species_regression_plot].constructor_args["visible"])
|
372
348
|
|
373
349
|
return update_dict
|
374
350
|
|
@@ -408,25 +384,6 @@ def build_review_tab():
|
|
408
384
|
def toggle_autoplay(value):
|
409
385
|
return gr.Audio(autoplay=value)
|
410
386
|
|
411
|
-
def download_plot(plot, filename=""):
|
412
|
-
from PIL import Image
|
413
|
-
|
414
|
-
imgdata = base64.b64decode(plot.plot.split(",", 1)[1])
|
415
|
-
res = gu._WINDOW.create_file_dialog(
|
416
|
-
gu.webview.SAVE_DIALOG,
|
417
|
-
file_types=("PNG (*.png)", "Webp (*.webp)", "JPG (*.jpg)"),
|
418
|
-
save_filename=filename,
|
419
|
-
)
|
420
|
-
|
421
|
-
if res:
|
422
|
-
if res.endswith(".webp"):
|
423
|
-
with open(res, "wb") as f:
|
424
|
-
f.write(imgdata)
|
425
|
-
else:
|
426
|
-
output_format = res.rsplit(".", 1)[-1].upper()
|
427
|
-
img = Image.open(io.BytesIO(imgdata))
|
428
|
-
img.save(res, output_format if output_format in ["PNG", "JPEG"] else "PNG")
|
429
|
-
|
430
387
|
autoplay_checkbox.change(toggle_autoplay, inputs=autoplay_checkbox, outputs=review_audio)
|
431
388
|
|
432
389
|
review_change_output = [
|
@@ -446,12 +403,8 @@ def build_review_tab():
|
|
446
403
|
regression_dl_btn,
|
447
404
|
]
|
448
405
|
|
449
|
-
spectrogram_dl_btn.click(
|
450
|
-
|
451
|
-
)
|
452
|
-
regression_dl_btn.click(
|
453
|
-
partial(download_plot, filename="regression"), show_progress=False, inputs=species_regression_plot
|
454
|
-
)
|
406
|
+
spectrogram_dl_btn.click(partial(gu.download_plot, filename="spectrogram"), show_progress=False, inputs=spectrogram_image)
|
407
|
+
regression_dl_btn.click(partial(gu.download_plot, filename="regression"), show_progress=False, inputs=species_regression_plot)
|
455
408
|
|
456
409
|
species_dropdown.change(
|
457
410
|
select_subdir,
|
birdnet_analyzer/gui/settings.py
CHANGED
@@ -1,15 +1,36 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
|
+
import sys
|
3
4
|
from pathlib import Path
|
4
5
|
|
5
|
-
import birdnet_analyzer.
|
6
|
+
import birdnet_analyzer.config as cfg
|
6
7
|
from birdnet_analyzer import utils
|
7
8
|
|
9
|
+
if utils.FROZEN:
|
10
|
+
# divert stdout & stderr to logs.txt file since we have no console when deployed
|
11
|
+
userdir = Path.home()
|
12
|
+
|
13
|
+
if sys.platform == "win32":
|
14
|
+
userdir /= "AppData/Roaming"
|
15
|
+
elif sys.platform == "linux":
|
16
|
+
userdir /= ".local/share"
|
17
|
+
elif sys.platform == "darwin":
|
18
|
+
userdir /= "Library/Application Support"
|
19
|
+
|
20
|
+
APPDIR = userdir / "BirdNET-Analyzer-GUI"
|
21
|
+
|
22
|
+
APPDIR.mkdir(parents=True, exist_ok=True)
|
23
|
+
|
24
|
+
sys.stderr = sys.stdout = open(str(APPDIR / "logs.txt"), "a") # noqa: SIM115
|
25
|
+
cfg.ERROR_LOG_FILE = str(APPDIR / os.path.basename(cfg.ERROR_LOG_FILE))
|
26
|
+
else:
|
27
|
+
APPDIR = ""
|
28
|
+
|
8
29
|
FALLBACK_LANGUAGE = "en"
|
9
30
|
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
|
10
|
-
GUI_SETTINGS_PATH = os.path.join(
|
31
|
+
GUI_SETTINGS_PATH = os.path.join(APPDIR if utils.FROZEN else os.path.dirname(SCRIPT_DIR), "gui-settings.json")
|
11
32
|
LANG_DIR = str(Path(SCRIPT_DIR).parent / "lang")
|
12
|
-
STATE_SETTINGS_PATH = os.path.join(
|
33
|
+
STATE_SETTINGS_PATH = os.path.join(APPDIR if utils.FROZEN else os.path.dirname(SCRIPT_DIR), "state.json")
|
13
34
|
|
14
35
|
|
15
36
|
def get_state_dict() -> dict:
|
@@ -35,7 +56,7 @@ def get_state_dict() -> dict:
|
|
35
56
|
return {}
|
36
57
|
|
37
58
|
|
38
|
-
def get_state(key: str, default=None)
|
59
|
+
def get_state(key: str, default=None):
|
39
60
|
"""
|
40
61
|
Retrieves the value associated with the given key from the state dictionary.
|
41
62
|
|
@@ -7,6 +7,8 @@ import birdnet_analyzer.gui.localization as loc
|
|
7
7
|
import birdnet_analyzer.gui.utils as gu
|
8
8
|
from birdnet_analyzer import audio, utils
|
9
9
|
|
10
|
+
MATPLOTLIB_FIGURE_NUM = "single-file-tab-spectrogram-plot"
|
11
|
+
|
10
12
|
|
11
13
|
@gu.gui_runtime_error_handler
|
12
14
|
def run_single_file_analysis(
|
@@ -65,6 +67,7 @@ def run_single_file_analysis(
|
|
65
67
|
custom_classifier_file,
|
66
68
|
"csv",
|
67
69
|
None,
|
70
|
+
False,
|
68
71
|
locale if locale else "en",
|
69
72
|
1,
|
70
73
|
4,
|
@@ -102,9 +105,7 @@ def build_single_analysis_tab():
|
|
102
105
|
audio_input = gr.Audio(type="filepath", label=loc.localize("single-audio-label"), sources=["upload"])
|
103
106
|
|
104
107
|
with gr.Group():
|
105
|
-
spectogram_output = gr.Plot(
|
106
|
-
label=loc.localize("review-tab-spectrogram-plot-label"), visible=False, show_label=False
|
107
|
-
)
|
108
|
+
spectogram_output = gr.Plot(label=loc.localize("review-tab-spectrogram-plot-label"), visible=False, show_label=False)
|
108
109
|
generate_spectrogram_cb = gr.Checkbox(
|
109
110
|
value=True,
|
110
111
|
label=loc.localize("single-tab-spectrogram-checkbox-label"),
|
@@ -138,17 +139,13 @@ def build_single_analysis_tab():
|
|
138
139
|
) = gu.species_lists(False)
|
139
140
|
locale_radio = gu.locale()
|
140
141
|
|
141
|
-
single_file_analyze = gr.Button(
|
142
|
-
loc.localize("analyze-start-button-label"), variant="huggingface", interactive=False
|
143
|
-
)
|
142
|
+
single_file_analyze = gr.Button(loc.localize("analyze-start-button-label"), variant="huggingface", interactive=False)
|
144
143
|
|
145
144
|
with gr.Row(visible=False) as action_row:
|
146
145
|
table_download_button = gr.Button(
|
147
146
|
loc.localize("single-tab-download-button-label"),
|
148
147
|
)
|
149
|
-
segment_audio = gr.Audio(
|
150
|
-
autoplay=True, type="numpy", show_download_button=True, show_label=False, editable=False, visible=False
|
151
|
-
)
|
148
|
+
segment_audio = gr.Audio(autoplay=True, type="numpy", show_download_button=True, show_label=False, editable=False, visible=False)
|
152
149
|
|
153
150
|
output_dataframe = gr.Dataframe(
|
154
151
|
type="pandas",
|
@@ -170,7 +167,7 @@ def build_single_analysis_tab():
|
|
170
167
|
return (
|
171
168
|
i["path"],
|
172
169
|
gr.Audio(label=os.path.basename(i["path"])),
|
173
|
-
gr.Plot(visible=True, value=utils.spectrogram_from_file(i["path"], fig_size=(20, 4)))
|
170
|
+
gr.Plot(visible=True, value=utils.spectrogram_from_file(i["path"], fig_size=(20, 4), fig_num=MATPLOTLIB_FIGURE_NUM))
|
174
171
|
if generate_spectrogram
|
175
172
|
else gr.Plot(visible=False),
|
176
173
|
gr.Button(interactive=True),
|
@@ -183,9 +180,7 @@ def build_single_analysis_tab():
|
|
183
180
|
def try_generate_spectrogram(audio_path, generate_spectrogram):
|
184
181
|
if audio_path and generate_spectrogram:
|
185
182
|
try:
|
186
|
-
return gr.Plot(
|
187
|
-
visible=True, value=utils.spectrogram_from_file(audio_path["path"], fig_size=(20, 4))
|
188
|
-
)
|
183
|
+
return gr.Plot(visible=True, value=utils.spectrogram_from_file(audio_path["path"], fig_size=(20, 4), fig_num=MATPLOTLIB_FIGURE_NUM))
|
189
184
|
except Exception as e:
|
190
185
|
raise gr.Error(loc.localize("single-tab-generate-spectrogram-error")) from e
|
191
186
|
else:
|
@@ -248,16 +243,18 @@ def build_single_analysis_tab():
|
|
248
243
|
def download_table(filepath):
|
249
244
|
if filepath:
|
250
245
|
ext = os.path.splitext(filepath)[1]
|
251
|
-
gu.save_file_dialog(
|
246
|
+
file_location = gu.save_file_dialog(
|
252
247
|
state_key="single-file-table",
|
253
248
|
default_filename=os.path.basename(filepath),
|
254
249
|
filetypes=(f"{ext[1:]} (*{ext})",),
|
255
250
|
)
|
256
251
|
|
252
|
+
if file_location:
|
253
|
+
with open(filepath, "rb") as src, open(file_location, "wb") as dst:
|
254
|
+
dst.write(src.read())
|
255
|
+
|
257
256
|
output_dataframe.select(get_selected_audio, inputs=audio_path_state, outputs=segment_audio)
|
258
|
-
single_file_analyze.click(
|
259
|
-
run_single_file_analysis, inputs=inputs, outputs=[output_dataframe, action_row, table_path_state]
|
260
|
-
)
|
257
|
+
single_file_analyze.click(run_single_file_analysis, inputs=inputs, outputs=[output_dataframe, action_row, table_path_state])
|
261
258
|
table_download_button.click(download_table, inputs=table_path_state)
|
262
259
|
|
263
260
|
return lat_number, lon_number, map_plot
|