diff --git a/birdnet.conf-defaults b/birdnet.conf-defaults index 8c2cd0b..f7a9d15 100644 --- a/birdnet.conf-defaults +++ b/birdnet.conf-defaults @@ -11,6 +11,7 @@ SITE_NAME="" #______________________used for detecting bird audio.__________________________# MODEL=BirdNET_6K_GLOBAL_MODEL +SF_THRESH=0.5 #--------------------- Required: Latitude, and Longitude ----------------------# diff --git a/scripts/config.php b/scripts/config.php index cbd2d62..019c497 100644 --- a/scripts/config.php +++ b/scripts/config.php @@ -35,6 +35,7 @@ if(isset($_GET["latitude"])){ $language = $_GET["language"]; $timezone = $_GET["timezone"]; $model = $_GET["model"]; + $sf_thresh = $_GET["sf_thresh"]; if(isset($_GET['apprise_notify_each_detection'])) { $apprise_notify_each_detection = 1; @@ -135,6 +136,7 @@ if(isset($_GET["latitude"])){ $contents = preg_replace("/FLICKR_FILTER_EMAIL=.*/", "FLICKR_FILTER_EMAIL=$flickr_filter_email", $contents); $contents = preg_replace("/APPRISE_MINIMUM_SECONDS_BETWEEN_NOTIFICATIONS_PER_SPECIES=.*/", "APPRISE_MINIMUM_SECONDS_BETWEEN_NOTIFICATIONS_PER_SPECIES=$minimum_time_limit", $contents); $contents = preg_replace("/MODEL=.*/", "MODEL=$model", $contents); + $contents = preg_replace("/SF_THRESH=.*/", "SF_THRESH=$sf_thresh", $contents); $contents2 = file_get_contents("./scripts/thisrun.txt"); $contents2 = preg_replace("/SITE_NAME=.*/", "SITE_NAME=\"$site_name\"", $contents2); @@ -152,6 +154,8 @@ if(isset($_GET["latitude"])){ $contents2 = preg_replace("/FLICKR_FILTER_EMAIL=.*/", "FLICKR_FILTER_EMAIL=$flickr_filter_email", $contents2); $contents2 = preg_replace("/APPRISE_MINIMUM_SECONDS_BETWEEN_NOTIFICATIONS_PER_SPECIES=.*/", "APPRISE_MINIMUM_SECONDS_BETWEEN_NOTIFICATIONS_PER_SPECIES=$minimum_time_limit", $contents2); $contents2 = preg_replace("/MODEL=.*/", "MODEL=$model", $contents2); + $contents2 = preg_replace("/SF_THRESH=.*/", "SF_THRESH=$sf_thresh", $contents2); + if($site_name != $config["SITE_NAME"]) { @@ -342,6 +346,10 @@ function sendTestNotification(e) { ?> + +

This value is used by the model to constrain the list of possible species that it will try to detect, given the minimum occurence frequency. A 0.05 threshold means that the species is seen on average at least 5% of the time, from historically collected data for your lat/lon.
If you'd like to tinker with this value and see the species list output, you can run the following command:

~/BirdNET-Pi/birdnet/bin/python3 species.py --threshold 0.7

+
+
BirdNET_6K_GLOBAL_MODEL (2020)

This model comes from BirdNET-Lite, with bird sound recognition for more than 6,000 species worldwide. This is the default option and will generally work very well for most use cases.
diff --git a/scripts/install_config.sh b/scripts/install_config.sh index ba08c46..a592407 100755 --- a/scripts/install_config.sh +++ b/scripts/install_config.sh @@ -32,6 +32,7 @@ LONGITUDE=$(curl -s4 ifconfig.co/json | jq .longitude) #______________________used for detecting bird audio.__________________________# MODEL=BirdNET_6K_GLOBAL_MODEL +SF_THRESH=0.5 #--------------------- BirdWeather Station Information -----------------------# #_____________The variable below can be set to have your BirdNET-Pi____________# diff --git a/scripts/species.py b/scripts/species.py new file mode 100644 index 0000000..3426f61 --- /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): + + # Make filter prediction + l_filter = predictFilter(lat, lon, week) + + # Apply threshold + l_filter = np.where(l_filter >= 0.03, 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) + + # 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 9efac14..9e69411 100755 --- a/scripts/update_birdnet_snippets.sh +++ b/scripts/update_birdnet_snippets.sh @@ -147,6 +147,9 @@ 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.5" >> /etc/birdnet/birdnet.conf +fi sudo systemctl daemon-reload restart_services.sh