Location
diff --git a/scripts/install_config.sh b/scripts/install_config.sh
index 09334ad..d3343a5 100755
--- a/scripts/install_config.sh
+++ b/scripts/install_config.sh
@@ -27,6 +27,13 @@ SITE_NAME="$HOSTNAME"
LATITUDE=$(curl -s4 ifconfig.co/json | jq .latitude)
LONGITUDE=$(curl -s4 ifconfig.co/json | jq .longitude)
+#--------------------------------- Model --------------------------------------#
+#_____________The variable below configures which BirdNET model is_____________#
+#______________________used for detecting bird audio.__________________________#
+
+MODEL=BirdNET_6K_GLOBAL_MODEL
+SF_THRESH=0.03
+
#--------------------- BirdWeather Station Information -----------------------#
#_____________The variable below can be set to have your BirdNET-Pi____________#
#__________________also act as a BirdWeather listening station_________________#
diff --git a/scripts/install_language_label.sh b/scripts/install_language_label.sh
index 4ba0b27..0129288 100755
--- a/scripts/install_language_label.sh
+++ b/scripts/install_language_label.sh
@@ -23,4 +23,11 @@ unzip -o $HOME/BirdNET-Pi/model/labels_l18n.zip $label_file_name \
&& mv -f $HOME/BirdNET-Pi/model/$label_file_name $HOME/BirdNET-Pi/model/labels.txt \
&& logger "[$0] Changed language label file to '$label_file_name'";
+label_file_name_flickr="labels_en.txt"
+
+unzip -o $HOME/BirdNET-Pi/model/labels_l18n.zip $label_file_name_flickr \
+ -d $HOME/BirdNET-Pi/model \
+ && mv -f $HOME/BirdNET-Pi/model/$label_file_name_flickr $HOME/BirdNET-Pi/model/labels_flickr.txt \
+ && logger "[$0] Set Flickr labels '$label_file_name_flickr'";
+
exit 0
diff --git a/scripts/install_language_label_nm.sh b/scripts/install_language_label_nm.sh
new file mode 100644
index 0000000..4569a0c
--- /dev/null
+++ b/scripts/install_language_label_nm.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+usage() { echo "Usage: $0 -l " 1>&2; exit 1; }
+
+while getopts "l:" o; do
+ case "${o}" in
+ l)
+ lang=${OPTARG}
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+HOME=$(awk -F: '/1000/ {print $6}' /etc/passwd)
+
+label_file_name="labels_${lang}.txt"
+
+unzip -o $HOME/BirdNET-Pi/model/labels_nm.zip $label_file_name \
+ -d $HOME/BirdNET-Pi/model \
+ && mv -f $HOME/BirdNET-Pi/model/$label_file_name $HOME/BirdNET-Pi/model/labels.txt \
+ && logger "[$0] Changed language label file to '$label_file_name'";
+
+label_file_name_flickr="labels_en.txt"
+
+unzip -o $HOME/BirdNET-Pi/model/labels_nm.zip $label_file_name_flickr \
+ -d $HOME/BirdNET-Pi/model \
+ && mv -f $HOME/BirdNET-Pi/model/$label_file_name_flickr $HOME/BirdNET-Pi/model/labels_flickr.txt \
+ && logger "[$0] Set Flickr labels '$label_file_name_flickr'";
+
+exit 0
diff --git a/scripts/server.py b/scripts/server.py
index 9d9e242..6aa5b86 100755
--- a/scripts/server.py
+++ b/scripts/server.py
@@ -35,6 +35,7 @@ DISCONNECT_MESSAGE = "!DISCONNECT"
userDir = os.path.expanduser('~')
DB_PATH = userDir + '/BirdNET-Pi/scripts/birds.db'
+PREDICTED_SPECIES_LIST = []
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -51,7 +52,12 @@ with open(userDir + '/BirdNET-Pi/scripts/thisrun.txt', 'r') as f:
this_run = f.readlines()
audiofmt = "." + str(str(str([i for i in this_run if i.startswith('AUDIOFMT')]).split('=')[1]).split('\\')[0])
priv_thresh = float("." + str(str(str([i for i in this_run if i.startswith('PRIVACY_THRESHOLD')]).split('=')[1]).split('\\')[0])) / 10
-
+ try:
+ model = str(str(str([i for i in this_run if i.startswith('MODEL')]).split('=')[1]).split('\\')[0])
+ sf_thresh = str(str(str([i for i in this_run if i.startswith('SF_THRESH')]).split('=')[1]).split('\\')[0])
+ except Exception as e:
+ model = "BirdNET_6K_GLOBAL_MODEL"
+ sf_thresh = 0.5
def loadModel():
@@ -63,7 +69,8 @@ def loadModel():
print('LOADING TF LITE MODEL...', end=' ')
# Load TFLite model and allocate tensors.
- modelpath = userDir + '/BirdNET-Pi/model/BirdNET_6K_GLOBAL_MODEL.tflite'
+ # model will either be BirdNET_GLOBAL_3K_V2.2_Model_FP16 (new) or BirdNET_6K_GLOBAL_MODEL (old)
+ modelpath = userDir + '/BirdNET-Pi/model/'+model+'.tflite'
myinterpreter = tflite.Interpreter(model_path=modelpath, num_threads=2)
myinterpreter.allocate_tensors()
@@ -73,7 +80,8 @@ def loadModel():
# Get input tensor index
INPUT_LAYER_INDEX = input_details[0]['index']
- MDATA_INPUT_INDEX = input_details[1]['index']
+ if model == "BirdNET_6K_GLOBAL_MODEL":
+ MDATA_INPUT_INDEX = input_details[1]['index']
OUTPUT_LAYER_INDEX = output_details[0]['index']
# Load labels
@@ -87,6 +95,70 @@ def loadModel():
return myinterpreter
+def loadMetaModel():
+
+ global M_INTERPRETER
+ global M_INPUT_LAYER_INDEX
+ global M_OUTPUT_LAYER_INDEX
+
+ # Load TFLite model and allocate tensors.
+ M_INTERPRETER = tflite.Interpreter(model_path=userDir + '/BirdNET-Pi/model/BirdNET_GLOBAL_3K_V2.2_MData_Model_FP16.tflite')
+ M_INTERPRETER.allocate_tensors()
+
+ # Get input and output tensors.
+ input_details = M_INTERPRETER.get_input_details()
+ output_details = M_INTERPRETER.get_output_details()
+
+ # Get input tensor index
+ M_INPUT_LAYER_INDEX = input_details[0]['index']
+ M_OUTPUT_LAYER_INDEX = output_details[0]['index']
+
+ print("loaded META model")
+
+def predictFilter(lat, lon, week):
+
+ global M_INTERPRETER
+
+ # Does interpreter exist?
+ try:
+ if M_INTERPRETER == None:
+ loadMetaModel()
+ except Exception as e:
+ loadMetaModel()
+
+ # Prepare mdata as sample
+ sample = np.expand_dims(np.array([lat, lon, week], dtype='float32'), 0)
+
+ # Run inference
+ M_INTERPRETER.set_tensor(M_INPUT_LAYER_INDEX, sample)
+ M_INTERPRETER.invoke()
+
+ return M_INTERPRETER.get_tensor(M_OUTPUT_LAYER_INDEX)[0]
+
+def explore(lat, lon, week):
+
+ # Make filter prediction
+ l_filter = predictFilter(lat, lon, week)
+
+ # Apply threshold
+ l_filter = np.where(l_filter >= float(sf_thresh), l_filter, 0)
+
+ # Zip with labels
+ l_filter = list(zip(l_filter, CLASSES))
+
+ # Sort by filter value
+ l_filter = sorted(l_filter, key=lambda x: x[0], reverse=True)
+
+ return l_filter
+
+def predictSpeciesList(lat, lon, week):
+
+ l_filter = explore(lat, lon, week)
+ for s in l_filter:
+ if s[0] >= float(sf_thresh):
+ #if there's a custom user-made include list, we only want to use the species in that
+ if(len(INCLUDE_LIST) == 0):
+ PREDICTED_SPECIES_LIST.append(s[1])
def loadCustomSpeciesList(path):
@@ -162,7 +234,8 @@ def predict(sample, sensitivity):
global INTERPRETER
# Make a prediction
INTERPRETER.set_tensor(INPUT_LAYER_INDEX, np.array(sample[0], dtype='float32'))
- INTERPRETER.set_tensor(MDATA_INPUT_INDEX, np.array(sample[1], dtype='float32'))
+ if model == "BirdNET_6K_GLOBAL_MODEL":
+ INTERPRETER.set_tensor(MDATA_INPUT_INDEX, np.array(sample[1], dtype='float32'))
INTERPRETER.invoke()
prediction = INTERPRETER.get_tensor(OUTPUT_LAYER_INDEX)[0]
@@ -197,6 +270,11 @@ def analyzeAudioData(chunks, lat, lon, week, sensitivity, overlap,):
start = time.time()
print('ANALYZING AUDIO...', end=' ', flush=True)
+ if model == "BirdNET_GLOBAL_3K_V2.2_Model_FP16":
+ if len(PREDICTED_SPECIES_LIST) == 0 or len(INCLUDE_LIST) != 0:
+ predictSpeciesList(lat,lon,week)
+
+
# Convert and prepare metadata
mdata = convertMetadata(np.array([lat, lon, week]))
mdata = np.expand_dims(mdata, 0)
@@ -242,8 +320,8 @@ def writeResultsToFile(detections, min_conf, path):
rfile.write('Start (s);End (s);Scientific name;Common name;Confidence\n')
for d in detections:
for entry in detections[d]:
- if entry[1] >= min_conf and ((entry[0] in INCLUDE_LIST or len(INCLUDE_LIST) == 0) and (entry[0] not in EXCLUDE_LIST or len(EXCLUDE_LIST) == 0)):
- rfile.write(d + ';' + entry[0].replace('_', ';') + ';' + str(entry[1]) + '\n')
+ if entry[1] >= min_conf and ((entry[0] in INCLUDE_LIST or len(INCLUDE_LIST) == 0) and (entry[0] not in EXCLUDE_LIST or len(EXCLUDE_LIST) == 0) and (entry[0] in PREDICTED_SPECIES_LIST or len(PREDICTED_SPECIES_LIST) == 0) ):
+ rfile.write(d + ';' + entry[0].replace('_', ';').split("/")[0] + ';' + str(entry[1]) + '\n')
rcnt += 1
print('DONE! WROTE', rcnt, 'RESULTS.')
return
@@ -364,16 +442,16 @@ def handle_client(conn, addr):
species_apprised_this_run = []
for entry in detections[d]:
if entry[1] >= min_conf and ((entry[0] in INCLUDE_LIST or len(INCLUDE_LIST) == 0)
- and (entry[0] not in EXCLUDE_LIST or len(EXCLUDE_LIST) == 0)):
+ and (entry[0] not in EXCLUDE_LIST or len(EXCLUDE_LIST) == 0) and (entry[0] in PREDICTED_SPECIES_LIST or len(PREDICTED_SPECIES_LIST) == 0) ):
# Write to text file.
- rfile.write(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';'
+ rfile.write(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';').split("/")[0] + ';'
+ str(entry[1]) + ";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';'
+ str(args.sensitivity) + ';' + str(args.overlap) + '\n')
# Write to database
Date = str(current_date)
Time = str(current_time)
- species = entry[0]
+ species = entry[0].split("/")[0]
Sci_Name, Com_Name = species.split('_')
score = entry[1]
Confidence = str(round(score * 100))
@@ -475,9 +553,14 @@ def handle_client(conn, addr):
post_soundscape_id = "\"soundscapeId\": " + str(soundscape_id) + ","
post_soundscape_start_time = "\"soundscapeStartTime\": " + start_time + ","
post_soundscape_end_time = "\"soundscapeEndTime\": " + end_time + ","
- post_commonName = "\"commonName\": \"" + entry[0].split('_')[1] + "\","
+ post_commonName = "\"commonName\": \"" + entry[0].split('_')[1].split("/")[0] + "\","
post_scientificName = "\"scientificName\": \"" + entry[0].split('_')[0] + "\","
- post_algorithm = "\"algorithm\": " + "\"alpha\"" + ","
+
+ if model == "BirdNET_GLOBAL_3K_V2.2_Model_FP16":
+ post_algorithm = "\"algorithm\": " + "\"2p2\"" + ","
+ else:
+ post_algorithm = "\"algorithm\": " + "\"alpha\"" + ","
+
post_confidence = "\"confidence\": " + str(entry[1])
post_end = " }"
diff --git a/scripts/species.py b/scripts/species.py
new file mode 100644
index 0000000..77da697
--- /dev/null
+++ b/scripts/species.py
@@ -0,0 +1,139 @@
+from pathlib import Path
+from tzlocal import get_localzone
+import datetime
+import sqlite3
+import requests
+import json
+import time
+import math
+import numpy as np
+import librosa
+import operator
+import socket
+import threading
+import os
+import sys
+import argparse
+import datetime
+
+try:
+ import tflite_runtime.interpreter as tflite
+except BaseException:
+ from tensorflow import lite as tflite
+
+
+
+def loadMetaModel():
+
+ global M_INTERPRETER
+ global M_INPUT_LAYER_INDEX
+ global M_OUTPUT_LAYER_INDEX
+ global CLASSES
+
+ # Load TFLite model and allocate tensors.
+ M_INTERPRETER = tflite.Interpreter(model_path=userDir + '/BirdNET-Pi/model/BirdNET_GLOBAL_3K_V2.2_MData_Model_FP16.tflite')
+ M_INTERPRETER.allocate_tensors()
+
+ # Get input and output tensors.
+ input_details = M_INTERPRETER.get_input_details()
+ output_details = M_INTERPRETER.get_output_details()
+
+ # Get input tensor index
+ M_INPUT_LAYER_INDEX = input_details[0]['index']
+ M_OUTPUT_LAYER_INDEX = output_details[0]['index']
+
+ # Load labels
+ CLASSES = []
+ labelspath = userDir + '/BirdNET-Pi/model/labels.txt'
+ with open(labelspath, 'r') as lfile:
+ for line in lfile.readlines():
+ CLASSES.append(line.replace('\n', ''))
+
+ print("loaded META model")
+
+def predictFilter(lat, lon, week):
+
+ global M_INTERPRETER
+
+ # Does interpreter exist?
+ try:
+ if M_INTERPRETER == None:
+ loadMetaModel()
+ except Exception as e:
+ loadMetaModel()
+
+ # Prepare mdata as sample
+ sample = np.expand_dims(np.array([lat, lon, week], dtype='float32'), 0)
+
+ # Run inference
+ M_INTERPRETER.set_tensor(M_INPUT_LAYER_INDEX, sample)
+ M_INTERPRETER.invoke()
+
+ return M_INTERPRETER.get_tensor(M_OUTPUT_LAYER_INDEX)[0]
+
+def explore(lat, lon, week, threshold):
+
+ # Make filter prediction
+ l_filter = predictFilter(lat, lon, week)
+
+ # Apply threshold
+ l_filter = np.where(l_filter >= threshold, l_filter, 0)
+
+ # Zip with labels
+ l_filter = list(zip(l_filter, CLASSES))
+
+ # Sort by filter value
+ l_filter = sorted(l_filter, key=lambda x: x[0], reverse=True)
+
+ return l_filter
+
+def getSpeciesList(lat, lon, week, threshold=0.05, sort=False):
+
+ print('Getting species list for {}/{}, Week {}...'.format(lat, lon, week), end='', flush=True)
+
+ # Extract species from model
+ pred = explore(lat, lon, week, threshold)
+
+ # Make species list
+ slist = []
+ for p in pred:
+ if p[0] >= threshold:
+ slist.append([p[1],p[0]])
+
+ return slist
+
+
+userDir = os.path.expanduser('~')
+DB_PATH = userDir + '/BirdNET-Pi/scripts/birds.db'
+with open(userDir + '/BirdNET-Pi/scripts/thisrun.txt', 'r') as f:
+
+ this_run = f.readlines()
+ lat = str(str(str([i for i in this_run if i.startswith('LATITUDE')]).split('=')[1]).split('\\')[0])
+ lon = str(str(str([i for i in this_run if i.startswith('LONGITUDE')]).split('=')[1]).split('\\')[0])
+
+weekofyear = datetime.datetime.today().isocalendar()[1]
+if __name__ == '__main__':
+
+ # Parse arguments
+ parser = argparse.ArgumentParser(description='Get list of species for a given location with BirdNET. Sorted by occurrence frequency.')
+ #parser.add_argument('--o', default='/home/pi/BirdNET-Pi/include_species_list.txt', help='Path to output file or folder. If this is a folder, file will be named \'species_list.txt\'.')
+ #parser.add_argument('--lat', type=float, default=##, help='Recording location latitude. Set -1 to ignore.')
+ #parser.add_argument('--lon', type=float, default=##, help='Recording location longitude. Set -1 to ignore.')
+ #parser.add_argument('--week', type=int, default=dayofweek, 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.')
+ parser.add_argument('--threshold', type=float, default=0.05, help='Occurrence frequency threshold. Defaults to 0.05.')
+ #parser.add_argument('--sortby', default='freq', help='Sort species by occurrence frequency or alphabetically. Values in [\'freq\', \'alpha\']. Defaults to \'freq\'.')
+
+ args = parser.parse_args()
+
+ LOCATION_FILTER_THRESHOLD = args.threshold
+
+ # Get species list
+ species_list = getSpeciesList(lat, lon, weekofyear, LOCATION_FILTER_THRESHOLD, False)
+ for x in range(len(species_list)):
+ print(species_list[x][0] + " - "+ str(species_list[x][1]))
+
+ print("\nThe above species list describes all of the species that have been historically observed at the specified lat/long ("+lat+", "+lon+") for this week of the year. The frequency threshold is the percentage of submitted eBird checklists that the species appeared on, meaning a higher threshold means that the species is more common.")
+ print("\nNOTE: no actual changes to your BirdNET-Pi species list were made by running this command. To set your desired frequency threshold, do it through the BirdNET-Pi web interface (Tools -> Settings -> Model)")
+
+
+
diff --git a/scripts/update_birdnet_snippets.sh b/scripts/update_birdnet_snippets.sh
index 5f0170c..be307e7 100755
--- a/scripts/update_birdnet_snippets.sh
+++ b/scripts/update_birdnet_snippets.sh
@@ -144,5 +144,13 @@ if ! grep HEARTBEAT_URL /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "HEARTBEAT_URL=" >> /etc/birdnet/birdnet.conf
fi
+if ! grep MODEL /etc/birdnet/birdnet.conf &>/dev/null;then
+ sudo -u$USER echo "MODEL=BirdNET_6K_GLOBAL_MODEL" >> /etc/birdnet/birdnet.conf
+fi
+if ! grep SF_THRESH /etc/birdnet/birdnet.conf &>/dev/null;then
+ sudo -u$USER echo "SF_THRESH=0.03" >> /etc/birdnet/birdnet.conf
+fi
+sudo chmod +x ~/BirdNET-Pi/scripts/install_language_label_nm.sh
+
sudo systemctl daemon-reload
restart_services.sh
|