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
@@ -1,5 +1,19 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
import birdnet_analyzer.config as cfg
|
1
4
|
from birdnet_analyzer.analyze.core import analyze
|
2
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
|
+
|
3
17
|
__all__ = [
|
4
18
|
"analyze",
|
5
19
|
]
|
birdnet_analyzer/analyze/cli.py
CHANGED
@@ -22,4 +22,9 @@ def main():
|
|
22
22
|
except Exception:
|
23
23
|
pass
|
24
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
|
+
|
25
30
|
analyze(**vars(args))
|
birdnet_analyzer/analyze/core.py
CHANGED
@@ -26,6 +26,7 @@ def analyze(
|
|
26
26
|
merge_consecutive: int = 1,
|
27
27
|
threads: int = 8,
|
28
28
|
locale: str = "en",
|
29
|
+
additional_columns: list[str] | None = None,
|
29
30
|
):
|
30
31
|
"""
|
31
32
|
Analyzes audio files for bird species detection using the BirdNET-Analyzer.
|
@@ -53,6 +54,7 @@ def analyze(
|
|
53
54
|
merge_consecutive (int, optional): Merge consecutive detections within this time window in seconds. Defaults to 1.
|
54
55
|
threads (int, optional): Number of CPU threads to use for analysis. Defaults to 8.
|
55
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.
|
56
58
|
Returns:
|
57
59
|
None
|
58
60
|
Raises:
|
@@ -95,6 +97,7 @@ def analyze(
|
|
95
97
|
skip_existing_results=skip_existing_results,
|
96
98
|
threads=threads,
|
97
99
|
labels_file=cfg.LABELS_FILE,
|
100
|
+
additional_columns=additional_columns,
|
98
101
|
)
|
99
102
|
|
100
103
|
print(f"Found {len(cfg.FILE_LIST)} files to analyze")
|
@@ -150,6 +153,7 @@ def _set_params(
|
|
150
153
|
merge_consecutive,
|
151
154
|
threads,
|
152
155
|
labels_file=None,
|
156
|
+
additional_columns=None,
|
153
157
|
):
|
154
158
|
import birdnet_analyzer.config as cfg
|
155
159
|
from birdnet_analyzer.analyze.utils import load_codes
|
@@ -162,7 +166,7 @@ def _set_params(
|
|
162
166
|
cfg.LOCATION_FILTER_THRESHOLD = sf_thresh
|
163
167
|
cfg.TOP_N = top_n
|
164
168
|
cfg.MERGE_CONSECUTIVE = merge_consecutive
|
165
|
-
cfg.INPUT_PATH = audio_input
|
169
|
+
cfg.INPUT_PATH = audio_input.replace("/", os.sep)
|
166
170
|
cfg.MIN_CONFIDENCE = min_conf
|
167
171
|
cfg.SIGMOID_SENSITIVITY = sensitivity
|
168
172
|
cfg.SIG_OVERLAP = overlap
|
@@ -172,6 +176,7 @@ def _set_params(
|
|
172
176
|
cfg.RESULT_TYPES = rtype
|
173
177
|
cfg.COMBINE_RESULTS = combine_results
|
174
178
|
cfg.BATCH_SIZE = bs
|
179
|
+
cfg.ADDITIONAL_COLUMNS = additional_columns
|
175
180
|
|
176
181
|
if not output:
|
177
182
|
if os.path.isfile(cfg.INPUT_PATH):
|
@@ -4,17 +4,17 @@ import datetime
|
|
4
4
|
import json
|
5
5
|
import operator
|
6
6
|
import os
|
7
|
+
from collections.abc import Sequence
|
7
8
|
|
8
9
|
import numpy as np
|
9
10
|
|
10
11
|
import birdnet_analyzer.config as cfg
|
11
12
|
from birdnet_analyzer import audio, model, utils
|
12
13
|
|
13
|
-
RAVEN_TABLE_HEADER =
|
14
|
-
|
15
|
-
KALEIDOSCOPE_HEADER = (
|
16
|
-
"INDIR,FOLDER,IN FILE,OFFSET,DURATION,scientific_name,common_name,confidence,lat,lon,week,overlap,sensitivity\n"
|
14
|
+
RAVEN_TABLE_HEADER = (
|
15
|
+
"Selection\tView\tChannel\tBegin Time (s)\tEnd Time (s)\tLow Freq (Hz)\tHigh Freq (Hz)\tCommon Name\tSpecies Code\tConfidence\tBegin Path\tFile Offset (s)\n"
|
17
16
|
)
|
17
|
+
KALEIDOSCOPE_HEADER = "INDIR,FOLDER,IN FILE,OFFSET,DURATION,scientific_name,common_name,confidence,lat,lon,week,overlap,sensitivity\n"
|
18
18
|
CSV_HEADER = "Start (s),End (s),Scientific name,Common name,Confidence,File\n"
|
19
19
|
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
|
20
20
|
|
@@ -80,7 +80,7 @@ def generate_raven_table(timestamps: list[str], result: dict[str, list], afile_p
|
|
80
80
|
|
81
81
|
high_freq = min(high_freq, int(cfg.SIG_FMAX / cfg.AUDIO_SPEED))
|
82
82
|
|
83
|
-
high_freq = min(high_freq, int(cfg.BANDPASS_FMAX / cfg.AUDIO_SPEED))
|
83
|
+
high_freq = int(min(high_freq, int(cfg.BANDPASS_FMAX / cfg.AUDIO_SPEED)))
|
84
84
|
low_freq = max(cfg.SIG_FMIN, int(cfg.BANDPASS_FMIN / cfg.AUDIO_SPEED))
|
85
85
|
|
86
86
|
# Extract valid predictions for every timestamp
|
@@ -92,7 +92,9 @@ def generate_raven_table(timestamps: list[str], result: dict[str, list], afile_p
|
|
92
92
|
selection_id += 1
|
93
93
|
label = cfg.TRANSLATED_LABELS[cfg.LABELS.index(c[0])]
|
94
94
|
code = cfg.CODES[c[0]] if c[0] in cfg.CODES else c[0]
|
95
|
-
rstring +=
|
95
|
+
rstring += (
|
96
|
+
f"{selection_id}\tSpectrogram 1\t1\t{start}\t{end}\t{low_freq}\t{high_freq}\t{label.split('_', 1)[-1]}\t{code}\t{c[1]:.4f}\t{afile_path}\t{start}\n"
|
97
|
+
)
|
96
98
|
|
97
99
|
# Write result string to file
|
98
100
|
out_string += rstring
|
@@ -103,9 +105,7 @@ def generate_raven_table(timestamps: list[str], result: dict[str, list], afile_p
|
|
103
105
|
# of file durations during the analysis.
|
104
106
|
if len(out_string) == len(RAVEN_TABLE_HEADER) and cfg.OUTPUT_PATH is not None:
|
105
107
|
selection_id += 1
|
106
|
-
out_string +=
|
107
|
-
f"{selection_id}\tSpectrogram 1\t1\t0\t3\t{low_freq}\t{high_freq}\tnocall\tnocall\t1.0\t{afile_path}\t0\n"
|
108
|
-
)
|
108
|
+
out_string += f"{selection_id}\tSpectrogram 1\t1\t0\t3\t{low_freq}\t{high_freq}\tnocall\tnocall\t1.0\t{afile_path}\t0\n"
|
109
109
|
|
110
110
|
utils.save_result_file(result_path, out_string)
|
111
111
|
|
@@ -202,7 +202,18 @@ def generate_csv(timestamps: list[str], result: dict[str, list], afile_path: str
|
|
202
202
|
Returns:
|
203
203
|
None
|
204
204
|
"""
|
205
|
+
from birdnet_analyzer.analyze import POSSIBLE_ADDITIONAL_COLUMNS_MAP
|
206
|
+
|
205
207
|
out_string = CSV_HEADER
|
208
|
+
columns_map = {}
|
209
|
+
|
210
|
+
if cfg.ADDITIONAL_COLUMNS:
|
211
|
+
for col in cfg.ADDITIONAL_COLUMNS:
|
212
|
+
if col in POSSIBLE_ADDITIONAL_COLUMNS_MAP:
|
213
|
+
columns_map[col] = POSSIBLE_ADDITIONAL_COLUMNS_MAP[col]()
|
214
|
+
|
215
|
+
if columns_map:
|
216
|
+
out_string = out_string[:-1] + "," + ",".join(columns_map) + "\n"
|
206
217
|
|
207
218
|
for timestamp in timestamps:
|
208
219
|
rstring = ""
|
@@ -210,7 +221,12 @@ def generate_csv(timestamps: list[str], result: dict[str, list], afile_path: str
|
|
210
221
|
for c in result[timestamp]:
|
211
222
|
start, end = timestamp.split("-", 1)
|
212
223
|
label = cfg.TRANSLATED_LABELS[cfg.LABELS.index(c[0])]
|
213
|
-
rstring += f"{start},{end},{label.split('_', 1)[0]},{label.split('_', 1)[-1]},{c[1]:.4f},{afile_path}
|
224
|
+
rstring += f"{start},{end},{label.split('_', 1)[0]},{label.split('_', 1)[-1]},{c[1]:.4f},{afile_path}"
|
225
|
+
|
226
|
+
if columns_map:
|
227
|
+
rstring += "," + ",".join(str(val) for val in columns_map.values())
|
228
|
+
|
229
|
+
rstring += "\n"
|
214
230
|
|
215
231
|
# Write result string to file
|
216
232
|
out_string += rstring
|
@@ -364,29 +380,23 @@ def combine_csv_files(saved_results: list[str]):
|
|
364
380
|
Args:
|
365
381
|
saved_results (list[str]): A list of file paths to the CSV files to be combined.
|
366
382
|
"""
|
367
|
-
|
368
|
-
with open(os.path.join(cfg.OUTPUT_PATH, cfg.OUTPUT_CSV_FILENAME), "w", encoding="utf-8") as f:
|
369
|
-
f.write(CSV_HEADER)
|
383
|
+
out_string = ""
|
370
384
|
|
371
|
-
|
385
|
+
for rfile in saved_results:
|
386
|
+
try:
|
372
387
|
with open(rfile, encoding="utf-8") as rf:
|
373
|
-
|
374
|
-
|
388
|
+
lines = rf.readlines()
|
389
|
+
out_string += "".join(lines[1:] if out_string else lines)
|
375
390
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
# skip header and add to file
|
381
|
-
for line in lines[1:]:
|
382
|
-
f.write(line)
|
391
|
+
except Exception as ex:
|
392
|
+
print(f"Error: Cannot combine results from {rfile}.\n", flush=True)
|
393
|
+
utils.write_error_log(ex)
|
383
394
|
|
384
|
-
|
385
|
-
|
386
|
-
utils.write_error_log(ex)
|
395
|
+
with open(os.path.join(cfg.OUTPUT_PATH, cfg.OUTPUT_CSV_FILENAME), "w", encoding="utf-8") as f:
|
396
|
+
f.write(out_string)
|
387
397
|
|
388
398
|
|
389
|
-
def combine_results(saved_results:
|
399
|
+
def combine_results(saved_results: Sequence[dict[str, str]| None]):
|
390
400
|
"""
|
391
401
|
Combines various types of result files based on the configuration settings.
|
392
402
|
This function checks the types of results specified in the configuration
|
@@ -403,9 +413,6 @@ def combine_results(saved_results: list[dict[str, str]]):
|
|
403
413
|
if "table" in cfg.RESULT_TYPES:
|
404
414
|
combine_raven_tables([f["table"] for f in saved_results if f])
|
405
415
|
|
406
|
-
# if "r" in cfg.RESULT_TYPES:
|
407
|
-
# combine_rtable_files([f["r"] for f in saved_results if f])
|
408
|
-
|
409
416
|
if "kaleidoscope" in cfg.RESULT_TYPES:
|
410
417
|
combine_kaleidoscope_files([f["kaleidoscope"] for f in saved_results if f])
|
411
418
|
|
@@ -509,9 +516,7 @@ def get_raw_audio_from_file(fpath: str, offset, duration):
|
|
509
516
|
The signal split into a list of chunks.
|
510
517
|
"""
|
511
518
|
# Open file
|
512
|
-
sig, rate = audio.open_audio_file(
|
513
|
-
fpath, cfg.SAMPLE_RATE, offset, duration, cfg.BANDPASS_FMIN, cfg.BANDPASS_FMAX, cfg.AUDIO_SPEED
|
514
|
-
)
|
519
|
+
sig, rate = audio.open_audio_file(fpath, cfg.SAMPLE_RATE, offset, duration, cfg.BANDPASS_FMIN, cfg.BANDPASS_FMAX, cfg.AUDIO_SPEED)
|
515
520
|
|
516
521
|
# Split into raw audio chunks
|
517
522
|
return audio.split_signal(sig, rate, cfg.SIG_LENGTH, cfg.SIG_OVERLAP, cfg.SIG_MINLEN)
|
@@ -563,16 +568,14 @@ def get_result_file_names(fpath: str):
|
|
563
568
|
# if "r" in cfg.RESULT_TYPES:
|
564
569
|
# result_names["r"] = os.path.join(cfg.OUTPUT_PATH, file_shorthand + ".BirdNET.results.r.csv")
|
565
570
|
if "kaleidoscope" in cfg.RESULT_TYPES:
|
566
|
-
result_names["kaleidoscope"] = os.path.join(
|
567
|
-
cfg.OUTPUT_PATH, file_shorthand + ".BirdNET.results.kaleidoscope.csv"
|
568
|
-
)
|
571
|
+
result_names["kaleidoscope"] = os.path.join(cfg.OUTPUT_PATH, file_shorthand + ".BirdNET.results.kaleidoscope.csv")
|
569
572
|
if "csv" in cfg.RESULT_TYPES:
|
570
573
|
result_names["csv"] = os.path.join(cfg.OUTPUT_PATH, file_shorthand + ".BirdNET.results.csv")
|
571
574
|
|
572
575
|
return result_names
|
573
576
|
|
574
577
|
|
575
|
-
def analyze_file(item):
|
578
|
+
def analyze_file(item) -> dict[str, str] | None:
|
576
579
|
"""
|
577
580
|
Analyzes an audio file and generates prediction results.
|
578
581
|
|
@@ -606,7 +609,7 @@ def analyze_file(item):
|
|
606
609
|
print(f"Analyzing {fpath}", flush=True)
|
607
610
|
|
608
611
|
try:
|
609
|
-
fileLengthSeconds =
|
612
|
+
fileLengthSeconds = audio.get_audio_file_length(fpath)
|
610
613
|
except Exception as ex:
|
611
614
|
# Write error log
|
612
615
|
print(f"Error: Cannot analyze audio file {fpath}. File corrupt?\n", flush=True)
|
@@ -649,8 +652,7 @@ def analyze_file(item):
|
|
649
652
|
p_labels = [
|
650
653
|
p
|
651
654
|
for p in zip(cfg.LABELS, pred, strict=True)
|
652
|
-
if (cfg.TOP_N or p[1] >= cfg.MIN_CONFIDENCE)
|
653
|
-
and (not cfg.SPECIES_LIST or p[0] in cfg.SPECIES_LIST)
|
655
|
+
if (cfg.TOP_N or p[1] >= cfg.MIN_CONFIDENCE) and (not cfg.SPECIES_LIST or p[0] in cfg.SPECIES_LIST)
|
654
656
|
]
|
655
657
|
|
656
658
|
# Sort by score
|
birdnet_analyzer/audio.py
CHANGED
@@ -61,7 +61,7 @@ def get_audio_file_length(path):
|
|
61
61
|
"""
|
62
62
|
# Open file with librosa (uses ffmpeg or libav)
|
63
63
|
|
64
|
-
return librosa.get_duration(
|
64
|
+
return librosa.get_duration(path=path, sr=None)
|
65
65
|
|
66
66
|
|
67
67
|
def get_sample_rate(path: str):
|
@@ -184,7 +184,7 @@ def split_signal(sig, rate, seconds, overlap, minlen, amount=None):
|
|
184
184
|
|
185
185
|
# Split signal with overlap
|
186
186
|
sig_splits = []
|
187
|
-
sig_splits.extend(data[i : i + chunksize] for i in range(0, lastchunkpos, stepsize))
|
187
|
+
sig_splits.extend(data[i : i + chunksize] for i in range(0, lastchunkpos + 1, stepsize))
|
188
188
|
|
189
189
|
return sig_splits
|
190
190
|
|
birdnet_analyzer/cli.py
CHANGED
@@ -31,7 +31,7 @@ ASCII_LOGO = r"""
|
|
31
31
|
**=====
|
32
32
|
***+==
|
33
33
|
****+
|
34
|
-
"""
|
34
|
+
""" # noqa: W291
|
35
35
|
|
36
36
|
|
37
37
|
def io_args():
|
@@ -80,10 +80,9 @@ def bandpass_args():
|
|
80
80
|
|
81
81
|
return p
|
82
82
|
|
83
|
-
|
84
|
-
def species_args():
|
83
|
+
def species_list_args():
|
85
84
|
"""
|
86
|
-
Creates an argument parser for species-
|
85
|
+
Creates an argument parser for species-list arguments.
|
87
86
|
Returns:
|
88
87
|
argparse.ArgumentParser: The argument parser with the following arguments:
|
89
88
|
--lat (float): Recording location latitude. Set -1 to ignore. Default is -1.
|
@@ -104,17 +103,33 @@ def species_args():
|
|
104
103
|
default=-1,
|
105
104
|
help="Week of the year when the recording was made. Values in [1, 48] (4 weeks per month). Set -1 for year-round species list.",
|
106
105
|
)
|
107
|
-
p.add_argument(
|
108
|
-
"--slist",
|
109
|
-
help='Path to species list file or folder. If folder is provided, species list needs to be named "species_list.txt". If lat and lon are provided, this list will be ignored.',
|
110
|
-
)
|
111
106
|
p.add_argument(
|
112
107
|
"--sf_thresh",
|
113
108
|
type=lambda a: max(0.0001, min(0.99, float(a))),
|
114
109
|
default=cfg.LOCATION_FILTER_THRESHOLD,
|
115
110
|
help="Minimum species occurrence frequency threshold for location filter. Values in [0.0001, 0.99].",
|
116
111
|
)
|
112
|
+
return p
|
117
113
|
|
114
|
+
def species_args():
|
115
|
+
"""
|
116
|
+
Creates an argument parser for species-related arguments including the species-list arguments.
|
117
|
+
Returns:
|
118
|
+
argparse.ArgumentParser: The argument parser with the following arguments:
|
119
|
+
--lat (float): Recording location latitude. Set -1 to ignore. Default is -1.
|
120
|
+
--lon (float): Recording location longitude. Set -1 to ignore. Default is -1.
|
121
|
+
--week (int): Week of the year when the recording was made. Values in [1, 48] (4 weeks per month).
|
122
|
+
Set -1 for year-round species list. Default is -1.
|
123
|
+
--sf_thresh (float): Minimum species occurrence frequency threshold for location filter. Values in [0.01, 0.99].
|
124
|
+
Defaults to cfg.LOCATION_FILTER_THRESHOLD.
|
125
|
+
--slist (str): Path to species list file or folder. If folder is provided, species list needs to be named
|
126
|
+
"species_list.txt". If lat and lon are provided, this list will be ignored.
|
127
|
+
"""
|
128
|
+
p = species_list_args()
|
129
|
+
p.add_argument(
|
130
|
+
"--slist",
|
131
|
+
help='Path to species list file or folder. If folder is provided, species list needs to be named "species_list.txt". If lat and lon are provided, this list will be ignored.',
|
132
|
+
)
|
118
133
|
return p
|
119
134
|
|
120
135
|
|
@@ -309,6 +324,7 @@ def analyzer_parser():
|
|
309
324
|
Returns:
|
310
325
|
argparse.ArgumentParser: Configured argument parser for the BirdNET Analyzer CLI.
|
311
326
|
"""
|
327
|
+
from birdnet_analyzer.analyze import POSSIBLE_ADDITIONAL_COLUMNS_MAP
|
312
328
|
parents = [
|
313
329
|
io_args(),
|
314
330
|
bandpass_args(),
|
@@ -339,6 +355,14 @@ def analyzer_parser():
|
|
339
355
|
help="Specifies output format. Values in `['table', 'audacity', 'kaleidoscope', 'csv']`.",
|
340
356
|
action=UniqueSetAction,
|
341
357
|
)
|
358
|
+
parser.add_argument(
|
359
|
+
"--additional_columns",
|
360
|
+
choices=POSSIBLE_ADDITIONAL_COLUMNS_MAP.keys(),
|
361
|
+
nargs="+",
|
362
|
+
help="Additional columns to include in the output, only applied to the csv output format. "
|
363
|
+
+ f"Values in [{','.join(POSSIBLE_ADDITIONAL_COLUMNS_MAP.keys())}].",
|
364
|
+
action=UniqueSetAction,
|
365
|
+
)
|
342
366
|
parser.add_argument(
|
343
367
|
"--combine_results",
|
344
368
|
help="Also outputs a combined file for all the selected result types. If not set combined tables will be generated.",
|
@@ -404,6 +428,10 @@ def embeddings_parser():
|
|
404
428
|
help="Path to input file or folder.",
|
405
429
|
)
|
406
430
|
|
431
|
+
parser.add_argument(
|
432
|
+
"--file_output",
|
433
|
+
)
|
434
|
+
|
407
435
|
return parser
|
408
436
|
|
409
437
|
|
@@ -498,9 +526,7 @@ def segments_parser():
|
|
498
526
|
)
|
499
527
|
parser.add_argument("audio_input", metavar="INPUT", help="Path to folder containing audio files.")
|
500
528
|
parser.add_argument("-r", "--results", help="Path to folder containing result files. Defaults to the `input` path.")
|
501
|
-
parser.add_argument(
|
502
|
-
"-o", "--output", help="Output folder path for extracted segments. Defaults to the `input` path."
|
503
|
-
)
|
529
|
+
parser.add_argument("-o", "--output", help="Output folder path for extracted segments. Defaults to the `input` path.")
|
504
530
|
parser.add_argument(
|
505
531
|
"--max_segments",
|
506
532
|
type=lambda a: max(1, int(a)),
|
@@ -535,9 +561,7 @@ def server_parser():
|
|
535
561
|
parser.add_argument("-p", "--port", type=int, default=8080, help="Port of API endpoint server.")
|
536
562
|
parser.add_argument(
|
537
563
|
"--spath",
|
538
|
-
default="uploads/"
|
539
|
-
if os.environ.get("IS_GITHUB_RUNNER", "false").lower() == "true"
|
540
|
-
else os.path.join(SCRIPT_DIR, "uploads"),
|
564
|
+
default="uploads/" if os.environ.get("IS_GITHUB_RUNNER", "false").lower() == "true" else os.path.join(SCRIPT_DIR, "uploads"),
|
541
565
|
help="Path to folder where uploaded files should be stored.",
|
542
566
|
)
|
543
567
|
|
@@ -555,7 +579,7 @@ def species_parser():
|
|
555
579
|
"""
|
556
580
|
parser = argparse.ArgumentParser(
|
557
581
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
558
|
-
parents=[
|
582
|
+
parents=[species_list_args()],
|
559
583
|
)
|
560
584
|
parser.add_argument(
|
561
585
|
"output",
|
@@ -603,9 +627,7 @@ def train_parser():
|
|
603
627
|
metavar="INPUT",
|
604
628
|
help="Path to training data folder. Subfolder names are used as labels.",
|
605
629
|
)
|
606
|
-
parser.add_argument(
|
607
|
-
"--test_data", help="Path to test data folder. If not specified, a random validation split will be used."
|
608
|
-
)
|
630
|
+
parser.add_argument("--test_data", help="Path to test data folder. If not specified, a random validation split will be used.")
|
609
631
|
parser.add_argument(
|
610
632
|
"--crop_mode",
|
611
633
|
default=cfg.SAMPLE_CROP_MODE,
|
@@ -633,6 +655,7 @@ def train_parser():
|
|
633
655
|
)
|
634
656
|
parser.add_argument(
|
635
657
|
"--focal-loss",
|
658
|
+
dest="use_focal_loss",
|
636
659
|
action="store_true",
|
637
660
|
help="Use focal loss for training (helps with imbalanced classes and hard examples).",
|
638
661
|
)
|
birdnet_analyzer/config.py
CHANGED
@@ -69,8 +69,8 @@ LOCATION_FILTER_THRESHOLD: float = 0.03
|
|
69
69
|
|
70
70
|
# If None or empty file, no custom species list will be used
|
71
71
|
# Note: Entries in this list have to match entries from the LABELS_FILE
|
72
|
-
# We use the
|
73
|
-
CODES_FILE: str = os.path.join(SCRIPT_DIR, "
|
72
|
+
# We use the 2024 eBird taxonomy for species names (Clements list)
|
73
|
+
CODES_FILE: str = os.path.join(SCRIPT_DIR, "eBird_taxonomy_codes_2024E.json")
|
74
74
|
SPECIES_LIST_FILE: str = os.path.join(SCRIPT_DIR, "example/species_list.txt")
|
75
75
|
|
76
76
|
# Supported file types
|
@@ -114,6 +114,7 @@ USE_NOISE: bool = False
|
|
114
114
|
# 'audacity' denotes a TXT file with the same format as Audacity timeline labels
|
115
115
|
# 'csv' denotes a generic CSV file with start, end, species and confidence.
|
116
116
|
RESULT_TYPES: set[str] | list[str] = {"table"}
|
117
|
+
ADDITIONAL_COLUMNS: list[str] | None = None
|
117
118
|
OUTPUT_RAVEN_FILENAME: str = "BirdNET_SelectionTable.txt" # this is for combined Raven selection tables only
|
118
119
|
# OUTPUT_RTABLE_FILENAME: str = "BirdNET_RTable.csv"
|
119
120
|
OUTPUT_KALEIDOSCOPE_FILENAME: str = "BirdNET_Kaleidoscope.csv"
|
@@ -226,7 +227,7 @@ FILE_STORAGE_PATH: str = ""
|
|
226
227
|
# Path to custom trained classifier
|
227
228
|
# If None, no custom classifier will be used
|
228
229
|
# Make sure to set the LABELS_FILE above accordingly
|
229
|
-
CUSTOM_CLASSIFIER = None
|
230
|
+
CUSTOM_CLASSIFIER: str | None = None
|
230
231
|
|
231
232
|
######################
|
232
233
|
# Get and set config #
|