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.

Files changed (154) hide show
  1. {megadetector-10.0.4/megadetector.egg-info → megadetector-10.0.6}/PKG-INFO +2 -2
  2. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/cct_json_utils.py +1 -0
  3. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/speciesnet_to_md.py +2 -2
  4. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/process_video.py +15 -6
  5. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/video_utils.py +132 -21
  6. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/classification_postprocessing.py +26 -10
  7. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/generate_csv_report.py +1 -1
  8. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/load_api_results.py +1 -1
  9. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/md_to_wi.py +1 -1
  10. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/postprocess_batch_results.py +1 -1
  11. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1 -1
  12. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/ct_utils.py +18 -0
  13. megadetector-10.0.6/megadetector/utils/wi_platform_utils.py +824 -0
  14. megadetector-10.0.6/megadetector/utils/wi_taxonomy_utils.py +1711 -0
  15. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/visualize_detector_output.py +1 -1
  16. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/visualize_video_output.py +1 -1
  17. {megadetector-10.0.4 → megadetector-10.0.6/megadetector.egg-info}/PKG-INFO +2 -2
  18. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector.egg-info/SOURCES.txt +2 -1
  19. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector.egg-info/requires.txt +1 -1
  20. {megadetector-10.0.4 → megadetector-10.0.6}/pyproject.toml +4 -3
  21. megadetector-10.0.4/megadetector/utils/wi_utils.py +0 -2674
  22. {megadetector-10.0.4 → megadetector-10.0.6}/LICENSE +0 -0
  23. {megadetector-10.0.4 → megadetector-10.0.6}/README-package.md +0 -0
  24. {megadetector-10.0.4 → megadetector-10.0.6}/README.md +0 -0
  25. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/__init__.py +0 -0
  26. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/__init__.py +0 -0
  27. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/digiKam/setup.py +0 -0
  28. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +0 -0
  29. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +0 -0
  30. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -0
  31. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +0 -0
  32. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/__init__.py +0 -0
  33. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/aggregate_classifier_probs.py +0 -0
  34. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/analyze_failed_images.py +0 -0
  35. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/cache_batchapi_outputs.py +0 -0
  36. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/create_classification_dataset.py +0 -0
  37. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/crop_detections.py +0 -0
  38. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/csv_to_json.py +0 -0
  39. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/detect_and_crop.py +0 -0
  40. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/efficientnet/__init__.py +0 -0
  41. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/efficientnet/model.py +0 -0
  42. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/efficientnet/utils.py +0 -0
  43. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/evaluate_model.py +0 -0
  44. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/identify_mislabeled_candidates.py +0 -0
  45. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/json_to_azcopy_list.py +0 -0
  46. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/json_validator.py +0 -0
  47. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/map_classification_categories.py +0 -0
  48. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/merge_classification_detection_output.py +0 -0
  49. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/prepare_classification_script.py +0 -0
  50. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/prepare_classification_script_mc.py +0 -0
  51. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/run_classifier.py +0 -0
  52. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/save_mislabeled.py +0 -0
  53. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/train_classifier.py +0 -0
  54. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/train_classifier_tf.py +0 -0
  55. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/classification/train_utils.py +0 -0
  56. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/__init__.py +0 -0
  57. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/animl_to_md.py +0 -0
  58. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/annotations/__init__.py +0 -0
  59. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/annotations/annotation_constants.py +0 -0
  60. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/camtrap_dp_to_coco.py +0 -0
  61. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/cct_to_md.py +0 -0
  62. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/cct_to_wi.py +0 -0
  63. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/coco_to_labelme.py +0 -0
  64. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/coco_to_yolo.py +0 -0
  65. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/__init__.py +0 -0
  66. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/add_width_and_height_to_db.py +0 -0
  67. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/combine_coco_camera_traps_files.py +0 -0
  68. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/integrity_check_json_db.py +0 -0
  69. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/databases/subset_json_db.py +0 -0
  70. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/generate_crops_from_cct.py +0 -0
  71. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/get_image_sizes.py +0 -0
  72. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/labelme_to_coco.py +0 -0
  73. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/labelme_to_yolo.py +0 -0
  74. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/__init__.py +0 -0
  75. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/create_lila_blank_set.py +0 -0
  76. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/create_lila_test_set.py +0 -0
  77. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/create_links_to_md_results_files.py +0 -0
  78. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/download_lila_subset.py +0 -0
  79. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/generate_lila_per_image_labels.py +0 -0
  80. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/get_lila_annotation_counts.py +0 -0
  81. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/get_lila_image_counts.py +0 -0
  82. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/lila_common.py +0 -0
  83. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/lila/test_lila_metadata_urls.py +0 -0
  84. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/mewc_to_md.py +0 -0
  85. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/ocr_tools.py +0 -0
  86. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/read_exif.py +0 -0
  87. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/remap_coco_categories.py +0 -0
  88. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/remove_exif.py +0 -0
  89. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/rename_images.py +0 -0
  90. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/resize_coco_dataset.py +0 -0
  91. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/wi_download_csv_to_coco.py +0 -0
  92. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/yolo_output_to_md_output.py +0 -0
  93. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/yolo_to_coco.py +0 -0
  94. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/data_management/zamba_to_md.py +0 -0
  95. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/__init__.py +0 -0
  96. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/change_detection.py +0 -0
  97. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/pytorch_detector.py +0 -0
  98. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_detector.py +0 -0
  99. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_detector_batch.py +0 -0
  100. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_inference_with_yolov5_val.py +0 -0
  101. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_md_and_speciesnet.py +0 -0
  102. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/run_tiled_inference.py +0 -0
  103. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/detection/tf_detector.py +0 -0
  104. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/__init__.py +0 -0
  105. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/add_max_conf.py +0 -0
  106. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/categorize_detections_by_size.py +0 -0
  107. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/combine_batch_outputs.py +0 -0
  108. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/compare_batch_results.py +0 -0
  109. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/convert_output_format.py +0 -0
  110. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/create_crop_folder.py +0 -0
  111. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/detector_calibration.py +0 -0
  112. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/md_to_coco.py +0 -0
  113. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/md_to_labelme.py +0 -0
  114. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/merge_detections.py +0 -0
  115. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/remap_detection_categories.py +0 -0
  116. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/render_detection_confusion_matrix.py +0 -0
  117. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +0 -0
  118. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +0 -0
  119. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/separate_detections_into_folders.py +0 -0
  120. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/subset_json_detector_output.py +0 -0
  121. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/top_folders_to_bottom.py +0 -0
  122. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/postprocessing/validate_batch_results.py +0 -0
  123. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/__init__.py +0 -0
  124. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +0 -0
  125. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/map_new_lila_datasets.py +0 -0
  126. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +0 -0
  127. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/preview_lila_taxonomy.py +0 -0
  128. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/retrieve_sample_image.py +0 -0
  129. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/simple_image_download.py +0 -0
  130. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/species_lookup.py +0 -0
  131. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/taxonomy_csv_checker.py +0 -0
  132. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/taxonomy_graph.py +0 -0
  133. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/taxonomy_mapping/validate_lila_category_mappings.py +0 -0
  134. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/tests/__init__.py +0 -0
  135. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/tests/test_nms_synthetic.py +0 -0
  136. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/__init__.py +0 -0
  137. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/directory_listing.py +0 -0
  138. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/extract_frames_from_video.py +0 -0
  139. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/gpu_test.py +0 -0
  140. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/md_tests.py +0 -0
  141. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/path_utils.py +0 -0
  142. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/process_utils.py +0 -0
  143. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/split_locations_into_train_val.py +0 -0
  144. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/string_utils.py +0 -0
  145. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/url_utils.py +0 -0
  146. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/utils/write_html_image_list.py +0 -0
  147. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/__init__.py +0 -0
  148. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/plot_utils.py +0 -0
  149. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/render_images_with_thumbnails.py +0 -0
  150. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/visualization_utils.py +0 -0
  151. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector/visualization/visualize_db.py +0 -0
  152. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector.egg-info/dependency_links.txt +0 -0
  153. {megadetector-10.0.4 → megadetector-10.0.6}/megadetector.egg-info/top_level.txt +0 -0
  154. {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.4
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>=10.0
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
@@ -298,6 +298,7 @@ class SequenceOptions:
298
298
  """
299
299
 
300
300
  def __init__(self):
301
+
301
302
  #: Images separated by <= this duration will be grouped into the same sequence.
302
303
  self.episode_interval_seconds = 60.0
303
304
 
@@ -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.wi_utils.generate_md_results_from_predictions_json.
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.wi_utils import generate_md_results_from_predictions_json
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 treated as a failure; this causes it to be treated as a video with no detections.
85
- self.allow_empty_videos = False
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
- allow_empty_videos=options.allow_empty_videos)
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
- allow_empty_videos=options.allow_empty_videos)
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('--allow_empty_videos',
425
+ parser.add_argument('--exit_on_empty_video',
418
426
  action='store_true',
419
- help='By default, videos with no retrievable frames cause an error, this makes it a warning')
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), 'File {} not found'.format(input_video_file)
161
- vidcap = cv2.VideoCapture(input_video_file)
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
- vidcap.release()
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 is an error).
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 = cv2.VideoCapture(input_video_file)
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
- success,image = vidcap.read()
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=allow_empty_videos)
551
+ allow_empty_videos=False)
444
552
 
445
553
  except Exception as e:
446
554
 
447
- print('Warning: error processing video {}: {}'.format(
448
- video_fn_abs,str(e)
449
- ))
450
- to_return['frame_rates'].append(-1.0)
451
- failure_result = {}
452
- failure_result['failure'] = 'Failure processing video: {}'.format(str(e))
453
- to_return['results'].append(failure_result)
454
- continue
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.wi_utils import clean_taxonomy_string
29
- from megadetector.utils.wi_utils import taxonomy_level_index
30
- from megadetector.utils.wi_utils import taxonomy_level_string_to_index
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.wi_utils import non_taxonomic_prediction_strings
33
- from megadetector.utils.wi_utils import human_prediction_string
34
- from megadetector.utils.wi_utils import animal_prediction_string
35
- from megadetector.utils.wi_utils import blank_prediction_string # noqa
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
- assert len(tokens) <= 2
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
- assert binomial_name not in target_latin_to_common
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
 
@@ -42,7 +42,7 @@ import pandas as pd
42
42
 
43
43
  from copy import deepcopy
44
44
 
45
- from megadetector.utils.wi_utils import load_md_or_speciesnet_file
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.wi_utils import load_md_or_speciesnet_file
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.wi_utils import generate_predictions_json_from_md_results
13
+ from megadetector.utils.wi_taxonomy_utils import generate_predictions_json_from_md_results
14
14
 
15
15
 
16
16
  #%% Command-line driver
@@ -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.wi_utils import load_md_or_speciesnet_file
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.