diff --git a/config/fetch_and_alert.yml b/config/fetch_and_alert.yml index a414ff8..f4bc807 100644 --- a/config/fetch_and_alert.yml +++ b/config/fetch_and_alert.yml @@ -4,6 +4,8 @@ email_alerts: True er_alerts: True #detector_model - path to detector model detector_model: /home/johnsmith/cougarvision/detector_models/megadetector.pt +#type of detection model expected ["MDV5", "MDV6", "YOLO", "ONNX"] +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 diff --git a/cougarvision_env.yml b/cougarvision_env.yml index 2f5df9f..e35d8ef 100644 --- a/cougarvision_env.yml +++ b/cougarvision_env.yml @@ -1,129 +1,29 @@ -name: cougarvision +name: cougarvision_env channels: - conda-forge - - defaults + - nodefaults dependencies: - - _libgcc_mutex=0.1=main - - _openmp_mutex=4.5=1_gnu - - _tflow_select=2.1.0=gpu - - absl-py=0.15.0=pyhd3eb1b0_0 - - aiohttp=3.8.1=py38h7f8727e_0 - - aiosignal=1.2.0=pyhd3eb1b0_0 - - astor=0.8.1=py38h06a4308_0 - - astunparse=1.6.3=py_0 - - async-timeout=4.0.1=pyhd3eb1b0_0 - - attrs=21.4.0=pyhd3eb1b0_0 - - blas=1.0=mkl - - blinker=1.4=py38h06a4308_0 - - brotlipy=0.7.0=py38h27cfd23_1003 - - c-ares=1.18.1=h7f8727e_0 - - ca-certificates=2022.3.29=h06a4308_0 - - cachetools=4.2.2=pyhd3eb1b0_0 - - certifi=2021.10.8=py38h06a4308_2 - - cffi=1.15.0=py38hd667e15_1 - - charset-normalizer=2.0.4=pyhd3eb1b0_0 - - click=8.0.4=py38h06a4308_0 - - cryptography=3.4.8=py38hd23ed53_0 - - cudatoolkit=10.1.243=h6bb024c_0 - - cudnn=7.6.5=cuda10.1_0 - - cupti=10.1.168=0 - - dataclasses=0.8=pyh6d0b6a4_7 - - frozenlist=1.2.0=py38h7f8727e_0 - - gast=0.4.0=pyhd3eb1b0_0 - - google-pasta=0.2.0=pyhd3eb1b0_0 - - hdf5=1.10.6=hb1b8bf9_0 - - idna=3.3=pyhd3eb1b0_0 - - importlib-metadata=4.8.2=py38h06a4308_0 - - intel-openmp=2021.4.0=h06a4308_3561 - - keras-preprocessing=1.1.2=pyhd8ed1ab_0 - - ld_impl_linux-64=2.35.1=h7274673_9 - - libffi=3.3=he6710b0_2 - - libgcc-ng=9.3.0=h5101ec6_17 - - libgfortran-ng=7.5.0=ha8ba4b0_17 - - libgfortran4=7.5.0=ha8ba4b0_17 - - libgomp=9.3.0=h5101ec6_17 - - libprotobuf=3.19.1=h4ff587b_0 - - libstdcxx-ng=9.3.0=hd4cf53a_17 - - markdown=3.3.4=py38h06a4308_0 - - mkl=2021.4.0=h06a4308_640 - - mkl-service=2.4.0=py38h7f8727e_0 - - mkl_fft=1.3.1=py38hd3c417c_0 - - mkl_random=1.2.2=py38h51133e4_0 - - multidict=5.2.0=py38h7f8727e_2 - - ncurses=6.3=h7f8727e_2 - - oauthlib=3.2.0=pyhd3eb1b0_0 - - openssl=1.1.1n=h7f8727e_0 - - opt_einsum=3.3.0=pyhd3eb1b0_1 - - pip=21.2.4=py38h06a4308_0 - - pyasn1=0.4.8=pyhd3eb1b0_0 - - pyasn1-modules=0.2.8=py_0 - - pycparser=2.21=pyhd3eb1b0_0 - - pyjwt=2.1.0=py38h06a4308_0 - - pyopenssl=21.0.0=pyhd3eb1b0_1 - - pysocks=1.7.1=py38h06a4308_0 - - python=3.8.12=h12debd9_0 - - python_abi=3.8=2_cp38 - - readline=8.1.2=h7f8727e_1 - - requests=2.27.1=pyhd3eb1b0_0 - - requests-oauthlib=1.3.0=py_0 - - rsa=4.7.2=pyhd3eb1b0_1 - - scipy=1.7.3=py38hc147768_0 - - selenium=3.141.0=py38h27cfd23_1000 - - setuptools=58.0.4=py38h06a4308_0 - - sqlite=3.37.2=hc218d9a_0 - - tensorboard-plugin-wit=1.6.0=py_0 - - tensorflow-estimator=2.6.0=pyh7b7c402_0 - - tensorflow-gpu=2.4.1=h30adc30_0 - - termcolor=1.1.0=py38h06a4308_1 - - tk=8.6.11=h1ccaba5_0 - - urllib3=1.26.8=pyhd3eb1b0_0 - - werkzeug=2.0.3=pyhd3eb1b0_0 - - wheel=0.37.1=pyhd3eb1b0_0 - - xz=5.2.5=h7b6447c_0 - - yaml=0.2.5=h516909a_0 - - yarl=1.6.3=py38h27cfd23_0 - - zipp=3.7.0=pyhd3eb1b0_0 - - zlib=1.2.11=h7f8727e_4 + - python=3.12 + - pip + - setuptools + - wheel + - numpy>=2.0.2 + - pandas>=2.2.2 + - scikit-learn>=1.5.2 + - pillow>=11.0.0 + - tqdm>=4.66.5 + - dill>=0.4.0 + - openssl + - sqlite + - zlib - pip: - - chardet==4.0.0 - - clang==5.0 - - datetime==5.2 - - flatbuffers==1.12 - - fonttools==4.29.1 - - google-auth==2.22.0 - - google-auth-oauthlib==1.0.0 - - grpcio==1.56.2 - - h5py==3.1.0 - - humanfriendly==10.0 - - imbox==0.9.8 - - jsonpickle==2.1.0 - - keras==2.6.0 - - keras-applications==1.0.2 - - kiwisolver==1.3.2 - - matplotlib==3.5.1 - - mmdnn==0.3.1 - - numpy==1.19.5 - - opencv-python==4.8.0.74 - - packaging==21.3 - - pandas==1.2.4 - - pillow==9.0.1 - - protobuf==4.23.4 - - pyparsing==3.0.7 - - python-dateutil==2.8.2 - - pytz==2023.3 - - pyyaml==6.0 - - sageranger==1.0.3 - - schedule==1.1.0 - - six==1.15.0 - - tensorboard==2.13.0 - - tensorboard-data-server==0.7.1 - - tensorflow==2.6.0 - - torch==1.10.2 - - torchvision==0.11.3 - - tqdm==4.63.0 - - typing-extensions==3.7.4.3 - - tzdata==2023.3 - - webdriver-manager==3.5.4 - - wrapt==1.12.1 - - zope-interface==6.0 -prefix: /home/katie/anaconda3/envs/cougarvision + - --extra-index-url https://download.pytorch.org/whl/cu124 + - torch>=2.6.0 + - torchvision>=0.21.0 + - onnxruntime-gpu>=1.23.2 + - opencv-python>=4.12.0.88 + - timm>=1.0.9 + - ultralytics>=8.3.95 + - wget>=3.2 + - schedule>=1.2.2 + - sageranger>=1.0.3 diff --git a/cougarvision_utils/detect_img.py b/cougarvision_utils/detect_img.py index 892a36e..1d8720f 100644 --- a/cougarvision_utils/detect_img.py +++ b/cougarvision_utils/detect_img.py @@ -15,9 +15,9 @@ import sys import yaml from PIL import Image -from animl import parse_results, classify, split +from animl import classification, split from sageranger import is_target, attach_image, post_event -from animl.detectMD import detect_MD_batch +from animl import detection from cougarvision_utils.cropping import draw_bounding_box_on_image from cougarvision_utils.alert import smtp_setup, send_alert @@ -28,7 +28,7 @@ sys.path.append(CAM_CONFIG['camera_traps_path']) -def detect(images, config, c_model, d_model): +def detect(images, config, c_model, d_model, class_list): ''' 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 @@ -46,7 +46,6 @@ def detect(images, config, c_model, d_model): log_dir = config['log_dir'] checkpoint_f = config['checkpoint_frequency'] confidence = config['confidence'] - classes = config['classes'] targets = config['alert_targets'] username = config['username'] password = config['password'] @@ -55,61 +54,66 @@ def detect(images, config, c_model, d_model): host = 'imap.gmail.com' token = config['token'] authorization = config['authorization'] + if len(images) > 0: # extract paths from dataframe image_paths = images[:, 2] + # detection.detect expects the image paths in a list + image_path_list = image_paths.tolist() # Run Detection - results = detect_MD_batch(d_model, - image_paths, - checkpoint_path=None, - confidence_threshold=confidence, - checkpoint_frequency=checkpoint_f, - results=None, - quiet=False, - image_size=None) + results = detection.detect(d_model, + image_path_list, + resize_width=1280, + resize_height=1280, + confidence_threshold=confidence, + checkpoint_frequency=checkpoint_f, + batch_size=4 + ) # Parse results - data_frame = parse_results.from_MD(results, None, None) + data_frame = detection.parse_detections(results) + # single classification function checks for the file extension so we add it + data_frame["extension"] = data_frame["filepath"].str.extract(r'(\.[^.]+)$', expand=False).str.lower() # 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) + other_df = 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)] + predictions_raw = classification.classify(c_model, + animal_df, + batch_size=4) + # single classification expects a list + class_list_for_series = class_list["species"].tolist() + preds = classification.single_classification(animal_df, + None, + predictions_raw, + class_list_for_series + ) + cougars = preds[preds['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 # flake8: disable-next - cougars['cam_name'] = cougars['file'].apply(lambda x: re.findall(r'[A-Z]\d+', x)[0]) # noqa: E501 # pylint: disable-msg=line-too-long + cougars['cam_name'] = cougars['filepath'].apply(lambda x: re.findall(r'[A-Z]\d+', x)[0]) # noqa: E501 # pylint: disable-msg=line-too-long # Sends alert for each cougar detection 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'] - img = Image.open(cougars.at[idx, 'file']) + prob = str(cougars.at[idx, 'confidence']) + img = Image.open(cougars.at[idx, 'filepath']) draw_bounding_box_on_image(img, - cougars.at[idx, 'bbox2'], - cougars.at[idx, 'bbox1'], + cougars.at[idx, 'bbox_y'], + cougars.at[idx, 'bbox_x'], cougars.at[idx, - 'bbox2'] + + 'bbox_y'] + cougars.at[idx, - 'bbox4'], + 'bbox_h'], cougars.at[idx, - 'bbox1'] + + 'bbox_x'] + cougars.at[idx, - 'bbox3'], + 'bbox_w'], expansion=0, use_normalized_coordinates=True) image_bytes = BytesIO() diff --git a/cougarvision_utils/get_images.py b/cougarvision_utils/get_images.py index d5428a8..c96523e 100644 --- a/cougarvision_utils/get_images.py +++ b/cougarvision_utils/get_images.py @@ -128,10 +128,11 @@ def fetch_image_api(config): continue newname = config['save_dir'] + camera newname += "_" + info['file_thumb_filename'] - print(newname) - urllib.request.urlretrieve(info['file_thumb_url'], newname) + # native extension from strikeforce is .JPG.jpeg for some reason + stripped_name = newname.replace(".JPG.jpeg", ".jpg") + urllib.request.urlretrieve(info['file_thumb_url'], stripped_name) new_photos.append([photos[i]['id'], - info['file_thumb_url'], newname]) + info['file_thumb_url'], stripped_name]) new_photos = np.array(new_photos) if len(new_photos) > 0: # update last image diff --git a/fetch_and_alert.py b/fetch_and_alert.py index 9798d74..3ca695f 100644 --- a/fetch_and_alert.py +++ b/fetch_and_alert.py @@ -30,8 +30,8 @@ from cougarvision_utils.alert import checkin from cougarvision_utils.get_images import fetch_image_api from sageranger.post_monthly import post_monthly_obs -from animl.classify import load_classifier -from animl import megadetector +from animl.classification import load_classifier +from animl.detection import load_detector # Numpy FutureWarnings from tensorflow import @@ -54,15 +54,15 @@ DETECTOR = CONFIG['detector_model'] DEV_EMAILS = CONFIG['dev_emails'] HOST = 'imap.gmail.com' - +CLASSES = CONFIG['classes'] +MODEL_TYPE = CONFIG['detector_model_type'] # Set interval for checking in CHECKIN_INTERVAL = CONFIG['checkin_interval'] # load models once -CLASSIFIER_MODEL = load_classifier(CLASSIFIER) -DETECTOR_MODEL = megadetector.MegaDetector(DETECTOR) - +CLASSIFIER_MODEL, CLASS_LIST = load_classifier(CLASSIFIER, CLASSES) +DETECTOR_MODEL = load_detector(DETECTOR, MODEL_TYPE) def logger(): '''Function for creating log file''' @@ -77,7 +77,7 @@ def fetch_detect_alert(): images = fetch_image_api(CONFIG) print('Finished fetching images') print('Starting Detection') - detect(images, CONFIG, CLASSIFIER_MODEL, DETECTOR_MODEL) + detect(images, CONFIG, CLASSIFIER_MODEL, DETECTOR_MODEL, CLASS_LIST) print('Finished Detection') print("Sleeping since: " + str(dt.now()))