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
@@ -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
  ]
@@ -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))
@@ -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 = "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" # noqa: E501
14
- RTABLE_HEADER = "filepath,start,end,scientific_name,common_name,confidence,lat,lon,week,overlap,sensitivity,min_conf,species_list,model\n"
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 += 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" # noqa: E501
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}\n"
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
- # Combine all files
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
- for rfile in saved_results:
385
+ for rfile in saved_results:
386
+ try:
372
387
  with open(rfile, encoding="utf-8") as rf:
373
- try:
374
- lines = rf.readlines()
388
+ lines = rf.readlines()
389
+ out_string += "".join(lines[1:] if out_string else lines)
375
390
 
376
- # make sure it's a selection table
377
- if "Start (s)" not in lines[0] or "Confidence" not in lines[0]:
378
- continue
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
- except Exception as ex:
385
- print(f"Error: Cannot combine results from {rfile}.\n", flush=True)
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: list[dict[str, str]]):
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 = int(audio.get_audio_file_length(fpath) / cfg.AUDIO_SPEED)
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(filename=path, sr=None)
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
- """ # noqa: W291
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-related arguments.
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=[species_args()],
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
  )
@@ -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 2021 eBird taxonomy for species names (Clements list)
73
- CODES_FILE: str = os.path.join(SCRIPT_DIR, "eBird_taxonomy_codes_2021E.json")
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 #