megadetector 10.0.4__tar.gz → 10.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- {megadetector-10.0.4/megadetector.egg-info → megadetector-10.0.6}/PKG-INFO +2 -2
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/cct_json_utils.py +1 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/speciesnet_to_md.py +2 -2
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/process_video.py +15 -6
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/video_utils.py +132 -21
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/classification_postprocessing.py +26 -10
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/generate_csv_report.py +1 -1
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/load_api_results.py +1 -1
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/md_to_wi.py +1 -1
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/postprocess_batch_results.py +1 -1
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1 -1
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/ct_utils.py +18 -0
- megadetector-10.0.6/megadetector/utils/wi_platform_utils.py +824 -0
- megadetector-10.0.6/megadetector/utils/wi_taxonomy_utils.py +1711 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/visualize_detector_output.py +1 -1
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/visualize_video_output.py +1 -1
- {megadetector-10.0.4 → megadetector-10.0.6/megadetector.egg-info}/PKG-INFO +2 -2
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector.egg-info/SOURCES.txt +2 -1
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector.egg-info/requires.txt +1 -1
- {megadetector-10.0.4 → megadetector-10.0.6}/pyproject.toml +4 -3
- megadetector-10.0.4/megadetector/utils/wi_utils.py +0 -2674
- {megadetector-10.0.4 → megadetector-10.0.6}/LICENSE +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/README-package.md +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/README.md +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/digiKam/setup.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/aggregate_classifier_probs.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/analyze_failed_images.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/cache_batchapi_outputs.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/create_classification_dataset.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/crop_detections.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/csv_to_json.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/detect_and_crop.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/efficientnet/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/efficientnet/model.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/efficientnet/utils.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/evaluate_model.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/identify_mislabeled_candidates.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/json_to_azcopy_list.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/json_validator.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/map_classification_categories.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/merge_classification_detection_output.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/prepare_classification_script.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/prepare_classification_script_mc.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/run_classifier.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/save_mislabeled.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/train_classifier.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/train_classifier_tf.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/train_utils.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/animl_to_md.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/annotations/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/annotations/annotation_constants.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/camtrap_dp_to_coco.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/cct_to_md.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/cct_to_wi.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/coco_to_labelme.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/coco_to_yolo.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/add_width_and_height_to_db.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/combine_coco_camera_traps_files.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/integrity_check_json_db.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/subset_json_db.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/generate_crops_from_cct.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/get_image_sizes.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/labelme_to_coco.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/labelme_to_yolo.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/create_lila_blank_set.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/create_lila_test_set.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/create_links_to_md_results_files.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/download_lila_subset.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/generate_lila_per_image_labels.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/get_lila_annotation_counts.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/get_lila_image_counts.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/lila_common.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/test_lila_metadata_urls.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/mewc_to_md.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/ocr_tools.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/read_exif.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/remap_coco_categories.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/remove_exif.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/rename_images.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/resize_coco_dataset.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/wi_download_csv_to_coco.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/yolo_output_to_md_output.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/yolo_to_coco.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/zamba_to_md.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/change_detection.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/pytorch_detector.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_detector.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_detector_batch.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_inference_with_yolov5_val.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_md_and_speciesnet.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_tiled_inference.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/tf_detector.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/add_max_conf.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/categorize_detections_by_size.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/combine_batch_outputs.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/compare_batch_results.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/convert_output_format.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/create_crop_folder.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/detector_calibration.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/md_to_coco.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/md_to_labelme.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/merge_detections.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/remap_detection_categories.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/render_detection_confusion_matrix.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/separate_detections_into_folders.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/subset_json_detector_output.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/top_folders_to_bottom.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/validate_batch_results.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/map_new_lila_datasets.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/preview_lila_taxonomy.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/retrieve_sample_image.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/simple_image_download.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/species_lookup.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/taxonomy_csv_checker.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/taxonomy_graph.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/validate_lila_category_mappings.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/tests/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/tests/test_nms_synthetic.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/directory_listing.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/extract_frames_from_video.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/gpu_test.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/md_tests.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/path_utils.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/process_utils.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/split_locations_into_train_val.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/string_utils.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/url_utils.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/write_html_image_list.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/__init__.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/plot_utils.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/render_images_with_thumbnails.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/visualization_utils.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/visualize_db.py +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector.egg-info/dependency_links.txt +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/megadetector.egg-info/top_level.txt +0 -0
- {megadetector-10.0.4 → megadetector-10.0.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: megadetector
|
|
3
|
-
Version: 10.0.
|
|
3
|
+
Version: 10.0.6
|
|
4
4
|
Summary: MegaDetector is an AI model that helps conservation folks spend less time doing boring things with camera trap images.
|
|
5
5
|
Author-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
6
6
|
Maintainer-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
@@ -38,7 +38,7 @@ Requires-Dist: numpy>=1.26.4
|
|
|
38
38
|
Requires-Dist: Pillow>=9.5
|
|
39
39
|
Requires-Dist: tqdm>=4.64.0
|
|
40
40
|
Requires-Dist: jsonpickle>=3.0.2
|
|
41
|
-
Requires-Dist: humanfriendly>=
|
|
41
|
+
Requires-Dist: humanfriendly>=2.1
|
|
42
42
|
Requires-Dist: matplotlib>=3.8.0
|
|
43
43
|
Requires-Dist: opencv-python>=4.8.0
|
|
44
44
|
Requires-Dist: requests>=2.31.0
|
{megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/speciesnet_to_md.py
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
speciesnet_to_md.py
|
|
4
4
|
|
|
5
5
|
Converts the WI (SpeciesNet) predictions.json format to MD .json format. This is just a
|
|
6
|
-
command-line wrapper around utils.
|
|
6
|
+
command-line wrapper around utils.wi_taxonomy_utils.generate_md_results_from_predictions_json.
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ command-line wrapper around utils.wi_utils.generate_md_results_from_predictions_
|
|
|
11
11
|
|
|
12
12
|
import sys
|
|
13
13
|
import argparse
|
|
14
|
-
from megadetector.utils.
|
|
14
|
+
from megadetector.utils.wi_taxonomy_utils import generate_md_results_from_predictions_json
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
#%% Command-line driver
|
|
@@ -81,8 +81,8 @@ class ProcessVideoOptions:
|
|
|
81
81
|
self.augment = False
|
|
82
82
|
|
|
83
83
|
#: By default, a video with no frames (or no frames retrievable with the current parameters)
|
|
84
|
-
#: is
|
|
85
|
-
self.
|
|
84
|
+
#: is silently stored as a failure; this causes it to halt execution.
|
|
85
|
+
self.exit_on_empty_video = False
|
|
86
86
|
|
|
87
87
|
#: Detector-specific options
|
|
88
88
|
self.detector_options = None
|
|
@@ -134,6 +134,14 @@ def process_videos(options):
|
|
|
134
134
|
# Check for incompatible options
|
|
135
135
|
_validate_video_options(options)
|
|
136
136
|
|
|
137
|
+
if options.output_json_file is None:
|
|
138
|
+
video_file = options.input_video_file.replace('\\','/')
|
|
139
|
+
if video_file.endswith('/'):
|
|
140
|
+
video_file = video_file[:-1]
|
|
141
|
+
options.output_json_file = video_file + '.json'
|
|
142
|
+
print('Output file not specified, defaulting to {}'.format(
|
|
143
|
+
options.output_json_file))
|
|
144
|
+
|
|
137
145
|
assert options.output_json_file.endswith('.json'), \
|
|
138
146
|
'Illegal output file {}'.format(options.output_json_file)
|
|
139
147
|
|
|
@@ -173,7 +181,7 @@ def process_videos(options):
|
|
|
173
181
|
every_n_frames=every_n_frames_param,
|
|
174
182
|
verbose=options.verbose,
|
|
175
183
|
files_to_process_relative=[video_bn],
|
|
176
|
-
|
|
184
|
+
error_on_empty_video=options.exit_on_empty_video)
|
|
177
185
|
|
|
178
186
|
else:
|
|
179
187
|
|
|
@@ -187,7 +195,7 @@ def process_videos(options):
|
|
|
187
195
|
every_n_frames=every_n_frames_param,
|
|
188
196
|
verbose=options.verbose,
|
|
189
197
|
recursive=options.recursive,
|
|
190
|
-
|
|
198
|
+
error_on_empty_video=options.exit_on_empty_video)
|
|
191
199
|
|
|
192
200
|
# ...whether we're processing a file or a folder
|
|
193
201
|
|
|
@@ -414,9 +422,10 @@ def main(): # noqa
|
|
|
414
422
|
action='store_true',
|
|
415
423
|
help='Enable image augmentation')
|
|
416
424
|
|
|
417
|
-
parser.add_argument('--
|
|
425
|
+
parser.add_argument('--exit_on_empty_video',
|
|
418
426
|
action='store_true',
|
|
419
|
-
help='By default, videos with no retrievable frames
|
|
427
|
+
help=('By default, videos with no retrievable frames are stored as failures; this' \
|
|
428
|
+
'causes them to halt execution'))
|
|
420
429
|
|
|
421
430
|
parser.add_argument(
|
|
422
431
|
'--detector_options',
|
|
@@ -101,6 +101,99 @@ def find_videos(dirname,
|
|
|
101
101
|
return find_video_strings(files)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
#%% Shared function for opening videos
|
|
105
|
+
|
|
106
|
+
DEFAULT_BACKEND = -1
|
|
107
|
+
|
|
108
|
+
# This is the order in which we'll try to open backends.
|
|
109
|
+
#
|
|
110
|
+
# In general, the defaults are as follows, though they vary depending
|
|
111
|
+
# on what's installed:
|
|
112
|
+
#
|
|
113
|
+
# Windows: CAP_DSHOW or CAP_MSMF
|
|
114
|
+
# Linux: CAP_FFMPEG
|
|
115
|
+
# macOS: CAP_AVFOUNDATION
|
|
116
|
+
#
|
|
117
|
+
# Technically if the default fails, we may try the same backend again, but this
|
|
118
|
+
# is rare, and it's not worth the complexity of figuring out what the system
|
|
119
|
+
# default is.
|
|
120
|
+
backend_id_to_name = {
|
|
121
|
+
DEFAULT_BACKEND:'default',
|
|
122
|
+
cv2.CAP_FFMPEG: 'CAP_FFMPEG',
|
|
123
|
+
cv2.CAP_DSHOW: 'CAP_DSHOW',
|
|
124
|
+
cv2.CAP_MSMF: 'CAP_MSMF',
|
|
125
|
+
cv2.CAP_AVFOUNDATION: 'CAP_AVFOUNDATION',
|
|
126
|
+
cv2.CAP_GSTREAMER: 'CAP_GSTREAMER'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
def open_video(video_path,verbose=False):
|
|
130
|
+
"""
|
|
131
|
+
Open the video at [video_path], trying multiple OpenCV backends if necessary.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
video_path (str): the file to open
|
|
135
|
+
verbose (bool, optional): enable additional debug output
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
(cv2.VideoCapture,image): a tuple containing (a) the open video capture device
|
|
139
|
+
(or None if no backends succeeded) and (b) the first frame of the video (or None)
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
if not os.path.isfile(video_path):
|
|
143
|
+
print('Video file {} not found'.format(video_path))
|
|
144
|
+
return None,None
|
|
145
|
+
|
|
146
|
+
backend_ids = backend_id_to_name.keys()
|
|
147
|
+
|
|
148
|
+
for backend_id in backend_ids:
|
|
149
|
+
|
|
150
|
+
backend_name = backend_id_to_name[backend_id]
|
|
151
|
+
if verbose:
|
|
152
|
+
print('Trying backend {}'.format(backend_name))
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
if backend_id == DEFAULT_BACKEND:
|
|
156
|
+
vidcap = cv2.VideoCapture(video_path)
|
|
157
|
+
else:
|
|
158
|
+
vidcap = cv2.VideoCapture(video_path, backend_id)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
if verbose:
|
|
161
|
+
print('Warning: error opening {} with backend {}: {}'.format(
|
|
162
|
+
video_path,backend_name,str(e)))
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
if not vidcap.isOpened():
|
|
166
|
+
if verbose:
|
|
167
|
+
print('Warning: isOpened() is False for {} with backend {}'.format(
|
|
168
|
+
video_path,backend_name))
|
|
169
|
+
try:
|
|
170
|
+
vidcap.release()
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
success, image = vidcap.read()
|
|
176
|
+
if success and (image is not None):
|
|
177
|
+
if verbose:
|
|
178
|
+
print('Successfully opened {} with backend: {}'.format(
|
|
179
|
+
video_path,backend_name))
|
|
180
|
+
return vidcap,image
|
|
181
|
+
|
|
182
|
+
print('Warning: failed to open {} with backend {}'.format(
|
|
183
|
+
video_path,backend_name))
|
|
184
|
+
try:
|
|
185
|
+
vidcap.release()
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
# ...for each backend
|
|
190
|
+
|
|
191
|
+
print('Error: failed to open {} with any backend'.format(video_path))
|
|
192
|
+
return None,None
|
|
193
|
+
|
|
194
|
+
# ...def open_video(...)
|
|
195
|
+
|
|
196
|
+
|
|
104
197
|
#%% Functions for rendering frames to video and vice-versa
|
|
105
198
|
|
|
106
199
|
# http://tsaith.github.io/combine-images-into-a-video-with-python-3-and-opencv-3.html
|
|
@@ -146,21 +239,32 @@ def frames_to_video(images, fs, output_file_name, codec_spec=default_fourcc):
|
|
|
146
239
|
cv2.destroyAllWindows()
|
|
147
240
|
|
|
148
241
|
|
|
149
|
-
def get_video_fs(input_video_file):
|
|
242
|
+
def get_video_fs(input_video_file,verbose=False):
|
|
150
243
|
"""
|
|
151
244
|
Retrieves the frame rate of [input_video_file].
|
|
152
245
|
|
|
153
246
|
Args:
|
|
154
247
|
input_video_file (str): video file for which we want the frame rate
|
|
248
|
+
verbose (bool, optional): enable additional debug output
|
|
155
249
|
|
|
156
250
|
Returns:
|
|
157
|
-
float: the frame rate of [input_video_file]
|
|
251
|
+
float: the frame rate of [input_video_file], or None if no frame
|
|
252
|
+
rate could be extracted
|
|
158
253
|
"""
|
|
159
254
|
|
|
160
|
-
assert os.path.isfile(input_video_file),
|
|
161
|
-
|
|
255
|
+
assert os.path.isfile(input_video_file), \
|
|
256
|
+
'File {} not found'.format(input_video_file)
|
|
257
|
+
vidcap,_ = open_video(input_video_file,verbose=verbose)
|
|
258
|
+
if vidcap is None:
|
|
259
|
+
if verbose:
|
|
260
|
+
print('Failed to get frame rate for {}'.format(input_video_file))
|
|
261
|
+
return None
|
|
162
262
|
fs = vidcap.get(cv2.CAP_PROP_FPS)
|
|
163
|
-
|
|
263
|
+
try:
|
|
264
|
+
vidcap.release()
|
|
265
|
+
except Exception as e:
|
|
266
|
+
print('Warning: error closing video handle for {}: {}'.format(
|
|
267
|
+
input_video_file,str(e)))
|
|
164
268
|
return fs
|
|
165
269
|
|
|
166
270
|
|
|
@@ -249,7 +353,7 @@ def run_callback_on_frames(input_video_file,
|
|
|
249
353
|
of the video, no frames are extracted. Can also be a single int, specifying
|
|
250
354
|
a single frame number.
|
|
251
355
|
allow_empty_videos (bool, optional): Just print a warning if a video appears to have no
|
|
252
|
-
frames (by default, this
|
|
356
|
+
frames (by default, this raises an Exception).
|
|
253
357
|
|
|
254
358
|
Returns:
|
|
255
359
|
dict: dict with keys 'frame_filenames' (list), 'frame_rate' (float), 'results' (list).
|
|
@@ -271,7 +375,7 @@ def run_callback_on_frames(input_video_file,
|
|
|
271
375
|
|
|
272
376
|
try:
|
|
273
377
|
|
|
274
|
-
vidcap =
|
|
378
|
+
vidcap,image = open_video(input_video_file,verbose=verbose)
|
|
275
379
|
n_frames = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
276
380
|
frame_rate = vidcap.get(cv2.CAP_PROP_FPS)
|
|
277
381
|
|
|
@@ -300,7 +404,11 @@ def run_callback_on_frames(input_video_file,
|
|
|
300
404
|
# frame_number = 0
|
|
301
405
|
for frame_number in range(0,n_frames):
|
|
302
406
|
|
|
303
|
-
|
|
407
|
+
# We've already read the first frame, when we opened the video
|
|
408
|
+
if frame_number != 0:
|
|
409
|
+
success,image = vidcap.read()
|
|
410
|
+
else:
|
|
411
|
+
success = True
|
|
304
412
|
|
|
305
413
|
if not success:
|
|
306
414
|
assert image is None
|
|
@@ -363,9 +471,9 @@ def run_callback_on_frames_for_folder(input_video_folder,
|
|
|
363
471
|
frame_callback,
|
|
364
472
|
every_n_frames=None,
|
|
365
473
|
verbose=False,
|
|
366
|
-
allow_empty_videos=False,
|
|
367
474
|
recursive=True,
|
|
368
|
-
files_to_process_relative=None
|
|
475
|
+
files_to_process_relative=None,
|
|
476
|
+
error_on_empty_video=False):
|
|
369
477
|
"""
|
|
370
478
|
Calls the function frame_callback(np.array,image_id) on all (or selected) frames in
|
|
371
479
|
all videos in [input_video_folder].
|
|
@@ -382,10 +490,10 @@ def run_callback_on_frames_for_folder(input_video_folder,
|
|
|
382
490
|
interpreted as a sampling rate in seconds, which is rounded to the nearest frame
|
|
383
491
|
sampling rate.
|
|
384
492
|
verbose (bool, optional): enable additional debug console output
|
|
385
|
-
allow_empty_videos (bool, optional): Just print a warning if a video appears to have no
|
|
386
|
-
frames (by default, this is an error).
|
|
387
493
|
recursive (bool, optional): recurse into [input_video_folder]
|
|
388
494
|
files_to_process_relative (list, optional): only process specific relative paths
|
|
495
|
+
error_on_empty_video (bool, optional): by default, videos with errors or no valid frames
|
|
496
|
+
are silently stored as failures; this turns them into exceptions
|
|
389
497
|
|
|
390
498
|
Returns:
|
|
391
499
|
dict: dict with keys 'video_filenames' (list of str), 'frame_rates' (list of floats),
|
|
@@ -440,18 +548,21 @@ def run_callback_on_frames_for_folder(input_video_folder,
|
|
|
440
548
|
every_n_frames=every_n_frames,
|
|
441
549
|
verbose=verbose,
|
|
442
550
|
frames_to_process=None,
|
|
443
|
-
allow_empty_videos=
|
|
551
|
+
allow_empty_videos=False)
|
|
444
552
|
|
|
445
553
|
except Exception as e:
|
|
446
554
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
555
|
+
if (not error_on_empty_video):
|
|
556
|
+
print('Warning: error processing video {}: {}'.format(
|
|
557
|
+
video_fn_abs,str(e)
|
|
558
|
+
))
|
|
559
|
+
to_return['frame_rates'].append(-1.0)
|
|
560
|
+
failure_result = {}
|
|
561
|
+
failure_result['failure'] = 'Failure processing video: {}'.format(str(e))
|
|
562
|
+
to_return['results'].append(failure_result)
|
|
563
|
+
continue
|
|
564
|
+
else:
|
|
565
|
+
raise
|
|
455
566
|
|
|
456
567
|
# ...try/except
|
|
457
568
|
|
|
@@ -25,14 +25,14 @@ from megadetector.utils.ct_utils import sort_dictionary_by_value
|
|
|
25
25
|
from megadetector.utils.ct_utils import sort_dictionary_by_key
|
|
26
26
|
from megadetector.utils.ct_utils import invert_dictionary
|
|
27
27
|
|
|
28
|
-
from megadetector.utils.
|
|
29
|
-
from megadetector.utils.
|
|
30
|
-
from megadetector.utils.
|
|
28
|
+
from megadetector.utils.wi_taxonomy_utils import clean_taxonomy_string
|
|
29
|
+
from megadetector.utils.wi_taxonomy_utils import taxonomy_level_index
|
|
30
|
+
from megadetector.utils.wi_taxonomy_utils import taxonomy_level_string_to_index
|
|
31
31
|
|
|
32
|
-
from megadetector.utils.
|
|
33
|
-
from megadetector.utils.
|
|
34
|
-
from megadetector.utils.
|
|
35
|
-
from megadetector.utils.
|
|
32
|
+
from megadetector.utils.wi_taxonomy_utils import non_taxonomic_prediction_strings
|
|
33
|
+
from megadetector.utils.wi_taxonomy_utils import human_prediction_string
|
|
34
|
+
from megadetector.utils.wi_taxonomy_utils import animal_prediction_string
|
|
35
|
+
from megadetector.utils.wi_taxonomy_utils import blank_prediction_string # noqa
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
#%% Options classes
|
|
@@ -1100,7 +1100,8 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1100
1100
|
input_file,
|
|
1101
1101
|
output_file,
|
|
1102
1102
|
allow_walk_down=False,
|
|
1103
|
-
add_pre_filtering_description=True
|
|
1103
|
+
add_pre_filtering_description=True,
|
|
1104
|
+
allow_redundant_latin_names=False):
|
|
1104
1105
|
"""
|
|
1105
1106
|
Given a prediction file in MD .json format, likely without having had
|
|
1106
1107
|
a geofence applied, apply a custom taxa list.
|
|
@@ -1123,6 +1124,10 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1123
1124
|
add_pre_filtering_description (bool, optional): should we add a new metadata
|
|
1124
1125
|
field that summarizes each image's classifications prior to taxonomic
|
|
1125
1126
|
restriction?
|
|
1127
|
+
allow_redundant_latin_names (bool, optional): if False, we'll raise an Exception
|
|
1128
|
+
if the same latin name appears twice in the taxonomy list; if True, we'll
|
|
1129
|
+
just print a warning and ignore all entries other than the first for this
|
|
1130
|
+
latin name
|
|
1126
1131
|
"""
|
|
1127
1132
|
|
|
1128
1133
|
##%% Read target taxa list
|
|
@@ -1137,11 +1142,14 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1137
1142
|
taxa_list = [s for s in taxa_list if len(s) > 0]
|
|
1138
1143
|
|
|
1139
1144
|
target_latin_to_common = {}
|
|
1145
|
+
|
|
1140
1146
|
for s in taxa_list:
|
|
1147
|
+
|
|
1141
1148
|
if s.strip().startswith('#'):
|
|
1142
1149
|
continue
|
|
1143
1150
|
tokens = s.split(',')
|
|
1144
|
-
|
|
1151
|
+
# We allow additional columns now
|
|
1152
|
+
# assert len(tokens) <= 2
|
|
1145
1153
|
binomial_name = tokens[0]
|
|
1146
1154
|
assert len(binomial_name.split(' ')) in (1,2,3), \
|
|
1147
1155
|
'Illegal binomial name in species list: {}'.format(binomial_name)
|
|
@@ -1149,9 +1157,17 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1149
1157
|
common_name = tokens[1].strip().lower()
|
|
1150
1158
|
else:
|
|
1151
1159
|
common_name = None
|
|
1152
|
-
|
|
1160
|
+
if binomial_name in target_latin_to_common:
|
|
1161
|
+
error_string = 'scientific name {} appears multiple times in the taxonomy list'.format(
|
|
1162
|
+
binomial_name)
|
|
1163
|
+
if allow_redundant_latin_names:
|
|
1164
|
+
print('Warning: {}'.format(error_string))
|
|
1165
|
+
else:
|
|
1166
|
+
raise ValueError(error_string)
|
|
1153
1167
|
target_latin_to_common[binomial_name] = common_name
|
|
1154
1168
|
|
|
1169
|
+
# ...for each line in the taxonomy file
|
|
1170
|
+
|
|
1155
1171
|
|
|
1156
1172
|
##%% Read taxonomy file
|
|
1157
1173
|
|
{megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/generate_csv_report.py
RENAMED
|
@@ -42,7 +42,7 @@ import pandas as pd
|
|
|
42
42
|
|
|
43
43
|
from copy import deepcopy
|
|
44
44
|
|
|
45
|
-
from megadetector.utils.
|
|
45
|
+
from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
|
|
46
46
|
from megadetector.utils.ct_utils import get_max_conf
|
|
47
47
|
from megadetector.utils.ct_utils import is_list_sorted
|
|
48
48
|
from megadetector.detection.run_detector import \
|
|
@@ -24,7 +24,7 @@ from collections.abc import Mapping
|
|
|
24
24
|
import pandas as pd
|
|
25
25
|
|
|
26
26
|
from megadetector.utils import ct_utils
|
|
27
|
-
from megadetector.utils.
|
|
27
|
+
from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
#%% Functions for loading .json results into a Pandas DataFrame, and writing back to .json
|
|
@@ -10,7 +10,7 @@ Converts the MD .json format to the WI predictions.json format.
|
|
|
10
10
|
|
|
11
11
|
import sys
|
|
12
12
|
import argparse
|
|
13
|
-
from megadetector.utils.
|
|
13
|
+
from megadetector.utils.wi_taxonomy_utils import generate_predictions_json_from_md_results
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
#%% Command-line driver
|
{megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/postprocess_batch_results.py
RENAMED
|
@@ -47,7 +47,7 @@ from tqdm import tqdm
|
|
|
47
47
|
from megadetector.visualization import visualization_utils as vis_utils
|
|
48
48
|
from megadetector.visualization import plot_utils
|
|
49
49
|
from megadetector.utils.write_html_image_list import write_html_image_list
|
|
50
|
-
from megadetector.utils.
|
|
50
|
+
from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
|
|
51
51
|
from megadetector.utils import path_utils
|
|
52
52
|
from megadetector.utils.ct_utils import args_to_object
|
|
53
53
|
from megadetector.utils.ct_utils import sets_overlap
|
|
@@ -637,7 +637,7 @@ def _find_matches_in_directory(dir_name_and_rows, options):
|
|
|
637
637
|
|
|
638
638
|
if 'max_detection_conf' not in row or 'detections' not in row or \
|
|
639
639
|
row['detections'] is None:
|
|
640
|
-
print('Skipping row {}'.format(i_directory_row))
|
|
640
|
+
# print('Skipping row {}'.format(i_directory_row))
|
|
641
641
|
continue
|
|
642
642
|
|
|
643
643
|
# Don't bother checking images with no detections above threshold
|
|
@@ -850,6 +850,24 @@ def isnan(v):
|
|
|
850
850
|
return False
|
|
851
851
|
|
|
852
852
|
|
|
853
|
+
def compare_values_nan_equal(v0,v1):
|
|
854
|
+
"""
|
|
855
|
+
Utility function for comparing two values when we want to return True if both
|
|
856
|
+
values are NaN.
|
|
857
|
+
|
|
858
|
+
Args:
|
|
859
|
+
v0 (object): the first value to compare
|
|
860
|
+
v1 (object): the second value to compare
|
|
861
|
+
|
|
862
|
+
Returns:
|
|
863
|
+
bool: True if v0 == v1, or if both v0 and v1 are NaN
|
|
864
|
+
"""
|
|
865
|
+
|
|
866
|
+
if isinstance(v0,float) and isinstance(v1,float) and np.isnan(v0) and np.isnan(v1):
|
|
867
|
+
return True
|
|
868
|
+
return v0 == v1
|
|
869
|
+
|
|
870
|
+
|
|
853
871
|
def sets_overlap(set1, set2):
|
|
854
872
|
"""
|
|
855
873
|
Determines whether two sets overlap.
|