From 6e064335512b1970dba08762742040aab401f65e Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Tue, 16 Jun 2026 11:30:50 -0700 Subject: [PATCH 01/37] Add try/except for sageranger function calls. --- cougarvision_utils/detect_img.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index 7571154..2691075 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -127,18 +127,27 @@ def detect(images, config): # pylint: disable=too-many-locals cam_name = cougars.at[idx, 'cam_name'] er_alerts = config.er_alerts if label in config.alert_targets and er_alerts is True: - is_target(cam_name, - config.authorization, label) + try: + is_target(cam_name, + config.authorization, + label) + except KeyError as e: + print(f"Invalid authorization token missing key {e}") + # Email or Earthranger alerts as dictated in the config yml if config.er_alerts is True: - event_id = post_event(label, - cam_name, - config.authorization) - response = attach_image(event_id, - img_byte, - config.authorization, - label) - print(response) + try: + event_id = post_event(label, + cam_name, + config.authorization) + response = attach_image(event_id, + img_byte, + config.authorization, + label) + print(response) + except KeyError as e: + print(f"Invalid authorization token missing key {e}") + if config.email_alerts is True: smtp_server = smtp_setup(config.username, config.password, From aa060368c1cc10abdbbddf9a19759432cf0a0172 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Tue, 16 Jun 2026 13:11:52 -0700 Subject: [PATCH 02/37] fix post monthly call, --- fetch_and_alert.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index a3a983b..e8bc780 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -119,8 +119,9 @@ def main(): config.host ) if config.post_monthly: - schedule.every(30).days.do(post_monthly_obs, - config.authorization) + schedule.every(30).days.do(lambda: post_monthly_obs( + config.authorization, + config.camera_names)) while True: From efa8c2d67a2ce5202148374ffe01c458655dec3f Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Tue, 16 Jun 2026 13:18:36 -0700 Subject: [PATCH 03/37] fix pylint/flake8 errors and fix docstrings. --- fetch_and_alert.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index e8bc780..3e41ac4 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -75,6 +75,13 @@ def get_config_info(class_type): This function maps values to the dataclasses found in get_info. + Args: + class_type (str): get info has two dataclasses + config info and display info + + Return: + dict: unpacked and mapped values to + class type """ args = parse_args() config_path = args.CONFIG @@ -90,7 +97,7 @@ def get_config_info(class_type): def main(): - '''Runs main program and schedules future runs''' + """Runs main program and schedules future runs""" # Numpy FutureWarnings from tensorflow import warnings.filterwarnings('ignore', category=FutureWarning) @@ -122,7 +129,6 @@ def main(): schedule.every(30).days.do(lambda: post_monthly_obs( config.authorization, config.camera_names)) - while True: schedule.run_pending() From 0ba724774b02d6c55ef4bfd8d8c3683c23cec63b Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Tue, 16 Jun 2026 13:22:57 -0700 Subject: [PATCH 04/37] fix flake8 errors. --- cougarvision_utils/detect_img.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index 2691075..55e5fcc 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -127,26 +127,28 @@ def detect(images, config): # pylint: disable=too-many-locals cam_name = cougars.at[idx, 'cam_name'] er_alerts = config.er_alerts if label in config.alert_targets and er_alerts is True: - try: + try: is_target(cam_name, config.authorization, label) except KeyError as e: - print(f"Invalid authorization token missing key {e}") + print("Invalid authorization", + f"token missing key {e}") # Email or Earthranger alerts as dictated in the config yml if config.er_alerts is True: try: event_id = post_event(label, - cam_name, - config.authorization) + cam_name, + config.authorization) response = attach_image(event_id, img_byte, config.authorization, label) print(response) except KeyError as e: - print(f"Invalid authorization token missing key {e}") + print("Invalid authorization.", + f"token missing key {e}") if config.email_alerts is True: smtp_server = smtp_setup(config.username, From b74ef9841a877a364258db3a82e9dd0428cd5847 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Fri, 26 Jun 2026 08:46:39 -0700 Subject: [PATCH 05/37] Fix checkin function call. --- fetch_and_alert.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index 3e41ac4..10b5d4a 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -118,13 +118,14 @@ def main(): ).minutes.do(lambda: fetch_detect_alert(config)) - schedule.every(config.checkin_interval).hours.do( - checkin, + schedule.every(config.checkin_interval).hours.do(lambda: + checkin( config.dev_emails, config.username, config.password, config.host ) + ) if config.post_monthly: schedule.every(30).days.do(lambda: post_monthly_obs( config.authorization, From 498a63095ad11f467448d0b848e9f27dd4a5511d Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Fri, 26 Jun 2026 08:48:28 -0700 Subject: [PATCH 06/37] Add log directory creation if it does not exist. --- cougarvision_utils/detect_img.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index 55e5fcc..4f63197 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -14,7 +14,8 @@ import re import os from PIL import Image -from animl import classification, split +import animl +from animl import classification # , split from animl import detection from sageranger import is_target, attach_image, post_event @@ -64,7 +65,7 @@ def detect(images, config): # pylint: disable=too-many-locals expand=False).str.lower() # filter out all non animal detections if not data_frame.empty: - animal_df = split.get_animals(data_frame) + animal_df = animl.get_animals(data_frame) #split area idek # other_df = split.get_empty(data_frame) # run classifier on animal detections if there are any if not animal_df.empty: @@ -168,4 +169,5 @@ def detect(images, config): # pylint: disable=too-many-locals # Write Dataframe to csv current_date = dt.now() formatted_dt = current_date.strftime("%m-%d-%Y_%H:%M:%S") + os.makedirs(config.log_dir, exist_ok=True) cougars.to_csv(f'{config.log_dir}dataframe_{formatted_dt}') From b17fe6a7ddda3db7397be225d8879ead29db92a0 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Fri, 26 Jun 2026 08:57:10 -0700 Subject: [PATCH 07/37] Change animl import to match latest update. --- cougarvision_utils/detect_img.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index 4f63197..06c1a85 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -15,7 +15,7 @@ import os from PIL import Image import animl -from animl import classification # , split +from animl import classification from animl import detection from sageranger import is_target, attach_image, post_event @@ -48,7 +48,7 @@ def detect(images, config): # pylint: disable=too-many-locals # confidendce and checkpoint frequency conf = config.confidence ch_f = config.checkpoint_frequency - results = detection.detect(config.detector_model_load, + results = animl.detect(config.detector_model_load, image_path_list, resize_width=1280, resize_height=1280, @@ -57,7 +57,7 @@ def detect(images, config): # pylint: disable=too-many-locals batch_size=4 ) # Parse results - data_frame = detection.parse_detections(results) + data_frame = animl.parse_detections(results) # single classification function checks for the file # extension so we add it data_frame["extension"] = data_frame["filepath"].str.extract( @@ -70,13 +70,13 @@ def detect(images, config): # pylint: disable=too-many-locals # run classifier on animal detections if there are any if not animal_df.empty: classifer_model = config.classifier_model_load - predictions_raw = classification.classify(classifer_model, + predictions_raw = animl.classify(classifer_model, animal_df, batch_size=4 ) # single classification expects a list class_list_series = config.class_list["species"].tolist() - preds = classification.single_classification(animal_df, + preds = animl.single_classification(animal_df, None, predictions_raw, class_list_series From ed1266ecfb427d426909a229f981309daa05af15 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Fri, 26 Jun 2026 08:57:50 -0700 Subject: [PATCH 08/37] Remove unused imports. --- cougarvision_utils/detect_img.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index 06c1a85..cf87af4 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -15,8 +15,6 @@ import os from PIL import Image import animl -from animl import classification -from animl import detection from sageranger import is_target, attach_image, post_event from cougarvision_utils.cropping import draw_bounding_box_on_image From e4950baee23853be2cdff4e8d8123e4f99ea18a2 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Fri, 26 Jun 2026 09:39:38 -0700 Subject: [PATCH 09/37] Add missing username and password variables. --- config/fetch_and_alert.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/fetch_and_alert.yml b/config/fetch_and_alert.yml index 116e3a5..e4092ef 100644 --- a/config/fetch_and_alert.yml +++ b/config/fetch_and_alert.yml @@ -13,11 +13,11 @@ detector_model_type: "" #classifier_model - path to classifier model classifier_model: /home/johnsmith/cougarvision/classifier_models/EfficientNetB5_456_Unfrozen_05_0.26_0.92.h5 checkpoint_frequency: -1 -#log_dir - path to logs (must create this folder first) +#log_dir - path to logs log_dir: /home/johnsmith/cougarvision/logs/ #classes - path to the class list for the classifier model classes: /home/johnsmith/cougarvision/labels/sw_classes.txt -#Run version that allows for images cast to two screens? True/False +#Run version that allows for images cast to two screens True/False visualize_output: True #for visualizing output, folder for cougarvision to put all images path_to_unlabeled_output: /path/to/unlabeled/img/folder @@ -34,9 +34,13 @@ threads: 8 #strike force api url strikeforce_api: https://api.strikeforcewireless.com/api/v2/ #strikeforce wireless username -username_scraper: yourusername.cam@gmail.com +#formmatt matters here +username_scraper: ['yourusername.cam@gmail.com'] #strikeforce wireless password -password_scraper: yourpassword +password_scraper: ['yourpassword'] +# without brackets +username: youremail@gmail.com +password: yourpassword #authorization token from Strike Force auth_token: #save_dir - path to where the images get stored (must create folder) From 9f9d58700a7e6a2e28ea1437beb3dc76d71d6c01 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Fri, 26 Jun 2026 11:44:01 -0700 Subject: [PATCH 10/37] Update function to print dictionary of cameras to camera id with added optional print for last synced. --- cougarvision_utils/strikeforcegetcameras.py | 31 +++++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/cougarvision_utils/strikeforcegetcameras.py b/cougarvision_utils/strikeforcegetcameras.py index 7e75184..8c6145f 100644 --- a/cougarvision_utils/strikeforcegetcameras.py +++ b/cougarvision_utils/strikeforcegetcameras.py @@ -21,16 +21,35 @@ def get_data(base, request, parameters, username, authentication_token): return json.loads(data_response) + BASE = "https://api.strikeforcewireless.com/api/v2/" REQUEST = "cameras" PARAMETERS = "" -USERNAME = "" -AUTH_TOKEN = "" +USERNAME = "username" +AUTH_TOKEN = "token" + data = get_data(BASE, REQUEST, PARAMETERS, USERNAME, AUTH_TOKEN) pretty_json = json.dumps(data, indent=4) cameras = [] -list_of_cam_info = data['data'] -for _, i in enumerate(list_of_cam_info): - cameras = list_of_cam_info[i]['id'] - print(cameras) +# print(list(data.keys())) + +#list_of_cam_data = data["data"] +#for idx, d in enumerate(list_of_cam_data): +# optional print for logging purposes +# last_synced = list_of_cam_data[idx]['attributes']['last_sync_time'] +# print("Name: " + name, "ID: " + id , "date: " + last_synced) + +list_of_cam_info = data["included"] + +for idx, (name) in enumerate(list_of_cam_info): + if list_of_cam_info[idx]['attributes'].get('camera_id') is not None: + name = list_of_cam_info[idx]['attributes']['name'] + id = list_of_cam_info[idx]['attributes']['camera_id'] + print(idx," ",id, " ", name) + + temp_tuple = id, name + cameras.append(temp_tuple) + +camera_dict = dict(cameras) +print(camera_dict) From 8a01ac71085c1a0c651bb5350ddd19c8bf2c45be Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Fri, 26 Jun 2026 12:16:12 -0700 Subject: [PATCH 11/37] Clean up comments. --- cougarvision_utils/strikeforcegetcameras.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cougarvision_utils/strikeforcegetcameras.py b/cougarvision_utils/strikeforcegetcameras.py index 8c6145f..ac16f03 100644 --- a/cougarvision_utils/strikeforcegetcameras.py +++ b/cougarvision_utils/strikeforcegetcameras.py @@ -22,6 +22,7 @@ def get_data(base, request, parameters, username, authentication_token): + BASE = "https://api.strikeforcewireless.com/api/v2/" REQUEST = "cameras" PARAMETERS = "" @@ -32,22 +33,20 @@ def get_data(base, request, parameters, username, authentication_token): data = get_data(BASE, REQUEST, PARAMETERS, USERNAME, AUTH_TOKEN) pretty_json = json.dumps(data, indent=4) cameras = [] -# print(list(data.keys())) -#list_of_cam_data = data["data"] -#for idx, d in enumerate(list_of_cam_data): -# optional print for logging purposes +# print(list(data.keys())) +# optional -- missing cam id for now +# list_of_cam_data = data["data"] +# for idx, d in enumerate(list_of_cam_data): # last_synced = list_of_cam_data[idx]['attributes']['last_sync_time'] -# print("Name: " + name, "ID: " + id , "date: " + last_synced) list_of_cam_info = data["included"] -for idx, (name) in enumerate(list_of_cam_info): +for idx, name in enumerate(list_of_cam_info): if list_of_cam_info[idx]['attributes'].get('camera_id') is not None: name = list_of_cam_info[idx]['attributes']['name'] id = list_of_cam_info[idx]['attributes']['camera_id'] - print(idx," ",id, " ", name) - + # print(idx," ID: ",id, " Name", name) temp_tuple = id, name cameras.append(temp_tuple) From 06faa6ec08d043d370f72bb7e3da9f97fea46c37 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 9 Jul 2026 08:46:41 -0700 Subject: [PATCH 12/37] Add quit commands after sending emails. --- cougarvision_utils/alert.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cougarvision_utils/alert.py b/cougarvision_utils/alert.py index 06c1668..71ff92e 100644 --- a/cougarvision_utils/alert.py +++ b/cougarvision_utils/alert.py @@ -83,11 +83,15 @@ def send_alert(alert, img, smtp_server, from_email, to_emails, dev, conf): # Server sends email message server = smtp_server server.send_message(email_message) + server.quit def checkin(to_emails, username, password, host): '''Sends server status to specified email at specified time interval''' print("Checking in at: " + str(dt.now())) + + smtp_server = smtp_setup(username, password, host) + # Construct Email Content email_message = EmailMessage() email_message.add_header('To', ', '.join(to_emails)) @@ -96,6 +100,6 @@ def checkin(to_emails, username, password, host): email_message.add_header('X-Priority', '1') # Urgency, 1 highest, 5 lowest email_message.set_content('Still Alive :)') # Server sends email message - smtp_server = smtp_setup(username, password, host) server = smtp_server server.send_message(email_message) + server.quit \ No newline at end of file From e3415e9f00c280902bc824215c5b0dba6cbbe080 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 9 Jul 2026 08:47:15 -0700 Subject: [PATCH 13/37] Remove incorrect line. --- cougarvision_utils/get_images.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cougarvision_utils/get_images.py b/cougarvision_utils/get_images.py index bc9a403..3f915ce 100644 --- a/cougarvision_utils/get_images.py +++ b/cougarvision_utils/get_images.py @@ -122,7 +122,7 @@ def fetch_image_api(config): # pylint: disable=too-many-locals # id_path is the path to the id text file path = config.id_path # try creating file throw exception if it - # does not exist + # does not existdatefmt='%Y-%m-%d %H:%M:%S' try: with open(path, "x", encoding="utf-8") as file: file.write(str(0)) # write first ID from sf @@ -172,9 +172,11 @@ def fetch_image_api(config): # pylint: disable=too-many-locals new_photos = np.array(new_photos) if len(new_photos) > 0: # update last image new_last = max(new_photos[:, 0]) + new_id = str(new_last) # write new id to .txt file with open(path, "w", encoding="utf-8") as file: file.writelines(new_id) return new_photos + \ No newline at end of file From feee6818e4ea29bc5b00c6dee0ec4afd780eead0 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 9 Jul 2026 08:47:58 -0700 Subject: [PATCH 14/37] Remove timestamp. --- fetch_and_alert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index 10b5d4a..dd3e29c 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -36,7 +36,7 @@ def logger(): """Function for creating log file""" - logging.basicConfig(filename='cougarvision.log', level=logging.INFO) + logging.basicConfig(filename='cougarvision.log', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') def fetch_detect_alert(config): @@ -50,7 +50,7 @@ def fetch_detect_alert(config): detect(images, config) print('Finished Detection') print("Sleeping since: " + str(dt.now())) - + def parse_args(): """Creates parser for config yaml. @@ -118,7 +118,7 @@ def main(): ).minutes.do(lambda: fetch_detect_alert(config)) - schedule.every(config.checkin_interval).hours.do(lambda: + schedule.every(config.run_scheduler).minutes.do(lambda: checkin( config.dev_emails, config.username, From 2893db932080fb889e2cb1f2f7d0778b234e8149 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 9 Jul 2026 08:54:15 -0700 Subject: [PATCH 15/37] fix flake8 and pylint errors. --- cougarvision_utils/alert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cougarvision_utils/alert.py b/cougarvision_utils/alert.py index 71ff92e..eca7059 100644 --- a/cougarvision_utils/alert.py +++ b/cougarvision_utils/alert.py @@ -102,4 +102,4 @@ def checkin(to_emails, username, password, host): # Server sends email message server = smtp_server server.send_message(email_message) - server.quit \ No newline at end of file + server.quit From 254823d71ae4787a3477421712065616549a20fb Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 9 Jul 2026 10:53:15 -0700 Subject: [PATCH 16/37] Remove unpacking config value logic and import from sageranger. --- fetch_and_alert.py | 47 ++-------------------------------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index dd3e29c..836e2a9 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -28,6 +28,7 @@ import yaml from sageranger.post_monthly import post_monthly_obs +from sageranger.unpack_info import get_config_info from cougarvision_utils.detect_img import detect from cougarvision_utils.alert import checkin from cougarvision_utils.get_images import fetch_image_api @@ -52,50 +53,6 @@ def fetch_detect_alert(config): print("Sleeping since: " + str(dt.now())) -def parse_args(): - """Creates parser for config yaml. - - This function creates an arguement parser that creates an - args container with the arguement 'CONFIG'. - - Returns: - argsparse.Namespace: An object containing all parsed arguement - values as attributes (e.g., args.CONFIG). - """ - parser = argparse.ArgumentParser(description='Retrieves images from \ - email & web scraper & runs detection') - parser.add_argument('CONFIG', type=str, help='Path to config file') - - return parser.parse_args() - - -def get_config_info(class_type): - """Parses through config file. - - This function maps values to the dataclasses - found in get_info. - - Args: - class_type (str): get info has two dataclasses - config info and display info - - Return: - dict: unpacked and mapped values to - class type - """ - args = parse_args() - config_path = args.CONFIG - - with open(config_path, 'r', encoding='utf-8') as file: - config_dict = yaml.safe_load(file) - - # for direct mapping only use fields in the class fields - valid_keys = {f.name for f in fields(class_type)} - filtered_keys = {k: v for k, v in config_dict.items() if k in valid_keys} - - return class_type(**filtered_keys) - - def main(): """Runs main program and schedules future runs""" @@ -118,7 +75,7 @@ def main(): ).minutes.do(lambda: fetch_detect_alert(config)) - schedule.every(config.run_scheduler).minutes.do(lambda: + schedule.every(config.checkin_interval).hours.do(lambda: checkin( config.dev_emails, config.username, From 067cd20a75c91678f5f29356026c8cee81f83a1b Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 9 Jul 2026 10:57:03 -0700 Subject: [PATCH 17/37] Fix pylint and flake8 errors. --- fetch_and_alert.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index 836e2a9..f8aaced 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -18,14 +18,11 @@ """ # Import local utilities -import argparse import time import warnings from datetime import datetime as dt import logging -from dataclasses import fields import schedule -import yaml from sageranger.post_monthly import post_monthly_obs from sageranger.unpack_info import get_config_info @@ -37,7 +34,8 @@ def logger(): """Function for creating log file""" - logging.basicConfig(filename='cougarvision.log', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') + logging.basicConfig(filename='cougarvision.log', level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') def fetch_detect_alert(config): @@ -51,7 +49,7 @@ def fetch_detect_alert(config): detect(images, config) print('Finished Detection') print("Sleeping since: " + str(dt.now())) - + def main(): """Runs main program and schedules future runs""" @@ -75,14 +73,12 @@ def main(): ).minutes.do(lambda: fetch_detect_alert(config)) - schedule.every(config.checkin_interval).hours.do(lambda: - checkin( - config.dev_emails, - config.username, - config.password, - config.host - ) - ) + schedule.every(config.checkin_interval + ).hours.do(lambda: + checkin(config.dev_emails, + config.username, + config.password, + config.host)) if config.post_monthly: schedule.every(30).days.do(lambda: post_monthly_obs( config.authorization, From d7810640174dd6fec053f17f3d4096c7333dd02f Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 9 Jul 2026 11:14:12 -0700 Subject: [PATCH 18/37] Change findall to str.extract. --- cougarvision_utils/detect_img.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index cf87af4..a5aee45 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -63,8 +63,8 @@ def detect(images, config): # pylint: disable=too-many-locals expand=False).str.lower() # filter out all non animal detections if not data_frame.empty: - animal_df = animl.get_animals(data_frame) #split area idek - # other_df = split.get_empty(data_frame) + animal_df = animl.get_animals(data_frame) + # run classifier on animal detections if there are any if not animal_df.empty: classifer_model = config.classifier_model_load @@ -86,8 +86,10 @@ def detect(images, config): # pylint: disable=too-many-locals cougars = cougars.reset_index(drop=True) # create a row in the dataframe containing only the camera name # flake8: disable-next - cougars['cam_name'] = cougars['filepath'].apply( - lambda x: re.findall(r'[A-Z]\d+', x)[0]) + print("######Cougars:",cougars) + #cougars['cam_name'] = cougars['filepath'].apply( + # lambda x: re.findall(r'[A-Z]\d+', x)[0]) + cougars["cam_name"] = cougars["filepath"].str.extract(r"([A-Z]\d+)") # Sends alert for each cougar detection for idx in range(len(cougars.index)): label = cougars.at[idx, 'prediction'] From b2a42e725851a6adfb785765dfc1966d15dc62f8 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 9 Jul 2026 11:17:26 -0700 Subject: [PATCH 19/37] Update to print out a dict of camera to sf id. --- cougarvision_utils/strikeforcegetcameras.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cougarvision_utils/strikeforcegetcameras.py b/cougarvision_utils/strikeforcegetcameras.py index ac16f03..547a8b3 100644 --- a/cougarvision_utils/strikeforcegetcameras.py +++ b/cougarvision_utils/strikeforcegetcameras.py @@ -26,8 +26,8 @@ def get_data(base, request, parameters, username, authentication_token): BASE = "https://api.strikeforcewireless.com/api/v2/" REQUEST = "cameras" PARAMETERS = "" -USERNAME = "username" -AUTH_TOKEN = "token" +USERNAME = " Date: Thu, 9 Jul 2026 11:27:30 -0700 Subject: [PATCH 20/37] Clean up functions, include main, fix spacing errors. --- cougarvision_utils/strikeforcegetcameras.py | 59 ++++++++++++--------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/cougarvision_utils/strikeforcegetcameras.py b/cougarvision_utils/strikeforcegetcameras.py index 547a8b3..083a784 100644 --- a/cougarvision_utils/strikeforcegetcameras.py +++ b/cougarvision_utils/strikeforcegetcameras.py @@ -2,9 +2,15 @@ import json import requests +BASE = "https://api.strikeforcewireless.com/api/v2/" +REQUEST = "cameras" +PARAMETERS = "" +USERNAME = "" +AUTH_TOKEN = "" + def get_data(base, request, parameters, username, authentication_token): - '''Function for retrieving camera info from strikeforce + """Function for retrieving camera info from strikeforce Args: base: strikeforce api link request: cameras @@ -13,7 +19,8 @@ def get_data(base, request, parameters, username, authentication_token): authentication_token: auth token obtained from strikeforceget.py Returns: - json with all camera info''' + json with all camera info + """ call = base + request + "?" + parameters headers = {"X-User-Email": username, "X-User-Token": authentication_token} response = requests.get(call, headers=headers, timeout=20) @@ -21,34 +28,36 @@ def get_data(base, request, parameters, username, authentication_token): return json.loads(data_response) +def main(): + """Main + This function parses the json response to retireve + the camera id and name and prints a dict of the values + mapped to the corresponding camera for the config value + 'camera_names' + """ -BASE = "https://api.strikeforcewireless.com/api/v2/" -REQUEST = "cameras" -PARAMETERS = "" -USERNAME = " Date: Thu, 9 Jul 2026 11:44:13 -0700 Subject: [PATCH 21/37] Update the dataclass module docstring. --- cougarvision_utils/get_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cougarvision_utils/get_info.py b/cougarvision_utils/get_info.py index 7873439..898c91f 100644 --- a/cougarvision_utils/get_info.py +++ b/cougarvision_utils/get_info.py @@ -3,8 +3,8 @@ Get_info holds the ConfigInfo data class that holds attribute values from the configuration file. It also hold the display_info class that has values from the config directly -related to the display file.Direct mapping is handled in fetch -and alert in get_config_info function.Fields defined in this class +related to the display file.Direct mapping is handled in sageranger +in get_config_info function.Fields defined in this class will be the values mapped.The field values must match exactly to the config file. Fetch_and_alert, get_images, display and detect_img rely on these attribute values. From 7305004594424654fda51492ccb3ebdc6131e9b6 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Tue, 14 Jul 2026 11:48:03 -0700 Subject: [PATCH 22/37] Fix log directory creation. --- cougarvision_utils/detect_img.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index a5aee45..a2c1f36 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -86,7 +86,7 @@ def detect(images, config): # pylint: disable=too-many-locals cougars = cougars.reset_index(drop=True) # create a row in the dataframe containing only the camera name # flake8: disable-next - print("######Cougars:",cougars) + # print("######Cougars:",cougars) #cougars['cam_name'] = cougars['filepath'].apply( # lambda x: re.findall(r'[A-Z]\d+', x)[0]) cougars["cam_name"] = cougars["filepath"].str.extract(r"([A-Z]\d+)") @@ -169,5 +169,6 @@ def detect(images, config): # pylint: disable=too-many-locals # Write Dataframe to csv current_date = dt.now() formatted_dt = current_date.strftime("%m-%d-%Y_%H:%M:%S") - os.makedirs(config.log_dir, exist_ok=True) - cougars.to_csv(f'{config.log_dir}dataframe_{formatted_dt}') + logs = config.log_dir + os.makedirs(logs, exist_ok=True) + cougars.to_csv(f'{logs}dataframe_{formatted_dt}') From 30761cea53c27b83a4441afe6307b56e950d5686 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 16 Jul 2026 13:14:02 -0700 Subject: [PATCH 23/37] Update alert email formatting and limit passed arguements. --- cougarvision_utils/alert.py | 71 ++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/cougarvision_utils/alert.py b/cougarvision_utils/alert.py index eca7059..17e786d 100644 --- a/cougarvision_utils/alert.py +++ b/cougarvision_utils/alert.py @@ -14,20 +14,21 @@ def smtp_setup(username, password, host): - '''SMTP Setup + """SMTP Setup This function creates a simple mail transfer protocol by taking in a host email, a username and password for an email. Args: - username: username for email to send message from, string from config - password: password for email message will be sent from, string from config - host: IMAP protocol to download gmail messages, initialized in - detect_img.py - - Returns: SMTP_SSL object logged into the mailing account specified in - config yml - ''' + username (str): username for email to send message from, string from config + password (str): password for email message will be sent from, string from config + host (str): IMAP protocol to download gmail messages, initialized in + detect_img.py + + Returns: + SMTP_SSL object logged into the mailing account specified in + config yml + """ # Init sending mail smtp_server = SMTP_SSL(host, port=SMTP_SSL_PORT) smtp_server.set_debuglevel(1) # Show SMTP server interactions @@ -35,8 +36,9 @@ def smtp_setup(username, password, host): return smtp_server -def send_alert(alert, img, smtp_server, from_email, to_emails, dev, conf): - '''Send Alert + +def send_alert(config, alert, img, dev,conf): #, from_email, to_emails, dev, conf) + """Send Alert This function takes in the animal label, the image of the animal of interest, the SMTP server created, and the to and from emails to send @@ -45,19 +47,18 @@ def send_alert(alert, img, smtp_server, from_email, to_emails, dev, conf): Args: alert: label of animal that the alert is being created for conf: confidence value of the classifier that the animal it says it - is is the animal it is + is is the animal it is img: the PIL.Image of the image that is to be sent, to be converted - to binary - smtp_server: where the email is to be sent from, SMTP_SSL object - from_email: the outgoing address for the alert - to_emails: the emails the alert will be sent to, defined in config yml file - ''' + to binary + config (dict): holds the values of username, password, host, + dev/consumeremails for email setup and info for recipients. + """ # Construct Email Content email_message = EmailMessage() - email_message.add_header('To', ', '.join(to_emails)) - email_message.add_header('From', from_email) - email_message.add_header('Subject', 'Alert!') - email_message.add_header('X-Priority', '1') # Urgency, 1 highest, 5 lowest + email_message['To'] = ', '.join(config.consumer_emails) + email_message['from'] = config.username + email_message['Subject'] = 'Alert!' + email_message['X-Priority'] = '1' # Urgency, 1 highest, 5 lowest if dev == 0: message = "Potential " + alert + " detected by CougarVision "\ + "system.\n\nPlease review attached image to verify"\ @@ -81,25 +82,29 @@ def send_alert(alert, img, smtp_server, from_email, to_emails, dev, conf): subtype=subtype, filename=filename) # Server sends email message - server = smtp_server + server = smtp_setup(config.username, config.password, config.host) server.send_message(email_message) - server.quit + server.quit() -def checkin(to_emails, username, password, host): - '''Sends server status to specified email at specified time interval''' - print("Checking in at: " + str(dt.now())) +def checkin(config): + """Sends server status to specified email at specified time interval - smtp_server = smtp_setup(username, password, host) + Args: + config (dict): holds the values of username, password, host, + dev/consumeremails for email setup and info for recipients. + """ + print("Checking in at: " + str(dt.now())) # Construct Email Content email_message = EmailMessage() - email_message.add_header('To', ', '.join(to_emails)) - email_message.add_header('From', username) - email_message.add_header('Subject', 'Checkin') + email_message['To'] = ', '.join(config.dev_emails) + email_message['from'] = config.username + email_message['Subject'] = 'Checkin' email_message.add_header('X-Priority', '1') # Urgency, 1 highest, 5 lowest - email_message.set_content('Still Alive :)') + message = "still Alive :) " + email_message.set_content(message) # Server sends email message - server = smtp_server + server = smtp_setup(config.username, config.password, config.host) server.send_message(email_message) - server.quit + server.quit() From 331c3c20eaa2152574ba70532729223511b1bdea Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 16 Jul 2026 13:14:37 -0700 Subject: [PATCH 24/37] Change arguements passed to checkin. --- fetch_and_alert.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index f8aaced..761cbd9 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -73,12 +73,9 @@ def main(): ).minutes.do(lambda: fetch_detect_alert(config)) - schedule.every(config.checkin_interval - ).hours.do(lambda: - checkin(config.dev_emails, - config.username, - config.password, - config.host)) + schedule.every(2 + ).minutes.do(lambda: + checkin(config)) if config.post_monthly: schedule.every(30).days.do(lambda: post_monthly_obs( config.authorization, From 252a056ceef1c901ee732edf30f601dd73ac12eb Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 16 Jul 2026 13:15:18 -0700 Subject: [PATCH 25/37] Change arguements passed to function alert. --- cougarvision_utils/detect_img.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index a2c1f36..6800d88 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -18,7 +18,7 @@ from sageranger import is_target, attach_image, post_event from cougarvision_utils.cropping import draw_bounding_box_on_image -from cougarvision_utils.alert import smtp_setup, send_alert +from cougarvision_utils.alert import send_alert def detect(images, config): # pylint: disable=too-many-locals @@ -152,18 +152,11 @@ def detect(images, config): # pylint: disable=too-many-locals f"token missing key {e}") if config.email_alerts is True: - smtp_server = smtp_setup(config.username, - config.password, - config.host - ) dev = 0 - send_alert(label, image_bytes, smtp_server, - config.username, config.consumer_emails, - dev, prob - ) + send_alert(label, image_bytes, config, + dev, prob) dev = 1 - send_alert(label, image_bytes, smtp_server, - config.username, config.dev_emails, + send_alert(label, image_bytes, config, dev, prob) # Write Dataframe to csv From 37f426722b58ee64bdcccaabc57550ef37d982c5 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 16 Jul 2026 13:24:52 -0700 Subject: [PATCH 26/37] Fix flake8 and pylint errors. --- cougarvision_utils/alert.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/cougarvision_utils/alert.py b/cougarvision_utils/alert.py index 17e786d..2a48677 100644 --- a/cougarvision_utils/alert.py +++ b/cougarvision_utils/alert.py @@ -20,10 +20,12 @@ def smtp_setup(username, password, host): host email, a username and password for an email. Args: - username (str): username for email to send message from, string from config - password (str): password for email message will be sent from, string from config + username (str): username for email to send message from + string from config + password (str): password for email message will be sent from, + string from config host (str): IMAP protocol to download gmail messages, initialized in - detect_img.py + detect_img.py Returns: SMTP_SSL object logged into the mailing account specified in @@ -36,8 +38,7 @@ def smtp_setup(username, password, host): return smtp_server - -def send_alert(config, alert, img, dev,conf): #, from_email, to_emails, dev, conf) +def send_alert(config, alert, img, dev, conf): """Send Alert This function takes in the animal label, the image of the animal of @@ -45,13 +46,13 @@ def send_alert(config, alert, img, dev,conf): #, from_email, to_emails, dev, con the alert containing that specific image along with the confidence value. Args: - alert: label of animal that the alert is being created for - conf: confidence value of the classifier that the animal it says it + alert (str): label of animal that the alert is being created for + conf (float): confidence value of the classifier that the animal it says it is is the animal it is - img: the PIL.Image of the image that is to be sent, to be converted + img(bytes): the PIL.Image of the image that is to be sent, to be converted to binary - config (dict): holds the values of username, password, host, - dev/consumeremails for email setup and info for recipients. + config (dict): holds the values of username, password, host, + dev/consumeremails for email setup and info for recipients. """ # Construct Email Content email_message = EmailMessage() @@ -59,6 +60,7 @@ def send_alert(config, alert, img, dev,conf): #, from_email, to_emails, dev, con email_message['from'] = config.username email_message['Subject'] = 'Alert!' email_message['X-Priority'] = '1' # Urgency, 1 highest, 5 lowest + message = "" if dev == 0: message = "Potential " + alert + " detected by CougarVision "\ + "system.\n\nPlease review attached image to verify"\ @@ -69,8 +71,6 @@ def send_alert(config, alert, img, dev,conf): #, from_email, to_emails, dev, con message = "Potential " + alert + " detected with confidence value: "\ + conf - email_message.set_content(message) - # Prepare Image format binary_data = img.getvalue() @@ -82,6 +82,7 @@ def send_alert(config, alert, img, dev,conf): #, from_email, to_emails, dev, con subtype=subtype, filename=filename) # Server sends email message + email_message.set_content(message) server = smtp_setup(config.username, config.password, config.host) server.send_message(email_message) server.quit() @@ -91,8 +92,8 @@ def checkin(config): """Sends server status to specified email at specified time interval Args: - config (dict): holds the values of username, password, host, - dev/consumeremails for email setup and info for recipients. + config (dict): holds the values of username, password, host, + dev/consumeremails for email setup and info for recipients. """ print("Checking in at: " + str(dt.now())) @@ -100,10 +101,11 @@ def checkin(config): email_message = EmailMessage() email_message['To'] = ', '.join(config.dev_emails) email_message['from'] = config.username - email_message['Subject'] = 'Checkin' + email_message['Subject'] = 'Checkin' email_message.add_header('X-Priority', '1') # Urgency, 1 highest, 5 lowest - message = "still Alive :) " + message = "still Alive :) " email_message.set_content(message) + # Server sends email message server = smtp_setup(config.username, config.password, config.host) server.send_message(email_message) From 6902eb841e7d907e0713243bc969296199dffb5e Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 23 Jul 2026 13:11:00 -0700 Subject: [PATCH 27/37] Change float confidence to a string value. --- cougarvision_utils/alert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cougarvision_utils/alert.py b/cougarvision_utils/alert.py index 2a48677..1c473bf 100644 --- a/cougarvision_utils/alert.py +++ b/cougarvision_utils/alert.py @@ -38,7 +38,7 @@ def smtp_setup(username, password, host): return smtp_server -def send_alert(config, alert, img, dev, conf): +def send_alert(config, alert, img, dev): """Send Alert This function takes in the animal label, the image of the animal of @@ -69,7 +69,7 @@ def send_alert(config, alert, img, dev, conf): + "and artifacts have been known to trigger the system." elif dev != 0: message = "Potential " + alert + " detected with confidence value: "\ - + conf + + str(config.confidence) # Prepare Image format binary_data = img.getvalue() From 31476cbabd2a318272010a6d3df0166106c4560b Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 30 Jul 2026 10:37:04 -0700 Subject: [PATCH 28/37] Replace end of image name with jpg. --- cougarvision_utils/get_images.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cougarvision_utils/get_images.py b/cougarvision_utils/get_images.py index 3f915ce..842792d 100644 --- a/cougarvision_utils/get_images.py +++ b/cougarvision_utils/get_images.py @@ -164,7 +164,13 @@ def fetch_image_api(config): # pylint: disable=too-many-locals newname += "_" + date_time newname += "_" + info['file_thumb_filename'] # native extension from strikeforce is .JPG.jpeg for some reason - stripped_name = newname.replace(".JPG.jpeg", ".jpg") + list_endings = [".JPG.jpeg", ".jpg.jpeg", ".jpeg", ",MP4.jpeg", ".AVI.jpeg"] + + for n in list_endings: + if n in newname: + stripped_name = newname.replace(str(n), ".jpg") + + # stripped_name = newname.replace(".JPG.jpeg", ".jpg") urllib.request.urlretrieve(info['file_thumb_url'], stripped_name) new_photos.append([photo['id'], info['file_thumb_url'], stripped_name]) From 08c85102bc82969ce2b4bf777820935d187ab345 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 30 Jul 2026 12:26:37 -0700 Subject: [PATCH 29/37] Add logging info to email notifcation and post observations. --- fetch_and_alert.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index 761cbd9..bfa6647 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -34,8 +34,7 @@ def logger(): """Function for creating log file""" - logging.basicConfig(filename='cougarvision.log', level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + logging.basicConfig(filename='cougarvision.log', level=logging.INFO) def fetch_detect_alert(config): @@ -73,13 +72,21 @@ def main(): ).minutes.do(lambda: fetch_detect_alert(config)) - schedule.every(2 - ).minutes.do(lambda: + schedule.every(config.checkin_interval + ).hours.do(lambda: checkin(config)) + + schedule.every(config.checkin_interval).hours.do(lambda:logging.info( + "Sent checkin email at " + + str(dt.now()))) if config.post_monthly: schedule.every(30).days.do(lambda: post_monthly_obs( config.authorization, config.camera_names)) + schedule.every(30).days.do(lambda:logging.info( + "Posted monthly observation at " + + str(dt.now()))) + while True: schedule.run_pending() From 5bb7f0d74e657a45105f108419490711b03f37f6 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 30 Jul 2026 12:33:41 -0700 Subject: [PATCH 30/37] Add logging info for skipped images and timeout errors. --- cougarvision_utils/get_images.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cougarvision_utils/get_images.py b/cougarvision_utils/get_images.py index 842792d..2833bf4 100644 --- a/cougarvision_utils/get_images.py +++ b/cougarvision_utils/get_images.py @@ -19,6 +19,7 @@ import os import requests import numpy as np +from datetime import datetime as dt # pylint: disable=pointless-string-statement """ @@ -86,18 +87,18 @@ def request_strikeforce(username, auth_token, base, request, parameters): except requests.exceptions.ConnectionError as excpt: logging.warning("Failed to connect attempt: %s error %s", {attempt + 1}, - {excpt}) + {excpt} + str(dt.now())) print(f'Connection Error {attempt + 1}: {excpt}') time.sleep(15) # wait 15 seconds except requests.exceptions.Timeout as excpt: logging.warning("Failed to connect to" " StrikeForce attempt: %s error %s", {attempt + 1}, - {excpt}) + {excpt} + str(dt.now())) print(f'Timeout Error {attempt + 1}: {excpt}') time.sleep(15) # wait 15 seconds - logging.error("Failed to connect after multiple attempts.") + logging.error("Failed to connect after multiple attempts at: " + str(dt.now())) # broad error raise RuntimeError("Failed to connect" "after multiple attempts.") @@ -152,7 +153,8 @@ def fetch_image_api(config): # pylint: disable=too-many-locals camera = config.camera_names[photo['relationships'] ['camera']['data']['id']] except KeyError: - logging.warning('skipped img: no associated cam ID') + logging.warning('skipped img: no associated cam ID for image', + photo['id'] ,"at: " + str(dt.now())) continue image_dir = config.save_dir From 8ef784bf71a2710725dc795dab27ac3f38ad22fe Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 30 Jul 2026 12:39:58 -0700 Subject: [PATCH 31/37] Add logging to show when cougarvision starts. --- fetch_and_alert.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index bfa6647..503eee7 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -57,8 +57,10 @@ def main(): warnings.filterwarnings('ignore', category=FutureWarning) logger() + logging.info("Starting cougarvision at: " + str(dt.now())) config = get_config_info(ConfigInfo) + # pass ConfigInfo dataclass object fetch_detect_alert(config) From 18544a8c1e224745a5abdeeea7be40ea149f4d93 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 6 Aug 2026 11:35:39 -0700 Subject: [PATCH 32/37] Add logging info about emails. --- cougarvision_utils/alert.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cougarvision_utils/alert.py b/cougarvision_utils/alert.py index 1c473bf..42a3b68 100644 --- a/cougarvision_utils/alert.py +++ b/cougarvision_utils/alert.py @@ -8,6 +8,7 @@ ''' import mimetypes +import logging from email.message import EmailMessage from smtplib import SMTP_SSL, SMTP_SSL_PORT from datetime import datetime as dt @@ -56,7 +57,7 @@ def send_alert(config, alert, img, dev): """ # Construct Email Content email_message = EmailMessage() - email_message['To'] = ', '.join(config.consumer_emails) + email_message['To'] = (', '.join(config.consumer_emails)) email_message['from'] = config.username email_message['Subject'] = 'Alert!' email_message['X-Priority'] = '1' # Urgency, 1 highest, 5 lowest @@ -76,15 +77,18 @@ def send_alert(config, alert, img, dev): # Attach image to email filename = 'detection.jpg' + email_message.set_content(message) + maintype, _, subtype = (mimetypes.guess_type(filename)[0] or 'application/octet-stream').partition("/") + email_message.add_attachment(binary_data, maintype=maintype, subtype=subtype, filename=filename) # Server sends email message - email_message.set_content(message) server = smtp_setup(config.username, config.password, config.host) server.send_message(email_message) + logging.info("Email Alert sent.") server.quit() @@ -109,4 +113,5 @@ def checkin(config): # Server sends email message server = smtp_setup(config.username, config.password, config.host) server.send_message(email_message) + logging.info("Checkin email sent.") server.quit() From df80c23f919a417d52e72eca798be26684c7d2dc Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 6 Aug 2026 11:37:52 -0700 Subject: [PATCH 33/37] Add logging info and fix function calls to send emails. --- cougarvision_utils/detect_img.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index 6800d88..b5f6936 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -11,8 +11,9 @@ from io import BytesIO from datetime import datetime as dt -import re +#import re import os +import logging from PIL import Image import animl from sageranger import is_target, attach_image, post_event @@ -133,6 +134,7 @@ def detect(images, config): # pylint: disable=too-many-locals config.authorization, label) except KeyError as e: + logging.warning("Invalid authorization token missing key: %s", str(e)) print("Invalid authorization", f"token missing key {e}") @@ -146,18 +148,20 @@ def detect(images, config): # pylint: disable=too-many-locals img_byte, config.authorization, label) + logging.info("Posted event on earthranger with associated img.") print(response) except KeyError as e: + logging.warning("Invalid authorization token missing key: %s", str(e)) print("Invalid authorization.", f"token missing key {e}") if config.email_alerts is True: dev = 0 - send_alert(label, image_bytes, config, - dev, prob) + send_alert(config,label,image_bytes, + dev) dev = 1 - send_alert(label, image_bytes, config, - dev, prob) + send_alert(config,label, image_bytes, + dev) # Write Dataframe to csv current_date = dt.now() From 2126ecf53f8c7153b27ca1754854ee0f1a8c4f92 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Thu, 6 Aug 2026 13:57:36 -0700 Subject: [PATCH 34/37] Update logging statements. --- cougarvision_utils/get_images.py | 77 +++++++++++++++----------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/cougarvision_utils/get_images.py b/cougarvision_utils/get_images.py index 2833bf4..2483255 100644 --- a/cougarvision_utils/get_images.py +++ b/cougarvision_utils/get_images.py @@ -10,6 +10,35 @@ last_id.txt as well, but it creates a new one if there is not one currently present. +Examples: + + request cameras + get list of camaras + request <- "cameras" + parameters <- "" + + recent photo count + request <- "photos/recent/count" + parameters <- "" + + get recent photos across cameras + request <- "photos/recent" + parameters <- "limit=100" + + get photos from specific camera (will need to loop through pages) + request <- "photos" + parameters <- "page=3&sort_date=desc&camera_id[]=59681" + + get photos from specific camera filtered by date (will need + to loop through pages) + request <- "photos" + parameters <- "page=1&sort_date=desc&camera_id[]= + 60272&date_start=2022-09-01&date_end=2022-10-07" + + get subscriptions + request <- "subscriptions" + parameters <- "" + """ import json @@ -21,36 +50,6 @@ import numpy as np from datetime import datetime as dt -# pylint: disable=pointless-string-statement -""" -#request examples -#get list of camaras -request <- "cameras" -parameters <- "" - -recent photo count -request <- "photos/recent/count" -parameters <- "" - -get recent photos across cameras -request <- "photos/recent" -parameters <- "limit=100" - -get photos from specific camera (will need to loop through pages) -request <- "photos" -parameters <- "page=3&sort_date=desc&camera_id[]=59681" - -get photos from specific camera filtered by date (will need -to loop through pages) -request <- "photos" -parameters <- "page=1&sort_date=desc&camera_id[]= -60272&date_start=2022-09-01&date_end=2022-10-07" - -get subscriptions -request <- "subscriptions" -parameters <- "" -""" - def request_strikeforce(username, auth_token, base, request, parameters): """Strikeforce API call request. @@ -85,20 +84,15 @@ def request_strikeforce(username, auth_token, base, request, parameters): return info except requests.exceptions.ConnectionError as excpt: - logging.warning("Failed to connect attempt: %s error %s", - {attempt + 1}, - {excpt} + str(dt.now())) + logging.warning( "Failed to connect to StrikeForce:%s", str(excpt)) print(f'Connection Error {attempt + 1}: {excpt}') time.sleep(15) # wait 15 seconds except requests.exceptions.Timeout as excpt: - logging.warning("Failed to connect to" - " StrikeForce attempt: %s error %s", - {attempt + 1}, - {excpt} + str(dt.now())) + logging.warning( "Timeout error strikeforce: %s", str(excpt)) print(f'Timeout Error {attempt + 1}: {excpt}') time.sleep(15) # wait 15 seconds - logging.error("Failed to connect after multiple attempts at: " + str(dt.now())) + logging.error("Failed to connect after multiple attempts.") # broad error raise RuntimeError("Failed to connect" "after multiple attempts.") @@ -153,8 +147,9 @@ def fetch_image_api(config): # pylint: disable=too-many-locals camera = config.camera_names[photo['relationships'] ['camera']['data']['id']] except KeyError: - logging.warning('skipped img: no associated cam ID for image', - photo['id'] ,"at: " + str(dt.now())) + id_camera = str(photo['id']) + logging.warning("skipped img: no associated cam ID for image: %s ", id_camera) + # id_camera, " at: " + str(dt.now())) continue image_dir = config.save_dir @@ -166,7 +161,7 @@ def fetch_image_api(config): # pylint: disable=too-many-locals newname += "_" + date_time newname += "_" + info['file_thumb_filename'] # native extension from strikeforce is .JPG.jpeg for some reason - list_endings = [".JPG.jpeg", ".jpg.jpeg", ".jpeg", ",MP4.jpeg", ".AVI.jpeg"] + list_endings = [".JPG.jpeg", ".jpg.jpeg", ".jpeg", ".MP4.jpeg", ".AVI.jpeg"] for n in list_endings: if n in newname: From fc750b07f5dd123d13e29969da4ee057b1e8375d Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Tue, 11 Aug 2026 11:20:07 -0700 Subject: [PATCH 35/37] Update function calls and add logging stucture and info. --- fetch_and_alert.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/fetch_and_alert.py b/fetch_and_alert.py index 503eee7..d90baa5 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -34,12 +34,14 @@ def logger(): """Function for creating log file""" - logging.basicConfig(filename='cougarvision.log', level=logging.INFO) + logging.basicConfig(filename='cougarvision.log', level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s') def fetch_detect_alert(config): """Function for fetching images, detection, and sending alerts""" # Run the scheduler + logging.info("Starting cougarvision.") print("Running fetch_and_alert") print("Fetching images") images = fetch_image_api(config) @@ -57,10 +59,8 @@ def main(): warnings.filterwarnings('ignore', category=FutureWarning) logger() - logging.info("Starting cougarvision at: " + str(dt.now())) config = get_config_info(ConfigInfo) - # pass ConfigInfo dataclass object fetch_detect_alert(config) @@ -70,25 +70,19 @@ def main(): ).seconds.do(lambda: fetch_detect_alert(config)) else: - schedule.every(config.run_scheduler + schedule.every(3 ).minutes.do(lambda: fetch_detect_alert(config)) schedule.every(config.checkin_interval - ).hours.do(lambda: + ).minutes.do(lambda: checkin(config)) - schedule.every(config.checkin_interval).hours.do(lambda:logging.info( - "Sent checkin email at " + - str(dt.now()))) if config.post_monthly: schedule.every(30).days.do(lambda: post_monthly_obs( config.authorization, config.camera_names)) - schedule.every(30).days.do(lambda:logging.info( - "Posted monthly observation at " - + str(dt.now()))) - + while True: schedule.run_pending() From c4761c31f159d99f834066c158ad741c9a37cedd Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Tue, 11 Aug 2026 11:26:39 -0700 Subject: [PATCH 36/37] Add missing parameter. --- cougarvision_utils/alert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cougarvision_utils/alert.py b/cougarvision_utils/alert.py index 42a3b68..fb562c6 100644 --- a/cougarvision_utils/alert.py +++ b/cougarvision_utils/alert.py @@ -39,7 +39,7 @@ def smtp_setup(username, password, host): return smtp_server -def send_alert(config, alert, img, dev): +def send_alert(config, alert, img, dev, prob): """Send Alert This function takes in the animal label, the image of the animal of @@ -70,7 +70,7 @@ def send_alert(config, alert, img, dev): + "and artifacts have been known to trigger the system." elif dev != 0: message = "Potential " + alert + " detected with confidence value: "\ - + str(config.confidence) + + prob # Prepare Image format binary_data = img.getvalue() From b7ec76d773698e4a213c0180e2137523eff698c5 Mon Sep 17 00:00:00 2001 From: Montserrat Jara Date: Tue, 11 Aug 2026 11:28:23 -0700 Subject: [PATCH 37/37] Add logging info and fix function calls missing parameters. --- cougarvision_utils/detect_img.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index b5f6936..8cd2c42 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -11,7 +11,6 @@ from io import BytesIO from datetime import datetime as dt -#import re import os import logging from PIL import Image @@ -158,10 +157,10 @@ def detect(images, config): # pylint: disable=too-many-locals if config.email_alerts is True: dev = 0 send_alert(config,label,image_bytes, - dev) + dev, prob) dev = 1 send_alert(config,label, image_bytes, - dev) + dev, prob) # Write Dataframe to csv current_date = dt.now()