birdnet-analyzer 2.1.0__py3-none-any.whl → 2.1.1__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 (120) hide show
  1. birdnet_analyzer/__init__.py +9 -9
  2. birdnet_analyzer/analyze/__init__.py +19 -19
  3. birdnet_analyzer/analyze/__main__.py +3 -3
  4. birdnet_analyzer/analyze/cli.py +30 -30
  5. birdnet_analyzer/analyze/core.py +268 -246
  6. birdnet_analyzer/analyze/utils.py +700 -694
  7. birdnet_analyzer/audio.py +368 -368
  8. birdnet_analyzer/cli.py +732 -732
  9. birdnet_analyzer/config.py +243 -243
  10. birdnet_analyzer/eBird_taxonomy_codes_2024E.json +13045 -13045
  11. birdnet_analyzer/embeddings/__init__.py +3 -3
  12. birdnet_analyzer/embeddings/__main__.py +3 -3
  13. birdnet_analyzer/embeddings/cli.py +12 -12
  14. birdnet_analyzer/embeddings/core.py +70 -70
  15. birdnet_analyzer/embeddings/utils.py +173 -220
  16. birdnet_analyzer/evaluation/__init__.py +189 -189
  17. birdnet_analyzer/evaluation/__main__.py +3 -3
  18. birdnet_analyzer/evaluation/assessment/metrics.py +388 -388
  19. birdnet_analyzer/evaluation/assessment/performance_assessor.py +364 -364
  20. birdnet_analyzer/evaluation/assessment/plotting.py +378 -378
  21. birdnet_analyzer/evaluation/preprocessing/data_processor.py +631 -631
  22. birdnet_analyzer/evaluation/preprocessing/utils.py +98 -98
  23. birdnet_analyzer/gui/__init__.py +19 -19
  24. birdnet_analyzer/gui/__main__.py +3 -3
  25. birdnet_analyzer/gui/analysis.py +179 -179
  26. birdnet_analyzer/gui/assets/arrow_down.svg +4 -4
  27. birdnet_analyzer/gui/assets/arrow_left.svg +4 -4
  28. birdnet_analyzer/gui/assets/arrow_right.svg +4 -4
  29. birdnet_analyzer/gui/assets/arrow_up.svg +4 -4
  30. birdnet_analyzer/gui/assets/gui.css +36 -36
  31. birdnet_analyzer/gui/assets/gui.js +89 -93
  32. birdnet_analyzer/gui/embeddings.py +638 -638
  33. birdnet_analyzer/gui/evaluation.py +801 -801
  34. birdnet_analyzer/gui/localization.py +75 -75
  35. birdnet_analyzer/gui/multi_file.py +265 -265
  36. birdnet_analyzer/gui/review.py +472 -472
  37. birdnet_analyzer/gui/segments.py +191 -191
  38. birdnet_analyzer/gui/settings.py +149 -149
  39. birdnet_analyzer/gui/single_file.py +264 -264
  40. birdnet_analyzer/gui/species.py +95 -95
  41. birdnet_analyzer/gui/train.py +687 -687
  42. birdnet_analyzer/gui/utils.py +803 -797
  43. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_af.txt +6522 -6522
  44. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ar.txt +6522 -6522
  45. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_bg.txt +6522 -6522
  46. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ca.txt +6522 -6522
  47. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_cs.txt +6522 -6522
  48. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_da.txt +6522 -6522
  49. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_de.txt +6522 -6522
  50. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_el.txt +6522 -6522
  51. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_en_uk.txt +6522 -6522
  52. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_es.txt +6522 -6522
  53. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_fi.txt +6522 -6522
  54. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_fr.txt +6522 -6522
  55. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_he.txt +6522 -6522
  56. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_hr.txt +6522 -6522
  57. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_hu.txt +6522 -6522
  58. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_in.txt +6522 -6522
  59. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_is.txt +6522 -6522
  60. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_it.txt +6522 -6522
  61. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ja.txt +6522 -6522
  62. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ko.txt +6522 -6522
  63. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_lt.txt +6522 -6522
  64. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ml.txt +6522 -6522
  65. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_nl.txt +6522 -6522
  66. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_no.txt +6522 -6522
  67. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_pl.txt +6522 -6522
  68. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_pt_BR.txt +6522 -6522
  69. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_pt_PT.txt +6522 -6522
  70. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ro.txt +6522 -6522
  71. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ru.txt +6522 -6522
  72. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sk.txt +6522 -6522
  73. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sl.txt +6522 -6522
  74. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sr.txt +6522 -6522
  75. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sv.txt +6522 -6522
  76. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_th.txt +6522 -6522
  77. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_tr.txt +6522 -6522
  78. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_uk.txt +6522 -6522
  79. birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_zh.txt +6522 -6522
  80. birdnet_analyzer/lang/de.json +342 -341
  81. birdnet_analyzer/lang/en.json +342 -341
  82. birdnet_analyzer/lang/fi.json +342 -341
  83. birdnet_analyzer/lang/fr.json +342 -341
  84. birdnet_analyzer/lang/id.json +342 -341
  85. birdnet_analyzer/lang/pt-br.json +342 -341
  86. birdnet_analyzer/lang/ru.json +342 -341
  87. birdnet_analyzer/lang/se.json +342 -341
  88. birdnet_analyzer/lang/tlh.json +342 -341
  89. birdnet_analyzer/lang/zh_TW.json +342 -341
  90. birdnet_analyzer/model.py +1213 -1212
  91. birdnet_analyzer/search/__init__.py +3 -3
  92. birdnet_analyzer/search/__main__.py +3 -3
  93. birdnet_analyzer/search/cli.py +11 -11
  94. birdnet_analyzer/search/core.py +78 -78
  95. birdnet_analyzer/search/utils.py +104 -107
  96. birdnet_analyzer/segments/__init__.py +3 -3
  97. birdnet_analyzer/segments/__main__.py +3 -3
  98. birdnet_analyzer/segments/cli.py +13 -13
  99. birdnet_analyzer/segments/core.py +81 -81
  100. birdnet_analyzer/segments/utils.py +383 -383
  101. birdnet_analyzer/species/__init__.py +3 -3
  102. birdnet_analyzer/species/__main__.py +3 -3
  103. birdnet_analyzer/species/cli.py +13 -13
  104. birdnet_analyzer/species/core.py +35 -35
  105. birdnet_analyzer/species/utils.py +73 -74
  106. birdnet_analyzer/train/__init__.py +3 -3
  107. birdnet_analyzer/train/__main__.py +3 -3
  108. birdnet_analyzer/train/cli.py +13 -13
  109. birdnet_analyzer/train/core.py +113 -113
  110. birdnet_analyzer/train/utils.py +878 -877
  111. birdnet_analyzer/translate.py +132 -133
  112. birdnet_analyzer/utils.py +425 -425
  113. {birdnet_analyzer-2.1.0.dist-info → birdnet_analyzer-2.1.1.dist-info}/METADATA +147 -146
  114. birdnet_analyzer-2.1.1.dist-info/RECORD +124 -0
  115. {birdnet_analyzer-2.1.0.dist-info → birdnet_analyzer-2.1.1.dist-info}/licenses/LICENSE +18 -18
  116. birdnet_analyzer/playground.py +0 -5
  117. birdnet_analyzer-2.1.0.dist-info/RECORD +0 -125
  118. {birdnet_analyzer-2.1.0.dist-info → birdnet_analyzer-2.1.1.dist-info}/WHEEL +0 -0
  119. {birdnet_analyzer-2.1.0.dist-info → birdnet_analyzer-2.1.1.dist-info}/entry_points.txt +0 -0
  120. {birdnet_analyzer-2.1.0.dist-info → birdnet_analyzer-2.1.1.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,9 @@
1
- from birdnet_analyzer.analyze import analyze
2
- from birdnet_analyzer.embeddings import embeddings
3
- from birdnet_analyzer.search import search
4
- from birdnet_analyzer.segments import segments
5
- from birdnet_analyzer.species import species
6
- from birdnet_analyzer.train import train
7
-
8
- __version__ = "2.0.0"
9
- __all__ = ["analyze", "embeddings", "search", "segments", "species", "train"]
1
+ from birdnet_analyzer.analyze import analyze
2
+ from birdnet_analyzer.embeddings import embeddings
3
+ from birdnet_analyzer.search import search
4
+ from birdnet_analyzer.segments import segments
5
+ from birdnet_analyzer.species import species
6
+ from birdnet_analyzer.train import train
7
+
8
+ __version__ = "2.0.0"
9
+ __all__ = ["analyze", "embeddings", "search", "segments", "species", "train"]
@@ -1,19 +1,19 @@
1
- import os
2
-
3
- import birdnet_analyzer.config as cfg
4
- from birdnet_analyzer.analyze.core import analyze
5
-
6
- POSSIBLE_ADDITIONAL_COLUMNS_MAP = {
7
- "lat": lambda: cfg.LATITUDE,
8
- "lon": lambda: cfg.LONGITUDE,
9
- "week": lambda: cfg.WEEK,
10
- "overlap": lambda: cfg.SIG_OVERLAP,
11
- "sensitivity": lambda: cfg.SIGMOID_SENSITIVITY,
12
- "min_conf": lambda: cfg.MIN_CONFIDENCE,
13
- "species_list": lambda: cfg.SPECIES_LIST_FILE or "",
14
- "model": lambda: os.path.basename(cfg.MODEL_PATH),
15
- }
16
-
17
- __all__ = [
18
- "analyze",
19
- ]
1
+ import os
2
+
3
+ import birdnet_analyzer.config as cfg
4
+ from birdnet_analyzer.analyze.core import analyze
5
+
6
+ POSSIBLE_ADDITIONAL_COLUMNS_MAP = {
7
+ "lat": lambda: cfg.LATITUDE,
8
+ "lon": lambda: cfg.LONGITUDE,
9
+ "week": lambda: cfg.WEEK,
10
+ "overlap": lambda: cfg.SIG_OVERLAP,
11
+ "sensitivity": lambda: cfg.SIGMOID_SENSITIVITY,
12
+ "min_conf": lambda: cfg.MIN_CONFIDENCE,
13
+ "species_list": lambda: cfg.SPECIES_LIST_FILE or "",
14
+ "model": lambda: os.path.basename(cfg.MODEL_PATH),
15
+ }
16
+
17
+ __all__ = [
18
+ "analyze",
19
+ ]
@@ -1,3 +1,3 @@
1
- from birdnet_analyzer.analyze.cli import main
2
-
3
- main()
1
+ from birdnet_analyzer.analyze.cli import main
2
+
3
+ main()
@@ -1,30 +1,30 @@
1
- from birdnet_analyzer import analyze
2
- from birdnet_analyzer.utils import runtime_error_handler
3
-
4
-
5
- @runtime_error_handler
6
- def main():
7
- import os
8
- from multiprocessing import freeze_support
9
-
10
- from birdnet_analyzer import cli
11
-
12
- # Freeze support for executable
13
- freeze_support()
14
-
15
- parser = cli.analyzer_parser()
16
-
17
- args = parser.parse_args()
18
-
19
- try:
20
- if os.get_terminal_size().columns >= 64:
21
- print(cli.ASCII_LOGO, flush=True)
22
- except Exception:
23
- pass
24
-
25
- if "additional_columns" in args and args.additional_columns and "csv" not in args.rtype:
26
- import warnings
27
-
28
- warnings.warn("The --additional_columns argument is only valid for CSV output. It will be ignored.", stacklevel=1)
29
-
30
- analyze(**vars(args))
1
+ from birdnet_analyzer import analyze
2
+ from birdnet_analyzer.utils import runtime_error_handler
3
+
4
+
5
+ @runtime_error_handler
6
+ def main():
7
+ import os
8
+ from multiprocessing import freeze_support
9
+
10
+ from birdnet_analyzer import cli
11
+
12
+ # Freeze support for executable
13
+ freeze_support()
14
+
15
+ parser = cli.analyzer_parser()
16
+
17
+ args = parser.parse_args()
18
+
19
+ try:
20
+ if os.get_terminal_size().columns >= 64:
21
+ print(cli.ASCII_LOGO, flush=True)
22
+ except Exception:
23
+ pass
24
+
25
+ if "additional_columns" in args and args.additional_columns and "csv" not in args.rtype:
26
+ import warnings
27
+
28
+ warnings.warn("The --additional_columns argument is only valid for CSV output. It will be ignored.", stacklevel=1)
29
+
30
+ analyze(**vars(args))
@@ -1,246 +1,268 @@
1
- import os
2
- from typing import Literal
3
-
4
-
5
- def analyze(
6
- audio_input: str,
7
- output: str | None = None,
8
- *,
9
- min_conf: float = 0.25,
10
- classifier: str | None = None,
11
- lat: float = -1,
12
- lon: float = -1,
13
- week: int = -1,
14
- slist: str | None = None,
15
- sensitivity: float = 1.0,
16
- overlap: float = 0,
17
- fmin: int = 0,
18
- fmax: int = 15000,
19
- audio_speed: float = 1.0,
20
- batch_size: int = 1,
21
- combine_results: bool = False,
22
- rtype: Literal["table", "audacity", "kaleidoscope", "csv"] | list[Literal["table", "audacity", "kaleidoscope", "csv"]] = "table",
23
- skip_existing_results: bool = False,
24
- sf_thresh: float = 0.03,
25
- top_n: int | None = None,
26
- merge_consecutive: int = 1,
27
- threads: int = 8,
28
- locale: str = "en",
29
- additional_columns: list[str] | None = None,
30
- ):
31
- """
32
- Analyzes audio files for bird species detection using the BirdNET-Analyzer.
33
- Args:
34
- audio_input (str): Path to the input directory or file containing audio data.
35
- output (str | None, optional): Path to the output directory for results. Defaults to None.
36
- min_conf (float, optional): Minimum confidence threshold for detections. Defaults to 0.25.
37
- classifier (str | None, optional): Path to a custom classifier file. Defaults to None.
38
- lat (float, optional): Latitude for location-based filtering. Defaults to -1.
39
- lon (float, optional): Longitude for location-based filtering. Defaults to -1.
40
- week (int, optional): Week of the year for seasonal filtering. Defaults to -1.
41
- slist (str | None, optional): Path to a species list file for filtering. Defaults to None.
42
- sensitivity (float, optional): Sensitivity of the detection algorithm. Defaults to 1.0.
43
- overlap (float, optional): Overlap between analysis windows in seconds. Defaults to 0.
44
- fmin (int, optional): Minimum frequency for analysis in Hz. Defaults to 0.
45
- fmax (int, optional): Maximum frequency for analysis in Hz. Defaults to 15000.
46
- audio_speed (float, optional): Speed factor for audio playback during analysis. Defaults to 1.0.
47
- batch_size (int, optional): Batch size for processing. Defaults to 1.
48
- combine_results (bool, optional): Whether to combine results into a single file. Defaults to False.
49
- rtype (Literal["table", "audacity", "kaleidoscope", "csv"] | List[Literal["table", "audacity", "kaleidoscope", "csv"]], optional):
50
- Output format(s) for results. Defaults to "table".
51
- skip_existing_results (bool, optional): Whether to skip analysis for files with existing results. Defaults to False.
52
- sf_thresh (float, optional): Threshold for species filtering. Defaults to 0.03.
53
- top_n (int | None, optional): Limit the number of top detections per file. Defaults to None.
54
- merge_consecutive (int, optional): Merge consecutive detections within this time window in seconds. Defaults to 1.
55
- threads (int, optional): Number of CPU threads to use for analysis. Defaults to 8.
56
- locale (str, optional): Locale for species names and output. Defaults to "en".
57
- additional_columns (list[str] | None, optional): Additional columns to include in the output. Defaults to None.
58
- Returns:
59
- None
60
- Raises:
61
- ValueError: If input path is invalid or required parameters are missing.
62
- Notes:
63
- - The function ensures the BirdNET model is available before analysis.
64
- - Results can be combined into a single file if `combine_results` is True.
65
- - Analysis parameters are saved to a file in the output directory.
66
- """
67
- from multiprocessing import Pool
68
-
69
- import birdnet_analyzer.config as cfg
70
- from birdnet_analyzer.analyze.utils import analyze_file, save_analysis_params
71
- from birdnet_analyzer.analyze.utils import combine_results as combine
72
- from birdnet_analyzer.utils import ensure_model_exists
73
-
74
- ensure_model_exists()
75
-
76
- flist = _set_params(
77
- audio_input=audio_input,
78
- output=output,
79
- min_conf=min_conf,
80
- custom_classifier=classifier,
81
- lat=lat,
82
- lon=lon,
83
- week=week,
84
- slist=slist,
85
- sensitivity=sensitivity,
86
- locale=locale,
87
- overlap=overlap,
88
- fmin=fmin,
89
- fmax=fmax,
90
- audio_speed=audio_speed,
91
- bs=batch_size,
92
- combine_results=combine_results,
93
- rtype=rtype,
94
- sf_thresh=sf_thresh,
95
- top_n=top_n,
96
- merge_consecutive=merge_consecutive,
97
- skip_existing_results=skip_existing_results,
98
- threads=threads,
99
- labels_file=cfg.LABELS_FILE,
100
- additional_columns=additional_columns,
101
- )
102
-
103
- print(f"Found {len(cfg.FILE_LIST)} files to analyze")
104
-
105
- if not cfg.SPECIES_LIST:
106
- print(f"Species list contains {len(cfg.LABELS)} species")
107
- else:
108
- print(f"Species list contains {len(cfg.SPECIES_LIST)} species")
109
-
110
- result_files = []
111
-
112
- # Analyze files
113
- if cfg.CPU_THREADS < 2 or len(flist) < 2:
114
- result_files.extend(analyze_file(f) for f in flist)
115
- else:
116
- with Pool(cfg.CPU_THREADS) as p:
117
- # Map analyzeFile function to each entry in flist
118
- results = p.map_async(analyze_file, flist)
119
- # Wait for all tasks to complete
120
- results.wait()
121
- result_files = results.get()
122
-
123
- # Combine results?
124
- if cfg.COMBINE_RESULTS:
125
- print(f"Combining results, writing to {cfg.OUTPUT_PATH}...", end="", flush=True)
126
- combine(result_files)
127
- print("done!", flush=True)
128
-
129
- save_analysis_params(os.path.join(cfg.OUTPUT_PATH, cfg.ANALYSIS_PARAMS_FILENAME))
130
-
131
-
132
- def _set_params(
133
- audio_input,
134
- output,
135
- min_conf,
136
- custom_classifier,
137
- lat,
138
- lon,
139
- week,
140
- slist,
141
- sensitivity,
142
- locale,
143
- overlap,
144
- fmin,
145
- fmax,
146
- audio_speed,
147
- bs,
148
- combine_results,
149
- rtype,
150
- skip_existing_results,
151
- sf_thresh,
152
- top_n,
153
- merge_consecutive,
154
- threads,
155
- labels_file=None,
156
- additional_columns=None,
157
- ):
158
- import birdnet_analyzer.config as cfg
159
- from birdnet_analyzer.analyze.utils import load_codes
160
- from birdnet_analyzer.species.utils import get_species_list
161
- from birdnet_analyzer.utils import collect_audio_files, read_lines
162
-
163
- cfg.CODES = load_codes()
164
- cfg.LABELS = read_lines(labels_file if labels_file else cfg.LABELS_FILE)
165
- cfg.SKIP_EXISTING_RESULTS = skip_existing_results
166
- cfg.LOCATION_FILTER_THRESHOLD = sf_thresh
167
- cfg.TOP_N = top_n
168
- cfg.MERGE_CONSECUTIVE = merge_consecutive
169
- cfg.INPUT_PATH = audio_input.replace("/", os.sep)
170
- cfg.MIN_CONFIDENCE = min_conf
171
- cfg.SIGMOID_SENSITIVITY = sensitivity
172
- cfg.SIG_OVERLAP = overlap
173
- cfg.BANDPASS_FMIN = fmin
174
- cfg.BANDPASS_FMAX = fmax
175
- cfg.AUDIO_SPEED = audio_speed
176
- cfg.RESULT_TYPES = rtype
177
- cfg.COMBINE_RESULTS = combine_results
178
- cfg.BATCH_SIZE = bs
179
- cfg.ADDITIONAL_COLUMNS = additional_columns
180
-
181
- if not output:
182
- if os.path.isfile(cfg.INPUT_PATH):
183
- cfg.OUTPUT_PATH = os.path.dirname(cfg.INPUT_PATH)
184
- else:
185
- cfg.OUTPUT_PATH = cfg.INPUT_PATH
186
- else:
187
- cfg.OUTPUT_PATH = output
188
-
189
- if os.path.isdir(cfg.INPUT_PATH):
190
- cfg.FILE_LIST = collect_audio_files(cfg.INPUT_PATH)
191
- else:
192
- cfg.FILE_LIST = [cfg.INPUT_PATH]
193
-
194
- if os.path.isdir(cfg.INPUT_PATH):
195
- cfg.CPU_THREADS = threads
196
- cfg.TFLITE_THREADS = 1
197
- else:
198
- cfg.CPU_THREADS = 1
199
- cfg.TFLITE_THREADS = threads
200
-
201
- if custom_classifier is not None:
202
- cfg.CUSTOM_CLASSIFIER = custom_classifier # we treat this as absolute path, so no need to join with dirname
203
-
204
- if custom_classifier.endswith(".tflite"):
205
- cfg.LABELS_FILE = custom_classifier.replace(".tflite", "_Labels.txt") # same for labels file
206
-
207
- if not os.path.isfile(cfg.LABELS_FILE):
208
- cfg.LABELS_FILE = custom_classifier.replace("Model_FP32.tflite", "Labels.txt")
209
-
210
- cfg.LABELS = read_lines(cfg.LABELS_FILE)
211
- else:
212
- cfg.APPLY_SIGMOID = False
213
- # our output format
214
- cfg.LABELS_FILE = os.path.join(custom_classifier, "labels", "label_names.csv")
215
-
216
- if not os.path.isfile(cfg.LABELS_FILE):
217
- cfg.LABELS_FILE = os.path.join(custom_classifier, "assets", "label.csv")
218
- cfg.LABELS = read_lines(cfg.LABELS_FILE)
219
- else:
220
- cfg.LABELS = [line.split(",")[1] for line in read_lines(cfg.LABELS_FILE)]
221
- else:
222
- cfg.LATITUDE, cfg.LONGITUDE, cfg.WEEK = lat, lon, week
223
- cfg.CUSTOM_CLASSIFIER = None
224
-
225
- if cfg.LATITUDE == -1 and cfg.LONGITUDE == -1:
226
- if not slist:
227
- cfg.SPECIES_LIST_FILE = None
228
- else:
229
- cfg.SPECIES_LIST_FILE = slist
230
-
231
- if os.path.isdir(cfg.SPECIES_LIST_FILE):
232
- cfg.SPECIES_LIST_FILE = os.path.join(cfg.SPECIES_LIST_FILE, "species_list.txt")
233
-
234
- cfg.SPECIES_LIST = read_lines(cfg.SPECIES_LIST_FILE)
235
- else:
236
- cfg.SPECIES_LIST_FILE = None
237
- cfg.SPECIES_LIST = get_species_list(cfg.LATITUDE, cfg.LONGITUDE, cfg.WEEK, cfg.LOCATION_FILTER_THRESHOLD)
238
-
239
- lfile = os.path.join(cfg.TRANSLATED_LABELS_PATH, os.path.basename(cfg.LABELS_FILE).replace(".txt", f"_{locale}.txt"))
240
-
241
- if locale not in ["en"] and os.path.isfile(lfile):
242
- cfg.TRANSLATED_LABELS = read_lines(lfile)
243
- else:
244
- cfg.TRANSLATED_LABELS = cfg.LABELS
245
-
246
- return [(f, cfg.get_config()) for f in cfg.FILE_LIST]
1
+ import os
2
+ from typing import Literal
3
+
4
+
5
+ def analyze(
6
+ audio_input: str,
7
+ output: str | None = None,
8
+ *,
9
+ min_conf: float = 0.25,
10
+ classifier: str | None = None,
11
+ lat: float = -1,
12
+ lon: float = -1,
13
+ week: int = -1,
14
+ slist: str | None = None,
15
+ sensitivity: float = 1.0,
16
+ overlap: float = 0,
17
+ fmin: int = 0,
18
+ fmax: int = 15000,
19
+ audio_speed: float = 1.0,
20
+ batch_size: int = 1,
21
+ combine_results: bool = False,
22
+ rtype: Literal["table", "audacity", "kaleidoscope", "csv"] | list[Literal["table", "audacity", "kaleidoscope", "csv"]] = "table",
23
+ skip_existing_results: bool = False,
24
+ sf_thresh: float = 0.03,
25
+ top_n: int | None = None,
26
+ merge_consecutive: int = 1,
27
+ threads: int = 8,
28
+ locale: str = "en",
29
+ additional_columns: list[str] | None = None,
30
+ ):
31
+ """
32
+ Analyzes audio files for bird species detection using the BirdNET-Analyzer.
33
+ Args:
34
+ audio_input (str): Path to the input directory or file containing audio data.
35
+ output (str | None, optional): Path to the output directory for results. Defaults to None.
36
+ min_conf (float, optional): Minimum confidence threshold for detections. Defaults to 0.25.
37
+ classifier (str | None, optional): Path to a custom classifier file. Defaults to None.
38
+ lat (float, optional): Latitude for location-based filtering. Defaults to -1.
39
+ lon (float, optional): Longitude for location-based filtering. Defaults to -1.
40
+ week (int, optional): Week of the year for seasonal filtering. Defaults to -1.
41
+ slist (str | None, optional): Path to a species list file for filtering. Defaults to None.
42
+ sensitivity (float, optional): Sensitivity of the detection algorithm. Defaults to 1.0.
43
+ overlap (float, optional): Overlap between analysis windows in seconds. Defaults to 0.
44
+ fmin (int, optional): Minimum frequency for analysis in Hz. Defaults to 0.
45
+ fmax (int, optional): Maximum frequency for analysis in Hz. Defaults to 15000.
46
+ audio_speed (float, optional): Speed factor for audio playback during analysis. Defaults to 1.0.
47
+ batch_size (int, optional): Batch size for processing. Defaults to 1.
48
+ combine_results (bool, optional): Whether to combine results into a single file. Defaults to False.
49
+ rtype (Literal["table", "audacity", "kaleidoscope", "csv"] | List[Literal["table", "audacity", "kaleidoscope", "csv"]], optional):
50
+ Output format(s) for results. Defaults to "table".
51
+ skip_existing_results (bool, optional): Whether to skip analysis for files with existing results. Defaults to False.
52
+ sf_thresh (float, optional): Threshold for species filtering. Defaults to 0.03.
53
+ top_n (int | None, optional): Limit the number of top detections per file. Defaults to None.
54
+ merge_consecutive (int, optional): Merge consecutive detections within this time window in seconds. Defaults to 1.
55
+ threads (int, optional): Number of CPU threads to use for analysis. Defaults to 8.
56
+ locale (str, optional): Locale for species names and output. Defaults to "en".
57
+ additional_columns (list[str] | None, optional): Additional columns to include in the output. Defaults to None.
58
+ Returns:
59
+ None
60
+ Raises:
61
+ ValueError: If input path is invalid or required parameters are missing.
62
+ Notes:
63
+ - The function ensures the BirdNET model is available before analysis.
64
+ - Results can be combined into a single file if `combine_results` is True.
65
+ - Analysis parameters are saved to a file in the output directory.
66
+ """
67
+ from multiprocessing import Pool
68
+
69
+ import birdnet_analyzer.config as cfg
70
+ from birdnet_analyzer.analyze.utils import analyze_file, save_analysis_params
71
+ from birdnet_analyzer.analyze.utils import combine_results as combine
72
+ from birdnet_analyzer.utils import ensure_model_exists
73
+
74
+ ensure_model_exists()
75
+
76
+ flist = _set_params(
77
+ audio_input=audio_input,
78
+ output=output,
79
+ min_conf=min_conf,
80
+ custom_classifier=classifier,
81
+ lat=lat,
82
+ lon=lon,
83
+ week=week,
84
+ slist=slist,
85
+ sensitivity=sensitivity,
86
+ locale=locale,
87
+ overlap=overlap,
88
+ fmin=fmin,
89
+ fmax=fmax,
90
+ audio_speed=audio_speed,
91
+ bs=batch_size,
92
+ combine_results=combine_results,
93
+ rtype=rtype,
94
+ sf_thresh=sf_thresh,
95
+ top_n=top_n,
96
+ merge_consecutive=merge_consecutive,
97
+ skip_existing_results=skip_existing_results,
98
+ threads=threads,
99
+ labels_file=cfg.LABELS_FILE,
100
+ additional_columns=additional_columns,
101
+ )
102
+
103
+ print(f"Found {len(cfg.FILE_LIST)} files to analyze")
104
+
105
+ if not cfg.SPECIES_LIST:
106
+ print(f"Species list contains {len(cfg.LABELS)} species")
107
+ else:
108
+ print(f"Species list contains {len(cfg.SPECIES_LIST)} species")
109
+
110
+ result_files = []
111
+
112
+ # Analyze files
113
+ if cfg.CPU_THREADS < 2 or len(flist) < 2:
114
+ result_files.extend(analyze_file(f) for f in flist)
115
+ else:
116
+ with Pool(cfg.CPU_THREADS) as p:
117
+ # Map analyzeFile function to each entry in flist
118
+ results = p.map_async(analyze_file, flist)
119
+ # Wait for all tasks to complete
120
+ results.wait()
121
+ result_files = results.get()
122
+
123
+ # Combine results?
124
+ if cfg.COMBINE_RESULTS:
125
+ print(f"Combining results, writing to {cfg.OUTPUT_PATH}...", end="", flush=True)
126
+ combine(result_files)
127
+ print("done!", flush=True)
128
+
129
+ save_analysis_params(os.path.join(cfg.OUTPUT_PATH, cfg.ANALYSIS_PARAMS_FILENAME))
130
+
131
+
132
+ def _set_params(
133
+ audio_input,
134
+ output,
135
+ min_conf,
136
+ custom_classifier,
137
+ lat,
138
+ lon,
139
+ week,
140
+ slist,
141
+ sensitivity,
142
+ locale,
143
+ overlap,
144
+ fmin,
145
+ fmax,
146
+ audio_speed,
147
+ bs,
148
+ combine_results,
149
+ rtype,
150
+ skip_existing_results,
151
+ sf_thresh,
152
+ top_n,
153
+ merge_consecutive,
154
+ threads,
155
+ labels_file=None,
156
+ additional_columns=None,
157
+ ):
158
+ import birdnet_analyzer.config as cfg
159
+ from birdnet_analyzer.analyze.utils import load_codes
160
+ from birdnet_analyzer.species.utils import get_species_list
161
+ from birdnet_analyzer.utils import collect_audio_files, read_lines
162
+
163
+ if not isinstance(overlap, int | float):
164
+ raise ValueError("Overlap must be a numeric value.")
165
+
166
+ if overlap < 0:
167
+ raise ValueError("Overlap must be a non-negative value.")
168
+
169
+ if overlap >= cfg.SIG_LENGTH:
170
+ raise ValueError(f"Overlap must be less than {cfg.SIG_LENGTH} seconds.")
171
+
172
+ if not isinstance(audio_speed, int | float):
173
+ raise ValueError("Audio speed must be a numeric value.")
174
+
175
+ if audio_speed <= 0:
176
+ raise ValueError("Audio speed must be a positive value.")
177
+
178
+ cfg.CODES = load_codes()
179
+ cfg.LABELS = read_lines(labels_file if labels_file else cfg.LABELS_FILE)
180
+ cfg.SKIP_EXISTING_RESULTS = skip_existing_results
181
+ cfg.LOCATION_FILTER_THRESHOLD = sf_thresh
182
+ cfg.TOP_N = top_n
183
+ cfg.MERGE_CONSECUTIVE = merge_consecutive
184
+ cfg.INPUT_PATH = audio_input.replace("/", os.sep)
185
+ cfg.MIN_CONFIDENCE = min_conf
186
+ cfg.SIGMOID_SENSITIVITY = sensitivity
187
+ cfg.SIG_OVERLAP = overlap
188
+ cfg.BANDPASS_FMIN = fmin
189
+ cfg.BANDPASS_FMAX = fmax
190
+ cfg.AUDIO_SPEED = audio_speed
191
+ cfg.RESULT_TYPES = rtype
192
+ cfg.COMBINE_RESULTS = combine_results
193
+ cfg.BATCH_SIZE = bs
194
+ cfg.ADDITIONAL_COLUMNS = additional_columns
195
+
196
+ if not output:
197
+ if os.path.isfile(cfg.INPUT_PATH):
198
+ cfg.OUTPUT_PATH = os.path.dirname(cfg.INPUT_PATH)
199
+ else:
200
+ cfg.OUTPUT_PATH = cfg.INPUT_PATH
201
+ else:
202
+ cfg.OUTPUT_PATH = output
203
+
204
+ if os.path.isdir(cfg.INPUT_PATH):
205
+ cfg.FILE_LIST = collect_audio_files(cfg.INPUT_PATH)
206
+ else:
207
+ cfg.FILE_LIST = [cfg.INPUT_PATH]
208
+
209
+ if os.path.isdir(cfg.INPUT_PATH):
210
+ cfg.CPU_THREADS = threads
211
+ cfg.TFLITE_THREADS = 1
212
+ else:
213
+ cfg.CPU_THREADS = 1
214
+ cfg.TFLITE_THREADS = threads
215
+
216
+ if custom_classifier is not None:
217
+ cfg.CUSTOM_CLASSIFIER = custom_classifier # we treat this as absolute path, so no need to join with dirname
218
+
219
+ if custom_classifier.endswith(".tflite"):
220
+ cfg.LABELS_FILE = custom_classifier.replace(".tflite", "_Labels.txt") # same for labels file
221
+
222
+ if not os.path.isfile(cfg.LABELS_FILE):
223
+ cfg.LABELS_FILE = custom_classifier.replace("Model_FP32.tflite", "Labels.txt")
224
+
225
+ if not custom_classifier.endswith("Model_FP32.tflite") or not os.path.isfile(cfg.LABELS_FILE):
226
+ cfg.LABELS_FILE = None
227
+ cfg.LABELS = None
228
+ else:
229
+ cfg.LABELS = read_lines(cfg.LABELS_FILE)
230
+ else:
231
+ cfg.APPLY_SIGMOID = False
232
+ # our output format
233
+ cfg.LABELS_FILE = os.path.join(custom_classifier, "labels", "label_names.csv")
234
+
235
+ if not os.path.isfile(cfg.LABELS_FILE):
236
+ cfg.LABELS_FILE = os.path.join(custom_classifier, "assets", "label.csv")
237
+ cfg.LABELS = read_lines(cfg.LABELS_FILE)
238
+ else:
239
+ cfg.LABELS = [line.split(",")[1] for line in read_lines(cfg.LABELS_FILE)]
240
+ else:
241
+ cfg.LATITUDE, cfg.LONGITUDE, cfg.WEEK = lat, lon, week
242
+ cfg.CUSTOM_CLASSIFIER = None
243
+
244
+ if cfg.LATITUDE == -1 and cfg.LONGITUDE == -1:
245
+ if not slist:
246
+ cfg.SPECIES_LIST_FILE = None
247
+ else:
248
+ cfg.SPECIES_LIST_FILE = slist
249
+
250
+ if os.path.isdir(cfg.SPECIES_LIST_FILE):
251
+ cfg.SPECIES_LIST_FILE = os.path.join(cfg.SPECIES_LIST_FILE, "species_list.txt")
252
+
253
+ cfg.SPECIES_LIST = read_lines(cfg.SPECIES_LIST_FILE)
254
+ else:
255
+ cfg.SPECIES_LIST_FILE = None
256
+ cfg.SPECIES_LIST = get_species_list(cfg.LATITUDE, cfg.LONGITUDE, cfg.WEEK, cfg.LOCATION_FILTER_THRESHOLD)
257
+
258
+ if cfg.LABELS_FILE:
259
+ lfile = os.path.join(cfg.TRANSLATED_LABELS_PATH, os.path.basename(cfg.LABELS_FILE).replace(".txt", f"_{locale}.txt"))
260
+
261
+ if locale not in ["en"] and os.path.isfile(lfile):
262
+ cfg.TRANSLATED_LABELS = read_lines(lfile)
263
+ else:
264
+ cfg.TRANSLATED_LABELS = cfg.LABELS
265
+ else:
266
+ cfg.TRANSLATED_LABELS = cfg.LABELS
267
+
268
+ return [(f, cfg.get_config()) for f in cfg.FILE_LIST]