Merge pull request #754 from bdoner/feature/add-confidencepct-variable-to-apprise-notification

Add confidencepct variable to apprise notification
This commit is contained in:
ehpersonal38
2023-05-30 12:30:25 -04:00
committed by GitHub
9 changed files with 79 additions and 68 deletions
+4 -2
View File
@@ -519,6 +519,8 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
<dd>Common Name</dd>
<dt>$confidence</dt>
<dd>Confidence Score</dd>
<dt>$confidencepct</dt>
<dd>Confidence Score as a percentage (eg. 0.91 => 91)</dd>
<dt>$listenurl</dt>
<dd>A link to the detection</dd>
<dt>$date</dt>
@@ -542,9 +544,9 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
</dl>
<p>Use the variables defined above to customize your notification title and body.</p>
<label for="apprise_notification_title">Notification Title: </label>
<input name="apprise_notification_title" type="text" value="<?php print($config['APPRISE_NOTIFICATION_TITLE']);?>" /><br>
<input name="apprise_notification_title" style="width: 100%" type="text" value="<?php print($config['APPRISE_NOTIFICATION_TITLE']);?>" /><br>
<label for="apprise_notification_body">Notification Body: </label>
<input name="apprise_notification_body" type="text" value='<?php print($config['APPRISE_NOTIFICATION_BODY']);?>' /><br>
<input name="apprise_notification_body" style="width: 100%" type="text" value='<?php print($config['APPRISE_NOTIFICATION_BODY']);?>' /><br>
<input type="checkbox" name="apprise_notify_new_species" <?php if($config['APPRISE_NOTIFY_NEW_SPECIES'] == 1 && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; };?> >
<label for="apprise_notify_new_species">Notify each new infrequent species detection (<5 visits per week)</label><br>
<input type="checkbox" name="apprise_notify_new_species_each_day" <?php if($config['APPRISE_NOTIFY_NEW_SPECIES_EACH_DAY'] == 1 && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; };?> >
+2 -1
View File
@@ -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"
+8 -8
View File
@@ -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)
+18 -9
View File
@@ -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,
+16 -31
View File
@@ -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)")
+14 -5
View File
@@ -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)\
+1 -1
View File
@@ -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]
+13 -8
View File
@@ -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)
+3 -3
View File
@@ -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.