megadetector 5.0.9__py3-none-any.whl → 5.0.11__py3-none-any.whl

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 (226) hide show
  1. {megadetector-5.0.9.dist-info → megadetector-5.0.11.dist-info}/LICENSE +0 -0
  2. {megadetector-5.0.9.dist-info → megadetector-5.0.11.dist-info}/METADATA +12 -11
  3. megadetector-5.0.11.dist-info/RECORD +5 -0
  4. megadetector-5.0.11.dist-info/top_level.txt +1 -0
  5. api/__init__.py +0 -0
  6. api/batch_processing/__init__.py +0 -0
  7. api/batch_processing/api_core/__init__.py +0 -0
  8. api/batch_processing/api_core/batch_service/__init__.py +0 -0
  9. api/batch_processing/api_core/batch_service/score.py +0 -439
  10. api/batch_processing/api_core/server.py +0 -294
  11. api/batch_processing/api_core/server_api_config.py +0 -98
  12. api/batch_processing/api_core/server_app_config.py +0 -55
  13. api/batch_processing/api_core/server_batch_job_manager.py +0 -220
  14. api/batch_processing/api_core/server_job_status_table.py +0 -152
  15. api/batch_processing/api_core/server_orchestration.py +0 -360
  16. api/batch_processing/api_core/server_utils.py +0 -92
  17. api/batch_processing/api_core_support/__init__.py +0 -0
  18. api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
  19. api/batch_processing/api_support/__init__.py +0 -0
  20. api/batch_processing/api_support/summarize_daily_activity.py +0 -152
  21. api/batch_processing/data_preparation/__init__.py +0 -0
  22. api/batch_processing/data_preparation/manage_local_batch.py +0 -2391
  23. api/batch_processing/data_preparation/manage_video_batch.py +0 -327
  24. api/batch_processing/integration/digiKam/setup.py +0 -6
  25. api/batch_processing/integration/digiKam/xmp_integration.py +0 -465
  26. api/batch_processing/integration/eMammal/test_scripts/config_template.py +0 -5
  27. api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -126
  28. api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +0 -55
  29. api/batch_processing/postprocessing/__init__.py +0 -0
  30. api/batch_processing/postprocessing/add_max_conf.py +0 -64
  31. api/batch_processing/postprocessing/categorize_detections_by_size.py +0 -163
  32. api/batch_processing/postprocessing/combine_api_outputs.py +0 -249
  33. api/batch_processing/postprocessing/compare_batch_results.py +0 -958
  34. api/batch_processing/postprocessing/convert_output_format.py +0 -397
  35. api/batch_processing/postprocessing/load_api_results.py +0 -195
  36. api/batch_processing/postprocessing/md_to_coco.py +0 -310
  37. api/batch_processing/postprocessing/md_to_labelme.py +0 -330
  38. api/batch_processing/postprocessing/merge_detections.py +0 -401
  39. api/batch_processing/postprocessing/postprocess_batch_results.py +0 -1904
  40. api/batch_processing/postprocessing/remap_detection_categories.py +0 -170
  41. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +0 -661
  42. api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +0 -211
  43. api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +0 -82
  44. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +0 -1631
  45. api/batch_processing/postprocessing/separate_detections_into_folders.py +0 -731
  46. api/batch_processing/postprocessing/subset_json_detector_output.py +0 -696
  47. api/batch_processing/postprocessing/top_folders_to_bottom.py +0 -223
  48. api/synchronous/__init__.py +0 -0
  49. api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  50. api/synchronous/api_core/animal_detection_api/api_backend.py +0 -152
  51. api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -266
  52. api/synchronous/api_core/animal_detection_api/config.py +0 -35
  53. api/synchronous/api_core/animal_detection_api/data_management/annotations/annotation_constants.py +0 -47
  54. api/synchronous/api_core/animal_detection_api/detection/detector_training/copy_checkpoints.py +0 -43
  55. api/synchronous/api_core/animal_detection_api/detection/detector_training/model_main_tf2.py +0 -114
  56. api/synchronous/api_core/animal_detection_api/detection/process_video.py +0 -543
  57. api/synchronous/api_core/animal_detection_api/detection/pytorch_detector.py +0 -304
  58. api/synchronous/api_core/animal_detection_api/detection/run_detector.py +0 -627
  59. api/synchronous/api_core/animal_detection_api/detection/run_detector_batch.py +0 -1029
  60. api/synchronous/api_core/animal_detection_api/detection/run_inference_with_yolov5_val.py +0 -581
  61. api/synchronous/api_core/animal_detection_api/detection/run_tiled_inference.py +0 -754
  62. api/synchronous/api_core/animal_detection_api/detection/tf_detector.py +0 -165
  63. api/synchronous/api_core/animal_detection_api/detection/video_utils.py +0 -495
  64. api/synchronous/api_core/animal_detection_api/md_utils/azure_utils.py +0 -174
  65. api/synchronous/api_core/animal_detection_api/md_utils/ct_utils.py +0 -262
  66. api/synchronous/api_core/animal_detection_api/md_utils/directory_listing.py +0 -251
  67. api/synchronous/api_core/animal_detection_api/md_utils/matlab_porting_tools.py +0 -97
  68. api/synchronous/api_core/animal_detection_api/md_utils/path_utils.py +0 -416
  69. api/synchronous/api_core/animal_detection_api/md_utils/process_utils.py +0 -110
  70. api/synchronous/api_core/animal_detection_api/md_utils/sas_blob_utils.py +0 -509
  71. api/synchronous/api_core/animal_detection_api/md_utils/string_utils.py +0 -59
  72. api/synchronous/api_core/animal_detection_api/md_utils/url_utils.py +0 -144
  73. api/synchronous/api_core/animal_detection_api/md_utils/write_html_image_list.py +0 -226
  74. api/synchronous/api_core/animal_detection_api/md_visualization/visualization_utils.py +0 -841
  75. api/synchronous/api_core/tests/__init__.py +0 -0
  76. api/synchronous/api_core/tests/load_test.py +0 -110
  77. classification/__init__.py +0 -0
  78. classification/aggregate_classifier_probs.py +0 -108
  79. classification/analyze_failed_images.py +0 -227
  80. classification/cache_batchapi_outputs.py +0 -198
  81. classification/create_classification_dataset.py +0 -627
  82. classification/crop_detections.py +0 -516
  83. classification/csv_to_json.py +0 -226
  84. classification/detect_and_crop.py +0 -855
  85. classification/efficientnet/__init__.py +0 -9
  86. classification/efficientnet/model.py +0 -415
  87. classification/efficientnet/utils.py +0 -610
  88. classification/evaluate_model.py +0 -520
  89. classification/identify_mislabeled_candidates.py +0 -152
  90. classification/json_to_azcopy_list.py +0 -63
  91. classification/json_validator.py +0 -695
  92. classification/map_classification_categories.py +0 -276
  93. classification/merge_classification_detection_output.py +0 -506
  94. classification/prepare_classification_script.py +0 -194
  95. classification/prepare_classification_script_mc.py +0 -228
  96. classification/run_classifier.py +0 -286
  97. classification/save_mislabeled.py +0 -110
  98. classification/train_classifier.py +0 -825
  99. classification/train_classifier_tf.py +0 -724
  100. classification/train_utils.py +0 -322
  101. data_management/__init__.py +0 -0
  102. data_management/annotations/__init__.py +0 -0
  103. data_management/annotations/annotation_constants.py +0 -34
  104. data_management/camtrap_dp_to_coco.py +0 -238
  105. data_management/cct_json_utils.py +0 -395
  106. data_management/cct_to_md.py +0 -176
  107. data_management/cct_to_wi.py +0 -289
  108. data_management/coco_to_labelme.py +0 -272
  109. data_management/coco_to_yolo.py +0 -662
  110. data_management/databases/__init__.py +0 -0
  111. data_management/databases/add_width_and_height_to_db.py +0 -33
  112. data_management/databases/combine_coco_camera_traps_files.py +0 -206
  113. data_management/databases/integrity_check_json_db.py +0 -477
  114. data_management/databases/subset_json_db.py +0 -115
  115. data_management/generate_crops_from_cct.py +0 -149
  116. data_management/get_image_sizes.py +0 -188
  117. data_management/importers/add_nacti_sizes.py +0 -52
  118. data_management/importers/add_timestamps_to_icct.py +0 -79
  119. data_management/importers/animl_results_to_md_results.py +0 -158
  120. data_management/importers/auckland_doc_test_to_json.py +0 -372
  121. data_management/importers/auckland_doc_to_json.py +0 -200
  122. data_management/importers/awc_to_json.py +0 -189
  123. data_management/importers/bellevue_to_json.py +0 -273
  124. data_management/importers/cacophony-thermal-importer.py +0 -796
  125. data_management/importers/carrizo_shrubfree_2018.py +0 -268
  126. data_management/importers/carrizo_trail_cam_2017.py +0 -287
  127. data_management/importers/cct_field_adjustments.py +0 -57
  128. data_management/importers/channel_islands_to_cct.py +0 -913
  129. data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  130. data_management/importers/eMammal/eMammal_helpers.py +0 -249
  131. data_management/importers/eMammal/make_eMammal_json.py +0 -223
  132. data_management/importers/ena24_to_json.py +0 -275
  133. data_management/importers/filenames_to_json.py +0 -385
  134. data_management/importers/helena_to_cct.py +0 -282
  135. data_management/importers/idaho-camera-traps.py +0 -1407
  136. data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  137. data_management/importers/jb_csv_to_json.py +0 -150
  138. data_management/importers/mcgill_to_json.py +0 -250
  139. data_management/importers/missouri_to_json.py +0 -489
  140. data_management/importers/nacti_fieldname_adjustments.py +0 -79
  141. data_management/importers/noaa_seals_2019.py +0 -181
  142. data_management/importers/pc_to_json.py +0 -365
  143. data_management/importers/plot_wni_giraffes.py +0 -123
  144. data_management/importers/prepare-noaa-fish-data-for-lila.py +0 -359
  145. data_management/importers/prepare_zsl_imerit.py +0 -131
  146. data_management/importers/rspb_to_json.py +0 -356
  147. data_management/importers/save_the_elephants_survey_A.py +0 -320
  148. data_management/importers/save_the_elephants_survey_B.py +0 -332
  149. data_management/importers/snapshot_safari_importer.py +0 -758
  150. data_management/importers/snapshot_safari_importer_reprise.py +0 -665
  151. data_management/importers/snapshot_serengeti_lila.py +0 -1067
  152. data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  153. data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  154. data_management/importers/sulross_get_exif.py +0 -65
  155. data_management/importers/timelapse_csv_set_to_json.py +0 -490
  156. data_management/importers/ubc_to_json.py +0 -399
  157. data_management/importers/umn_to_json.py +0 -507
  158. data_management/importers/wellington_to_json.py +0 -263
  159. data_management/importers/wi_to_json.py +0 -441
  160. data_management/importers/zamba_results_to_md_results.py +0 -181
  161. data_management/labelme_to_coco.py +0 -548
  162. data_management/labelme_to_yolo.py +0 -272
  163. data_management/lila/__init__.py +0 -0
  164. data_management/lila/add_locations_to_island_camera_traps.py +0 -97
  165. data_management/lila/add_locations_to_nacti.py +0 -147
  166. data_management/lila/create_lila_blank_set.py +0 -557
  167. data_management/lila/create_lila_test_set.py +0 -151
  168. data_management/lila/create_links_to_md_results_files.py +0 -106
  169. data_management/lila/download_lila_subset.py +0 -177
  170. data_management/lila/generate_lila_per_image_labels.py +0 -515
  171. data_management/lila/get_lila_annotation_counts.py +0 -170
  172. data_management/lila/get_lila_image_counts.py +0 -111
  173. data_management/lila/lila_common.py +0 -300
  174. data_management/lila/test_lila_metadata_urls.py +0 -132
  175. data_management/ocr_tools.py +0 -874
  176. data_management/read_exif.py +0 -681
  177. data_management/remap_coco_categories.py +0 -84
  178. data_management/remove_exif.py +0 -66
  179. data_management/resize_coco_dataset.py +0 -189
  180. data_management/wi_download_csv_to_coco.py +0 -246
  181. data_management/yolo_output_to_md_output.py +0 -441
  182. data_management/yolo_to_coco.py +0 -676
  183. detection/__init__.py +0 -0
  184. detection/detector_training/__init__.py +0 -0
  185. detection/detector_training/model_main_tf2.py +0 -114
  186. detection/process_video.py +0 -703
  187. detection/pytorch_detector.py +0 -337
  188. detection/run_detector.py +0 -779
  189. detection/run_detector_batch.py +0 -1219
  190. detection/run_inference_with_yolov5_val.py +0 -917
  191. detection/run_tiled_inference.py +0 -935
  192. detection/tf_detector.py +0 -188
  193. detection/video_utils.py +0 -606
  194. docs/source/conf.py +0 -43
  195. md_utils/__init__.py +0 -0
  196. md_utils/azure_utils.py +0 -174
  197. md_utils/ct_utils.py +0 -612
  198. md_utils/directory_listing.py +0 -246
  199. md_utils/md_tests.py +0 -968
  200. md_utils/path_utils.py +0 -1044
  201. md_utils/process_utils.py +0 -157
  202. md_utils/sas_blob_utils.py +0 -509
  203. md_utils/split_locations_into_train_val.py +0 -228
  204. md_utils/string_utils.py +0 -92
  205. md_utils/url_utils.py +0 -323
  206. md_utils/write_html_image_list.py +0 -225
  207. md_visualization/__init__.py +0 -0
  208. md_visualization/plot_utils.py +0 -293
  209. md_visualization/render_images_with_thumbnails.py +0 -275
  210. md_visualization/visualization_utils.py +0 -1537
  211. md_visualization/visualize_db.py +0 -551
  212. md_visualization/visualize_detector_output.py +0 -406
  213. megadetector-5.0.9.dist-info/RECORD +0 -224
  214. megadetector-5.0.9.dist-info/top_level.txt +0 -8
  215. taxonomy_mapping/__init__.py +0 -0
  216. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +0 -491
  217. taxonomy_mapping/map_new_lila_datasets.py +0 -154
  218. taxonomy_mapping/prepare_lila_taxonomy_release.py +0 -142
  219. taxonomy_mapping/preview_lila_taxonomy.py +0 -591
  220. taxonomy_mapping/retrieve_sample_image.py +0 -71
  221. taxonomy_mapping/simple_image_download.py +0 -218
  222. taxonomy_mapping/species_lookup.py +0 -834
  223. taxonomy_mapping/taxonomy_csv_checker.py +0 -159
  224. taxonomy_mapping/taxonomy_graph.py +0 -346
  225. taxonomy_mapping/validate_lila_category_mappings.py +0 -83
  226. {megadetector-5.0.9.dist-info → megadetector-5.0.11.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: megadetector
3
- Version: 5.0.9
3
+ Version: 5.0.11
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>
@@ -28,7 +28,7 @@ Project-URL: Homepage, https://github.com/agentmorris/MegaDetector
28
28
  Project-URL: Documentation, https://megadetector.readthedocs.io
29
29
  Project-URL: Bug Reports, https://github.com/agentmorris/MegaDetector/issues
30
30
  Project-URL: Source, https://github.com/agentmorris/MegaDetector
31
- Keywords: camera traps,conservation,wildlife,ai
31
+ Keywords: camera traps,conservation,wildlife,ai,megadetector
32
32
  Classifier: Development Status :: 3 - Alpha
33
33
  Classifier: License :: OSI Approved :: MIT License
34
34
  Classifier: Programming Language :: Python :: 3
@@ -52,9 +52,9 @@ Requires-Dist: ultralytics-yolov5 ==0.1.1
52
52
 
53
53
  # MegaDetector
54
54
 
55
- This package is a pip-installable version of the support/inference code for [MegaDetector](https://github.com/agentmorris/MegaDetector), an object detection model that helps conservation biologists spend less time doing boring things with camera trap images. Complete documentation for this Python package is available at <megadetector.readthedocs.io>.
55
+ This package is a pip-installable version of the support/inference code for [MegaDetector](https://github.com/agentmorris/MegaDetector/?tab=readme-ov-file#megadetector), an object detection model that helps conservation biologists spend less time doing boring things with camera trap images. Complete documentation for this Python package is available at [megadetector.readthedocs.io](https://megadetector.readthedocs.io).
56
56
 
57
- If you aren't looking for the Python package specificaly, and you just want to learn more about what MegaDetector is all about, head over to the [MegaDetector repo](https://github.com/agentmorris/MegaDetector).
57
+ If you aren't looking for the Python package specificaly, and you just want to learn more about what MegaDetector is all about, head over to the [MegaDetector repo](https://github.com/agentmorris/MegaDetector/?tab=readme-ov-file#megadetector).
58
58
 
59
59
 
60
60
  ## Reasons you probably aren't looking for this package
@@ -69,7 +69,7 @@ If you are a computer-vision-y person looking to run or fine-tune MegaDetector p
69
69
 
70
70
  ## Reasons you might want to use this package
71
71
 
72
- If you want to programmatically interact with the postprocessing tools from the MegaDetector repo, or programmatically run MegaDetector in a way that produces [Timelapse](https://saul.cpsc.ucalgary.ca/timelapse)-friendly output (i.e., output in the standard [MegaDetector output format](https://github.com/agentmorris/MegaDetector/tree/main/api/batch_processing#megadetector-batch-output-format)), this package might be for you.
72
+ If you want to programmatically interact with the postprocessing tools from the MegaDetector repo, or programmatically run MegaDetector in a way that produces [Timelapse](https://saul.cpsc.ucalgary.ca/timelapse)-friendly output (i.e., output in the standard [MegaDetector output format](https://github.com/agentmorris/MegaDetector/tree/main/megadetector/api/batch_processing#megadetector-batch-output-format)), this package might be for you.
73
73
 
74
74
  Although even if that describes you, you <i>still</i> might be better off cloning the MegaDetector repo. Pip-installability requires that some dependencies be newer than what was available at the time MDv5 was trained, so results are <i>very slightly</i> different than results produced in the "official" environment. These differences <i>probably</i> don't matter much, but they have not been formally characterized.
75
75
 
@@ -83,7 +83,7 @@ MegaDetector model weights aren't downloaded at pip-install time, but they will
83
83
 
84
84
  ## Package reference
85
85
 
86
- See <megadetector.readthedocs.io>.
86
+ See [megadetector.readthedocs.io](https://megadetector.readthedocs.io).
87
87
 
88
88
 
89
89
  ## Examples of things you can do with this package
@@ -91,9 +91,9 @@ See <megadetector.readthedocs.io>.
91
91
  ### Run MegaDetector on one image and count the number of detections
92
92
 
93
93
  ```
94
- from md_utils import url_utils
95
- from md_visualization import visualization_utils as vis_utils
96
- from detection import run_detector
94
+ from megadetector.utils import url_utils
95
+ from megadetector.visualization import visualization_utils as vis_utils
96
+ from megadetector.detection import run_detector
97
97
 
98
98
  # This is the image at the bottom of this page, it has one animal in it
99
99
  image_url = 'https://github.com/agentmorris/MegaDetector/raw/main/images/orinoquia-thumb-web.jpg'
@@ -113,8 +113,9 @@ print('Found {} detections above threshold'.format(len(detections_above_threshol
113
113
  ### Run MegaDetector on a folder of images
114
114
 
115
115
  ```
116
- from detection.run_detector_batch import load_and_run_detector_batch,write_results_to_file
117
- from md_utils import path_utils
116
+ from megadetector.detection.run_detector_batch import \
117
+ load_and_run_detector_batch,write_results_to_file
118
+ from megadetector.utils import path_utils
118
119
  import os
119
120
 
120
121
  # Pick a folder to run MD on recursively, and an output file
@@ -0,0 +1,5 @@
1
+ megadetector-5.0.11.dist-info/LICENSE,sha256=RMa3qq-7Cyk7DdtqRj_bP1oInGFgjyHn9-PZ3PcrqIs,1100
2
+ megadetector-5.0.11.dist-info/METADATA,sha256=75C8pju-LB8QrMl4VEPOa-6y5q0_vlLN83xlYdd8IB8,7892
3
+ megadetector-5.0.11.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
4
+ megadetector-5.0.11.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
+ megadetector-5.0.11.dist-info/RECORD,,
api/__init__.py DELETED
File without changes
File without changes
File without changes
File without changes
@@ -1,439 +0,0 @@
1
- import io
2
- import json
3
- import math
4
- import os
5
- import sys
6
- from datetime import datetime
7
- from io import BytesIO
8
- from typing import Union
9
-
10
- from PIL import Image
11
- import numpy as np
12
- import requests
13
- import tensorflow as tf
14
- from azure.storage.blob import ContainerClient
15
-
16
- print('score.py, tensorflow version:', tf.__version__)
17
- print('score.py, tf.test.is_gpu_available:', tf.test.is_gpu_available())
18
-
19
- PRINT_EVERY = 500
20
-
21
-
22
- #%% Helper functions *copied* from ct_utils.py and md_visualization/visualization_utils.py
23
-
24
- IMAGE_ROTATIONS = {
25
- 3: 180,
26
- 6: 270,
27
- 8: 90
28
- }
29
-
30
- def truncate_float(x, precision=3):
31
- """
32
- Function for truncating a float scalar to the defined precision.
33
- For example: truncate_float(0.0003214884) --> 0.000321
34
- This function is primarily used to achieve a certain float representation
35
- before exporting to JSON
36
- Args:
37
- x (float) Scalar to truncate
38
- precision (int) The number of significant digits to preserve, should be
39
- greater or equal 1
40
- """
41
-
42
- assert precision > 0
43
-
44
- if np.isclose(x, 0):
45
- return 0
46
- else:
47
- # Determine the factor, which shifts the decimal point of x
48
- # just behind the last significant digit
49
- factor = math.pow(10, precision - 1 - math.floor(math.log10(abs(x))))
50
- # Shift decimal point by multiplicatipon with factor, flooring, and
51
- # division by factor
52
- return math.floor(x * factor)/factor
53
-
54
-
55
- def open_image(input_file: Union[str, BytesIO]) -> Image:
56
- """Opens an image in binary format using PIL.Image and converts to RGB mode.
57
-
58
- This operation is lazy; image will not be actually loaded until the first
59
- operation that needs to load it (for example, resizing), so file opening
60
- errors can show up later.
61
-
62
- Args:
63
- input_file: str or BytesIO, either a path to an image file (anything
64
- that PIL can open), or an image as a stream of bytes
65
-
66
- Returns:
67
- an PIL image object in RGB mode
68
- """
69
- if (isinstance(input_file, str)
70
- and input_file.startswith(('http://', 'https://'))):
71
- response = requests.get(input_file)
72
- image = Image.open(BytesIO(response.content))
73
- try:
74
- response = requests.get(input_file)
75
- image = Image.open(BytesIO(response.content))
76
- except Exception as e:
77
- print(f'Error opening image {input_file}: {e}')
78
- raise
79
- else:
80
- image = Image.open(input_file)
81
- if image.mode not in ('RGBA', 'RGB', 'L'):
82
- raise AttributeError(f'Image {input_file} uses unsupported mode {image.mode}')
83
- if image.mode == 'RGBA' or image.mode == 'L':
84
- # PIL.Image.convert() returns a converted copy of this image
85
- image = image.convert(mode='RGB')
86
-
87
- # alter orientation as needed according to EXIF tag 0x112 (274) for Orientation
88
- # https://gist.github.com/dangtrinhnt/a577ece4cbe5364aad28
89
- # https://www.media.mit.edu/pia/Research/deepview/exif.html
90
- try:
91
- exif = image._getexif()
92
- orientation: int = exif.get(274, None) # 274 is the key for the Orientation field
93
- if orientation is not None and orientation in IMAGE_ROTATIONS:
94
- image = image.rotate(IMAGE_ROTATIONS[orientation], expand=True) # returns a rotated copy
95
- except Exception:
96
- pass
97
-
98
- return image
99
-
100
-
101
- def load_image(input_file: Union[str, BytesIO]) -> Image.Image:
102
- """Loads the image at input_file as a PIL Image into memory.
103
- Image.open() used in open_image() is lazy and errors will occur downstream
104
- if not explicitly loaded.
105
- Args:
106
- input_file: str or BytesIO, either a path to an image file (anything
107
- that PIL can open), or an image as a stream of bytes
108
- Returns: PIL.Image.Image, in RGB mode
109
- """
110
- image = open_image(input_file)
111
- image.load()
112
- return image
113
-
114
-
115
- #%% TFDetector class, an unmodified *copy* of the class in detection/tf_detector.py,
116
- # so we do not have to import the packages required by run_detector.py
117
-
118
- class TFDetector:
119
- """
120
- A detector model loaded at the time of initialization. It is intended to be used with
121
- MegaDetector (TF). The inference batch size is set to 1; code needs to be modified
122
- to support larger batch sizes, including resizing appropriately.
123
- """
124
-
125
- # Number of decimal places to round to for confidence and bbox coordinates
126
- CONF_DIGITS = 3
127
- COORD_DIGITS = 4
128
-
129
- # MegaDetector was trained with batch size of 1, and the resizing function is a part
130
- # of the inference graph
131
- BATCH_SIZE = 1
132
-
133
- # An enumeration of failure reasons
134
- FAILURE_TF_INFER = 'Failure TF inference'
135
- FAILURE_IMAGE_OPEN = 'Failure image access'
136
-
137
- DEFAULT_RENDERING_CONFIDENCE_THRESHOLD = 0.85 # to render bounding boxes
138
- DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD = 0.1 # to include in the output json file
139
-
140
- DEFAULT_DETECTOR_LABEL_MAP = {
141
- '1': 'animal',
142
- '2': 'person',
143
- '3': 'vehicle' # available in megadetector v4+
144
- }
145
-
146
- NUM_DETECTOR_CATEGORIES = 4 # animal, person, group, vehicle - for color assignment
147
-
148
- def __init__(self, model_path):
149
- """Loads model from model_path and starts a tf.Session with this graph. Obtains
150
- input and output tensor handles."""
151
- detection_graph = TFDetector.__load_model(model_path)
152
- self.tf_session = tf.Session(graph=detection_graph)
153
-
154
- self.image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')
155
- self.box_tensor = detection_graph.get_tensor_by_name('detection_boxes:0')
156
- self.score_tensor = detection_graph.get_tensor_by_name('detection_scores:0')
157
- self.class_tensor = detection_graph.get_tensor_by_name('detection_classes:0')
158
-
159
- @staticmethod
160
- def round_and_make_float(d, precision=4):
161
- return truncate_float(float(d), precision=precision)
162
-
163
- @staticmethod
164
- def __convert_coords(tf_coords):
165
- """Converts coordinates from the model's output format [y1, x1, y2, x2] to the
166
- format used by our API and MegaDB: [x1, y1, width, height]. All coordinates
167
- (including model outputs) are normalized in the range [0, 1].
168
- Args:
169
- tf_coords: np.array of predicted bounding box coordinates from the TF detector,
170
- has format [y1, x1, y2, x2]
171
- Returns: list of Python float, predicted bounding box coordinates [x1, y1, width, height]
172
- """
173
- # change from [y1, x1, y2, x2] to [x1, y1, width, height]
174
- width = tf_coords[3] - tf_coords[1]
175
- height = tf_coords[2] - tf_coords[0]
176
-
177
- new = [tf_coords[1], tf_coords[0], width, height] # must be a list instead of np.array
178
-
179
- # convert numpy floats to Python floats
180
- for i, d in enumerate(new):
181
- new[i] = TFDetector.round_and_make_float(d, precision=TFDetector.COORD_DIGITS)
182
- return new
183
-
184
- @staticmethod
185
- def convert_to_tf_coords(array):
186
- """From [x1, y1, width, height] to [y1, x1, y2, x2], where x1 is x_min, x2 is x_max
187
- This is an extraneous step as the model outputs [y1, x1, y2, x2] but were converted to the API
188
- output format - only to keep the interface of the sync API.
189
- """
190
- x1 = array[0]
191
- y1 = array[1]
192
- width = array[2]
193
- height = array[3]
194
- x2 = x1 + width
195
- y2 = y1 + height
196
- return [y1, x1, y2, x2]
197
-
198
- @staticmethod
199
- def __load_model(model_path):
200
- """Loads a detection model (i.e., create a graph) from a .pb file.
201
- Args:
202
- model_path: .pb file of the model.
203
- Returns: the loaded graph.
204
- """
205
- print('TFDetector: Loading graph...')
206
- detection_graph = tf.Graph()
207
- with detection_graph.as_default():
208
- od_graph_def = tf.GraphDef()
209
- with tf.gfile.GFile(model_path, 'rb') as fid:
210
- serialized_graph = fid.read()
211
- od_graph_def.ParseFromString(serialized_graph)
212
- tf.import_graph_def(od_graph_def, name='')
213
- print('TFDetector: Detection graph loaded.')
214
-
215
- return detection_graph
216
-
217
- def _generate_detections_one_image(self, image):
218
- np_im = np.asarray(image, np.uint8)
219
- im_w_batch_dim = np.expand_dims(np_im, axis=0)
220
-
221
- # need to change the above line to the following if supporting a batch size > 1 and resizing to the same size
222
- # np_images = [np.asarray(image, np.uint8) for image in images]
223
- # images_stacked = np.stack(np_images, axis=0) if len(images) > 1 else np.expand_dims(np_images[0], axis=0)
224
-
225
- # performs inference
226
- (box_tensor_out, score_tensor_out, class_tensor_out) = self.tf_session.run(
227
- [self.box_tensor, self.score_tensor, self.class_tensor],
228
- feed_dict={self.image_tensor: im_w_batch_dim})
229
-
230
- return box_tensor_out, score_tensor_out, class_tensor_out
231
-
232
- def generate_detections_one_image(self, image, image_id,
233
- detection_threshold=DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD):
234
- """Apply the detector to an image.
235
- Args:
236
- image: the PIL Image object
237
- image_id: a path to identify the image; will be in the "file" field of the output object
238
- detection_threshold: confidence above which to include the detection proposal
239
- Returns:
240
- A dict with the following fields, see the 'images' key in https://github.com/agentmorris/MegaDetector/tree/master/api/batch_processing#batch-processing-api-output-format
241
- - 'file' (always present)
242
- - 'max_detection_conf'
243
- - 'detections', which is a list of detection objects containing keys 'category', 'conf' and 'bbox'
244
- - 'failure'
245
- """
246
- result = {
247
- 'file': image_id
248
- }
249
- try:
250
- b_box, b_score, b_class = self._generate_detections_one_image(image)
251
-
252
- # our batch size is 1; need to loop the batch dim if supporting batch size > 1
253
- boxes, scores, classes = b_box[0], b_score[0], b_class[0]
254
-
255
- detections_cur_image = [] # will be empty for an image with no confident detections
256
- max_detection_conf = 0.0
257
- for b, s, c in zip(boxes, scores, classes):
258
- if s > detection_threshold:
259
- detection_entry = {
260
- 'category': str(int(c)), # use string type for the numerical class label, not int
261
- 'conf': truncate_float(float(s), # cast to float for json serialization
262
- precision=TFDetector.CONF_DIGITS),
263
- 'bbox': TFDetector.__convert_coords(b)
264
- }
265
- detections_cur_image.append(detection_entry)
266
- if s > max_detection_conf:
267
- max_detection_conf = s
268
-
269
- result['max_detection_conf'] = truncate_float(float(max_detection_conf),
270
- precision=TFDetector.CONF_DIGITS)
271
- result['detections'] = detections_cur_image
272
-
273
- except Exception as e:
274
- result['failure'] = TFDetector.FAILURE_TF_INFER
275
- print('TFDetector: image {} failed during inference: {}'.format(image_id, str(e)))
276
-
277
- return result
278
-
279
-
280
- #%% Scoring script
281
-
282
- class BatchScorer:
283
- """
284
- Coordinates scoring images in this Task.
285
-
286
- 1. have a synchronized queue that download tasks enqueue and scoring function dequeues - but need to be able to
287
- limit the size of the queue. We do not want to write the image to disk and then load it in the scoring func.
288
- """
289
- def __init__(self, **kwargs):
290
- print('score.py BatchScorer, __init__()')
291
-
292
- detector_path = kwargs.get('detector_path')
293
- self.detector = TFDetector(detector_path)
294
-
295
- self.use_url = kwargs.get('use_url')
296
- if not self.use_url:
297
- input_container_sas = kwargs.get('input_container_sas')
298
- self.input_container_client = ContainerClient.from_container_url(input_container_sas)
299
-
300
- self.detection_threshold = kwargs.get('detection_threshold')
301
-
302
- self.image_ids_to_score = kwargs.get('image_ids_to_score')
303
-
304
- # determine if there is metadata attached to each image_id
305
- self.metadata_available = True if isinstance(self.image_ids_to_score[0], list) else False
306
-
307
- def _download_image(self, image_file) -> Image:
308
- """
309
- Args:
310
- image_file: Public URL if use_url, else the full path from container root
311
-
312
- Returns:
313
- PIL image loaded
314
- """
315
- if not self.use_url:
316
- downloader = self.input_container_client.download_blob(image_file)
317
- image_file = io.BytesIO()
318
- blob_props = downloader.download_to_stream(image_file)
319
-
320
- image = open_image(image_file)
321
- return image
322
-
323
- def score_images(self) -> list:
324
- detections = []
325
-
326
- for i in self.image_ids_to_score:
327
-
328
- if self.metadata_available:
329
- image_id = i[0]
330
- image_metadata = i[1]
331
- else:
332
- image_id = i
333
-
334
- try:
335
- image = self._download_image(image_id)
336
- except Exception as e:
337
- print(f'score.py BatchScorer, score_images, download_image exception: {e}')
338
- result = {
339
- 'file': image_id,
340
- 'failure': TFDetector.FAILURE_IMAGE_OPEN
341
- }
342
- else:
343
- result = self.detector.generate_detections_one_image(
344
- image, image_id, detection_threshold=self.detection_threshold)
345
-
346
- if self.metadata_available:
347
- result['meta'] = image_metadata
348
-
349
- detections.append(result)
350
- if len(detections) % PRINT_EVERY == 0:
351
- print(f'scored {len(detections)} images')
352
-
353
- return detections
354
-
355
-
356
- def main():
357
- print('score.py, main()')
358
-
359
- # information to determine input and output locations
360
- api_instance_name = os.environ['API_INSTANCE_NAME']
361
- job_id = os.environ['AZ_BATCH_JOB_ID']
362
- task_id = os.environ['AZ_BATCH_TASK_ID']
363
- mount_point = os.environ['AZ_BATCH_NODE_MOUNTS_DIR']
364
-
365
- # other parameters for the task
366
- begin_index = int(os.environ['TASK_BEGIN_INDEX'])
367
- end_index = int(os.environ['TASK_END_INDEX'])
368
-
369
- input_container_sas = os.environ.get('JOB_CONTAINER_SAS', None) # could be None if use_url
370
- use_url = os.environ.get('JOB_USE_URL', None)
371
-
372
- if use_url and use_url.lower() == 'true': # bool of any non-empty string is True
373
- use_url = True
374
- else:
375
- use_url = False
376
-
377
- detection_threshold = float(os.environ['DETECTION_CONF_THRESHOLD'])
378
-
379
- print(f'score.py, main(), api_instance_name: {api_instance_name}, job_id: {job_id}, task_id: {task_id}, '
380
- f'mount_point: {mount_point}, begin_index: {begin_index}, end_index: {end_index}, '
381
- f'input_container_sas: {input_container_sas}, use_url (parsed): {use_url}'
382
- f'detection_threshold: {detection_threshold}')
383
-
384
- job_folder_mounted = os.path.join(mount_point, 'batch-api', f'api_{api_instance_name}', f'job_{job_id}')
385
- task_out_dir = os.path.join(job_folder_mounted, 'task_outputs')
386
- os.makedirs(task_out_dir, exist_ok=True)
387
- task_output_path = os.path.join(task_out_dir, f'job_{job_id}_task_{task_id}.json')
388
-
389
- # test that we can write to output path; also in case there is no image to process
390
- with open(task_output_path, 'w') as f:
391
- json.dump([], f)
392
-
393
- # list images to process
394
- list_images_path = os.path.join(job_folder_mounted, f'{job_id}_images.json')
395
- with open(list_images_path) as f:
396
- list_images = json.load(f)
397
- print(f'score.py, main(), length of list_images: {len(list_images)}')
398
-
399
- if (not isinstance(list_images, list)) or len(list_images) == 0:
400
- print('score.py, main(), zero images in specified overall list, exiting...')
401
- sys.exit(0)
402
-
403
- # items in this list can be strings or [image_id, metadata]
404
- list_images = list_images[begin_index: end_index]
405
- if len(list_images) == 0:
406
- print('score.py, main(), zero images in the shard, exiting')
407
- sys.exit(0)
408
-
409
- print(f'score.py, main(), processing {len(list_images)} images in this Task')
410
-
411
- # model path
412
- # Path to .pb TensorFlow detector model file, relative to the
413
- # models/megadetector_copies folder in mounted container
414
- detector_model_rel_path = os.environ['DETECTOR_REL_PATH']
415
- detector_path = os.path.join(mount_point, 'models', 'megadetector_copies', detector_model_rel_path)
416
- assert os.path.exists(detector_path), f'detector is not found at the specified path: {detector_path}'
417
-
418
- # score the images
419
- scorer = BatchScorer(
420
- detector_path=detector_path,
421
- use_url=use_url,
422
- input_container_sas=input_container_sas,
423
- detection_threshold=detection_threshold,
424
- image_ids_to_score=list_images
425
- )
426
-
427
- try:
428
- tick = datetime.now()
429
- detections = scorer.score_images()
430
- duration = datetime.now() - tick
431
- print(f'score.py, main(), score_images() duration: {duration}')
432
- except Exception as e:
433
- raise RuntimeError(f'score.py, main(), exception in score_images(): {e}')
434
-
435
- with open(task_output_path, 'w', encoding='utf-8') as f:
436
- json.dump(detections, f, ensure_ascii=False)
437
-
438
- if __name__ == '__main__':
439
- main()