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.
Files changed (48) hide show
  1. birdnet_analyzer/analyze/__init__.py +14 -0
  2. birdnet_analyzer/analyze/cli.py +5 -0
  3. birdnet_analyzer/analyze/core.py +6 -1
  4. birdnet_analyzer/analyze/utils.py +42 -40
  5. birdnet_analyzer/audio.py +2 -2
  6. birdnet_analyzer/cli.py +41 -18
  7. birdnet_analyzer/config.py +4 -3
  8. birdnet_analyzer/eBird_taxonomy_codes_2024E.json +13046 -0
  9. birdnet_analyzer/embeddings/core.py +2 -1
  10. birdnet_analyzer/embeddings/utils.py +42 -1
  11. birdnet_analyzer/evaluation/__init__.py +6 -13
  12. birdnet_analyzer/evaluation/assessment/performance_assessor.py +12 -57
  13. birdnet_analyzer/evaluation/assessment/plotting.py +61 -62
  14. birdnet_analyzer/evaluation/preprocessing/data_processor.py +1 -1
  15. birdnet_analyzer/gui/analysis.py +5 -1
  16. birdnet_analyzer/gui/assets/gui.css +8 -0
  17. birdnet_analyzer/gui/embeddings.py +37 -18
  18. birdnet_analyzer/gui/evaluation.py +14 -8
  19. birdnet_analyzer/gui/multi_file.py +25 -5
  20. birdnet_analyzer/gui/review.py +16 -63
  21. birdnet_analyzer/gui/settings.py +25 -4
  22. birdnet_analyzer/gui/single_file.py +14 -17
  23. birdnet_analyzer/gui/train.py +7 -16
  24. birdnet_analyzer/gui/utils.py +42 -55
  25. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ca.txt +1 -1
  26. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_pl.txt +1 -1
  27. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sr.txt +108 -108
  28. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_zh.txt +1 -1
  29. birdnet_analyzer/lang/de.json +7 -0
  30. birdnet_analyzer/lang/en.json +7 -0
  31. birdnet_analyzer/lang/fi.json +7 -0
  32. birdnet_analyzer/lang/fr.json +7 -0
  33. birdnet_analyzer/lang/id.json +7 -0
  34. birdnet_analyzer/lang/pt-br.json +7 -0
  35. birdnet_analyzer/lang/ru.json +36 -29
  36. birdnet_analyzer/lang/se.json +7 -0
  37. birdnet_analyzer/lang/tlh.json +7 -0
  38. birdnet_analyzer/lang/zh_TW.json +7 -0
  39. birdnet_analyzer/model.py +21 -21
  40. birdnet_analyzer/search/core.py +1 -1
  41. birdnet_analyzer/utils.py +3 -4
  42. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/METADATA +18 -9
  43. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/RECORD +47 -47
  44. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/WHEEL +1 -1
  45. birdnet_analyzer/eBird_taxonomy_codes_2021E.json +0 -25280
  46. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/entry_points.txt +0 -0
  47. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/licenses/LICENSE +0 -0
  48. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.0.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ import birdnet_analyzer.config as cfg
9
9
  import birdnet_analyzer.gui.localization as loc
10
10
  import birdnet_analyzer.gui.utils as gu
11
11
  from birdnet_analyzer import utils
12
+ from birdnet_analyzer.gui.settings import APPDIR
12
13
 
13
14
  _GRID_MAX_HEIGHT = 240
14
15
 
@@ -198,16 +199,14 @@ def start_training(
198
199
 
199
200
  def trial_progression(trial):
200
201
  if progress is not None:
201
- progress(
202
- (trial, autotune_trials), total=autotune_trials, unit="trials", desc=loc.localize("progress-autotune")
203
- )
202
+ progress((trial, autotune_trials), total=autotune_trials, unit="trials", desc=loc.localize("progress-autotune"))
204
203
 
205
204
  try:
206
205
  history_result = train_model(
207
206
  on_epoch_end=epoch_progression,
208
207
  on_trial_result=trial_progression,
209
208
  on_data_load_end=data_load_progression,
210
- autotune_directory=gu.APPDIR if utils.FROZEN else "autotune",
209
+ autotune_directory=APPDIR if utils.FROZEN else "autotune",
211
210
  )
212
211
 
213
212
  # Unpack history and metrics
@@ -315,9 +314,7 @@ def build_train_tab():
315
314
  info=loc.localize("training-tab-cache-mode-radio-info"),
316
315
  )
317
316
  with gr.Column(visible=False) as new_cache_file_row:
318
- select_cache_file_directory_btn = gr.Button(
319
- loc.localize("training-tab-cache-select-directory-button-label")
320
- )
317
+ select_cache_file_directory_btn = gr.Button(loc.localize("training-tab-cache-select-directory-button-label"))
321
318
 
322
319
  with gr.Column():
323
320
  cache_file_name = gr.Textbox(
@@ -426,9 +423,7 @@ def build_train_tab():
426
423
 
427
424
  def on_crop_select(new_crop_mode):
428
425
  # Make overlap slider visible for both "segments" and "smart" crop modes
429
- return gr.Number(
430
- visible=new_crop_mode in ["segments", "smart"], interactive=new_crop_mode in ["segments", "smart"]
431
- )
426
+ return gr.Number(visible=new_crop_mode in ["segments", "smart"], interactive=new_crop_mode in ["segments", "smart"])
432
427
 
433
428
  crop_mode.change(on_crop_select, inputs=crop_mode, outputs=crop_overlap)
434
429
 
@@ -576,9 +571,7 @@ def build_train_tab():
576
571
  def on_focal_loss_change(value):
577
572
  return gr.Row(visible=value)
578
573
 
579
- use_focal_loss.change(
580
- on_focal_loss_change, inputs=use_focal_loss, outputs=focal_loss_params, show_progress=False
581
- )
574
+ use_focal_loss.change(on_focal_loss_change, inputs=use_focal_loss, outputs=focal_loss_params, show_progress=False)
582
575
 
583
576
  def on_autotune_change(value):
584
577
  return (
@@ -610,9 +603,7 @@ def build_train_tab():
610
603
  visible=False,
611
604
  label="Model Performance Metrics (Default Threshold 0.5)",
612
605
  )
613
- start_training_button = gr.Button(
614
- loc.localize("training-tab-start-training-button-label"), variant="huggingface"
615
- )
606
+ start_training_button = gr.Button(loc.localize("training-tab-start-training-button-label"), variant="huggingface")
616
607
 
617
608
  def train_and_show_metrics(*args):
618
609
  history, metrics = start_training(*args)
@@ -1,4 +1,6 @@
1
1
  # ruff: noqa: PLW0603
2
+ import base64
3
+ import io
2
4
  import multiprocessing
3
5
  import os
4
6
  import sys
@@ -10,27 +12,8 @@ import gradio as gr
10
12
  import webview
11
13
 
12
14
  import birdnet_analyzer.config as cfg
13
- from birdnet_analyzer import utils
14
-
15
- if utils.FROZEN:
16
- # divert stdout & stderr to logs.txt file since we have no console when deployed
17
- userdir = Path.home()
18
-
19
- if sys.platform == "win32":
20
- userdir /= "AppData/Roaming"
21
- elif sys.platform == "linux":
22
- userdir /= ".local/share"
23
- elif sys.platform == "darwin":
24
- userdir /= "Library/Application Support"
25
-
26
- APPDIR = userdir / "BirdNET-Analyzer-GUI"
27
-
28
- APPDIR.mkdir(parents=True, exist_ok=True)
29
-
30
- sys.stderr = sys.stdout = open(str(APPDIR / "logs.txt"), "a") # noqa: SIM115
31
- cfg.ERROR_LOG_FILE = str(APPDIR / os.path.basename(cfg.ERROR_LOG_FILE))
32
-
33
15
  import birdnet_analyzer.gui.localization as loc
16
+ from birdnet_analyzer import utils
34
17
  from birdnet_analyzer.gui import settings
35
18
 
36
19
  loc.load_local_state()
@@ -41,11 +24,12 @@ _CUSTOM_SPECIES = loc.localize("species-list-radio-option-custom-list")
41
24
  _PREDICT_SPECIES = loc.localize("species-list-radio-option-predict-list")
42
25
  _CUSTOM_CLASSIFIER = loc.localize("species-list-radio-option-custom-classifier")
43
26
  _ALL_SPECIES = loc.localize("species-list-radio-option-all")
44
- _WINDOW: webview.Window = None
27
+ _WINDOW: webview.Window | None = None
45
28
  _URL = ""
29
+ _HEART_LOGO = "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBkYXRhLXZpZXctY29tcG9uZW50PSJ0cnVlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICAgIDxwYXRoIGQ9Im04IDE0LjI1LjM0NS42NjZhLjc1Ljc1IDAgMCAxLS42OSAwbC0uMDA4LS4wMDQtLjAxOC0uMDFhNy4xNTIgNy4xNTIgMCAwIDEtLjMxLS4xNyAyMi4wNTUgMjIuMDU1IDAgMCAxLTMuNDM0LTIuNDE0QzIuMDQ1IDEwLjczMSAwIDguMzUgMCA1LjUgMCAyLjgzNiAyLjA4NiAxIDQuMjUgMSA1Ljc5NyAxIDcuMTUzIDEuODAyIDggMy4wMiA4Ljg0NyAxLjgwMiAxMC4yMDMgMSAxMS43NSAxIDEzLjkxNCAxIDE2IDIuODM2IDE2IDUuNWMwIDIuODUtMi4wNDUgNS4yMzEtMy44ODUgNi44MThhMjIuMDY2IDIyLjA2NiAwIDAgMS0zLjc0NCAyLjU4NGwtLjAxOC4wMS0uMDA2LjAwM2gtLjAwMlpNNC4yNSAyLjVjLTEuMzM2IDAtMi43NSAxLjE2NC0yLjc1IDMgMCAyLjE1IDEuNTggNC4xNDQgMy4zNjUgNS42ODJBMjAuNTggMjAuNTggMCAwIDAgOCAxMy4zOTNhMjAuNTggMjAuNTggMCAwIDAgMy4xMzUtMi4yMTFDMTIuOTIgOS42NDQgMTQuNSA3LjY1IDE0LjUgNS41YzAtMS44MzYtMS40MTQtMy0yLjc1LTMtMS4zNzMgMC0yLjYwOS45ODYtMy4wMjkgMi40NTZhLjc0OS43NDkgMCAwIDEtMS40NDIgMEM2Ljg1OSAzLjQ4NiA1LjYyMyAyLjUgNC4yNSAyLjVaIj48L3BhdGg+DQo8L3N2Zz4=" # noqa: E501
46
30
 
47
31
 
48
- def gui_runtime_error_handler(f: callable):
32
+ def gui_runtime_error_handler(f):
49
33
  """
50
34
  A decorator function to handle errors during the execution of a callable.
51
35
 
@@ -196,9 +180,7 @@ def select_directory(collect_files=True, max_files=None, state_key=None):
196
180
 
197
181
  files = utils.collect_audio_files(dir_name, max_files=max_files)
198
182
 
199
- return dir_name, [
200
- [os.path.relpath(file, dir_name), format_seconds(librosa.get_duration(filename=file))] for file in files
201
- ]
183
+ return dir_name, [[os.path.relpath(file, dir_name), format_seconds(librosa.get_duration(filename=file))] for file in files]
202
184
 
203
185
  return dir_name if dir_name else None
204
186
 
@@ -228,9 +210,12 @@ def build_footer():
228
210
  <div>Model version: {cfg.MODEL_VERSION}</div>
229
211
  </div>
230
212
  <div>K. Lisa Yang Center for Conservation Bioacoustics<br>Chemnitz University of Technology</div>
231
- <div>{loc.localize("footer-help")}:<br><a href='https://birdnet.cornell.edu/analyzer'
232
- target='_blank'>birdnet.cornell.edu/analyzer</a></div>
233
- </div>"""
213
+ <div>{loc.localize("footer-help")}:&nbsp;<a href='https://birdnet.cornell.edu/analyzer'
214
+ target='_blank'>birdnet.cornell.edu/analyzer</a>
215
+ <br><img id='heart' src='{_HEART_LOGO}'>{loc.localize("footer-support")}: <a href='https://birdnet.cornell.edu/donate' target='_blank'>birdnet.cornell.edu/donate</a>
216
+ </div>
217
+
218
+ </div>""" # noqa: E501
234
219
  )
235
220
 
236
221
 
@@ -417,9 +402,7 @@ def locale():
417
402
  The dropdown element.
418
403
  """
419
404
  label_files = os.listdir(ORIGINAL_TRANSLATED_LABELS_PATH)
420
- options = ["EN"] + [
421
- label_file.split("BirdNET_GLOBAL_6K_V2.4_Labels_", 1)[1].split(".txt")[0].upper() for label_file in label_files
422
- ]
405
+ options = ["EN"] + [label_file.split("BirdNET_GLOBAL_6K_V2.4_Labels_", 1)[1].split(".txt")[0].upper() for label_file in label_files]
423
406
 
424
407
  return gr.Dropdown(
425
408
  options,
@@ -460,18 +443,12 @@ def species_list_coordinates(show_map=False):
460
443
 
461
444
  map_plot = gr.Plot(plot_map_scatter_mapbox(0, 0), show_label=False, scale=2, visible=show_map)
462
445
 
463
- lat_number.change(
464
- plot_map_scatter_mapbox, inputs=[lat_number, lon_number], outputs=map_plot, show_progress=False
465
- )
466
- lon_number.change(
467
- plot_map_scatter_mapbox, inputs=[lat_number, lon_number], outputs=map_plot, show_progress=False
468
- )
446
+ lat_number.change(plot_map_scatter_mapbox, inputs=[lat_number, lon_number], outputs=map_plot, show_progress=False)
447
+ lon_number.change(plot_map_scatter_mapbox, inputs=[lat_number, lon_number], outputs=map_plot, show_progress=False)
469
448
 
470
449
  with gr.Group():
471
450
  with gr.Row():
472
- yearlong_checkbox = gr.Checkbox(
473
- True, label=loc.localize("species-list-coordinates-yearlong-checkbox-label")
474
- )
451
+ yearlong_checkbox = gr.Checkbox(True, label=loc.localize("species-list-coordinates-yearlong-checkbox-label"))
475
452
  week_number = gr.Slider(
476
453
  minimum=1,
477
454
  maximum=48,
@@ -509,9 +486,7 @@ def save_file_dialog(filetypes=(), state_key=None, default_filename=""):
509
486
  The selected file or None of the dialog was canceled.
510
487
  """
511
488
  initial_selection = settings.get_state(state_key, "") if state_key else ""
512
- file = _WINDOW.create_file_dialog(
513
- webview.SAVE_DIALOG, file_types=filetypes, directory=initial_selection, save_filename=default_filename
514
- )
489
+ file = _WINDOW.create_file_dialog(webview.SAVE_DIALOG, file_types=filetypes, directory=initial_selection, save_filename=default_filename)
515
490
 
516
491
  if file:
517
492
  if state_key:
@@ -608,19 +583,13 @@ def species_lists(opened=True):
608
583
  )
609
584
 
610
585
  with gr.Column(visible=False) as position_row:
611
- lat_number, lon_number, week_number, sf_thresh_number, yearlong_checkbox, map_plot = (
612
- species_list_coordinates()
613
- )
586
+ lat_number, lon_number, week_number, sf_thresh_number, yearlong_checkbox, map_plot = species_list_coordinates()
614
587
 
615
- species_file_input = gr.File(
616
- file_types=[".txt"], visible=False, label=loc.localize("species-list-custom-list-file-label")
617
- )
588
+ species_file_input = gr.File(file_types=[".txt"], visible=False, label=loc.localize("species-list-custom-list-file-label"))
618
589
  empty_col = gr.Column()
619
590
 
620
591
  with gr.Column(visible=False) as custom_classifier_selector:
621
- classifier_selection_button = gr.Button(
622
- loc.localize("species-list-custom-classifier-selection-button-label")
623
- )
592
+ classifier_selection_button = gr.Button(loc.localize("species-list-custom-classifier-selection-button-label"))
624
593
  classifier_file_input = gr.Files(file_types=[".tflite"], visible=False, interactive=False)
625
594
  selected_classifier_state = gr.State()
626
595
 
@@ -663,6 +632,26 @@ def species_lists(opened=True):
663
632
  )
664
633
 
665
634
 
635
+ def download_plot(plot, filename=""):
636
+ from PIL import Image
637
+
638
+ imgdata = base64.b64decode(plot.plot.split(",", 1)[1])
639
+ res = _WINDOW.create_file_dialog(
640
+ webview.SAVE_DIALOG,
641
+ file_types=("PNG (*.png)", "Webp (*.webp)", "JPG (*.jpg)"),
642
+ save_filename=filename,
643
+ )
644
+
645
+ if res:
646
+ if res.endswith(".webp"):
647
+ with open(res, "wb") as f:
648
+ f.write(imgdata)
649
+ else:
650
+ output_format = res.rsplit(".", 1)[-1].upper()
651
+ img = Image.open(io.BytesIO(imgdata))
652
+ img.save(res, output_format if output_format in ["PNG", "JPEG"] else "PNG")
653
+
654
+
666
655
  def _get_network_shortcuts():
667
656
  """
668
657
  Retrieves a list of network shortcut paths from the user's Network Shortcuts folder.
@@ -696,9 +685,7 @@ def _get_network_shortcuts():
696
685
  try:
697
686
  # https://learn.microsoft.com/de-de/windows/win32/shell/links
698
687
  # CLSID_ShellLink: Class ID for Shell Link object
699
- shell_link = pythoncom.CoCreateInstance(
700
- shell.CLSID_ShellLink, None, pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink
701
- )
688
+ shell_link = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None, pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
702
689
 
703
690
  # https://learn.microsoft.com/de-de/windows/win32/api/objidl/nn-objidl-ipersistfile
704
691
  # Query IPersistFile interface used to
@@ -3211,7 +3211,7 @@ Lichenostomus cratitius_Menjamel de Boqueres
3211
3211
  Lichenostomus melanops_Menjamel Crestagroc
3212
3212
  Lichmera incana_Menjamel d'Orelles Grises
3213
3213
  Lichmera indistincta_Menjamel Bru
3214
- Lichmera indistincta_Menjamel Bru
3214
+ Lichmera limbata_Indonesian Honeyeater
3215
3215
  Lichmera squamata_Menjamel Escatós
3216
3216
  Limnoctites rectirostris_Jonquer Becdret
3217
3217
  Limnoctites sulphuriferus_Cuaespinós Gorjagroc
@@ -3211,7 +3211,7 @@ Lichenostomus cratitius_eukaliptusowczyk purpurowy
3211
3211
  Lichenostomus melanops_eukaliptusowczyk żółtoczelny
3212
3212
  Lichmera incana_miodojadek szarouchy
3213
3213
  Lichmera indistincta_miodojadek brązowy
3214
- Lichmera indistincta_miodojadek brązowy
3214
+ Lichmera limbata_Indonesian Honeyeater
3215
3215
  Lichmera squamata_miodojadek łuskowany
3216
3216
  Limnoctites rectirostris_szydłodziobek
3217
3217
  Limnoctites sulphuriferus_moczarnik żółtogardy