Use the variables defined above to customize your notification title and body.
-
+
- ' />
+ ' />
>
>
diff --git a/scripts/daily_plot.py b/scripts/daily_plot.py
index db989ae..ff9c839 100755
--- a/scripts/daily_plot.py
+++ b/scripts/daily_plot.py
@@ -48,7 +48,8 @@ readings = 10
plt_top10_today = (df_plt_today['Com_Name'].value_counts()[:readings])
df_plt_top10_today = df_plt_today[df_plt_today.Com_Name.isin(plt_top10_today.index)]
-if df_plt_top10_today.empty: exit(0)
+if df_plt_top10_today.empty:
+ exit(0)
# Set Palette for graphics
pal = "Greens"
diff --git a/scripts/plotly_streamlit.py b/scripts/plotly_streamlit.py
index 467e353..f9910a8 100755
--- a/scripts/plotly_streamlit.py
+++ b/scripts/plotly_streamlit.py
@@ -95,7 +95,7 @@ else:
def date_filter(df, start_date, end_date):
filt = (df2.index >= pd.Timestamp(start_date)) & (df2.index <= pd.Timestamp(end_date + timedelta(days=1)))
df = df[filt]
- return(df)
+ return (df)
df2 = date_filter(df2, start_date, end_date)
@@ -142,7 +142,7 @@ def time_resample(df, resample_time):
else:
df_resample = df.resample(resample_time)['Com_Name'].aggregate('unique').explode()
- return(df_resample)
+ return (df_resample)
top_bird = df2['Com_Name'].mode()[0]
@@ -236,10 +236,10 @@ if daily is False:
if resample_time != '1D':
specie = st.selectbox(
- 'Which bird would you like to explore for the dates '
- + str(start_date) + ' to ' + str(end_date) + '?',
- species,
- index=0)
+ 'Which bird would you like to explore for the dates '
+ + str(start_date) + ' to ' + str(end_date) + '?',
+ species,
+ index=0)
# filt = df2['Com_Name'] == specie
if specie == 'All':
@@ -256,7 +256,7 @@ if daily is False:
# '{:.2f}%'.format(max(df2[df2['Com_Name'] == specie]['Confidence']) * 100)) +
# ' ' + ' Median:' + str(
# '{:.2f}%'.format(np.median(df2[df2['Com_Name'] == specie]['Confidence']) * 100))
- )
+ )
)
fig.layout.annotations[1].update(x=0.7, y=0.25, font_size=15)
@@ -316,7 +316,7 @@ if daily is False:
fig = make_subplots(
rows=3, cols=1,
specs=[[{"type": "polar", "rowspan": 2}], [{"rowspan": 1}], [{"type": "xy", "rowspan": 1}]]
- )
+ )
# Set 360 degrees, 24 hours for polar plot
theta = np.linspace(0.0, 360, 24, endpoint=False)
diff --git a/scripts/server.py b/scripts/server.py
index ccae348..8108e3a 100755
--- a/scripts/server.py
+++ b/scripts/server.py
@@ -57,10 +57,11 @@ with open(userDir + '/BirdNET-Pi/scripts/thisrun.txt', 'r') as f:
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:
+ except Exception:
model = "BirdNET_6K_GLOBAL_MODEL"
sf_thresh = 0.03
+
def loadModel():
global INPUT_LAYER_INDEX
@@ -97,6 +98,7 @@ def loadModel():
return myinterpreter
+
def loadMetaModel():
global M_INTERPRETER
@@ -117,15 +119,16 @@ def loadMetaModel():
print("loaded META model")
+
def predictFilter(lat, lon, week):
global M_INTERPRETER
# Does interpreter exist?
try:
- if M_INTERPRETER == None:
+ if M_INTERPRETER is None:
loadMetaModel()
- except Exception as e:
+ except Exception:
loadMetaModel()
# Prepare mdata as sample
@@ -137,6 +140,7 @@ def predictFilter(lat, lon, week):
return M_INTERPRETER.get_tensor(M_OUTPUT_LAYER_INDEX)[0]
+
def explore(lat, lon, week):
# Make filter prediction
@@ -153,15 +157,17 @@ def explore(lat, lon, week):
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):
+ # 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):
slist = []
@@ -274,8 +280,7 @@ def analyzeAudioData(chunks, lat, lon, week, sensitivity, overlap,):
if model == "BirdNET_GLOBAL_3K_V2.3_Model_FP16":
if len(PREDICTED_SPECIES_LIST) == 0 or len(INCLUDE_LIST) != 0:
- predictSpeciesList(lat,lon,week)
-
+ predictSpeciesList(lat, lon, week)
# Convert and prepare metadata
mdata = convertMetadata(np.array([lat, lon, week]))
@@ -322,7 +327,9 @@ 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) and (entry[0] in PREDICTED_SPECIES_LIST or len(PREDICTED_SPECIES_LIST) == 0) ):
+ 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.')
@@ -468,7 +475,8 @@ 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] in PREDICTED_SPECIES_LIST or len(PREDICTED_SPECIES_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('_', ';').split("/")[0] + ';'
+ str(entry[1]) + ";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';'
@@ -511,6 +519,7 @@ def handle_client(conn, addr):
settings_dict = config_to_settings(userDir + '/BirdNET-Pi/scripts/thisrun.txt')
sendAppriseNotifications(species,
str(score),
+ str(round(score * 100)),
File_Name,
Date,
Time,
diff --git a/scripts/species.py b/scripts/species.py
index 7a13287..0f171fd 100644
--- a/scripts/species.py
+++ b/scripts/species.py
@@ -1,18 +1,5 @@
-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
@@ -22,7 +9,6 @@ except BaseException:
from tensorflow import lite as tflite
-
def loadMetaModel():
global M_INTERPRETER
@@ -51,15 +37,16 @@ def loadMetaModel():
print("loaded META model")
+
def predictFilter(lat, lon, week):
global M_INTERPRETER
# Does interpreter exist?
try:
- if M_INTERPRETER == None:
+ if M_INTERPRETER is None:
loadMetaModel()
- except Exception as e:
+ except Exception:
loadMetaModel()
# Prepare mdata as sample
@@ -71,6 +58,7 @@ def predictFilter(lat, lon, week):
return M_INTERPRETER.get_tensor(M_OUTPUT_LAYER_INDEX)[0]
+
def explore(lat, lon, week, threshold):
# Make filter prediction
@@ -87,6 +75,7 @@ def explore(lat, lon, week, threshold):
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)
@@ -98,7 +87,7 @@ def getSpeciesList(lat, lon, week, threshold=0.05, sort=False):
slist = []
for p in pred:
if p[0] >= threshold:
- slist.append([p[1],p[0]])
+ slist.append([p[1], p[0]])
return slist
@@ -112,17 +101,14 @@ with open(userDir + '/BirdNET-Pi/scripts/thisrun.txt', 'r') as f:
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__':
+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 = argparse.ArgumentParser(
+ description='Get list of species for a given location with BirdNET. Sorted by occurrence frequency.'
+ )
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
@@ -130,10 +116,9 @@ if __name__ == '__main__':
# 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 the species that the model will attempt to detect. If you don't see a species you want detected on this list, decrease your threshold.")
- 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)")
-
-
+ print(species_list[x][0] + " - " + str(species_list[x][1]))
+ print("\nThe above species list describes all the species that the model will attempt to detect. \
+ If you don't see a species you want detected on this list, decrease your threshold.")
+ 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/utils/notifications.py b/scripts/utils/notifications.py
index 99265a5..94df122 100644
--- a/scripts/utils/notifications.py
+++ b/scripts/utils/notifications.py
@@ -24,6 +24,7 @@ config = apprise.AppriseConfig()
config.add(APPRISE_CONFIG)
apobj.add(config)
+
def notify(body, title, attached=""):
if attached != "":
apobj.notify(
@@ -38,7 +39,9 @@ def notify(body, title, attached=""):
)
-def sendAppriseNotifications(species, confidence, path, date, time, week, latitude, longitude, cutoff, sens, overlap, settings_dict, db_path=DB_PATH):
+def sendAppriseNotifications(species, confidence, confidencepct, path,
+ date, time, week, latitude, longitude, cutoff,
+ sens, overlap, settings_dict, db_path=DB_PATH):
# print(sendAppriseNotifications)
# print(settings_dict)
if os.path.exists(APPRISE_CONFIG) and os.path.getsize(APPRISE_CONFIG) > 0:
@@ -67,7 +70,7 @@ def sendAppriseNotifications(species, confidence, path, date, time, week, latitu
print("APPRISE NOTIFICATION EXCEPTION: "+str(e))
return
- #TODO: this all needs to be changed, we changed the caddy default to allow direct IP access, so birdnetpi.local shouldn't be relied on anymore
+ # TODO: this all needs to be changed, we changed the caddy default to allow direct IP access, so birdnetpi.local shouldn't be relied on anymore
try:
websiteurl = settings_dict.get('BIRDNETPI_URL')
if len(websiteurl) == 0:
@@ -79,27 +82,28 @@ def sendAppriseNotifications(species, confidence, path, date, time, week, latitu
image_url = ""
if len(settings_dict.get('FLICKR_API_KEY')) > 0 and "$flickrimage" in body:
- if not comName in flickr_images:
+ if comName not in flickr_images:
try:
# TODO: Make this work with non-english comnames. Implement the "// convert sci name to English name" logic from overview.php here
headers = {'User-Agent': 'Python_Flickr/1.0'}
url = 'https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key='+str(settings_dict.get('FLICKR_API_KEY'))+'&text='+str(comName)+' bird&sort=relevance&per_page=5&media=photos&format=json&license=2%2C3%2C4%2C5%2C6%2C9&nojsoncallback=1'
resp = requests.get(url=url, headers=headers)
+
resp.encoding = "utf-8"
data = resp.json()["photos"]["photo"][0]
image_url = 'https://farm'+str(data["farm"])+'.static.flickr.com/'+str(data["server"])+'/'+str(data["id"])+'_'+str(data["secret"])+'_n.jpg'
flickr_images[comName] = image_url
except Exception as e:
- print("FLICKR API ERROR: "+str(e))
+ print("FLICKR API ERROR: "+str(e))
image_url = ""
else:
image_url = flickr_images[comName]
-
if settings_dict.get('APPRISE_NOTIFY_EACH_DETECTION') == "1":
notify_body = body.replace("$sciname", sciName)\
.replace("$comname", comName)\
+ .replace("$confidencepct", confidencepct)\
.replace("$confidence", confidence)\
.replace("$listenurl", listenurl)\
.replace("$date", date)\
@@ -113,6 +117,7 @@ def sendAppriseNotifications(species, confidence, path, date, time, week, latitu
.replace("$overlap", overlap)
notify_title = title.replace("$sciname", sciName)\
.replace("$comname", comName)\
+ .replace("$confidencepct", confidencepct)\
.replace("$confidence", confidence)\
.replace("$listenurl", listenurl)\
.replace("$date", date)\
@@ -143,6 +148,7 @@ def sendAppriseNotifications(species, confidence, path, date, time, week, latitu
print("send the notification")
notify_body = body.replace("$sciname", sciName)\
.replace("$comname", comName)\
+ .replace("$confidencepct", confidencepct)\
.replace("$confidence", confidence)\
.replace("$listenurl", listenurl)\
.replace("$date", date)\
@@ -157,6 +163,7 @@ def sendAppriseNotifications(species, confidence, path, date, time, week, latitu
+ " (first time today)"
notify_title = title.replace("$sciname", sciName)\
.replace("$comname", comName)\
+ .replace("$confidencepct", confidencepct)\
.replace("$confidence", confidence)\
.replace("$listenurl", listenurl)\
.replace("$date", date)\
@@ -191,6 +198,7 @@ def sendAppriseNotifications(species, confidence, path, date, time, week, latitu
if numberDetections > 0 and numberDetections <= 5:
notify_body = body.replace("$sciname", sciName)\
.replace("$comname", comName)\
+ .replace("$confidencepct", confidencepct)\
.replace("$confidence", confidence)\
.replace("$listenurl", listenurl)\
.replace("$date", date)\
@@ -205,6 +213,7 @@ def sendAppriseNotifications(species, confidence, path, date, time, week, latitu
+ " (only seen " + str(int(numberDetections)) + " times in last 7d)"
notify_title = title.replace("$sciname", sciName)\
.replace("$comname", comName)\
+ .replace("$confidencepct", confidencepct)\
.replace("$confidence", confidence)\
.replace("$listenurl", listenurl)\
.replace("$date", date)\
diff --git a/scripts/utils/parse_settings.py b/scripts/utils/parse_settings.py
index d0e2166..723560e 100644
--- a/scripts/utils/parse_settings.py
+++ b/scripts/utils/parse_settings.py
@@ -6,7 +6,7 @@ def config_to_settings(path):
this_run = f.readlines()
for i in this_run:
key = i.split("=")[0]
- value = i.split("=")[1][:-1]
+ value = "=".join(i.split("=")[1:])[:-1]
# Trim for strings, not ideal
if value.startswith('"') and value.endswith('"'):
value = value[1:-1]
diff --git a/tests/test_apprise_notifications.py b/tests/test_apprise_notifications.py
index 0041e43..15a81ad 100644
--- a/tests/test_apprise_notifications.py
+++ b/tests/test_apprise_notifications.py
@@ -50,6 +50,7 @@ def test_notifications(mocker):
"APPRISE_NOTIFY_NEW_SPECIES_EACH_DAY": "0"
}
sendAppriseNotifications("Myiarchus crinitus_Great Crested Flycatcher",
+ "0.91",
"91",
"filename",
"1666-06-06",
@@ -64,12 +65,13 @@ def test_notifications(mocker):
"test.db")
# No active apprise notifcations configured. Confirm no notifications.
- assert(notify_call.call_count == 0) # No notification should be sent.
+ assert (notify_call.call_count == 0) # No notification should be sent.
# Add daily notification.
notify_call.reset_mock()
settings_dict["APPRISE_NOTIFY_NEW_SPECIES_EACH_DAY"] = "1"
sendAppriseNotifications("Myiarchus crinitus_Great Crested Flycatcher",
+ "0.91",
"91",
"filename",
"1666-06-06",
@@ -83,8 +85,8 @@ def test_notifications(mocker):
settings_dict,
"test.db")
- assert(notify_call.call_count == 1)
- assert(
+ assert (notify_call.call_count == 1)
+ assert (
notify_call.call_args_list[0][0][0] == "A Great Crested Flycatcher (Myiarchus crinitus) was just detected with a confidence of 91 (first time today)"
)
@@ -92,6 +94,7 @@ def test_notifications(mocker):
notify_call.reset_mock()
settings_dict["APPRISE_NOTIFY_NEW_SPECIES"] = "1"
sendAppriseNotifications("Myiarchus crinitus_Great Crested Flycatcher",
+ "0.91",
"91",
"filename",
"1666-06-06",
@@ -105,18 +108,20 @@ def test_notifications(mocker):
settings_dict,
"test.db")
- assert(notify_call.call_count == 2)
- assert(
+ assert (notify_call.call_count == 2)
+ assert (
notify_call.call_args_list[0][0][0] == "A Great Crested Flycatcher (Myiarchus crinitus) was just detected with a confidence of 91 (first time today)"
)
- assert(
- notify_call.call_args_list[1][0][0] == "A Great Crested Flycatcher (Myiarchus crinitus) was just detected with a confidence of 91 (only seen 1 times in last 7d)"
+ assert (
+ notify_call.call_args_list[1][0][0] == "A Great Crested Flycatcher (Myiarchus crinitus) was just detected with a confidence \
+ of 91 (only seen 1 times in last 7d)"
)
# Add each species notification.
notify_call.reset_mock()
settings_dict["APPRISE_NOTIFY_EACH_DETECTION"] = "1"
sendAppriseNotifications("Myiarchus crinitus_Great Crested Flycatcher",
+ "0.91",
"91",
"filename",
"1666-06-06",
@@ -130,4 +135,4 @@ def test_notifications(mocker):
settings_dict,
"test.db")
- assert(notify_call.call_count == 3)
+ assert (notify_call.call_count == 3)
diff --git a/tests/test_settings_parse.py b/tests/test_settings_parse.py
index 82b5678..303338c 100644
--- a/tests/test_settings_parse.py
+++ b/tests/test_settings_parse.py
@@ -39,6 +39,6 @@ IDFILE=/home/pi/BirdNET-Pi/IdentifiedSoFar.txt"""
f.write(text)
settings = config_to_settings(filename.name)
- assert(settings["APPRISE_NOTIFICATION_TITLE"] == "Bird!")
- assert(settings["FULL_DISK"] == "purge")
- assert(settings["OVERLAP"] == "0.0") # Yes, it's a string at this point.
+ assert (settings["APPRISE_NOTIFICATION_TITLE"] == "Bird!")
+ assert (settings["FULL_DISK"] == "purge")
+ assert (settings["OVERLAP"] == "0.0") # Yes, it's a string at this point.