birdnet-analyzer 2.0.1__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 (121) hide show
  1. birdnet_analyzer/__init__.py +9 -9
  2. birdnet_analyzer/analyze/__init__.py +19 -5
  3. birdnet_analyzer/analyze/__main__.py +3 -3
  4. birdnet_analyzer/analyze/cli.py +30 -25
  5. birdnet_analyzer/analyze/core.py +268 -241
  6. birdnet_analyzer/analyze/utils.py +700 -692
  7. birdnet_analyzer/audio.py +368 -368
  8. birdnet_analyzer/cli.py +732 -709
  9. birdnet_analyzer/config.py +243 -242
  10. birdnet_analyzer/eBird_taxonomy_codes_2024E.json +13046 -0
  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 -69
  15. birdnet_analyzer/embeddings/utils.py +173 -179
  16. birdnet_analyzer/evaluation/__init__.py +189 -196
  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 -409
  20. birdnet_analyzer/evaluation/assessment/plotting.py +378 -379
  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 -175
  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 -28
  31. birdnet_analyzer/gui/assets/gui.js +89 -93
  32. birdnet_analyzer/gui/embeddings.py +638 -619
  33. birdnet_analyzer/gui/evaluation.py +801 -795
  34. birdnet_analyzer/gui/localization.py +75 -75
  35. birdnet_analyzer/gui/multi_file.py +265 -245
  36. birdnet_analyzer/gui/review.py +472 -519
  37. birdnet_analyzer/gui/segments.py +191 -191
  38. birdnet_analyzer/gui/settings.py +149 -128
  39. birdnet_analyzer/gui/single_file.py +264 -267
  40. birdnet_analyzer/gui/species.py +95 -95
  41. birdnet_analyzer/gui/train.py +687 -696
  42. birdnet_analyzer/gui/utils.py +803 -810
  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 -334
  81. birdnet_analyzer/lang/en.json +342 -334
  82. birdnet_analyzer/lang/fi.json +342 -334
  83. birdnet_analyzer/lang/fr.json +342 -334
  84. birdnet_analyzer/lang/id.json +342 -334
  85. birdnet_analyzer/lang/pt-br.json +342 -334
  86. birdnet_analyzer/lang/ru.json +342 -334
  87. birdnet_analyzer/lang/se.json +342 -334
  88. birdnet_analyzer/lang/tlh.json +342 -334
  89. birdnet_analyzer/lang/zh_TW.json +342 -334
  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 -426
  113. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.1.dist-info}/METADATA +147 -137
  114. birdnet_analyzer-2.1.1.dist-info/RECORD +124 -0
  115. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.1.dist-info}/WHEEL +1 -1
  116. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.1.dist-info}/licenses/LICENSE +18 -18
  117. birdnet_analyzer/eBird_taxonomy_codes_2021E.json +0 -25280
  118. birdnet_analyzer/playground.py +0 -5
  119. birdnet_analyzer-2.0.1.dist-info/RECORD +0 -125
  120. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.1.dist-info}/entry_points.txt +0 -0
  121. {birdnet_analyzer-2.0.1.dist-info → birdnet_analyzer-2.1.1.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,3 @@
1
- from birdnet_analyzer.embeddings.core import embeddings
2
-
3
- __all__ = ["embeddings"]
1
+ from birdnet_analyzer.embeddings.core import embeddings
2
+
3
+ __all__ = ["embeddings"]
@@ -1,3 +1,3 @@
1
- from birdnet_analyzer.embeddings.cli import main
2
-
3
- main()
1
+ from birdnet_analyzer.embeddings.cli import main
2
+
3
+ main()
@@ -1,12 +1,12 @@
1
- from birdnet_analyzer import embeddings
2
- from birdnet_analyzer.utils import runtime_error_handler
3
-
4
-
5
- @runtime_error_handler
6
- def main():
7
- from birdnet_analyzer import cli
8
-
9
- parser = cli.embeddings_parser()
10
- args = parser.parse_args()
11
-
12
- embeddings(**vars(args))
1
+ from birdnet_analyzer import embeddings
2
+ from birdnet_analyzer.utils import runtime_error_handler
3
+
4
+
5
+ @runtime_error_handler
6
+ def main():
7
+ from birdnet_analyzer import cli
8
+
9
+ parser = cli.embeddings_parser()
10
+ args = parser.parse_args()
11
+
12
+ embeddings(**vars(args))
@@ -1,69 +1,70 @@
1
- def embeddings(
2
- audio_input: str,
3
- database: str,
4
- *,
5
- overlap: float = 0.0,
6
- audio_speed: float = 1.0,
7
- fmin: int = 0,
8
- fmax: int = 15000,
9
- threads: int = 8,
10
- batch_size: int = 1,
11
- ):
12
- """
13
- Generates embeddings for audio files using the BirdNET-Analyzer.
14
- This function processes audio files to extract embeddings, which are
15
- representations of audio features. The embeddings can be used for
16
- further analysis or comparison.
17
- Args:
18
- audio_input (str): Path to the input audio file or directory containing audio files.
19
- database (str): Path to the database where embeddings will be stored.
20
- overlap (float, optional): Overlap between consecutive audio segments in seconds. Defaults to 0.0.
21
- audio_speed (float, optional): Speed factor for audio processing. Defaults to 1.0.
22
- fmin (int, optional): Minimum frequency (in Hz) for audio analysis. Defaults to 0.
23
- fmax (int, optional): Maximum frequency (in Hz) for audio analysis. Defaults to 15000.
24
- threads (int, optional): Number of threads to use for processing. Defaults to 8.
25
- batch_size (int, optional): Number of audio segments to process in a single batch. Defaults to 1.
26
- Raises:
27
- FileNotFoundError: If the input path or database path does not exist.
28
- ValueError: If any of the parameters are invalid.
29
- Note:
30
- Ensure that the required model files are downloaded and available before
31
- calling this function. The `ensure_model_exists` function is used to
32
- verify this.
33
- Example:
34
- embeddings(
35
- "path/to/audio",
36
- "path/to/database",
37
- overlap=0.5,
38
- audio_speed=1.0,
39
- fmin=500,
40
- fmax=10000,
41
- threads=4,
42
- batch_size=2
43
- )
44
- """
45
- from birdnet_analyzer.embeddings.utils import run
46
- from birdnet_analyzer.utils import ensure_model_exists
47
-
48
- ensure_model_exists()
49
- run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batch_size)
50
-
51
-
52
- def get_database(db_path: str):
53
- """Get the database object. Creates or opens the databse.
54
- Args:
55
- db: The path to the database.
56
- Returns:
57
- The database object.
58
- """
59
- import os
60
-
61
- from perch_hoplite.db import sqlite_usearch_impl
62
-
63
- if not os.path.exists(db_path):
64
- os.makedirs(os.path.dirname(db_path), exist_ok=True)
65
- return sqlite_usearch_impl.SQLiteUsearchDB.create(
66
- db_path=db_path,
67
- usearch_cfg=sqlite_usearch_impl.get_default_usearch_config(embedding_dim=1024), # TODO: dont hardcode this
68
- )
69
- return sqlite_usearch_impl.SQLiteUsearchDB.create(db_path=db_path)
1
+ def embeddings(
2
+ audio_input: str,
3
+ database: str,
4
+ *,
5
+ overlap: float = 0.0,
6
+ audio_speed: float = 1.0,
7
+ fmin: int = 0,
8
+ fmax: int = 15000,
9
+ threads: int = 8,
10
+ batch_size: int = 1,
11
+ file_output: str | None = None,
12
+ ):
13
+ """
14
+ Generates embeddings for audio files using the BirdNET-Analyzer.
15
+ This function processes audio files to extract embeddings, which are
16
+ representations of audio features. The embeddings can be used for
17
+ further analysis or comparison.
18
+ Args:
19
+ audio_input (str): Path to the input audio file or directory containing audio files.
20
+ database (str): Path to the database where embeddings will be stored.
21
+ overlap (float, optional): Overlap between consecutive audio segments in seconds. Defaults to 0.0.
22
+ audio_speed (float, optional): Speed factor for audio processing. Defaults to 1.0.
23
+ fmin (int, optional): Minimum frequency (in Hz) for audio analysis. Defaults to 0.
24
+ fmax (int, optional): Maximum frequency (in Hz) for audio analysis. Defaults to 15000.
25
+ threads (int, optional): Number of threads to use for processing. Defaults to 8.
26
+ batch_size (int, optional): Number of audio segments to process in a single batch. Defaults to 1.
27
+ Raises:
28
+ FileNotFoundError: If the input path or database path does not exist.
29
+ ValueError: If any of the parameters are invalid.
30
+ Note:
31
+ Ensure that the required model files are downloaded and available before
32
+ calling this function. The `ensure_model_exists` function is used to
33
+ verify this.
34
+ Example:
35
+ embeddings(
36
+ "path/to/audio",
37
+ "path/to/database",
38
+ overlap=0.5,
39
+ audio_speed=1.0,
40
+ fmin=500,
41
+ fmax=10000,
42
+ threads=4,
43
+ batch_size=2
44
+ )
45
+ """
46
+ from birdnet_analyzer.embeddings.utils import run
47
+ from birdnet_analyzer.utils import ensure_model_exists
48
+
49
+ ensure_model_exists()
50
+ run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batch_size, file_output)
51
+
52
+
53
+ def get_database(db_path: str):
54
+ """Get the database object. Creates or opens the databse.
55
+ Args:
56
+ db: The path to the database.
57
+ Returns:
58
+ The database object.
59
+ """
60
+ import os
61
+
62
+ from perch_hoplite.db import sqlite_usearch_impl
63
+
64
+ if not os.path.exists(db_path):
65
+ os.makedirs(os.path.dirname(db_path), exist_ok=True)
66
+ return sqlite_usearch_impl.SQLiteUsearchDB.create(
67
+ db_path=db_path,
68
+ usearch_cfg=sqlite_usearch_impl.get_default_usearch_config(embedding_dim=1024), # TODO: dont hardcode this
69
+ )
70
+ return sqlite_usearch_impl.SQLiteUsearchDB.create(db_path=db_path)
@@ -1,179 +1,173 @@
1
- """Module used to extract embeddings for samples."""
2
-
3
- import datetime
4
- import os
5
- from functools import partial
6
- from multiprocessing import Pool
7
-
8
- import numpy as np
9
- from ml_collections import ConfigDict
10
- from perch_hoplite.db import interface as hoplite
11
- from perch_hoplite.db import sqlite_usearch_impl
12
- from tqdm import tqdm
13
-
14
- import birdnet_analyzer.config as cfg
15
- from birdnet_analyzer import audio, model, utils
16
- from birdnet_analyzer.analyze.utils import get_raw_audio_from_file
17
- from birdnet_analyzer.embeddings.core import get_database
18
-
19
- DATASET_NAME: str = "birdnet_analyzer_dataset"
20
-
21
-
22
- def analyze_file(item, db: sqlite_usearch_impl.SQLiteUsearchDB):
23
- """Extracts the embeddings for a file.
24
-
25
- Args:
26
- item: (filepath, config)
27
- """
28
- # Get file path and restore cfg
29
- fpath: str = item[0]
30
- cfg.set_config(item[1])
31
-
32
- offset = 0
33
- duration = cfg.FILE_SPLITTING_DURATION
34
-
35
- try:
36
- fileLengthSeconds = int(audio.get_audio_file_length(fpath))
37
- except Exception as ex:
38
- # Write error log
39
- print(f"Error: Cannot analyze audio file {fpath}. File corrupt?\n", flush=True)
40
- utils.write_error_log(ex)
41
-
42
- return
43
-
44
- # Start time
45
- start_time = datetime.datetime.now()
46
-
47
- # Status
48
- print(f"Analyzing {fpath}", flush=True)
49
-
50
- source_id = fpath
51
-
52
- # Process each chunk
53
- try:
54
- while offset < fileLengthSeconds:
55
- chunks = get_raw_audio_from_file(fpath, offset, duration)
56
- start, end = offset, cfg.SIG_LENGTH + offset
57
- samples = []
58
- timestamps = []
59
-
60
- for c in range(len(chunks)):
61
- # Add to batch
62
- samples.append(chunks[c])
63
- timestamps.append([start, end])
64
-
65
- # Advance start and end
66
- start += cfg.SIG_LENGTH - cfg.SIG_OVERLAP
67
- end = start + cfg.SIG_LENGTH
68
-
69
- # Check if batch is full or last chunk
70
- if len(samples) < cfg.BATCH_SIZE and c < len(chunks) - 1:
71
- continue
72
-
73
- # Prepare sample and pass through model
74
- data = np.array(samples, dtype="float32")
75
- e = model.embeddings(data)
76
-
77
- # Add to results
78
- for i in range(len(samples)):
79
- # Get timestamp
80
- s_start, s_end = timestamps[i]
81
-
82
- # Check if embedding already exists
83
- existing_embedding = db.get_embeddings_by_source(DATASET_NAME, source_id, np.array([s_start, s_end]))
84
-
85
- if existing_embedding.size == 0:
86
- # Get prediction
87
- embeddings = e[i]
88
-
89
- # Store embeddings
90
- embeddings_source = hoplite.EmbeddingSource(DATASET_NAME, source_id, np.array([s_start, s_end]))
91
-
92
- # Insert into database
93
- db.insert_embedding(embeddings, embeddings_source)
94
- db.commit()
95
-
96
- # Reset batch
97
- samples = []
98
- timestamps = []
99
-
100
- offset = offset + duration
101
-
102
- except Exception as ex:
103
- # Write error log
104
- print(f"Error: Cannot analyze audio file {fpath}.", flush=True)
105
- utils.write_error_log(ex)
106
-
107
- return
108
-
109
- delta_time = (datetime.datetime.now() - start_time).total_seconds()
110
- print(f"Finished {fpath} in {delta_time:.2f} seconds", flush=True)
111
-
112
-
113
- def check_database_settings(db: sqlite_usearch_impl.SQLiteUsearchDB):
114
- try:
115
- settings = db.get_metadata("birdnet_analyzer_settings")
116
- if settings["BANDPASS_FMIN"] != cfg.BANDPASS_FMIN or settings["BANDPASS_FMAX"] != cfg.BANDPASS_FMAX or settings["AUDIO_SPEED"] != cfg.AUDIO_SPEED:
117
- raise ValueError(
118
- "Database settings do not match current configuration. DB Settings are: fmin:"
119
- + f"{settings['BANDPASS_FMIN']}, fmax: {settings['BANDPASS_FMAX']}, audio_speed: {settings['AUDIO_SPEED']}"
120
- )
121
- except KeyError:
122
- settings = ConfigDict({"BANDPASS_FMIN": cfg.BANDPASS_FMIN, "BANDPASS_FMAX": cfg.BANDPASS_FMAX, "AUDIO_SPEED": cfg.AUDIO_SPEED})
123
- db.insert_metadata("birdnet_analyzer_settings", settings)
124
- db.commit()
125
-
126
-
127
- def run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batchsize):
128
- ### Make sure to comment out appropriately if you are not using args. ###
129
-
130
- # Set input and output path
131
- cfg.INPUT_PATH = audio_input
132
-
133
- # Parse input files
134
- if os.path.isdir(cfg.INPUT_PATH):
135
- cfg.FILE_LIST = utils.collect_audio_files(cfg.INPUT_PATH)
136
- else:
137
- cfg.FILE_LIST = [cfg.INPUT_PATH]
138
-
139
- # Set overlap
140
- cfg.SIG_OVERLAP = max(0.0, min(2.9, float(overlap)))
141
-
142
- # Set audio speed
143
- cfg.AUDIO_SPEED = max(0.01, audio_speed)
144
-
145
- # Set bandpass frequency range
146
- cfg.BANDPASS_FMIN = max(0, min(cfg.SIG_FMAX, int(fmin)))
147
- cfg.BANDPASS_FMAX = max(cfg.SIG_FMIN, min(cfg.SIG_FMAX, int(fmax)))
148
-
149
- # Set number of threads
150
- if os.path.isdir(cfg.INPUT_PATH):
151
- cfg.CPU_THREADS = max(1, int(threads))
152
- cfg.TFLITE_THREADS = 1
153
- else:
154
- cfg.CPU_THREADS = 1
155
- cfg.TFLITE_THREADS = max(1, int(threads))
156
-
157
- cfg.CPU_THREADS = 1 # TODO: with the current implementation, we can't use more than 1 thread
158
-
159
- # Set batch size
160
- cfg.BATCH_SIZE = max(1, int(batchsize))
161
-
162
- # Add config items to each file list entry.
163
- # We have to do this for Windows which does not
164
- # support fork() and thus each process has to
165
- # have its own config. USE LINUX!
166
- flist = [(f, cfg.get_config()) for f in cfg.FILE_LIST]
167
-
168
- db = get_database(database)
169
- check_database_settings(db)
170
-
171
- # Analyze files
172
- if cfg.CPU_THREADS < 2:
173
- for entry in tqdm(flist):
174
- analyze_file(entry, db)
175
- else:
176
- with Pool(cfg.CPU_THREADS) as p:
177
- tqdm(p.imap(partial(analyze_file, db=db), flist))
178
-
179
- db.db.close()
1
+ """Module used to extract embeddings for samples."""
2
+
3
+ import datetime
4
+ import os
5
+ from functools import partial
6
+ from multiprocessing import Pool
7
+
8
+ import numpy as np
9
+ from ml_collections import ConfigDict
10
+ from perch_hoplite.db import interface as hoplite
11
+ from perch_hoplite.db import sqlite_usearch_impl
12
+ from tqdm import tqdm
13
+
14
+ import birdnet_analyzer.config as cfg
15
+ from birdnet_analyzer import utils
16
+ from birdnet_analyzer.analyze.utils import iterate_audio_chunks
17
+ from birdnet_analyzer.embeddings.core import get_database
18
+
19
+ DATASET_NAME: str = "birdnet_analyzer_dataset"
20
+
21
+
22
+ def analyze_file(item, db: sqlite_usearch_impl.SQLiteUsearchDB):
23
+ """Extracts the embeddings for a file.
24
+
25
+ Args:
26
+ item: (filepath, config)
27
+ """
28
+
29
+ # Get file path and restore cfg
30
+ fpath: str = item[0]
31
+ cfg.set_config(item[1])
32
+
33
+ # Start time
34
+ start_time = datetime.datetime.now()
35
+
36
+ # Status
37
+ print(f"Analyzing {fpath}", flush=True)
38
+
39
+ source_id = fpath
40
+
41
+ # Process each chunk
42
+ try:
43
+ for s_start, s_end, embeddings in iterate_audio_chunks(fpath, embeddings=True):
44
+ # Check if embedding already exists
45
+ existing_embedding = db.get_embeddings_by_source(DATASET_NAME, source_id, np.array([s_start, s_end]))
46
+
47
+ if existing_embedding.size == 0:
48
+ # Store embeddings
49
+ embeddings_source = hoplite.EmbeddingSource(DATASET_NAME, source_id, np.array([s_start, s_end]))
50
+
51
+ # Insert into database
52
+ db.insert_embedding(embeddings, embeddings_source)
53
+ db.commit()
54
+
55
+ except Exception as ex:
56
+ # Write error log
57
+ print(f"Error: Cannot analyze audio file {fpath}.", flush=True)
58
+ utils.write_error_log(ex)
59
+
60
+ return
61
+
62
+ delta_time = (datetime.datetime.now() - start_time).total_seconds()
63
+ print(f"Finished {fpath} in {delta_time:.2f} seconds", flush=True)
64
+
65
+
66
+ def check_database_settings(db: sqlite_usearch_impl.SQLiteUsearchDB):
67
+ try:
68
+ settings = db.get_metadata("birdnet_analyzer_settings")
69
+ if settings["BANDPASS_FMIN"] != cfg.BANDPASS_FMIN or settings["BANDPASS_FMAX"] != cfg.BANDPASS_FMAX or settings["AUDIO_SPEED"] != cfg.AUDIO_SPEED:
70
+ raise ValueError(
71
+ "Database settings do not match current configuration. DB Settings are: fmin:"
72
+ + f"{settings['BANDPASS_FMIN']}, fmax: {settings['BANDPASS_FMAX']}, audio_speed: {settings['AUDIO_SPEED']}"
73
+ )
74
+ except KeyError:
75
+ settings = ConfigDict({"BANDPASS_FMIN": cfg.BANDPASS_FMIN, "BANDPASS_FMAX": cfg.BANDPASS_FMAX, "AUDIO_SPEED": cfg.AUDIO_SPEED})
76
+ db.insert_metadata("birdnet_analyzer_settings", settings)
77
+ db.commit()
78
+
79
+
80
+ def create_file_output(output_path: str, db: sqlite_usearch_impl.SQLiteUsearchDB):
81
+ """Creates a file output for the database.
82
+
83
+ Args:
84
+ output_path: Path to the output file.
85
+ db: Database object.
86
+ """
87
+ # Check if output path exists
88
+ if not os.path.exists(output_path):
89
+ os.makedirs(output_path)
90
+ # Get all embeddings
91
+ embedding_ids = db.get_embedding_ids()
92
+
93
+ # Write embeddings to file
94
+ for embedding_id in embedding_ids:
95
+ embedding = db.get_embedding(embedding_id)
96
+ source = db.get_embedding_source(embedding_id)
97
+
98
+ # Get start and end time
99
+ start, end = source.offsets
100
+
101
+ source_id = source.source_id.rsplit(".", 1)[0]
102
+
103
+ filename = f"{source_id}_{start}_{end}.birdnet.embeddings.txt"
104
+
105
+ # Get the common prefix between the output path and the filename
106
+ common_prefix = os.path.commonpath([output_path, os.path.dirname(filename)])
107
+ relative_filename = os.path.relpath(filename, common_prefix)
108
+ target_path = os.path.join(output_path, relative_filename)
109
+
110
+ # Ensure the target directory exists
111
+ os.makedirs(os.path.dirname(target_path), exist_ok=True)
112
+
113
+ # Write embedding values to a text file
114
+ with open(target_path, "w") as f:
115
+ f.write(",".join(map(str, embedding.tolist())))
116
+
117
+
118
+ def run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batchsize, file_output):
119
+ ### Make sure to comment out appropriately if you are not using args. ###
120
+
121
+ # Set input and output path
122
+ cfg.INPUT_PATH = audio_input
123
+
124
+ # Parse input files
125
+ if os.path.isdir(cfg.INPUT_PATH):
126
+ cfg.FILE_LIST = utils.collect_audio_files(cfg.INPUT_PATH)
127
+ else:
128
+ cfg.FILE_LIST = [cfg.INPUT_PATH]
129
+
130
+ # Set overlap
131
+ cfg.SIG_OVERLAP = max(0.0, min(2.9, float(overlap)))
132
+
133
+ # Set audio speed
134
+ cfg.AUDIO_SPEED = max(0.01, audio_speed)
135
+
136
+ # Set bandpass frequency range
137
+ cfg.BANDPASS_FMIN = max(0, min(cfg.SIG_FMAX, int(fmin)))
138
+ cfg.BANDPASS_FMAX = max(cfg.SIG_FMIN, min(cfg.SIG_FMAX, int(fmax)))
139
+
140
+ # Set number of threads
141
+ if os.path.isdir(cfg.INPUT_PATH):
142
+ cfg.CPU_THREADS = max(1, int(threads))
143
+ cfg.TFLITE_THREADS = 1
144
+ else:
145
+ cfg.CPU_THREADS = 1
146
+ cfg.TFLITE_THREADS = max(1, int(threads))
147
+
148
+ cfg.CPU_THREADS = 1 # TODO: with the current implementation, we can't use more than 1 thread
149
+
150
+ # Set batch size
151
+ cfg.BATCH_SIZE = max(1, int(batchsize))
152
+
153
+ # Add config items to each file list entry.
154
+ # We have to do this for Windows which does not
155
+ # support fork() and thus each process has to
156
+ # have its own config. USE LINUX!
157
+ flist = [(f, cfg.get_config()) for f in cfg.FILE_LIST]
158
+
159
+ db = get_database(database)
160
+ check_database_settings(db)
161
+
162
+ # Analyze files
163
+ if cfg.CPU_THREADS < 2:
164
+ for entry in tqdm(flist):
165
+ analyze_file(entry, db)
166
+ else:
167
+ with Pool(cfg.CPU_THREADS) as p:
168
+ tqdm(p.imap(partial(analyze_file, db=db), flist))
169
+
170
+ if file_output:
171
+ create_file_output(file_output, db)
172
+
173
+ db.db.close()