Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,3 @@ These scripts rely on the [CameraTraps Repo](https://github.com/microsoft/Camera
# Processing Batch Images
Run_batch_images.py can take in an input folder and apply the detector/classifier pipeline to render annotated images along with an output.json file in directory classifications. The output.json contains the final classifications on the cropped images and can be used to compare against a ground truth set. There is also an intermediate output.json which holds the crop detections and is used by the script to crop images, this one can be moved by configuration in the yml file.
These scripts rely on the [CameraTraps Repo](https://github.com/microsoft/CameraTraps). Download this repo and place it anywhere. Add that path to config. The CameraTraps repo will then be added to the python sys environment variables upon loading an individual script.

# Improvement Efforts
If you encounter any issues upon install or would like a feature added, create an issue in the issue tracker on this github and we will do our best to accomodate! Specifically we are looking to add new integrations for other types of cellular camera traps.
48 changes: 31 additions & 17 deletions config/fetch_and_alert.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,60 @@
#home_dir - path to local cougarvision repo
home_dir: /home/johnsmith/cougarvision
#True to send detections to consumer emails
email_alerts: True
er_alerts: True
#True to send detections to earthranger
# 1 = email alerts and 2 = earthranger alerts
email_alerts: True
er_alerts: True
#detector_model - path to detector model
detector_model: /home/johnsmith/cougarvision/detector_models/megadetector.pt
detector_model: /home/johnsmith/cougarvision/detector_models/megadetector_v4.1.pb
#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: /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
visualize_output: True
#for visualizing output, folder for cougarvision to put all images
path_to_unlabeled_output: /path/to/unlabeled/img/folder
#for visualizing output, folder for cougarvision detections with bounding boxes
path_to_labeled_output: /path/to/labeled/img/folder
#the emails that will receive email alerts, can be multiple emails
consumer_emails: [<insert emails>]
# the amiul (s) you with to receive the developmemt email alert- contains confidence value of detection
dev_emails: [<insert emails>]
consumer_emails: [your@email.com]
#emails to recieve daily check-in and confidence of detections
dev_emails: [your@email.com]
#threshold confidence score
confidence: 0.7
#cpu threads
threads: 8
# Web Scraping
#strike force api url
strikeforce_api: https://api.strikeforcewireless.com/api/v2/
#strikeforce wireless username
username_scraper: [yourusername.cam@gmail.com]
username_scraper: yourusername.cam@gmail.com
#strikeforce wireless password
password_scraper: [yourpassword]
#authorization token from Strike Force, can be obtained with login credentials and using strikeforceget.py in /cougarvision_utils folder
auth_token: ["<insert account token>"]
password_scraper: yourpassword
#authorization token from Strike Force
auth_token: <must get from web dev tools after strikeforcewireless login>
#save_dir - path to where the images get stored (must create folder)
save_dir: /home/johnsmith/images/
#dictionary containing the strikeforce unique ID with the actual 4 digit camera name, ID can be obtained using strikeforcegetcameras.py within the /cougarvision_utils folder
camera_names: {'<strikeforce id>': <camera name>, ...}
#dict of camera names and their respective strikeforce wireless ids, these must be retrieved from
#strikeforce wireless web dev tools in the code and currently the cam names must be 4 chars long
camera_names: {'<strikeforceid>': <camera name>, '<strikeforceid>': <camera name>...}
#animals that the detector will send alerts for if detected (from class list)
alert_targets: [cougar, bobcat, skunk, deer, dog, coyote]
alert_targets: [cougar, bobcat, skunk, deer, dog, coyote, rabbit, elephant]
#checkn_interval - time interval between still-alive emails to let us know no errors have crashed
#cougarvision if it's running constantly
checkin_interval: 24
#email to send still-alive email to
admin: admin@email.com
#x-csrf token retreived from earthranger interactive api example requests, must be logged into an
#existing earthranger account with interactive api access
token: '<insert token>'
token: [insert token]
#authorization bearer token retreived from same earthranger interactive api example requests
authorization: 'Bearer <insert authorization>'
authorization: Bearer [insert authorization]
#the time between repeating running fetch_and_alert, if visualize_output = True, it will be in seconds and if it = False it will be in minutes
run_scheduler: 10
#color of the bounding box
color: 'LawnGreen'
#color: 'Orange'
#color: 'Azure'
63 changes: 40 additions & 23 deletions cougarvision_utils/detect_img.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,25 @@
from datetime import datetime as dt
import re
import sys
import logging
import yaml
from PIL import Image
from animl import parse_results, classify, split
from animl import inference, split
from sageranger import is_target, attach_image, post_event
from animl.detectMD import detect_MD_batch
from animl.detect import detect_MD_batch, parse_MD

from cougarvision_utils.cropping import draw_bounding_box_on_image
from cougarvision_utils.alert import smtp_setup, send_alert
from cougarvision_visualize.visualize_helper import get_last_file_number
from cougarvision_visualize.visualize_helper import create_folder


with open("config/cameratraps.yml", 'r') as stream:
CAM_CONFIG = yaml.safe_load(stream)
sys.path.append(CAM_CONFIG['camera_traps_path'])
camera_traps_config = yaml.safe_load(stream)
sys.path.append(camera_traps_config['camera_traps_path'])


def detect(images, config, c_model, d_model):
def detect(images, config, c_model, classes, d_model):
'''
This function takes in a dataframe of images and runs a detector model,
classifies the species of interest, and sends alerts either to email or an
Expand All @@ -41,12 +44,13 @@ def detect(images, config, c_model, d_model):
config: the unpacked config values from fetch_and_alert.yml that contains
necessary parameters the function needs
'''
# use_variation = int(config['use_variation'])
email_alerts = bool(config['email_alerts'])
er_alerts = bool(config['er_alerts'])
log_dir = config['log_dir']
class_list = config['classes']
checkpoint_f = config['checkpoint_frequency']
confidence = config['confidence']
classes = config['classes']
targets = config['alert_targets']
username = config['username']
password = config['password']
Expand All @@ -55,6 +59,9 @@ def detect(images, config, c_model, d_model):
host = 'imap.gmail.com'
token = config['token']
authorization = config['authorization']
color = config['color']
visualize_output = config['visualize_output']
labeled_img = config['path_to_labeled_output']
if len(images) > 0:
# extract paths from dataframe
image_paths = images[:, 2]
Expand All @@ -64,29 +71,28 @@ def detect(images, config, c_model, d_model):
checkpoint_path=None,
confidence_threshold=confidence,
checkpoint_frequency=checkpoint_f,
results=None,
quiet=False,
image_size=None)
# Parse results
data_frame = parse_results.from_MD(results, None, None)
data_frame = parse_MD(results, None, None)
# filter out all non animal detections
if not data_frame.empty:
animal_df = split.getAnimals(data_frame)
other_df = split.getEmpty(data_frame)
animal_df = split.get_animals(data_frame)
otherdf = split.get_empty(data_frame)
# run classifier on animal detections if there are any
if not animal_df.empty:
# create generator for images
predictions = classify.predict_species(animal_df, c_model,
batch=4)
# Parse results
max_df = parse_results.from_classifier(animal_df,
predictions,
classes,
None)
# Creates a data frame with all relevant data
cougars = max_df[max_df['prediction'].isin(targets)]
start = time.time()
predictions = inference.predict_species(animal_df.reset_index(drop=True), c_model, classes, file_col="file")
end = time.time()
cls_time = end - start
print("Time to classify: ")
print(cls_time)
logging.debug('Time to classify: ' + str(cls_time))
# checks to see if predicted class is in targets
cougars = predictions[predictions['prediction'].isin(targets)]
# drops all detections with confidence less than threshold
cougars = cougars[cougars['conf'] >= confidence]
cougars = cougars[cougars['confidence'] >= confidence]
# reset dataframe index
cougars = cougars.reset_index(drop=True)
# create a row in the dataframe containing only the camera name
Expand All @@ -96,8 +102,7 @@ def detect(images, config, c_model, d_model):
for idx in range(len(cougars.index)):
label = cougars.at[idx, 'prediction']
# uncomment this line to use conf value for dev email alert
prob = str(cougars.at[idx, 'conf'])
label = cougars.at[idx, 'class']
prob = str(cougars.at[idx, 'confidence'])
img = Image.open(cougars.at[idx, 'file'])
draw_bounding_box_on_image(img,
cougars.at[idx, 'bbox2'],
Expand All @@ -115,6 +120,15 @@ def detect(images, config, c_model, d_model):
image_bytes = BytesIO()
img.save(image_bytes, format="JPEG")
img_byte = image_bytes.getvalue()
if visualize_output is True:
folder_path = create_folder(labeled_img)
last_file_number = get_last_file_number(folder_path)
new_file_number = last_file_number + 1
new_file_name = f"{folder_path}/image_{new_file_number}.jpg"

with open(new_file_name, "wb") as folder:
folder.write(img_byte)

cam_name = cougars.at[idx, 'cam_name']
if label in targets and er_alerts is True:
is_target(cam_name, token, authorization, label)
Expand All @@ -129,7 +143,10 @@ def detect(images, config, c_model, d_model):
token,
authorization,
label)
print(response)
logging.info(response)

logging.info('Sending detection email')

if email_alerts is True:
smtp_server = smtp_setup(username, password, host)
dev = 0
Expand Down
68 changes: 48 additions & 20 deletions cougarvision_utils/get_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,39 @@
import json
import urllib.request
import os.path
import logging
import requests
import numpy as np
import logging
from cougarvision_visualize.visualize_helper import get_last_file_number
from cougarvision_visualize.visualize_helper import create_folder


'''
#request examples
request examples

#get list of camaras
get list of camaras
request <- "cameras"
parameters <- ""

#recent photo count
recent photo count
request <- "photos/recent/count"
parameters <- ""

#get recent photos across cameras
get recent photos across cameras
request <- "photos/recent"
parameters <- "limit=100"

#get photos from specific camera (will need to loop through pages)
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)
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"
60272&date_start=2022-09-01&date_end=2022-10-07"

#get subscriptions
get subscriptions
request <- "subscriptions"
parameters <- ""
'''
Expand All @@ -68,11 +70,21 @@ def request_strikeforce(username, auth_token, base, request, parameters):
strikeforce
'''
call = base + request + "?" + parameters
response = requests.get(call, headers={"X-User-Email": username,
"X-User-Token": auth_token})
print(response.text)
info = json.loads(response.text)
return info
try:
logging.debug("Getting new Strikeforce image data from: " + username)
response = requests.get(call, headers={"X-User-Email": username,
"X-User-Token": auth_token})
try:
info = json.loads(response.text)
return info
except json.decoder.JSONDecodeError:
logging.warning('An error occurred while decoding JSON')
info = 0
return info
except requests.exceptions.ConnectionError:
logging.warning("Connection Error, max retries exceeded")
info = 0
return info


def fetch_image_api(config):
Expand All @@ -91,12 +103,13 @@ def fetch_image_api(config):
accounts = config['username_scraper']
tokens = config['auth_token']
path = "./last_id.txt"
password = config['password_scraper']
visualize_output = config['visualize_output']
unlabeled_img = config['path_to_unlabeled_output']
checkfile = os.path.exists(path)
if checkfile is False:
new_file = open("last_id.txt", "x")
new_file.close()
first_id = str(0) # function to get the most recent id from sf)
first_id = str(0) # function to get the most recent id from sf)
new_file = open('last_id.txt', 'w')
new_file.writelines(first_id)
new_file.close()
Expand All @@ -112,6 +125,13 @@ def fetch_image_api(config):
for account, token in zip(accounts, tokens):
data = request_strikeforce(account, token, base,
"photos/recent", "limit=12")
if data == 0:
new_photos = []
logging.warning('Returning to main loop after failed http request')
error_message = "Warning: Failed http request, will retry "
error_message = error_message + "from main loop, check connection"
print(error_message)
return new_photos
photos += data['photos']['data']

new_photos = []
Expand All @@ -121,10 +141,9 @@ def fetch_image_api(config):
print(info)
try:
camera = camera_names[photos[i]['relationships']
['camera']['data']['id']]
['camera']['data']['id']]
except KeyError:
logging.warning('Cannot retrieve photo from camera\
as there is no asssociated ID in the config file')
logging.warning('skipped img: no associated cam ID')
continue
newname = config['save_dir'] + camera
newname += "_" + info['file_thumb_filename']
Expand All @@ -133,6 +152,15 @@ def fetch_image_api(config):
new_photos.append([photos[i]['id'],
info['file_thumb_url'], newname])

if visualize_output is True:
file_path = create_folder(unlabeled_img)
newname = file_path + 'image'
new_file_num = get_last_file_number(file_path)
new_file_num = new_file_num + 1
new_file_num = str(new_file_num)
newname += "_" + new_file_num
urllib.request.urlretrieve(info['file_thumb_url'], newname)

new_photos = np.array(new_photos)
if len(new_photos) > 0: # update last image
new_last = max(new_photos[:, 0])
Expand Down
19 changes: 0 additions & 19 deletions cougarvision_utils/strikeforceget.py

This file was deleted.

Loading