megadetector 5.0.20__tar.gz → 5.0.21__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 (212) hide show
  1. {megadetector-5.0.20/megadetector.egg-info → megadetector-5.0.21}/PKG-INFO +1 -1
  2. {megadetector-5.0.20 → megadetector-5.0.21}/README.md +2 -1
  3. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/osu-small-animals-to-json.py +4 -4
  4. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/yolo_output_to_md_output.py +18 -5
  5. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/video_utils.py +19 -7
  6. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/combine_api_outputs.py +1 -1
  7. megadetector-5.0.21/megadetector/postprocessing/detector_calibration.py +367 -0
  8. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/md_to_coco.py +2 -1
  9. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/postprocess_batch_results.py +32 -20
  10. megadetector-5.0.21/megadetector/postprocessing/validate_batch_results.py +246 -0
  11. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/md_tests.py +14 -12
  12. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/path_utils.py +139 -30
  13. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/write_html_image_list.py +16 -5
  14. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/visualization/visualization_utils.py +126 -23
  15. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/visualization/visualize_db.py +104 -63
  16. {megadetector-5.0.20 → megadetector-5.0.21/megadetector.egg-info}/PKG-INFO +1 -1
  17. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector.egg-info/SOURCES.txt +1 -1
  18. {megadetector-5.0.20 → megadetector-5.0.21}/pyproject.toml +1 -1
  19. megadetector-5.0.20/megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +0 -359
  20. megadetector-5.0.20/megadetector/postprocessing/validate_batch_results.py +0 -186
  21. {megadetector-5.0.20 → megadetector-5.0.21}/LICENSE +0 -0
  22. {megadetector-5.0.20 → megadetector-5.0.21}/README-package.md +0 -0
  23. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/__init__.py +0 -0
  24. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/__init__.py +0 -0
  25. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/__init__.py +0 -0
  26. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  27. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/batch_service/score.py +0 -0
  28. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/server.py +0 -0
  29. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/server_api_config.py +0 -0
  30. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/server_app_config.py +0 -0
  31. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -0
  32. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -0
  33. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/server_orchestration.py +0 -0
  34. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core/server_utils.py +0 -0
  35. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  36. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -0
  37. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_support/__init__.py +0 -0
  38. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -0
  39. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  40. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/integration/digiKam/setup.py +0 -0
  41. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +0 -0
  42. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +0 -0
  43. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -0
  44. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +0 -0
  45. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/synchronous/__init__.py +0 -0
  46. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  47. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -0
  48. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -0
  49. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -0
  50. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  51. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/api/synchronous/api_core/tests/load_test.py +0 -0
  52. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/__init__.py +0 -0
  53. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/aggregate_classifier_probs.py +0 -0
  54. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/analyze_failed_images.py +0 -0
  55. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/cache_batchapi_outputs.py +0 -0
  56. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/create_classification_dataset.py +0 -0
  57. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/crop_detections.py +0 -0
  58. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/csv_to_json.py +0 -0
  59. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/detect_and_crop.py +0 -0
  60. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/efficientnet/__init__.py +0 -0
  61. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/efficientnet/model.py +0 -0
  62. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/efficientnet/utils.py +0 -0
  63. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/evaluate_model.py +0 -0
  64. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/identify_mislabeled_candidates.py +0 -0
  65. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/json_to_azcopy_list.py +0 -0
  66. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/json_validator.py +0 -0
  67. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/map_classification_categories.py +0 -0
  68. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/merge_classification_detection_output.py +0 -0
  69. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/prepare_classification_script.py +0 -0
  70. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/prepare_classification_script_mc.py +0 -0
  71. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/run_classifier.py +0 -0
  72. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/save_mislabeled.py +0 -0
  73. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/train_classifier.py +0 -0
  74. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/train_classifier_tf.py +0 -0
  75. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/classification/train_utils.py +0 -0
  76. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/__init__.py +0 -0
  77. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/annotations/__init__.py +0 -0
  78. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/annotations/annotation_constants.py +0 -0
  79. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/camtrap_dp_to_coco.py +0 -0
  80. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/cct_json_utils.py +0 -0
  81. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/cct_to_md.py +0 -0
  82. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/cct_to_wi.py +0 -0
  83. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/coco_to_labelme.py +0 -0
  84. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/coco_to_yolo.py +0 -0
  85. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/databases/__init__.py +0 -0
  86. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/databases/add_width_and_height_to_db.py +0 -0
  87. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/databases/combine_coco_camera_traps_files.py +0 -0
  88. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/databases/integrity_check_json_db.py +0 -0
  89. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/databases/subset_json_db.py +0 -0
  90. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/generate_crops_from_cct.py +0 -0
  91. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/get_image_sizes.py +0 -0
  92. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/add_nacti_sizes.py +0 -0
  93. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/add_timestamps_to_icct.py +0 -0
  94. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/animl_results_to_md_results.py +0 -0
  95. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -0
  96. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/auckland_doc_to_json.py +0 -0
  97. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/awc_to_json.py +0 -0
  98. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/bellevue_to_json.py +0 -0
  99. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/cacophony-thermal-importer.py +0 -0
  100. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -0
  101. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -0
  102. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/cct_field_adjustments.py +0 -0
  103. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/channel_islands_to_cct.py +0 -0
  104. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -0
  105. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -0
  106. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -0
  107. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/ena24_to_json.py +0 -0
  108. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/filenames_to_json.py +0 -0
  109. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/helena_to_cct.py +0 -0
  110. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/idaho-camera-traps.py +0 -0
  111. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -0
  112. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -0
  113. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/jb_csv_to_json.py +0 -0
  114. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/mcgill_to_json.py +0 -0
  115. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/missouri_to_json.py +0 -0
  116. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -0
  117. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/noaa_seals_2019.py +0 -0
  118. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/pc_to_json.py +0 -0
  119. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/plot_wni_giraffes.py +0 -0
  120. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/prepare_zsl_imerit.py +0 -0
  121. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/rspb_to_json.py +0 -0
  122. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -0
  123. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -0
  124. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/snapshot_safari_importer.py +0 -0
  125. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -0
  126. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -0
  127. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -0
  128. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/sulross_get_exif.py +0 -0
  129. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -0
  130. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/ubc_to_json.py +0 -0
  131. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/umn_to_json.py +0 -0
  132. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/wellington_to_json.py +0 -0
  133. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/wi_to_json.py +0 -0
  134. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/importers/zamba_results_to_md_results.py +0 -0
  135. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/labelme_to_coco.py +0 -0
  136. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/labelme_to_yolo.py +0 -0
  137. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/__init__.py +0 -0
  138. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -0
  139. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/add_locations_to_nacti.py +0 -0
  140. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/create_lila_blank_set.py +0 -0
  141. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/create_lila_test_set.py +0 -0
  142. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/create_links_to_md_results_files.py +0 -0
  143. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/download_lila_subset.py +0 -0
  144. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/generate_lila_per_image_labels.py +0 -0
  145. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/get_lila_annotation_counts.py +0 -0
  146. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/get_lila_image_counts.py +0 -0
  147. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/lila_common.py +0 -0
  148. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/lila/test_lila_metadata_urls.py +0 -0
  149. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/ocr_tools.py +0 -0
  150. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/read_exif.py +0 -0
  151. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/remap_coco_categories.py +0 -0
  152. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/remove_exif.py +0 -0
  153. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/rename_images.py +0 -0
  154. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/resize_coco_dataset.py +0 -0
  155. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/wi_download_csv_to_coco.py +0 -0
  156. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/data_management/yolo_to_coco.py +0 -0
  157. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/__init__.py +0 -0
  158. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/detector_training/__init__.py +0 -0
  159. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/detector_training/model_main_tf2.py +0 -0
  160. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/process_video.py +0 -0
  161. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/pytorch_detector.py +0 -0
  162. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/run_detector.py +0 -0
  163. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/run_detector_batch.py +0 -0
  164. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/run_inference_with_yolov5_val.py +0 -0
  165. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/run_tiled_inference.py +0 -0
  166. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/detection/tf_detector.py +0 -0
  167. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/__init__.py +0 -0
  168. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/add_max_conf.py +0 -0
  169. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/categorize_detections_by_size.py +0 -0
  170. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/classification_postprocessing.py +0 -0
  171. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/compare_batch_results.py +0 -0
  172. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/convert_output_format.py +0 -0
  173. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/load_api_results.py +0 -0
  174. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/md_to_labelme.py +0 -0
  175. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/merge_detections.py +0 -0
  176. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/remap_detection_categories.py +0 -0
  177. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/render_detection_confusion_matrix.py +0 -0
  178. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +0 -0
  179. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +0 -0
  180. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +0 -0
  181. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/separate_detections_into_folders.py +0 -0
  182. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/subset_json_detector_output.py +0 -0
  183. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/postprocessing/top_folders_to_bottom.py +0 -0
  184. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/__init__.py +0 -0
  185. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +0 -0
  186. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/map_new_lila_datasets.py +0 -0
  187. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +0 -0
  188. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/preview_lila_taxonomy.py +0 -0
  189. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/retrieve_sample_image.py +0 -0
  190. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/simple_image_download.py +0 -0
  191. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/species_lookup.py +0 -0
  192. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/taxonomy_csv_checker.py +0 -0
  193. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/taxonomy_graph.py +0 -0
  194. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/taxonomy_mapping/validate_lila_category_mappings.py +0 -0
  195. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/__init__.py +0 -0
  196. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/azure_utils.py +0 -0
  197. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/ct_utils.py +0 -0
  198. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/directory_listing.py +0 -0
  199. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/process_utils.py +0 -0
  200. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/sas_blob_utils.py +0 -0
  201. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/split_locations_into_train_val.py +0 -0
  202. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/string_utils.py +0 -0
  203. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/torch_test.py +0 -0
  204. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/utils/url_utils.py +0 -0
  205. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/visualization/__init__.py +0 -0
  206. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/visualization/plot_utils.py +0 -0
  207. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/visualization/render_images_with_thumbnails.py +0 -0
  208. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector/visualization/visualize_detector_output.py +0 -0
  209. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector.egg-info/dependency_links.txt +0 -0
  210. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector.egg-info/requires.txt +0 -0
  211. {megadetector-5.0.20 → megadetector-5.0.21}/megadetector.egg-info/top_level.txt +0 -0
  212. {megadetector-5.0.20 → megadetector-5.0.21}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: megadetector
3
- Version: 5.0.20
3
+ Version: 5.0.21
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>
@@ -129,7 +129,8 @@ Here are a few of the organizations that have used MegaDetector... we're only li
129
129
  * [Q42](https://www.q42.nl/en) ([blog post](https://engineering.q42.nl/ai-bear-repeller/))
130
130
  * [Agouti](https://agouti.eu/) ([report](https://efsa.onlinelibrary.wiley.com/doi/pdf/10.2903/sp.efsa.2023.EN-8217))
131
131
  * [Trapper](https://trapper-project.readthedocs.io/en/latest/overview.html) ([tutorial](https://trapper-project.readthedocs.io/en/latest/tutorial.html))
132
- * [BirdLife Malta](https://birdlifemalta.org/) ([tweet](https://x.com/BirdLife_Malta/status/1817456839862173783?t=S-KRiZ5R1-CoW8-tbYNjqQ&s=03))
132
+ * [BirdLife Malta](https://birdlifemalta.org/) ([tweet](https://x.com/BirdLife_Malta/status/1817456839862173783?t=S-KRiZ5R1-CoW8-tbYNjqQ&s=03)) ([LI post](https://www.linkedin.com/posts/birdlifemalta_worldnatureconservationday-shearwater-colony-activity-7223220656589463553-X2Mc/?utm_source=share&utm_medium=member_android))
133
+ * [Endangered Landscapes and Seascapes Programme](https://www.endangeredlandscapes.org/), Cambridge Conservation Initiative ([blog post](https://www.endangeredlandscapes.org/news/ai-for-wildlife-monitoring-a-real-time-alert-system-for-bears-and-wild-boars-in-romanias-carpathian-mountains/))
133
134
 
134
135
  * [Road Ecology Center](https://roadecology.ucdavis.edu/), University of California, Davis ([Wildlife Observer Network platform](https://wildlifeobserver.net/))
135
136
  * [The Nature Conservancy in California](https://www.nature.org/en-us/about-us/where-we-work/united-states/california/) ([Animl platform](https://github.com/tnc-ca-geo/animl-frontend)) ([story](https://www.vision-systems.com/non-factory/environment-agriculture/article/14304433/the-nature-conservancy-brings-cameras-ai-to-invasive-species-prevention))
@@ -12,17 +12,17 @@ Prepare the OSU Small Animals dataset for LILA release:
12
12
 
13
13
  import os
14
14
 
15
- input_folder = r'G:\temp\osu-small-animals'
15
+ input_folder = os.path.expanduser('~/osu-small-animals')
16
16
  assert os.path.isdir(input_folder)
17
17
 
18
- output_folder = r'G:\temp\osu-small-animals-lila'
18
+ output_folder = os.path.expanduser('~/osu-small-animals-lila')
19
19
  os.makedirs(output_folder,exist_ok=True)
20
20
  output_file = os.path.join(output_folder,'osu-small-animals.json')
21
21
 
22
- preview_folder = r'G:\temp\osu-small-animals-preview'
22
+ preview_folder = os.path.expanduser('~/osu-small-animals-preview')
23
23
  os.makedirs(preview_folder,exist_ok=True)
24
24
 
25
- common_to_latin_file = r'c:\git\agentmorrisprivate\camera-traps\osu-small-animals-common-to-latin.txt'
25
+ common_to_latin_file = r'osu-small-animals-common-to-latin.txt'
26
26
  assert os.path.isfile(common_to_latin_file)
27
27
 
28
28
 
@@ -59,19 +59,21 @@ def read_classes_from_yolo_dataset_file(fn):
59
59
  integer category IDs to string category names.
60
60
 
61
61
  Args:
62
- fn (str): YOLOv5/YOLOv8 dataset file with a .yml or .yaml extension, or a .json file
63
- mapping integer category IDs to category names.
62
+ fn (str): YOLOv5/YOLOv8 dataset file with a .yml or .yaml extension, a .json file
63
+ mapping integer category IDs to category names, or a .txt file with a flat
64
+ list of classes.
64
65
 
65
66
  Returns:
66
67
  dict: a mapping from integer category IDs to category names
67
68
  """
68
-
69
+
70
+ category_id_to_name = {}
71
+
69
72
  if fn.endswith('.yml') or fn.endswith('.yaml'):
70
73
 
71
74
  with open(fn,'r') as f:
72
75
  lines = f.readlines()
73
76
 
74
- category_id_to_name = {}
75
77
  pat = '\d+:.+'
76
78
  for s in lines:
77
79
  if re.search(pat,s) is not None:
@@ -83,10 +85,21 @@ def read_classes_from_yolo_dataset_file(fn):
83
85
 
84
86
  with open(fn,'r') as f:
85
87
  d_in = json.load(f)
86
- category_id_to_name = {}
87
88
  for k in d_in.keys():
88
89
  category_id_to_name[int(k)] = d_in[k]
89
90
 
91
+ elif fn.endswith('.txt'):
92
+
93
+ with open(fn,'r') as f:
94
+ lines = f.readlines()
95
+ next_category_id = 0
96
+ for line in lines:
97
+ s = line.strip()
98
+ if len(s) == 0:
99
+ continue
100
+ category_id_to_name[next_category_id] = s
101
+ next_category_id += 1
102
+
90
103
  else:
91
104
 
92
105
  raise ValueError('Unrecognized category file type: {}'.format(fn))
@@ -678,12 +678,18 @@ def _video_to_frames_for_folder(relative_fn,input_folder,output_folder_base,
678
678
  return frame_filenames,fs
679
679
 
680
680
 
681
- def video_folder_to_frames(input_folder, output_folder_base,
682
- recursive=True, overwrite=True,
683
- n_threads=1, every_n_frames=None,
684
- verbose=False, parallelization_uses_threads=True,
685
- quality=None, max_width=None,
686
- frames_to_extract=None, allow_empty_videos=False):
681
+ def video_folder_to_frames(input_folder,
682
+ output_folder_base,
683
+ recursive=True,
684
+ overwrite=True,
685
+ n_threads=1,
686
+ every_n_frames=None,
687
+ verbose=False,
688
+ parallelization_uses_threads=True,
689
+ quality=None,
690
+ max_width=None,
691
+ frames_to_extract=None,
692
+ allow_empty_videos=False):
687
693
  """
688
694
  For every video file in input_folder, creates a folder within output_folder_base, and
689
695
  renders frame of that video to images in that folder.
@@ -709,6 +715,8 @@ def video_folder_to_frames(input_folder, output_folder_base,
709
715
  each video; mutually exclusive with every_n_frames. If all values are beyond
710
716
  the length of a video, no frames are extracted. Can also be a single int,
711
717
  specifying a single frame number.
718
+ allow_empty_videos (bool, optional): Just print a warning if a video appears to have no
719
+ frames (by default, this is an error).
712
720
 
713
721
  Returns:
714
722
  tuple: a length-3 tuple containing:
@@ -719,8 +727,11 @@ def video_folder_to_frames(input_folder, output_folder_base,
719
727
  """
720
728
 
721
729
  # Recursively enumerate video files
730
+ if verbose:
731
+ print('Enumerating videos in {}'.format(input_folder))
722
732
  input_files_full_paths = find_videos(input_folder,recursive=recursive)
723
- print('Found {} videos in folder {}'.format(len(input_files_full_paths),input_folder))
733
+ if verbose:
734
+ print('Found {} videos in folder {}'.format(len(input_files_full_paths),input_folder))
724
735
  if len(input_files_full_paths) == 0:
725
736
  return [],[],[]
726
737
 
@@ -974,6 +985,7 @@ if False:
974
985
  results_file = r'results.json'
975
986
  confidence_threshold = 0.75
976
987
 
988
+
977
989
  #%% Load detector output
978
990
 
979
991
  with open(results_file,'r') as f:
@@ -192,7 +192,7 @@ def combine_api_shard_files(input_files, output_file=None):
192
192
 
193
193
  Args:
194
194
  input_files (list of str): files to merge
195
- output_file (str, optiona): file to which we should write merged results
195
+ output_file (str, optional): file to which we should write merged results
196
196
 
197
197
  Returns:
198
198
  dict: merged results
@@ -0,0 +1,367 @@
1
+ """
2
+
3
+ detector_calibration.py
4
+
5
+ Tools for comparing/calibrating confidence values from detectors, particularly different
6
+ versions of MegaDetector.
7
+
8
+ """
9
+
10
+ #%% Constants and imports
11
+
12
+ import random
13
+
14
+ from tqdm import tqdm
15
+ from enum import IntEnum
16
+ from collections import defaultdict
17
+
18
+ import numpy as np
19
+ import matplotlib
20
+ import matplotlib.pyplot as plt
21
+
22
+ from megadetector.postprocessing.validate_batch_results import \
23
+ validate_batch_results, ValidateBatchResultsOptions
24
+ from megadetector.utils.ct_utils import get_iou
25
+
26
+
27
+ #%% Classes
28
+
29
+ class CalibrationOptions:
30
+ """
31
+ Options controlling comparison/calibration behavior.
32
+ """
33
+
34
+ def __init__(self):
35
+
36
+ #: IoU threshold used for determining whether two detections are the same
37
+ #:
38
+ #: When multiple detections match, we will only use the highest-matching IoU.
39
+ self.iou_threshold = 0.75
40
+
41
+ #: Minimum confidence threshold to consider for calibration (should be lower than
42
+ #: the lowest value you would use in realistic situations)
43
+ self.confidence_threshold = 0.025
44
+
45
+ #: Should we populate the data_a and data_b fields in the return value?
46
+ self.return_data = False
47
+
48
+ #: Model name to use in printouts and plots for result set A
49
+ self.model_name_a = 'model_a'
50
+
51
+ #: Model name to use in printouts and plots for result set B
52
+ self.model_name_b = 'model_b'
53
+
54
+ #: Maximum number of samples to use for plotting or calibration per category,
55
+ #: or None to use all paired values.
56
+ self.max_samples_per_category = None
57
+
58
+ #: List of category IDs to use for plotting comparisons, or None to plot
59
+ #: all categories.
60
+ self.categories_to_plot = None
61
+
62
+ #: Optionally map category ID to name in plot labels
63
+ self.category_id_to_name = None
64
+
65
+ # ...class CalibrationOptions
66
+
67
+ class ConfidenceMatchColumns(IntEnum):
68
+
69
+ COLUMN_CONF_A = 0
70
+ COLUMN_CONF_B = 1
71
+ COLUMN_CONF_IOU = 2
72
+ COLUMN_CONF_I_IMAGE = 3
73
+ COLUMN_CONF_CATEGORY_ID = 4
74
+
75
+ class CalibrationResults:
76
+ """
77
+ Results of a model-to-model comparison.
78
+ """
79
+
80
+ def __init__(self):
81
+
82
+ #: List of tuples: [conf_a, conf_b, iou, i_image, category_id]
83
+ self.confidence_matches = []
84
+
85
+ #: Populated with the data loaded from json_filename_a if options.return_data is True
86
+ self.data_a = None
87
+
88
+ #: Populated with the data loaded from json_filename_b if options.return_data is True
89
+ self.data_b = None
90
+
91
+ # ...class CalibrationResults
92
+
93
+
94
+ #%% Calibration functions
95
+
96
+ def compare_model_confidence_values(json_filename_a,json_filename_b,options=None):
97
+ """
98
+ Compare confidence values across two .json results files. Compares only detections that
99
+ can be matched by IoU, i.e., does not do anything with detections that only appear in one file.
100
+
101
+ Args:
102
+ json_filename_a (str or dict): filename containing results from the first model to be compared;
103
+ should refer to the same images as [json_filename_b]. Can also be a loaded results dict.
104
+ json_filename_b (str or dict): filename containing results from the second model to be compared;
105
+ should refer to the same images as [json_filename_a]. Can also be a loaded results dict.
106
+ options (CalibrationOptions, optional): all the parameters used to control this process, see
107
+ CalibrationOptions for details
108
+
109
+ Returns:
110
+ CalibrationResults: description of the comparison results
111
+ """
112
+
113
+ ## Option handling
114
+
115
+ if options is None:
116
+ options = CalibrationOptions()
117
+
118
+ validation_options = ValidateBatchResultsOptions()
119
+ validation_options.return_data = True
120
+
121
+ if isinstance(json_filename_a,str):
122
+ results_a = validate_batch_results(json_filename_a,options=validation_options)
123
+ assert len(results_a['validation_results']['errors']) == 0
124
+ else:
125
+ assert isinstance(json_filename_a,dict)
126
+ results_a = json_filename_a
127
+
128
+ if isinstance(json_filename_b,str):
129
+ results_b = validate_batch_results(json_filename_b,options=validation_options)
130
+ assert len(results_b['validation_results']['errors']) == 0
131
+ else:
132
+ assert isinstance(json_filename_b,dict)
133
+ results_b = json_filename_b
134
+
135
+
136
+ ## Make sure these results sets are comparable
137
+
138
+ image_filenames_a = [im['file'] for im in results_a['images']]
139
+ image_filenames_b = [im['file'] for im in results_b['images']]
140
+
141
+ assert set(image_filenames_a) == set(image_filenames_b), \
142
+ 'Cannot calibrate non-matching image sets'
143
+
144
+ categories_a = results_a['detection_categories']
145
+ categories_b = results_b['detection_categories']
146
+ assert set(categories_a.keys()) == set(categories_b.keys())
147
+ for k in categories_a.keys():
148
+ assert categories_a[k] == categories_b[k], 'Category mismatch'
149
+
150
+
151
+ ## Compare detections
152
+
153
+ image_filename_b_to_im = {}
154
+ for im in results_b['images']:
155
+ image_filename_b_to_im[im['file']] = im
156
+
157
+ n_detections_a = 0
158
+ n_detections_a_queried = 0
159
+ n_detections_a_matched = 0
160
+
161
+ confidence_matches = []
162
+
163
+ # For each image
164
+ # im_a = results_a['images'][0]
165
+ for i_image,im_a in tqdm(enumerate(results_a['images']),total=len(results_a['images'])):
166
+
167
+ fn = im_a['file']
168
+ im_b = image_filename_b_to_im[fn]
169
+
170
+ if 'detections' not in im_a or im_a['detections'] is None:
171
+ continue
172
+ if 'detections' not in im_b or im_b['detections'] is None:
173
+ continue
174
+
175
+ # For each detection in result set A...
176
+ #
177
+ # det_a = im_a['detections'][0]
178
+ for det_a in im_a['detections']:
179
+
180
+ n_detections_a += 1
181
+
182
+ conf_a = det_a['conf']
183
+ category_id = det_a['category']
184
+
185
+ # Is this above threshold?
186
+ if conf_a < options.confidence_threshold:
187
+ continue
188
+
189
+ n_detections_a_queried += 1
190
+
191
+ bbox_a = det_a['bbox']
192
+
193
+ best_iou = None
194
+ best_iou_conf = None
195
+
196
+ # For each detection in result set B...
197
+ #
198
+ # det_b = im_b['detections'][0]
199
+ for det_b in im_b['detections']:
200
+
201
+ # Is this the same category?
202
+ if det_b['category'] != category_id:
203
+ continue
204
+
205
+ conf_b = det_b['conf']
206
+
207
+ # Is this above threshold?
208
+ if conf_b < options.confidence_threshold:
209
+ continue
210
+
211
+ bbox_b = det_b['bbox']
212
+
213
+ iou = get_iou(bbox_a,bbox_b)
214
+
215
+ # Is this an adequate IoU to consider?
216
+ if iou < options.iou_threshold:
217
+ continue
218
+
219
+ # Is this the best match so far?
220
+ if best_iou is None or iou > best_iou:
221
+ best_iou = iou
222
+ best_iou_conf = conf_b
223
+
224
+ # ...for each detection in im_b
225
+
226
+ if best_iou is not None:
227
+ n_detections_a_matched += 1
228
+ conf_result = [conf_a,best_iou_conf,best_iou,i_image,category_id]
229
+ confidence_matches.append(conf_result)
230
+
231
+ # ...for each detection in im_a
232
+
233
+ # ...for each image in result set A
234
+
235
+ print('\nOf {} detections in result set A, queried {}, matched {}'.format(
236
+ n_detections_a,n_detections_a_queried,n_detections_a_matched))
237
+ assert len(confidence_matches) == n_detections_a_matched
238
+
239
+ calibration_results = CalibrationResults()
240
+ calibration_results.confidence_matches = confidence_matches
241
+
242
+ if options.return_data:
243
+ calibration_results.data_a = results_a
244
+ calibration_results.data_b = results_b
245
+
246
+ return calibration_results
247
+
248
+ # ...def compare_model_confidence_values(...)
249
+
250
+
251
+ #%% Plotting functions
252
+
253
+ def plot_matched_confidence_values(calibration_results,output_filename,options=None):
254
+ """
255
+ Given a set of paired confidence values for matching detections (from
256
+ compare_model_confidence_values), plot histograms of those pairs for each
257
+ detection category.
258
+
259
+ Args:
260
+ calibration_results (CalibrationResults): output from a call to
261
+ compare_model_confidence_values, containing paired confidence
262
+ values for two sets of detection results.
263
+ output_filename (str): filename to write the plot (.png or .jpg)
264
+ options (CalibrationOptions, optional): plotting options, see
265
+ CalibrationOptions for details.
266
+ """
267
+
268
+ fig_w = 12
269
+ fig_h = 8
270
+ n_hist_bins = 80
271
+
272
+ if options is None:
273
+ options = CalibrationOptions()
274
+
275
+ # Find matched confidence pairs for each category ID
276
+ category_to_matches = defaultdict(list)
277
+
278
+ confidence_matches = calibration_results.confidence_matches
279
+ for m in confidence_matches:
280
+ category_id = m[ConfidenceMatchColumns.COLUMN_CONF_CATEGORY_ID]
281
+ category_to_matches[category_id].append(m)
282
+
283
+ # Optionally sample matches
284
+ category_to_samples = defaultdict(list)
285
+
286
+ for i_category,category_id in enumerate(category_to_matches.keys()):
287
+
288
+ matches_this_category = category_to_matches[category_id]
289
+
290
+ if (options.max_samples_per_category is None) or \
291
+ (len(matches_this_category) <= options.max_samples_per_category):
292
+ category_to_samples[category_id] = matches_this_category
293
+ else:
294
+ assert len(matches_this_category) > options.max_samples_per_category
295
+ category_to_samples[category_id] = random.sample(matches_this_category,options.max_samples_per_category)
296
+
297
+ del category_to_matches
298
+ del confidence_matches
299
+
300
+ categories_to_plot = list(category_to_samples.keys())
301
+
302
+ if options.categories_to_plot is not None:
303
+ categories_to_plot = [category_id for category_id in categories_to_plot if\
304
+ category_id in options.categories_to_plot]
305
+
306
+ n_subplots = len(categories_to_plot)
307
+
308
+ plt.ioff()
309
+
310
+ fig = matplotlib.figure.Figure(figsize=(fig_w, fig_h), tight_layout=True)
311
+ # fig,axes = plt.subplots(nrows=n_subplots,ncols=1)
312
+
313
+ axes = fig.subplots(n_subplots, 1)
314
+
315
+ # i_category = 0; category_id = categories_to_plot[i_category]
316
+ for i_category,category_id in enumerate(categories_to_plot):
317
+
318
+ ax = axes[i_category]
319
+
320
+ category_string = category_id
321
+ if options.category_id_to_name is not None and \
322
+ category_id in options.category_id_to_name:
323
+ category_string = options.category_id_to_name[category_id]
324
+
325
+ samples_this_category = category_to_samples[category_id]
326
+ x = [m[0] for m in samples_this_category]
327
+ y = [m[1] for m in samples_this_category]
328
+
329
+ weights_a = np.ones_like(x)/float(len(x))
330
+ weights_b = np.ones_like(y)/float(len(y))
331
+ ax.hist(x,histtype='step',bins=n_hist_bins,density=False,color='red',weights=weights_a)
332
+ ax.hist(y,histtype='step',bins=n_hist_bins,density=False,color='blue',weights=weights_b)
333
+ ax.legend([options.model_name_a,options.model_name_b])
334
+ ax.set_ylabel(category_string)
335
+ # plt.tight_layout()
336
+
337
+ # I experimented with heat maps, but they weren't very informative.
338
+ # Leaving this code here in case I revisit. Note to self: scatter plots
339
+ # were a disaster.
340
+ if False:
341
+ heatmap, xedges, yedges = np.histogram2d(x, y, bins=30)
342
+ extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
343
+ plt.imshow(heatmap.T, extent=extent, origin='lower', norm='log')
344
+
345
+ # ...for each category for which we need to generate a histogram
346
+
347
+ plt.close(fig)
348
+ fig.savefig(output_filename,dpi=100)
349
+
350
+ # ...def plot_matched_confidence_values(...)
351
+
352
+
353
+ #%% Interactive driver(s)
354
+
355
+ if False:
356
+
357
+ #%%
358
+
359
+ options = ValidateBatchResultsOptions()
360
+ # json_filename = r'g:\temp\format.json'
361
+ # json_filename = r'g:\temp\test-videos\video_results.json'
362
+ json_filename = r'g:\temp\test-videos\image_results.json'
363
+ options.check_image_existence = True
364
+ options.relative_path_base = r'g:\temp\test-videos'
365
+ validate_batch_results(json_filename,options)
366
+
367
+
@@ -41,7 +41,8 @@ def md_to_coco(md_results_file,
41
41
  The default confidence threshold is not 0; the assumption is that by default, you are
42
42
  going to treat the resulting COCO file as a set of labels. If you are using the resulting COCO
43
43
  file to evaluate a detector, you likely want a default confidence threshold of 0. Confidence
44
- values will be written to the semi-standard "score" field for each image
44
+ values will be written to the semi-standard "score" field for each image if
45
+ preserve_nonstandard_metadata is True.
45
46
 
46
47
  A folder of images is required if width and height information are not available
47
48
  in the MD results file.
@@ -92,16 +92,18 @@ class PostProcessingOptions:
92
92
  #: Optional .json file containing ground truth information
93
93
  self.ground_truth_json_file = ''
94
94
 
95
- #: Classes we'll treat as negative
95
+ #: List of classes we'll treat as negative (defaults to "empty", typically includes
96
+ #: classes like "blank", "misfire", etc.).
96
97
  #:
97
98
  #: Include the token "#NO_LABELS#" to indicate that an image with no annotations
98
99
  #: should be considered empty.
99
100
  self.negative_classes = DEFAULT_NEGATIVE_CLASSES
100
101
 
101
- #: Classes we'll treat as neither positive nor negative
102
+ #: List of classes we'll treat as neither positive nor negative (defaults to
103
+ #: "unknown", typically includes classes like "unidentifiable").
102
104
  self.unlabeled_classes = DEFAULT_UNKNOWN_CLASSES
103
105
 
104
- #: A list of output sets that we should count, but not render images for.
106
+ #: List of output sets that we should count, but not render images for.
105
107
  #:
106
108
  #: Typically used to preview sets with lots of empties, where you don't want to
107
109
  #: subset but also don't want to render 100,000 empty images.
@@ -198,11 +200,16 @@ class PostProcessingOptions:
198
200
 
199
201
  #: When classification results are present, should be sort alphabetically by class name (False)
200
202
  #: or in descending order by frequency (True)?
201
- self.sort_classification_results_by_count = False
203
+ self.sort_classification_results_by_count = False
202
204
 
203
205
  #: Should we split individual pages up into smaller pages if there are more than
204
206
  #: N images?
205
207
  self.max_figures_per_html_file = None
208
+
209
+ #: Footer text for the index page
210
+ # self.footer_text = '<br/><p style="font-size:80%;">Preview page created with the <a href="{}">MegaDetector Python package</a>.</p>'.\
211
+ # format('https://megadetector.readthedocs.io')
212
+ self.footer_text = ''
206
213
 
207
214
  # ...__init__()
208
215
 
@@ -590,6 +597,7 @@ def _prepare_html_subpages(images_html, output_dir, options=None):
590
597
  html_image_list_options = {}
591
598
  html_image_list_options['maxFiguresPerHtmlFile'] = options.max_figures_per_html_file
592
599
  html_image_list_options['headerHtml'] = '<h1>{}</h1>'.format(res.upper())
600
+ html_image_list_options['pageTitle'] = '{}'.format(res.lower())
593
601
 
594
602
  # Don't write empty pages
595
603
  if len(array) == 0:
@@ -762,7 +770,7 @@ def _render_image_no_gt(file_info,detection_categories_to_results_name,
762
770
  if len(rendered_image_html_info) > 0:
763
771
 
764
772
  image_result = [[res, rendered_image_html_info]]
765
-
773
+ classes_rendered_this_image = set()
766
774
  max_conf = 0
767
775
 
768
776
  for det in detections:
@@ -782,11 +790,14 @@ def _render_image_no_gt(file_info,detection_categories_to_results_name,
782
790
  # confidence threshold
783
791
  if (options.classification_confidence_threshold < 0) or \
784
792
  (top1_class_score >= options.classification_confidence_threshold):
785
- image_result.append(['class_{}'.format(top1_class_name),
786
- rendered_image_html_info])
793
+ class_string = 'class_{}'.format(top1_class_name)
787
794
  else:
788
- image_result.append(['class_unreliable',
795
+ class_string = 'class_unreliable'
796
+
797
+ if class_string not in classes_rendered_this_image:
798
+ image_result.append([class_string,
789
799
  rendered_image_html_info])
800
+ classes_rendered_this_image.add(class_string)
790
801
 
791
802
  # ...if this detection has classification info
792
803
 
@@ -1083,7 +1094,8 @@ def process_batch_results(options):
1083
1094
 
1084
1095
  output_html_file = ''
1085
1096
 
1086
- style_header = """<head>
1097
+ style_header = """<head>
1098
+ <title>Detection results preview</title>
1087
1099
  <style type="text/css">
1088
1100
  a { text-decoration: none; }
1089
1101
  body { font-family: segoe ui, calibri, "trebuchet ms", verdana, arial, sans-serif; }
@@ -1424,7 +1436,7 @@ def process_batch_results(options):
1424
1436
  else:
1425
1437
  confidence_threshold_string = str(options.confidence_threshold)
1426
1438
 
1427
- index_page = """<html>
1439
+ index_page = """<html>
1428
1440
  {}
1429
1441
  <body>
1430
1442
  <h2>Evaluation</h2>
@@ -1509,7 +1521,7 @@ def process_batch_results(options):
1509
1521
  index_page += '</div>'
1510
1522
 
1511
1523
  # Close body and html tags
1512
- index_page += '</body></html>'
1524
+ index_page += '{}</body></html>'.format(options.footer_text)
1513
1525
  output_html_file = os.path.join(output_dir, 'index.html')
1514
1526
  with open(output_html_file, 'w') as f:
1515
1527
  f.write(index_page)
@@ -1529,7 +1541,6 @@ def process_batch_results(options):
1529
1541
  # for each category
1530
1542
  images_html = collections.defaultdict(list)
1531
1543
 
1532
-
1533
1544
  # Add default entries by accessing them for the first time
1534
1545
 
1535
1546
  # Maps sorted tuples of detection category IDs (string ints) - e.g. ("1"), ("1", "4", "7") - to
@@ -1637,14 +1648,15 @@ def process_batch_results(options):
1637
1648
  files_to_render), total=len(files_to_render)))
1638
1649
  else:
1639
1650
  for file_info in tqdm(files_to_render):
1640
- rendering_results.append(_render_image_no_gt(file_info,
1641
- detection_categories_to_results_name,
1642
- detection_categories,
1643
- classification_categories,
1644
- options=options))
1651
+ rendering_result = _render_image_no_gt(file_info,
1652
+ detection_categories_to_results_name,
1653
+ detection_categories,
1654
+ classification_categories,
1655
+ options=options)
1656
+ rendering_results.append(rendering_result)
1645
1657
 
1646
- elapsed = time.time() - start_time
1647
-
1658
+ elapsed = time.time() - start_time
1659
+
1648
1660
  # Do we have classification results in addition to detection results?
1649
1661
  has_classification_info = False
1650
1662
 
@@ -1793,7 +1805,7 @@ def process_batch_results(options):
1793
1805
  cname, cname.lower(), ccount)
1794
1806
  index_page += '</div>\n'
1795
1807
 
1796
- index_page += '</body></html>'
1808
+ index_page += '{}</body></html>'.format(options.footer_text)
1797
1809
  output_html_file = os.path.join(output_dir, 'index.html')
1798
1810
  with open(output_html_file, 'w') as f:
1799
1811
  f.write(index_page)