birdnet-analyzer 2.0.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/__init__.py +8 -0
- birdnet_analyzer/analyze/__init__.py +5 -0
- birdnet_analyzer/analyze/__main__.py +4 -0
- birdnet_analyzer/analyze/cli.py +25 -0
- birdnet_analyzer/analyze/core.py +245 -0
- birdnet_analyzer/analyze/utils.py +701 -0
- birdnet_analyzer/audio.py +372 -0
- birdnet_analyzer/cli.py +707 -0
- birdnet_analyzer/config.py +242 -0
- birdnet_analyzer/eBird_taxonomy_codes_2021E.json +25280 -0
- birdnet_analyzer/embeddings/__init__.py +4 -0
- birdnet_analyzer/embeddings/__main__.py +3 -0
- birdnet_analyzer/embeddings/cli.py +13 -0
- birdnet_analyzer/embeddings/core.py +70 -0
- birdnet_analyzer/embeddings/utils.py +193 -0
- birdnet_analyzer/evaluation/__init__.py +195 -0
- birdnet_analyzer/evaluation/__main__.py +3 -0
- birdnet_analyzer/gui/__init__.py +23 -0
- birdnet_analyzer/gui/__main__.py +3 -0
- birdnet_analyzer/gui/analysis.py +174 -0
- birdnet_analyzer/gui/assets/arrow_down.svg +4 -0
- birdnet_analyzer/gui/assets/arrow_left.svg +4 -0
- birdnet_analyzer/gui/assets/arrow_right.svg +4 -0
- birdnet_analyzer/gui/assets/arrow_up.svg +4 -0
- birdnet_analyzer/gui/assets/gui.css +29 -0
- birdnet_analyzer/gui/assets/gui.js +94 -0
- birdnet_analyzer/gui/assets/img/birdnet-icon.ico +0 -0
- birdnet_analyzer/gui/assets/img/birdnet_logo.png +0 -0
- birdnet_analyzer/gui/assets/img/birdnet_logo_no_transparent.png +0 -0
- birdnet_analyzer/gui/assets/img/clo-logo-bird.svg +1 -0
- birdnet_analyzer/gui/embeddings.py +620 -0
- birdnet_analyzer/gui/evaluation.py +813 -0
- birdnet_analyzer/gui/localization.py +68 -0
- birdnet_analyzer/gui/multi_file.py +246 -0
- birdnet_analyzer/gui/review.py +527 -0
- birdnet_analyzer/gui/segments.py +191 -0
- birdnet_analyzer/gui/settings.py +129 -0
- birdnet_analyzer/gui/single_file.py +269 -0
- birdnet_analyzer/gui/species.py +95 -0
- birdnet_analyzer/gui/train.py +698 -0
- birdnet_analyzer/gui/utils.py +808 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_af.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ar.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_bg.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ca.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_cs.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_da.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_de.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_el.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_en_uk.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_es.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_fi.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_fr.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_he.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_hr.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_hu.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_in.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_is.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_it.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ja.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ko.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_lt.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ml.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_nl.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_no.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_pl.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_pt_BR.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_pt_PT.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ro.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_ru.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sk.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sl.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sr.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_sv.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_th.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_tr.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_uk.txt +6522 -0
- birdnet_analyzer/labels/V2.4/BirdNET_GLOBAL_6K_V2.4_Labels_zh.txt +6522 -0
- birdnet_analyzer/lang/de.json +335 -0
- birdnet_analyzer/lang/en.json +335 -0
- birdnet_analyzer/lang/fi.json +335 -0
- birdnet_analyzer/lang/fr.json +335 -0
- birdnet_analyzer/lang/id.json +335 -0
- birdnet_analyzer/lang/pt-br.json +335 -0
- birdnet_analyzer/lang/ru.json +335 -0
- birdnet_analyzer/lang/se.json +335 -0
- birdnet_analyzer/lang/tlh.json +335 -0
- birdnet_analyzer/lang/zh_TW.json +335 -0
- birdnet_analyzer/model.py +1243 -0
- birdnet_analyzer/search/__init__.py +3 -0
- birdnet_analyzer/search/__main__.py +3 -0
- birdnet_analyzer/search/cli.py +12 -0
- birdnet_analyzer/search/core.py +78 -0
- birdnet_analyzer/search/utils.py +111 -0
- birdnet_analyzer/segments/__init__.py +3 -0
- birdnet_analyzer/segments/__main__.py +3 -0
- birdnet_analyzer/segments/cli.py +14 -0
- birdnet_analyzer/segments/core.py +78 -0
- birdnet_analyzer/segments/utils.py +394 -0
- birdnet_analyzer/species/__init__.py +3 -0
- birdnet_analyzer/species/__main__.py +3 -0
- birdnet_analyzer/species/cli.py +14 -0
- birdnet_analyzer/species/core.py +35 -0
- birdnet_analyzer/species/utils.py +75 -0
- birdnet_analyzer/train/__init__.py +3 -0
- birdnet_analyzer/train/__main__.py +3 -0
- birdnet_analyzer/train/cli.py +14 -0
- birdnet_analyzer/train/core.py +113 -0
- birdnet_analyzer/train/utils.py +847 -0
- birdnet_analyzer/translate.py +104 -0
- birdnet_analyzer/utils.py +419 -0
- birdnet_analyzer-2.0.0.dist-info/METADATA +129 -0
- birdnet_analyzer-2.0.0.dist-info/RECORD +117 -0
- birdnet_analyzer-2.0.0.dist-info/WHEEL +5 -0
- birdnet_analyzer-2.0.0.dist-info/entry_points.txt +11 -0
- birdnet_analyzer-2.0.0.dist-info/licenses/LICENSE +19 -0
- birdnet_analyzer-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
"""Module for translating species labels.
|
2
|
+
|
3
|
+
Can be used to translate species names into other languages.
|
4
|
+
|
5
|
+
Uses the requests to the eBird-API.
|
6
|
+
"""
|
7
|
+
import json
|
8
|
+
import os
|
9
|
+
import urllib.request
|
10
|
+
|
11
|
+
import birdnet_analyzer.config as cfg
|
12
|
+
import birdnet_analyzer.utils as utils
|
13
|
+
|
14
|
+
LOCALES = ['af', 'ar', 'cs', 'da', 'de', 'en_uk', 'es', 'fi', 'fr', 'hu', 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt_BR', 'pt_PT', 'ro', 'ru', 'sk', 'sl', 'sv', 'th', 'tr', 'uk', 'zh']
|
15
|
+
""" Locales for 26 common languages (according to GitHub Copilot) """
|
16
|
+
|
17
|
+
API_TOKEN = "yourAPIToken"
|
18
|
+
""" Sign up for your personal access token here: https://ebird.org/api/keygen """
|
19
|
+
|
20
|
+
|
21
|
+
def get_locale_data(locale: str):
|
22
|
+
"""Download eBird locale species data.
|
23
|
+
|
24
|
+
Requests the locale data through the eBird API.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
locale: Two character string of a language.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
A data object containing the response from eBird.
|
31
|
+
"""
|
32
|
+
url = "https://api.ebird.org/v2/ref/taxonomy/ebird?cat=species&fmt=json&locale=" + locale
|
33
|
+
header = {"X-eBirdAPIToken": API_TOKEN}
|
34
|
+
|
35
|
+
req = urllib.request.Request(url, headers=header)
|
36
|
+
response = urllib.request.urlopen(req)
|
37
|
+
|
38
|
+
return json.loads(response.read())
|
39
|
+
|
40
|
+
|
41
|
+
def translate(locale: str):
|
42
|
+
"""Translates species names for a locale.
|
43
|
+
|
44
|
+
Translates species names for the given language with the eBird API.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
locale: Two character string of a language.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
The translated list of labels.
|
51
|
+
"""
|
52
|
+
print(f"Translating species names for {locale}...", end="", flush=True)
|
53
|
+
|
54
|
+
# Get locale data
|
55
|
+
data = get_locale_data(locale)
|
56
|
+
|
57
|
+
# Create list of translated labels
|
58
|
+
labels: list[str] = []
|
59
|
+
|
60
|
+
for label in cfg.LABELS:
|
61
|
+
has_translation = False
|
62
|
+
for entry in data:
|
63
|
+
if label.split("_", 1)[0] == entry["sciName"]:
|
64
|
+
labels.append("{}_{}".format(label.split("_", 1)[0], entry["comName"]))
|
65
|
+
has_translation = True
|
66
|
+
break
|
67
|
+
if not has_translation:
|
68
|
+
labels.append(label)
|
69
|
+
|
70
|
+
print("Done.", flush=True)
|
71
|
+
|
72
|
+
return labels
|
73
|
+
|
74
|
+
|
75
|
+
def save_labels_file(labels: list[str], locale: str):
|
76
|
+
"""Saves localized labels to a file.
|
77
|
+
|
78
|
+
Saves the given labels into a file with the format:
|
79
|
+
"{config.LABELSFILE}_{locale}.txt"
|
80
|
+
|
81
|
+
Args:
|
82
|
+
labels: List of labels.
|
83
|
+
locale: Two character string of a language.
|
84
|
+
"""
|
85
|
+
# Create folder
|
86
|
+
os.makedirs(cfg.TRANSLATED_LABELS_PATH, exist_ok=True)
|
87
|
+
|
88
|
+
# Save labels file
|
89
|
+
fpath = os.path.join(
|
90
|
+
cfg.TRANSLATED_LABELS_PATH, "{}_{}.txt".format(os.path.basename(cfg.LABELS_FILE).rsplit(".", 1)[0], locale)
|
91
|
+
)
|
92
|
+
with open(fpath, "w", encoding="utf-8") as f:
|
93
|
+
for label in labels:
|
94
|
+
f.write(label + "\n")
|
95
|
+
|
96
|
+
|
97
|
+
if __name__ == "__main__":
|
98
|
+
# Load labels
|
99
|
+
cfg.LABELS = utils.read_lines(cfg.LABELS_FILE)
|
100
|
+
|
101
|
+
# Translate labels
|
102
|
+
for locale in LOCALES:
|
103
|
+
labels = translate(locale)
|
104
|
+
save_labels_file(labels, locale)
|
@@ -0,0 +1,419 @@
|
|
1
|
+
"""Module containing common function."""
|
2
|
+
|
3
|
+
import sys
|
4
|
+
import itertools
|
5
|
+
import os
|
6
|
+
import traceback
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
import birdnet_analyzer.config as cfg
|
10
|
+
|
11
|
+
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
|
12
|
+
FROZEN = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
|
13
|
+
|
14
|
+
|
15
|
+
def runtime_error_handler(f: callable):
|
16
|
+
"""Decorator to catch runtime errors and write them to the error log.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
f: The function to be decorated.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
The decorated function.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def wrapper(*args, **kwargs):
|
26
|
+
try:
|
27
|
+
return f(*args, **kwargs)
|
28
|
+
except Exception as ex:
|
29
|
+
write_error_log(ex)
|
30
|
+
raise
|
31
|
+
|
32
|
+
return wrapper
|
33
|
+
|
34
|
+
|
35
|
+
def batched(iterable, n, *, strict=False):
|
36
|
+
# TODO: Remove this function when Python 3.12 is the minimum version
|
37
|
+
# batched('ABCDEFG', 3) → ABC DEF G
|
38
|
+
if n < 1:
|
39
|
+
raise ValueError("n must be at least one")
|
40
|
+
iterator = iter(iterable)
|
41
|
+
while batch := tuple(itertools.islice(iterator, n)):
|
42
|
+
if strict and len(batch) != n:
|
43
|
+
raise ValueError("batched(): incomplete batch")
|
44
|
+
yield batch
|
45
|
+
|
46
|
+
|
47
|
+
def spectrogram_from_file(path, fig_num=None, fig_size=None, offset=0, duration=None, fmin=None, fmax=None, speed=1.0):
|
48
|
+
"""
|
49
|
+
Generate a spectrogram from an audio file.
|
50
|
+
|
51
|
+
Parameters:
|
52
|
+
path (str): The path to the audio file.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
matplotlib.figure.Figure: The generated spectrogram figure.
|
56
|
+
"""
|
57
|
+
import birdnet_analyzer.audio as audio
|
58
|
+
|
59
|
+
# s, sr = librosa.load(path, offset=offset, duration=duration)
|
60
|
+
s, sr = audio.open_audio_file(path, offset=offset, duration=duration, fmin=fmin, fmax=fmax, speed=speed)
|
61
|
+
|
62
|
+
return spectrogram_from_audio(s, sr, fig_num, fig_size)
|
63
|
+
|
64
|
+
|
65
|
+
def spectrogram_from_audio(s, sr, fig_num=None, fig_size=None):
|
66
|
+
"""
|
67
|
+
Generate a spectrogram from an audio signal.
|
68
|
+
|
69
|
+
Parameters:
|
70
|
+
s: The signal
|
71
|
+
sr: The sample rate
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
matplotlib.figure.Figure: The generated spectrogram figure.
|
75
|
+
"""
|
76
|
+
import librosa
|
77
|
+
import librosa.display
|
78
|
+
import matplotlib
|
79
|
+
import matplotlib.pyplot as plt
|
80
|
+
import numpy as np
|
81
|
+
|
82
|
+
matplotlib.use("agg")
|
83
|
+
|
84
|
+
if isinstance(fig_size, tuple):
|
85
|
+
f = plt.figure(fig_num, figsize=fig_size)
|
86
|
+
elif fig_size == "auto":
|
87
|
+
duration = librosa.get_duration(y=s, sr=sr)
|
88
|
+
width = min(12, max(3, duration / 10))
|
89
|
+
f = plt.figure(fig_num, figsize=(width, 3))
|
90
|
+
else:
|
91
|
+
f = plt.figure(fig_num)
|
92
|
+
|
93
|
+
f.clf()
|
94
|
+
|
95
|
+
ax = f.add_subplot(111)
|
96
|
+
|
97
|
+
ax.set_axis_off()
|
98
|
+
f.tight_layout(pad=0)
|
99
|
+
|
100
|
+
D = librosa.stft(s, n_fft=1024, hop_length=512) # STFT of y
|
101
|
+
S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
|
102
|
+
|
103
|
+
return librosa.display.specshow(S_db, ax=ax, n_fft=1024, hop_length=512).figure
|
104
|
+
|
105
|
+
|
106
|
+
def collect_audio_files(path: str, max_files: int = None):
|
107
|
+
"""Collects all audio files in the given directory.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
path: The directory to be searched.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
A sorted list of all audio files in the directory.
|
114
|
+
"""
|
115
|
+
# Get all files in directory with os.walk
|
116
|
+
files = []
|
117
|
+
|
118
|
+
for root, _, flist in os.walk(path):
|
119
|
+
for f in flist:
|
120
|
+
if not f.startswith(".") and f.rsplit(".", 1)[-1].lower() in cfg.ALLOWED_FILETYPES:
|
121
|
+
files.append(os.path.join(root, f))
|
122
|
+
|
123
|
+
if max_files and len(files) >= max_files:
|
124
|
+
return sorted(files)
|
125
|
+
|
126
|
+
return sorted(files)
|
127
|
+
|
128
|
+
|
129
|
+
def collect_all_files(path: str, filetypes: list[str], pattern: str = ""):
|
130
|
+
"""Collects all files of the given filetypes in the given directory.
|
131
|
+
|
132
|
+
Args:
|
133
|
+
path: The directory to be searched.
|
134
|
+
filetypes: A list of filetypes to be collected.
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
A sorted list of all files in the directory.
|
138
|
+
"""
|
139
|
+
|
140
|
+
files = []
|
141
|
+
|
142
|
+
for root, _, flist in os.walk(path):
|
143
|
+
for f in flist:
|
144
|
+
if not f.startswith(".") and f.rsplit(".", 1)[-1].lower() in filetypes and (pattern in f or not pattern):
|
145
|
+
files.append(os.path.join(root, f))
|
146
|
+
|
147
|
+
return sorted(files)
|
148
|
+
|
149
|
+
|
150
|
+
def read_lines(path: str):
|
151
|
+
"""Reads the lines into a list.
|
152
|
+
|
153
|
+
Opens the file and reads its contents into a list.
|
154
|
+
It is expected to have one line for each species or label.
|
155
|
+
|
156
|
+
Args:
|
157
|
+
path: Absolute path to the species file.
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
A list of all species inside the file.
|
161
|
+
"""
|
162
|
+
return Path(path).read_text(encoding="utf-8").splitlines() if path else []
|
163
|
+
|
164
|
+
|
165
|
+
def list_subdirectories(path: str):
|
166
|
+
"""Lists all directories inside a path.
|
167
|
+
|
168
|
+
Retrieves all the subdirectories in a given path without recursion.
|
169
|
+
|
170
|
+
Args:
|
171
|
+
path: Directory to be searched.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
A filter sequence containing the absolute paths to all directories.
|
175
|
+
"""
|
176
|
+
return filter(lambda el: os.path.isdir(os.path.join(path, el)), os.listdir(path))
|
177
|
+
|
178
|
+
|
179
|
+
def save_to_cache(path, x_train, y_train, x_test, y_test, labels):
|
180
|
+
"""Saves training data to cache.
|
181
|
+
|
182
|
+
Args:
|
183
|
+
path: Path to the cache file.
|
184
|
+
x_train: Training samples.
|
185
|
+
y_train: Training labels.
|
186
|
+
x_test: Test samples.
|
187
|
+
y_test: Test labels.
|
188
|
+
labels: Labels.
|
189
|
+
"""
|
190
|
+
import numpy as np
|
191
|
+
|
192
|
+
# Make directory if needed
|
193
|
+
directory = os.path.dirname(path)
|
194
|
+
if directory and not os.path.exists(directory):
|
195
|
+
os.makedirs(directory)
|
196
|
+
|
197
|
+
# Save cache file with training data, test data, labels and configuration
|
198
|
+
np.savez(
|
199
|
+
path,
|
200
|
+
x_train=x_train,
|
201
|
+
y_train=y_train,
|
202
|
+
x_test=x_test,
|
203
|
+
y_test=y_test,
|
204
|
+
labels=np.array(labels, dtype=object),
|
205
|
+
binary_classification=cfg.BINARY_CLASSIFICATION,
|
206
|
+
multi_label=cfg.MULTI_LABEL,
|
207
|
+
fmin=cfg.BANDPASS_FMIN,
|
208
|
+
fmax=cfg.BANDPASS_FMAX,
|
209
|
+
audio_speed=cfg.AUDIO_SPEED,
|
210
|
+
crop_mode=cfg.SAMPLE_CROP_MODE,
|
211
|
+
overlap=cfg.SIG_OVERLAP,
|
212
|
+
)
|
213
|
+
|
214
|
+
|
215
|
+
def load_from_cache(path):
|
216
|
+
"""Loads training data from cache.
|
217
|
+
|
218
|
+
Args:
|
219
|
+
path: Path to the cache file.
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
A tuple of (x_train, y_train, labels, binary_classification, multi_label).
|
223
|
+
"""
|
224
|
+
import numpy as np
|
225
|
+
|
226
|
+
# Load cache file
|
227
|
+
data = np.load(path, allow_pickle=True)
|
228
|
+
|
229
|
+
# Check if cache contains needed preprocessing parameters
|
230
|
+
if "fmin" in data and "fmax" in data and "audio_speed" in data and "crop_mode" in data and "overlap" in data:
|
231
|
+
# Check if preprocessing parameters match current settings
|
232
|
+
if (
|
233
|
+
data["fmin"] != cfg.BANDPASS_FMIN
|
234
|
+
or data["fmax"] != cfg.BANDPASS_FMAX
|
235
|
+
or data["audio_speed"] != cfg.AUDIO_SPEED
|
236
|
+
or data["crop_mode"] != cfg.SAMPLE_CROP_MODE
|
237
|
+
or data["overlap"] != cfg.SIG_OVERLAP
|
238
|
+
):
|
239
|
+
print("\t...WARNING: Cache preprocessing parameters don't match current settings!", flush=True)
|
240
|
+
print(f"\t Cache: fmin={data['fmin']}, fmax={data['fmax']}, speed={data['audio_speed']}", flush=True)
|
241
|
+
print(f"\t Cache: crop_mode={data['crop_mode']}, overlap={data['overlap']}", flush=True)
|
242
|
+
print(
|
243
|
+
f"\t Current: fmin={cfg.BANDPASS_FMIN}, fmax={cfg.BANDPASS_FMAX}, speed={cfg.AUDIO_SPEED}", flush=True
|
244
|
+
)
|
245
|
+
print(f"\t Current: crop_mode={cfg.SAMPLE_CROP_MODE}, overlap={cfg.SIG_OVERLAP}", flush=True)
|
246
|
+
|
247
|
+
# Extract and return data
|
248
|
+
x_train = data["x_train"]
|
249
|
+
y_train = data["y_train"]
|
250
|
+
|
251
|
+
# Handle test data which might not be in older cache files
|
252
|
+
x_test = data.get("x_test", np.array([]))
|
253
|
+
y_test = data.get("y_test", np.array([]))
|
254
|
+
|
255
|
+
labels = data["labels"]
|
256
|
+
binary_classification = bool(data.get("binary_classification", False))
|
257
|
+
multi_label = bool(data.get("multi_label", False))
|
258
|
+
|
259
|
+
return x_train, y_train, x_test, y_test, labels, binary_classification, multi_label
|
260
|
+
|
261
|
+
|
262
|
+
def clear_error_log():
|
263
|
+
"""Clears the error log file.
|
264
|
+
|
265
|
+
For debugging purposes.
|
266
|
+
"""
|
267
|
+
if os.path.isfile(cfg.ERROR_LOG_FILE):
|
268
|
+
os.remove(cfg.ERROR_LOG_FILE)
|
269
|
+
|
270
|
+
|
271
|
+
def write_error_log(ex: Exception):
|
272
|
+
"""Writes an exception to the error log.
|
273
|
+
|
274
|
+
Formats the stacktrace and writes it in the error log file configured in the config.
|
275
|
+
|
276
|
+
Args:
|
277
|
+
ex: An exception that occurred.
|
278
|
+
"""
|
279
|
+
import datetime
|
280
|
+
|
281
|
+
with open(cfg.ERROR_LOG_FILE, "a") as elog:
|
282
|
+
elog.write(
|
283
|
+
datetime.datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
|
284
|
+
+ "\n"
|
285
|
+
+ "".join(traceback.TracebackException.from_exception(ex).format())
|
286
|
+
+ "\n"
|
287
|
+
)
|
288
|
+
|
289
|
+
|
290
|
+
def img2base64(path):
|
291
|
+
import base64
|
292
|
+
|
293
|
+
with open(path, "rb") as img_file:
|
294
|
+
return base64.b64encode(img_file.read()).decode("utf-8")
|
295
|
+
|
296
|
+
|
297
|
+
def save_params(file_path, headers, values):
|
298
|
+
"""Saves the params used to train the custom classifier.
|
299
|
+
|
300
|
+
The hyperparams will be saved to disk in a file named 'model_params.csv'.
|
301
|
+
|
302
|
+
Args:
|
303
|
+
file_path: The path to the file.
|
304
|
+
headers: The headers of the csv file.
|
305
|
+
values: The values of the csv file.
|
306
|
+
"""
|
307
|
+
import csv
|
308
|
+
|
309
|
+
with open(file_path, "w", newline="") as paramsfile:
|
310
|
+
paramswriter = csv.writer(paramsfile)
|
311
|
+
paramswriter.writerow(headers)
|
312
|
+
paramswriter.writerow(values)
|
313
|
+
|
314
|
+
|
315
|
+
def save_result_file(result_path: str, out_string: str):
|
316
|
+
"""Saves the result to a file.
|
317
|
+
|
318
|
+
Args:
|
319
|
+
result_path: The path to the result file.
|
320
|
+
out_string: The string to be written to the file.
|
321
|
+
"""
|
322
|
+
|
323
|
+
# Make directory if it doesn't exist
|
324
|
+
os.makedirs(os.path.dirname(result_path), exist_ok=True)
|
325
|
+
|
326
|
+
# Write the result to the file
|
327
|
+
with open(result_path, "w", encoding="utf-8") as rfile:
|
328
|
+
rfile.write(out_string)
|
329
|
+
|
330
|
+
|
331
|
+
def check_model_files():
|
332
|
+
checkpoint_dir = os.path.join(SCRIPT_DIR, "checkpoints", "V2.4")
|
333
|
+
required_files = [
|
334
|
+
"BirdNET_GLOBAL_6K_V2.4_Model/variables/variables.data-00000-of-00001",
|
335
|
+
"BirdNET_GLOBAL_6K_V2.4_Model/variables/variables.index",
|
336
|
+
"BirdNET_GLOBAL_6K_V2.4_Model/saved_model.pb",
|
337
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/group1-shard1of8.bin",
|
338
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/group1-shard2of8.bin",
|
339
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/group1-shard3of8.bin",
|
340
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/group1-shard4of8.bin",
|
341
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/group1-shard5of8.bin",
|
342
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/group1-shard6of8.bin",
|
343
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/group1-shard7of8.bin",
|
344
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/group1-shard8of8.bin",
|
345
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/mdata/model.json",
|
346
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard1of13.bin",
|
347
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard2of13.bin",
|
348
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard3of13.bin",
|
349
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard4of13.bin",
|
350
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard5of13.bin",
|
351
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard6of13.bin",
|
352
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard7of13.bin",
|
353
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard8of13.bin",
|
354
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard9of13.bin",
|
355
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard10of13.bin",
|
356
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard11of13.bin",
|
357
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard12of13.bin",
|
358
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/group1-shard13of13.bin",
|
359
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/model.json",
|
360
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/model/labels.json",
|
361
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/main.js",
|
362
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/static/sample.wav",
|
363
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/templates/index.html",
|
364
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_TFJS/app.py",
|
365
|
+
"BirdNET_GLOBAL_6K_V2.4_Labels.txt",
|
366
|
+
"BirdNET_GLOBAL_6K_V2.4_MData_Model_V2_FP16.tflite",
|
367
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_FP16.tflite",
|
368
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_FP32.tflite",
|
369
|
+
"BirdNET_GLOBAL_6K_V2.4_Model_INT8.tflite",
|
370
|
+
]
|
371
|
+
|
372
|
+
for file in required_files:
|
373
|
+
if not os.path.exists(os.path.join(checkpoint_dir, file)):
|
374
|
+
print(f"Missing {file}")
|
375
|
+
|
376
|
+
return False
|
377
|
+
|
378
|
+
print("Model found!")
|
379
|
+
|
380
|
+
return True
|
381
|
+
|
382
|
+
|
383
|
+
def ensure_model_exists():
|
384
|
+
import zipfile
|
385
|
+
|
386
|
+
import requests
|
387
|
+
from tqdm import tqdm
|
388
|
+
|
389
|
+
if FROZEN or check_model_files():
|
390
|
+
return
|
391
|
+
|
392
|
+
checkpoint_dir = os.path.join(SCRIPT_DIR, "checkpoints")
|
393
|
+
|
394
|
+
os.makedirs(checkpoint_dir, exist_ok=True)
|
395
|
+
|
396
|
+
url = "https://tuc.cloud/index.php/s/3BsizWy5M7CtQ5w/download/V2.4.zip"
|
397
|
+
download_path = os.path.join(checkpoint_dir, "V2.4.zip")
|
398
|
+
|
399
|
+
response = requests.get(url, stream=True, timeout=30)
|
400
|
+
total_size = int(response.headers.get("content-length", 0))
|
401
|
+
block_size = 1024
|
402
|
+
|
403
|
+
with tqdm(total=total_size, unit="iB", unit_scale=True, desc="Downloading model") as tqdm_bar:
|
404
|
+
with open(download_path, "wb") as file:
|
405
|
+
for data in response.iter_content(block_size):
|
406
|
+
tqdm_bar.update(len(data))
|
407
|
+
file.write(data)
|
408
|
+
|
409
|
+
if response.status_code != 200 or (total_size not in (0, tqdm_bar.n)):
|
410
|
+
raise ValueError(f"Failed to download the file. Status code: {response.status_code}")
|
411
|
+
|
412
|
+
with zipfile.ZipFile(download_path, "r") as zip_ref:
|
413
|
+
zip_ref.extractall(os.path.dirname(download_path))
|
414
|
+
|
415
|
+
os.remove(download_path)
|
416
|
+
|
417
|
+
|
418
|
+
if __name__ == "__main__":
|
419
|
+
ensure_model_exists()
|
@@ -0,0 +1,129 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: birdnet_analyzer
|
3
|
+
Version: 2.0.0
|
4
|
+
Summary: BirdNET analyzer for scientific audio data processing and bird classification.
|
5
|
+
Author: Stefan Kahl
|
6
|
+
Maintainer: Josef Haupt, Max Mauermann
|
7
|
+
License: MIT
|
8
|
+
Project-URL: Homepage, https://birdnet.cornell.edu/birdnet
|
9
|
+
Project-URL: Documentation, https://birdnet-team.github.io/BirdNET-Analyzer/
|
10
|
+
Project-URL: Repository, https://github.com/birdnet-team/BirdNET-Analyzer
|
11
|
+
Project-URL: Issues, https://github.com/birdnet-team/BirdNET-Analyzer/issues
|
12
|
+
Project-URL: Download, https://github.com/birdnet-team/BirdNET-Analyzer/releases/latest
|
13
|
+
Keywords: birdnet,birdnet-analyzer
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
15
|
+
Classifier: Operating System :: OS Independent
|
16
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
17
|
+
Classifier: Topic :: Scientific/Engineering
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
20
|
+
Requires-Python: >=3.11
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
License-File: LICENSE
|
23
|
+
Requires-Dist: librosa
|
24
|
+
Requires-Dist: resampy
|
25
|
+
Requires-Dist: tensorflow==2.15.1
|
26
|
+
Requires-Dist: scikit-learn==1.6.1
|
27
|
+
Requires-Dist: tqdm
|
28
|
+
Provides-Extra: train
|
29
|
+
Requires-Dist: keras-tuner; extra == "train"
|
30
|
+
Provides-Extra: server
|
31
|
+
Requires-Dist: bottle; extra == "server"
|
32
|
+
Requires-Dist: requests; extra == "server"
|
33
|
+
Provides-Extra: gui
|
34
|
+
Requires-Dist: birdnet-analyzer[embeddings,train]; extra == "gui"
|
35
|
+
Requires-Dist: gradio==5.25.2; extra == "gui"
|
36
|
+
Requires-Dist: pywebview; extra == "gui"
|
37
|
+
Requires-Dist: matplotlib; extra == "gui"
|
38
|
+
Requires-Dist: plotly[express]; extra == "gui"
|
39
|
+
Requires-Dist: seaborn; extra == "gui"
|
40
|
+
Requires-Dist: pywin32; platform_system == "Windows" and extra == "gui"
|
41
|
+
Provides-Extra: embeddings
|
42
|
+
Requires-Dist: perch-hoplite; extra == "embeddings"
|
43
|
+
Provides-Extra: all
|
44
|
+
Requires-Dist: birdnet-analyzer[gui,server]; extra == "all"
|
45
|
+
Dynamic: license-file
|
46
|
+
|
47
|
+
<div align="center">
|
48
|
+
<h1>BirdNET-Analyzer</h1>
|
49
|
+
<a href="https://birdnet-team.github.io/BirdNET-Analyzer/">
|
50
|
+
<img src="https://github.com/birdnet-team/BirdNET-Analyzer/blob/main/docs/_static/logo_birdnet_big.png?raw=true" width="300" alt="BirdNET-Logo" />
|
51
|
+
</a>
|
52
|
+
</div>
|
53
|
+
<br>
|
54
|
+
<div align="center">
|
55
|
+
|
56
|
+

|
57
|
+

|
58
|
+
[](https://www.python.org/downloads/release/python-3110/)
|
59
|
+

|
60
|
+

|
61
|
+
|
62
|
+
[](https://github.com/birdnet-team/BirdNET-Analyzer/actions/workflows/docker-build.yml)
|
63
|
+
[](https://www.reddit.com/r/BirdNET_Analyzer/)
|
64
|
+

|
65
|
+
[](https://github.com/birdnet-team/BirdNET-Analyzer/releases/latest)
|
66
|
+
|
67
|
+
</div>
|
68
|
+
|
69
|
+
This repo contains BirdNET models and scripts for processing large amounts of audio data or single audio files.
|
70
|
+
This is the most advanced version of BirdNET for acoustic analyses and we will keep this repository up-to-date with new models and improved interfaces to enable scientists with no CS background to run the analysis.
|
71
|
+
|
72
|
+
Feel free to use BirdNET for your acoustic analyses and research.
|
73
|
+
If you do, please cite as:
|
74
|
+
|
75
|
+
```bibtex
|
76
|
+
@article{kahl2021birdnet,
|
77
|
+
title={BirdNET: A deep learning solution for avian diversity monitoring},
|
78
|
+
author={Kahl, Stefan and Wood, Connor M and Eibl, Maximilian and Klinck, Holger},
|
79
|
+
journal={Ecological Informatics},
|
80
|
+
volume={61},
|
81
|
+
pages={101236},
|
82
|
+
year={2021},
|
83
|
+
publisher={Elsevier}
|
84
|
+
}
|
85
|
+
```
|
86
|
+
|
87
|
+
## Documentation
|
88
|
+
|
89
|
+
You can access documentation for this project [here](https://birdnet-team.github.io/BirdNET-Analyzer/).
|
90
|
+
|
91
|
+
## Download
|
92
|
+
|
93
|
+
You can download installers for Windows and macOS from the [releases page](https://github.com/birdnet-team/BirdNET-Analyzer/releases/latest).
|
94
|
+
|
95
|
+
## About
|
96
|
+
|
97
|
+
Developed by the [K. Lisa Yang Center for Conservation Bioacoustics](https://www.birds.cornell.edu/ccb/) at the [Cornell Lab of Ornithology](https://www.birds.cornell.edu/home) in collaboration with [Chemnitz University of Technology](https://www.tu-chemnitz.de/index.html.en).
|
98
|
+
|
99
|
+
Go to https://birdnet.cornell.edu to learn more about the project.
|
100
|
+
|
101
|
+
Want to use BirdNET to analyze a large dataset? Don't hesitate to contact us: ccb-birdnet@cornell.edu
|
102
|
+
|
103
|
+
**Have a question, remark, or feature request? Please start a new issue thread to let us know. Feel free to submit a pull request.**
|
104
|
+
|
105
|
+
## License
|
106
|
+
|
107
|
+
- **Source Code**: The source code for this project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
108
|
+
- **Models**: The models used in this project are licensed under the [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/).
|
109
|
+
|
110
|
+
Please ensure you review and adhere to the specific license terms provided with each model.
|
111
|
+
|
112
|
+
*Please note that all educational and research purposes are considered non-commercial use and it is therefore freely permitted to use BirdNET models in any way.*
|
113
|
+
|
114
|
+
## Funding
|
115
|
+
|
116
|
+
This project is supported by Jake Holshuh (Cornell class of ´69) and The Arthur Vining Davis Foundations.
|
117
|
+
Our work in the K. Lisa Yang Center for Conservation Bioacoustics is made possible by the generosity of K. Lisa Yang to advance innovative conservation technologies to inspire and inform the conservation of wildlife and habitats.
|
118
|
+
|
119
|
+
The development of BirdNET is supported by the German Federal Ministry of Education and Research through the project “BirdNET+” (FKZ 01|S22072).
|
120
|
+
The German Federal Ministry for the Environment, Nature Conservation and Nuclear Safety contributes through the “DeepBirdDetect” project (FKZ 67KI31040E).
|
121
|
+
In addition, the Deutsche Bundesstiftung Umwelt supports BirdNET through the project “RangerSound” (project 39263/01).
|
122
|
+
|
123
|
+
## Partners
|
124
|
+
|
125
|
+
BirdNET is a joint effort of partners from academia and industry.
|
126
|
+
Without these partnerships, this project would not have been possible.
|
127
|
+
Thank you!
|
128
|
+
|
129
|
+

|