Skip to content

Commit c448d24

Browse files
Brought back the option to output files for embeddings (#679)
* . * embeddings documentation * audio-speed elements are not hidden in the gui after running embeddings * file output * cli argument for file output * gui with en + de translations * fix sorting for linter * removed file type when constructing the name for the embedding output file. progress on the documentation * . * more docu and fixed export filename generation in search script * added more docu * fixed errors when db directory selection is cancelled * removed unnecessary required and default for cli * . * added missing translations via copilot and slightly modiefied the test to show the missing keys * . * fixed the test
1 parent 68c0c62 commit c448d24

File tree

22 files changed

+260
-24
lines changed

22 files changed

+260
-24
lines changed

birdnet_analyzer/cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,10 @@ def embeddings_parser():
428428
help="Path to input file or folder.",
429429
)
430430

431+
parser.add_argument(
432+
"--file_output",
433+
)
434+
431435
return parser
432436

433437

birdnet_analyzer/embeddings/core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def embeddings(
88
fmax: int = 15000,
99
threads: int = 8,
1010
batch_size: int = 1,
11+
file_output: str | None = None,
1112
):
1213
"""
1314
Generates embeddings for audio files using the BirdNET-Analyzer.
@@ -46,7 +47,7 @@ def embeddings(
4647
from birdnet_analyzer.utils import ensure_model_exists
4748

4849
ensure_model_exists()
49-
run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batch_size)
50+
run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batch_size, file_output)
5051

5152

5253
def get_database(db_path: str):

birdnet_analyzer/embeddings/utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def analyze_file(item, db: sqlite_usearch_impl.SQLiteUsearchDB):
2525
Args:
2626
item: (filepath, config)
2727
"""
28+
2829
# Get file path and restore cfg
2930
fpath: str = item[0]
3031
cfg.set_config(item[1])
@@ -124,7 +125,44 @@ def check_database_settings(db: sqlite_usearch_impl.SQLiteUsearchDB):
124125
db.commit()
125126

126127

127-
def run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batchsize):
128+
def create_file_output(output_path: str, db: sqlite_usearch_impl.SQLiteUsearchDB):
129+
"""Creates a file output for the database.
130+
131+
Args:
132+
output_path: Path to the output file.
133+
db: Database object.
134+
"""
135+
# Check if output path exists
136+
if not os.path.exists(output_path):
137+
os.makedirs(output_path)
138+
# Get all embeddings
139+
embedding_ids = db.get_embedding_ids()
140+
141+
# Write embeddings to file
142+
for embedding_id in embedding_ids:
143+
embedding = db.get_embedding(embedding_id)
144+
source = db.get_embedding_source(embedding_id)
145+
146+
# Get start and end time
147+
start, end = source.offsets
148+
149+
source_id = source.source_id.rsplit(".", 1)[0]
150+
151+
filename = f"{source_id}_{start}_{end}.birdnet.embeddings.txt"
152+
153+
# Get the common prefix between the output path and the filename
154+
common_prefix = os.path.commonpath([output_path, os.path.dirname(filename)])
155+
relative_filename = os.path.relpath(filename, common_prefix)
156+
target_path = os.path.join(output_path, relative_filename)
157+
158+
# Ensure the target directory exists
159+
os.makedirs(os.path.dirname(target_path), exist_ok=True)
160+
161+
# Write embedding values to a text file
162+
with open(target_path, "w") as f:
163+
f.write(",".join(map(str, embedding.tolist())))
164+
165+
def run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batchsize, file_output):
128166
### Make sure to comment out appropriately if you are not using args. ###
129167

130168
# Set input and output path
@@ -176,4 +214,7 @@ def run(audio_input, database, overlap, audio_speed, fmin, fmax, threads, batchs
176214
with Pool(cfg.CPU_THREADS) as p:
177215
tqdm(p.imap(partial(analyze_file, db=db), flist))
178216

217+
if file_output:
218+
create_file_output(file_output, db)
219+
179220
db.db.close()

birdnet_analyzer/gui/embeddings.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from birdnet_analyzer.search.core import get_database as get_search_database
1111

1212
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
13-
PAGE_SIZE = 4
13+
PAGE_SIZE = 6
1414

1515

1616
def play_audio(audio_infos):
@@ -37,7 +37,7 @@ def update_export_state(audio_infos, checkbox_value, export_state: dict):
3737
return export_state
3838

3939

40-
def rum_embeddings_with_tqdm_tracking(
40+
def run_embeddings_with_tqdm_tracking(
4141
input_path,
4242
db_directory,
4343
db_name,
@@ -47,6 +47,7 @@ def rum_embeddings_with_tqdm_tracking(
4747
audio_speed,
4848
fmin,
4949
fmax,
50+
file_output,
5051
progress=gr.Progress(track_tqdm=True),
5152
):
5253
return run_embeddings(
@@ -59,6 +60,7 @@ def rum_embeddings_with_tqdm_tracking(
5960
audio_speed,
6061
fmin,
6162
fmax,
63+
file_output,
6264
progress,
6365
)
6466

@@ -74,6 +76,7 @@ def run_embeddings(
7476
audio_speed,
7577
fmin,
7678
fmax,
79+
file_output,
7780
progress,
7881
):
7982
from birdnet_analyzer.embeddings.utils import run
@@ -97,6 +100,7 @@ def run_embeddings(
97100
settings["BANDPASS_FMAX"],
98101
threads,
99102
batch_size,
103+
file_output,
100104
)
101105
except Exception as e:
102106
db.db.close()
@@ -106,11 +110,11 @@ def run_embeddings(
106110
if fmin is None or fmax is None or fmin < cfg.SIG_FMIN or fmax > cfg.SIG_FMAX or fmin > fmax:
107111
raise gr.Error(f"{loc.localize('validation-no-valid-frequency')} [{cfg.SIG_FMIN}, {cfg.SIG_FMAX}]") from e
108112

109-
run(input_path, db_path, overlap, audio_speed, fmin, fmax, threads, batch_size)
113+
run(input_path, db_path, overlap, audio_speed, fmin, fmax, threads, batch_size, file_output)
110114

111115
gr.Info(f"{loc.localize('embeddings-tab-finish-info')} {db_path}")
112116

113-
return gr.Plot(), gr.Slider(visible=False), gr.Number(visible=False), gr.Number(visible=False)
117+
return gr.Plot(), gr.Slider(interactive=False), gr.Number(interactive=False), gr.Number(interactive=False)
114118

115119

116120
@gu.gui_runtime_error_handler
@@ -275,7 +279,7 @@ def select_directory_and_update_tb(db_name):
275279
gr.Number(interactive=True),
276280
)
277281

278-
return None, None, gr.Slider(interactive=True), gr.Number(interactive=True), gr.Number(interactive=True)
282+
return None, gr.Textbox(visible=False), gr.Slider(interactive=True), gr.Number(interactive=True), gr.Number(interactive=True)
279283

280284
select_db_directory_btn.click(
281285
select_directory_and_update_tb,
@@ -284,6 +288,32 @@ def select_directory_and_update_tb(db_name):
284288
show_progress=False,
285289
)
286290

291+
with gr.Accordion(loc.localize("embedding-file-output-accordion-label"), open=False):
292+
with gr.Row():
293+
select_file_output_directory_btn = gr.Button(loc.localize("embeddings-select-file-output-directory-button-label"))
294+
295+
with gr.Row():
296+
file_output_tb = gr.Textbox(
297+
value=None,
298+
placeholder=loc.localize("embeddings-tab-file-output-directory-textbox-placeholder"),
299+
interactive=True,
300+
label=loc.localize("embeddings-tab-file-output-directory-textbox-label"),
301+
)
302+
303+
def select_file_output_directory_and_update_tb():
304+
dir_name = gu.select_directory(state_key="embeddings-file-output-dir", collect_files=False)
305+
306+
if dir_name:
307+
return dir_name
308+
309+
return None
310+
311+
select_file_output_directory_btn.click(
312+
select_file_output_directory_and_update_tb,
313+
inputs=[],
314+
outputs=[file_output_tb],
315+
)
316+
287317
def check_settings(dir_name, db_name):
288318
db_path = os.path.join(dir_name, db_name)
289319

@@ -316,7 +346,7 @@ def check_settings(dir_name, db_name):
316346
start_btn = gr.Button(loc.localize("embeddings-tab-start-button-label"), variant="huggingface")
317347

318348
start_btn.click(
319-
rum_embeddings_with_tqdm_tracking,
349+
run_embeddings_with_tqdm_tracking,
320350
inputs=[
321351
input_directory_state,
322352
db_directory_state,
@@ -327,10 +357,10 @@ def check_settings(dir_name, db_name):
327357
audio_speed_slider,
328358
fmin_number,
329359
fmax_number,
360+
file_output_tb,
330361
],
331362
outputs=[progress_plot, audio_speed_slider, fmin_number, fmax_number],
332363
show_progress_on=progress_plot,
333-
show_progress=True,
334364
)
335365

336366

@@ -409,17 +439,6 @@ def _build_search_tab():
409439
value="cosine",
410440
interactive=True,
411441
)
412-
max_samples_number = gr.Number(
413-
label=loc.localize("embeddings-search-max-samples-number-label"),
414-
value=10,
415-
interactive=True,
416-
)
417-
score_fn_select = gr.Radio(
418-
label=loc.localize("embeddings-search-score-fn-select-label"),
419-
choices=["cosine", "dot", "euclidean"],
420-
value="cosine",
421-
interactive=True,
422-
)
423442
search_btn = gr.Button(loc.localize("embeddings-search-start-button-label"), variant="huggingface")
424443

425444
with gr.Column():

birdnet_analyzer/lang/de.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
"embeddings-db-already-exists-info": "Die Datenbank ist bereits vorhanden, wobei die Optionen für Audiogeschwindigkeit und Bandpassfilter aus den Datenbankeinstellungen verwendet werden.",
1919
"embeddings-db-dir-validation-message": "Bitte wählen Sie ein Datenbankverzeichnis aus",
2020
"embeddings-db-name-validation-message": "Bitte geben Sie einen Namen für die Datenbank an",
21+
"embedding-file-output-accordion-label": "Dateiausgabe",
22+
"embeddings-tab-file-output-directory-textbox-placeholder": "Wenn nicht ausgewählt, werden keine Ausgabedateien erzeugt.",
23+
"embeddings-tab-file-output-directory-textbox-label": "Ausgabeverzeichnis",
24+
"embeddings-select-file-output-directory-button-label": "Ausgabeverzeichnis auswählen",
2125
"embeddings-extract-tab-title": "Extrahieren",
2226
"embeddings-input-dir-validation-message": "Bitte wählen Sie ein Eingabeverzeichnis aus",
2327
"embeddings-search-crop-mode-radio-info": "Anpassen, wie das Abfrage Beispiel zugeschnitten wird, falls es länger als der Modellinput ist.",

birdnet_analyzer/lang/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
"embeddings-db-already-exists-info": "Database already exists, using audio speed and bandpass filter options from database settings.",
1919
"embeddings-db-dir-validation-message": "Please select a directory for the database",
2020
"embeddings-db-name-validation-message": "Please specify a name for the database",
21+
"embedding-file-output-accordion-label": "File output settings",
22+
"embeddings-tab-file-output-directory-textbox-placeholder": "If not selected, file output will be omitted.",
23+
"embeddings-tab-file-output-directory-textbox-label": "File output directory",
24+
"embeddings-select-file-output-directory-button-label": "Select file output directory",
2125
"embeddings-extract-tab-title": "Extract",
2226
"embeddings-input-dir-validation-message": "Please select an input directory",
2327
"embeddings-search-crop-mode-radio-info": "Adjust how to crop the query sample if it is longer than the model input.",

birdnet_analyzer/lang/fi.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"embeddings-tab-select-input-directory-button-label": "Valitse syötehakemisto (rekursiivinen)",
5050
"embeddings-tab-start-button-label": "Poimi upotukset",
5151
"embeddings-tab-title": "Upotukset",
52+
"embedding-file-output-accordion-label": "Tiedostotulosteen asetukset",
53+
"embeddings-tab-file-output-directory-textbox-placeholder": "Jos ei valittu, tiedostotulostetta ei luoda.",
54+
"embeddings-tab-file-output-directory-textbox-label": "Tiedostotulosteen hakemisto",
55+
"embeddings-select-file-output-directory-button-label": "Valitse tiedostotulosteen hakemisto",
5256
"eval-tab-accuracy-checkbox-info": "Tarkkuus mittaa mallin oikeiden ennusteiden prosenttiosuutta.",
5357
"eval-tab-annotation-col-accordion-label": "Merkintäsarakkeet",
5458
"eval-tab-annotation-selection-button-label": "Valitse merkintäkansio",

birdnet_analyzer/lang/fr.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"embeddings-tab-select-input-directory-button-label": "Sélectionner le répertoire d'entrée (récursif)",
5050
"embeddings-tab-start-button-label": "Extraire les incorporations",
5151
"embeddings-tab-title": "Incorporations",
52+
"embedding-file-output-accordion-label": "Paramètres de sortie de fichier",
53+
"embeddings-tab-file-output-directory-textbox-placeholder": "Si non sélectionné, la sortie de fichier sera omise.",
54+
"embeddings-tab-file-output-directory-textbox-label": "Répertoire de sortie de fichier",
55+
"embeddings-select-file-output-directory-button-label": "Sélectionner le répertoire de sortie de fichier",
5256
"eval-tab-accuracy-checkbox-info": "La précision mesure le pourcentage de prédictions correctes faites par le modèle.",
5357
"eval-tab-annotation-col-accordion-label": "Colonnes d'annotation",
5458
"eval-tab-annotation-selection-button-label": "Sélectionner le répertoire d'annotations",

birdnet_analyzer/lang/id.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"embeddings-tab-select-input-directory-button-label": "Pilih direktori input (rekursif)",
5050
"embeddings-tab-start-button-label": "Ekstrak embedding",
5151
"embeddings-tab-title": "Embedding",
52+
"embedding-file-output-accordion-label": "Pengaturan file output",
53+
"embeddings-tab-file-output-directory-textbox-placeholder": "Jika tidak dipilih, file output akan diabaikan.",
54+
"embeddings-tab-file-output-directory-textbox-label": "Direktori file output",
55+
"embeddings-select-file-output-directory-button-label": "Pilih direktori file output",
5256
"eval-tab-accuracy-checkbox-info": "Akurasi mengukur persentase prediksi benar yang dibuat oleh model.",
5357
"eval-tab-annotation-col-accordion-label": "Kolom anotasi",
5458
"eval-tab-annotation-selection-button-label": "Pilih direktori anotasi",

birdnet_analyzer/lang/pt-br.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"embeddings-tab-select-input-directory-button-label": "Selecionar diretório de entrada (recursivo)",
5050
"embeddings-tab-start-button-label": "Extrair embeddings",
5151
"embeddings-tab-title": "Embeddings",
52+
"embedding-file-output-accordion-label": "Configurações de saída de arquivo",
53+
"embeddings-tab-file-output-directory-textbox-placeholder": "Se não selecionado, a saída de arquivo será omitida.",
54+
"embeddings-tab-file-output-directory-textbox-label": "Diretório de saída de arquivo",
55+
"embeddings-select-file-output-directory-button-label": "Selecionar diretório de saída de arquivo",
5256
"eval-tab-accuracy-checkbox-info": "A precisão mede a porcentagem de previsões corretas feitas pelo modelo.",
5357
"eval-tab-annotation-col-accordion-label": "Colunas de anotação",
5458
"eval-tab-annotation-selection-button-label": "Selecionar diretório de anotações",

birdnet_analyzer/lang/ru.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"embeddings-tab-select-input-directory-button-label": "Выбрать входную папку (рекурсивно)",
5050
"embeddings-tab-start-button-label": "Извлечь эмбеддинги",
5151
"embeddings-tab-title": "Эмбеддинги",
52+
"embedding-file-output-accordion-label": "Настройки вывода файлов",
53+
"embeddings-tab-file-output-directory-textbox-placeholder": "Если не выбрано, вывод файлов будет пропущен.",
54+
"embeddings-tab-file-output-directory-textbox-label": "Каталог вывода файлов",
55+
"embeddings-select-file-output-directory-button-label": "Выбрать каталог вывода файлов",
5256
"eval-tab-accuracy-checkbox-info": "Точность измеряет процент правильных прогнозов, сделанных моделью.",
5357
"eval-tab-annotation-col-accordion-label": "Колонки аннотаций",
5458
"eval-tab-annotation-selection-button-label": "Выбрать каталог аннотаций",

birdnet_analyzer/lang/se.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"embeddings-tab-select-input-directory-button-label": "Välj indatakatalog (rekursiv)",
5050
"embeddings-tab-start-button-label": "Extrahera inbäddningar",
5151
"embeddings-tab-title": "Inbäddningar",
52+
"embedding-file-output-accordion-label": "Filutdata-inställningar",
53+
"embeddings-tab-file-output-directory-textbox-placeholder": "Om inte vald, kommer filutdata att utelämnas.",
54+
"embeddings-tab-file-output-directory-textbox-label": "Filutdatakatalog",
55+
"embeddings-select-file-output-directory-button-label": "Välj filutdatakatalog",
5256
"eval-tab-accuracy-checkbox-info": "Noggrannhet mäter procentandelen korrekta förutsägelser som modellen gör.",
5357
"eval-tab-annotation-col-accordion-label": "Anteckningskolumner",
5458
"eval-tab-annotation-selection-button-label": "Välj anteckningskatalog",

birdnet_analyzer/lang/tlh.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"embeddings-tab-select-input-directory-button-label": "wav Daq yIwIv (rap)",
5050
"embeddings-tab-start-button-label": "qelmey yInob",
5151
"embeddings-tab-title": "qelmey",
52+
"embedding-file-output-accordion-label": "wavmey chu' chut mutlh",
53+
"embeddings-tab-file-output-directory-textbox-placeholder": "wIvHa'chugh, wavmey chu'Ha'.",
54+
"embeddings-tab-file-output-directory-textbox-label": "wavmey chu' Daq",
55+
"embeddings-select-file-output-directory-button-label": "wavmey chu' Daq yIwIv",
5256
"eval-tab-accuracy-checkbox-info": "qel patlh vIt.",
5357
"eval-tab-annotation-col-accordion-label": "QInmey",
5458
"eval-tab-annotation-selection-button-label": "QIn Daq yIwIv",

birdnet_analyzer/lang/zh_TW.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"embeddings-tab-select-input-directory-button-label": "選擇輸入目錄(遞歸)",
5050
"embeddings-tab-start-button-label": "提取嵌入",
5151
"embeddings-tab-title": "嵌入",
52+
"embedding-file-output-accordion-label": "檔案輸出設定",
53+
"embeddings-tab-file-output-directory-textbox-placeholder": "若無選擇,將不進行檔案輸出。",
54+
"embeddings-tab-file-output-directory-textbox-label": "檔案輸出資料夾",
55+
"embeddings-select-file-output-directory-button-label": "選擇檔案輸出資料夾",
5256
"eval-tab-accuracy-checkbox-info": "準確度測量模型做出正確預測的百分比。",
5357
"eval-tab-annotation-col-accordion-label": "註釋欄位",
5458
"eval-tab-annotation-selection-button-label": "選擇註釋目錄",

birdnet_analyzer/search/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def search(
6868
offset = embedding_source.offsets[0] * audio_speed
6969
duration = cfg.SIG_LENGTH * audio_speed
7070
sig, rate = audio.open_audio_file(file, offset=offset, duration=duration, sample_rate=None)
71-
result_path = os.path.join(output, f"{file[4]:.5f}_{filebasename}_{offset}_{offset + duration}.wav")
71+
result_path = os.path.join(output, f"{r.sort_score:.5f}_{filebasename}_{offset}_{offset + duration}.wav")
7272
audio.save_signal(sig, result_path, rate)
7373

7474

docs/best-practices.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ Best practices
66

77
best-practices/species-lists
88
best-practices/segment-review
9-
best-practices/training
9+
best-practices/training
10+
best-practices/embeddings

0 commit comments

Comments
 (0)