megadetector 5.0.10__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.10.dist-info → megadetector-5.0.11.dist-info}/LICENSE +0 -0
  2. {megadetector-5.0.10.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.10.dist-info/RECORD +0 -224
  214. megadetector-5.0.10.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.10.dist-info → megadetector-5.0.11.dist-info}/WHEEL +0 -0
detection/run_detector.py DELETED
@@ -1,779 +0,0 @@
1
- """
2
-
3
- run_detector.py
4
-
5
- Module to run an animal detection model on images. The main function in this script also renders
6
- the predicted bounding boxes on images and saves the resulting images (with bounding boxes).
7
-
8
- **This script is not a good way to process lots of images**. It does not produce a useful
9
- output format, and it does not facilitate checkpointing the results so if it crashes you
10
- would have to start from scratch. **If you want to run a detector on lots of images, you should
11
- check out run_detector_batch.py**.
12
-
13
- That said, this script (run_detector.py) is a good way to test our detector on a handful of images
14
- and get super-satisfying, graphical results.
15
-
16
- If you would like to *not* use the GPU on the machine, set the environment
17
- variable CUDA_VISIBLE_DEVICES to "-1".
18
-
19
- This script will only consider detections with > 0.005 confidence at all times.
20
- The threshold you provide is only for rendering the results. If you need to
21
- see lower-confidence detections, you can change DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD.
22
-
23
- """
24
-
25
- #%% Constants, imports, environment
26
-
27
- import argparse
28
- import os
29
- import statistics
30
- import sys
31
- import time
32
- import warnings
33
-
34
- import humanfriendly
35
- from tqdm import tqdm
36
-
37
- import md_utils.path_utils as path_utils
38
- import md_visualization.visualization_utils as vis_utils
39
-
40
- # ignoring all "PIL cannot read EXIF metainfo for the images" warnings
41
- warnings.filterwarnings('ignore', '(Possibly )?corrupt EXIF data', UserWarning)
42
-
43
- # Metadata Warning, tag 256 had too many entries: 42, expected 1
44
- warnings.filterwarnings('ignore', 'Metadata warning', UserWarning)
45
-
46
- # Numpy FutureWarnings from tensorflow import
47
- warnings.filterwarnings('ignore', category=FutureWarning)
48
-
49
- # Useful hack to force CPU inference
50
- # os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
51
-
52
-
53
- # An enumeration of failure reasons
54
- FAILURE_INFER = 'Failure inference'
55
- FAILURE_IMAGE_OPEN = 'Failure image access'
56
-
57
- # Number of decimal places to round to for confidence and bbox coordinates
58
- CONF_DIGITS = 3
59
- COORD_DIGITS = 4
60
-
61
- # Label mapping for MegaDetector
62
- DEFAULT_DETECTOR_LABEL_MAP = {
63
- '1': 'animal',
64
- '2': 'person',
65
- '3': 'vehicle' # available in megadetector v4+
66
- }
67
-
68
- # Should we allow classes that don't look anything like the MegaDetector classes?
69
- #
70
- # By default, we error if we see unfamiliar classes.
71
- #
72
- # TODO: the use of a global variable to manage this was fine when this was really
73
- # experimental, but this is really sloppy now that we actually use this code for
74
- # models other than MegaDetector.
75
- USE_MODEL_NATIVE_CLASSES = False
76
-
77
- # Each version of the detector is associated with some "typical" values
78
- # that are included in output files, so that downstream applications can
79
- # use them as defaults.
80
- DETECTOR_METADATA = {
81
- 'v2.0.0':
82
- {'megadetector_version':'v2.0.0',
83
- 'typical_detection_threshold':0.8,
84
- 'conservative_detection_threshold':0.3},
85
- 'v3.0.0':
86
- {'megadetector_version':'v3.0.0',
87
- 'typical_detection_threshold':0.8,
88
- 'conservative_detection_threshold':0.3},
89
- 'v4.1.0':
90
- {'megadetector_version':'v4.1.0',
91
- 'typical_detection_threshold':0.8,
92
- 'conservative_detection_threshold':0.3},
93
- 'v5a.0.0':
94
- {'megadetector_version':'v5a.0.0',
95
- 'typical_detection_threshold':0.2,
96
- 'conservative_detection_threshold':0.05},
97
- 'v5b.0.0':
98
- {'megadetector_version':'v5b.0.0',
99
- 'typical_detection_threshold':0.2,
100
- 'conservative_detection_threshold':0.05}
101
- }
102
-
103
- DEFAULT_RENDERING_CONFIDENCE_THRESHOLD = DETECTOR_METADATA['v5b.0.0']['typical_detection_threshold']
104
- DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD = 0.005
105
-
106
- DEFAULT_BOX_THICKNESS = 4
107
- DEFAULT_BOX_EXPANSION = 0
108
- DEFAULT_LABEL_FONT_SIZE = 16
109
- DETECTION_FILENAME_INSERT = '_detections'
110
-
111
- # The model filenames "MDV5A", "MDV5B", and "MDV4" are special; they will trigger an
112
- # automatic model download to the system temp folder, or they will use the paths specified in the
113
- # $MDV4, $MDV5A, or $MDV5B environment variables if they exist.
114
- downloadable_models = {
115
- 'MDV4':'https://github.com/agentmorris/MegaDetector/releases/download/v4.1/md_v4.1.0.pb',
116
- 'MDV5A':'https://github.com/agentmorris/MegaDetector/releases/download/v5.0/md_v5a.0.0.pt',
117
- 'MDV5B':'https://github.com/agentmorris/MegaDetector/releases/download/v5.0/md_v5b.0.0.pt'
118
- }
119
-
120
- model_string_to_model_version = {
121
- 'v2':'v2.0.0',
122
- 'v3':'v3.0.0',
123
- 'v4.1':'v4.1.0',
124
- 'v5a.0.0':'v5a.0.0',
125
- 'v5b.0.0':'v5b.0.0',
126
- 'mdv5a':'v5a.0.0',
127
- 'mdv5b':'v5b.0.0',
128
- 'mdv4':'v4.1.0',
129
- 'mdv3':'v3.0.0'
130
- }
131
-
132
- # Approximate inference speeds (in images per second) for MDv5 based on
133
- # benchmarks, only used for reporting very coarse expectations about inference time.
134
- device_token_to_mdv5_inference_speed = {
135
- '4090':17.6,
136
- '3090':11.4,
137
- '3080':9.5,
138
- '3050':4.2,
139
- 'P2000':2.1,
140
- # These are written this way because they're MDv4 benchmarks, and MDv5
141
- # is around 3.5x faster than MDv4.
142
- 'V100':2.79*3.5,
143
- '2080':2.3*3.5,
144
- '2060':1.6*3.5
145
- }
146
-
147
-
148
- #%% Utility functions
149
-
150
- def convert_to_tf_coords(array):
151
- """
152
- Converts a bounding box from [x1, y1, width, height] to [y1, x1, y2, x2]. This
153
- is mostly not helpful, this function only exists to maintain backwards compatibility
154
- in the synchronous API, which possibly zero people in the world are using.
155
-
156
- Args:
157
- array (list): a bounding box in [x,y,w,h] format
158
-
159
- Returns:
160
- list: a bounding box in [y1,x1,y2,x2] format
161
- """
162
-
163
- x1 = array[0]
164
- y1 = array[1]
165
- width = array[2]
166
- height = array[3]
167
- x2 = x1 + width
168
- y2 = y1 + height
169
-
170
- return [y1, x1, y2, x2]
171
-
172
-
173
- def get_detector_metadata_from_version_string(detector_version):
174
- """
175
- Given a MegaDetector version string (e.g. "v4.1.0"), returns the metadata for
176
- the model. Used for writing standard defaults to batch output files.
177
-
178
- Args:
179
- detector_version (str): a detection version string, e.g. "v4.1.0", which you
180
- can extract from a filename using get_detector_version_from_filename()
181
-
182
- Returns:
183
- dict: metadata for this model, suitable for writing to a MD output file
184
- """
185
-
186
- if detector_version not in DETECTOR_METADATA:
187
- print('Warning: no metadata for unknown detector version {}'.format(detector_version))
188
- default_detector_metadata = {
189
- 'megadetector_version':'unknown',
190
- 'typical_detection_threshold':0.5,
191
- 'conservative_detection_threshold':0.25
192
- }
193
- return default_detector_metadata
194
- else:
195
- return DETECTOR_METADATA[detector_version]
196
-
197
-
198
- def get_detector_version_from_filename(detector_filename):
199
- r"""
200
- Gets the version number component of the detector from the model filename.
201
-
202
- [detector_filename] will almost always end with one of the following:
203
-
204
- * megadetector_v2.pb
205
- * megadetector_v3.pb
206
- * megadetector_v4.1 (not produed by run_detector_batch.py, only found in output files from the deprecated Azure Batch API)
207
- * md_v4.1.0.pb
208
- * md_v5a.0.0.pt
209
- * md_v5b.0.0.pt
210
-
211
- This function identifies the version number as "v2.0.0", "v3.0.0", "v4.1.0",
212
- "v4.1.0", "v5a.0.0", and "v5b.0.0", respectively.
213
-
214
- Args:
215
- detector_filename (str): model filename, e.g. c:/x/z/md_v5a.0.0.pt
216
-
217
- Returns:
218
- str: a detector version string, e.g. "v5a.0.0", or "multiple" if I'm confused
219
- """
220
-
221
- fn = os.path.basename(detector_filename).lower()
222
- matches = []
223
- for s in model_string_to_model_version.keys():
224
- if s in fn:
225
- matches.append(s)
226
- if len(matches) == 0:
227
- print('Warning: could not determine MegaDetector version for model file {}'.format(detector_filename))
228
- return 'unknown'
229
- elif len(matches) > 1:
230
- print('Warning: multiple MegaDetector versions for model file {}'.format(detector_filename))
231
- return 'multiple'
232
- else:
233
- return model_string_to_model_version[matches[0]]
234
-
235
-
236
- def estimate_md_images_per_second(model_file, device_name=None):
237
- r"""
238
- Estimates how fast MegaDetector will run, based on benchmarks. Defaults to querying
239
- the current device. Returns None if no data is available for the current card/model.
240
- Estimates only available for a small handful of GPUs. Uses an absurdly simple lookup
241
- approach, e.g. if the string "4090" appears in the device name, congratulations,
242
- you have an RTX 4090.
243
-
244
- Args:
245
- model_file (str): model filename, e.g. c:/x/z/md_v5a.0.0.pt
246
- device_name (str, optional): device name, e.g. blah-blah-4090-blah-blah
247
-
248
- Returns:
249
- float: the approximate number of images this model version can process on this
250
- device per second
251
- """
252
-
253
- if device_name is None:
254
- try:
255
- import torch
256
- device_name = torch.cuda.get_device_name()
257
- except Exception as e:
258
- print('Error querying device name: {}'.format(e))
259
- return None
260
-
261
- model_file = model_file.lower().strip()
262
- if model_file in model_string_to_model_version.values():
263
- model_version = model_file
264
- else:
265
- model_version = get_detector_version_from_filename(model_file)
266
- if model_version not in model_string_to_model_version.values():
267
- print('Error determining model version for model file {}'.format(model_file))
268
- return None
269
-
270
- mdv5_inference_speed = None
271
- for device_token in device_token_to_mdv5_inference_speed.keys():
272
- if device_token in device_name:
273
- mdv5_inference_speed = device_token_to_mdv5_inference_speed[device_token]
274
- break
275
-
276
- if mdv5_inference_speed is None:
277
- print('No speed estimate available for {}'.format(device_name))
278
-
279
- if 'v5' in model_version:
280
- return mdv5_inference_speed
281
- elif 'v2' in model_version or 'v3' in model_version or 'v4' in model_version:
282
- return mdv5_inference_speed / 3.5
283
- else:
284
- print('Could not estimate inference speed for model file {}'.format(model_file))
285
- return None
286
-
287
-
288
- def get_typical_confidence_threshold_from_results(results):
289
- """
290
- Given the .json data loaded from a MD results file, returns a typical confidence
291
- threshold based on the detector version.
292
-
293
- Args:
294
- results (dict): a dict of MD results, as it would be loaded from a MD results .json file
295
-
296
- Returns:
297
- float: a sensible default threshold for this model
298
- """
299
-
300
- if 'detector_metadata' in results['info'] and \
301
- 'typical_detection_threshold' in results['info']['detector_metadata']:
302
- default_threshold = results['info']['detector_metadata']['typical_detection_threshold']
303
- elif ('detector' not in results['info']) or (results['info']['detector'] is None):
304
- print('Warning: detector version not available in results file, using MDv5 defaults')
305
- detector_metadata = get_detector_metadata_from_version_string('v5a.0.0')
306
- default_threshold = detector_metadata['typical_detection_threshold']
307
- else:
308
- print('Warning: detector metadata not available in results file, inferring from MD version')
309
- detector_filename = results['info']['detector']
310
- detector_version = get_detector_version_from_filename(detector_filename)
311
- detector_metadata = get_detector_metadata_from_version_string(detector_version)
312
- default_threshold = detector_metadata['typical_detection_threshold']
313
-
314
- return default_threshold
315
-
316
-
317
- def is_gpu_available(model_file):
318
- r"""
319
- Determines whether a GPU is available, importing PyTorch or TF depending on the extension
320
- of model_file. Does not actually load model_file, just uses that to determine how to check
321
- for GPU availability (PT vs. TF).
322
-
323
- Args:
324
- model_file (str): model filename, e.g. c:/x/z/md_v5a.0.0.pt
325
-
326
- Returns:
327
- bool: whether a GPU is available
328
- """
329
-
330
- if model_file.endswith('.pb'):
331
- import tensorflow.compat.v1 as tf
332
- gpu_available = tf.test.is_gpu_available()
333
- print('TensorFlow version:', tf.__version__)
334
- print('tf.test.is_gpu_available:', gpu_available)
335
- return gpu_available
336
- elif model_file.endswith('.pt'):
337
- import torch
338
- gpu_available = torch.cuda.is_available()
339
- print('PyTorch reports {} available CUDA devices'.format(torch.cuda.device_count()))
340
- if not gpu_available:
341
- try:
342
- # mps backend only available in torch >= 1.12.0
343
- if torch.backends.mps.is_built and torch.backends.mps.is_available():
344
- gpu_available = True
345
- print('PyTorch reports Metal Performance Shaders are available')
346
- except AttributeError:
347
- pass
348
- return gpu_available
349
- else:
350
- raise ValueError('Unrecognized model file extension for model {}'.format(model_file))
351
-
352
-
353
- def load_detector(model_file, force_cpu=False):
354
- r"""
355
- Loads a TF or PT detector, depending on the extension of model_file.
356
-
357
- Args:
358
- model_file (str): model filename, e.g. c:/x/z/md_v5a.0.0.pt
359
-
360
- Returns:
361
- object: loaded detector object
362
- """
363
-
364
- # Possibly automatically download the model
365
- model_file = try_download_known_detector(model_file)
366
-
367
- start_time = time.time()
368
- if model_file.endswith('.pb'):
369
- from detection.tf_detector import TFDetector
370
- if force_cpu:
371
- raise ValueError('force_cpu is not currently supported for TF detectors, ' + \
372
- 'use CUDA_VISIBLE_DEVICES=-1 instead')
373
- detector = TFDetector(model_file)
374
- elif model_file.endswith('.pt'):
375
- from detection.pytorch_detector import PTDetector
376
- detector = PTDetector(model_file, force_cpu, USE_MODEL_NATIVE_CLASSES)
377
- else:
378
- raise ValueError('Unrecognized model format: {}'.format(model_file))
379
- elapsed = time.time() - start_time
380
- print('Loaded model in {}'.format(humanfriendly.format_timespan(elapsed)))
381
-
382
- return detector
383
-
384
-
385
- #%% Main function
386
-
387
- def load_and_run_detector(model_file,
388
- image_file_names,
389
- output_dir,
390
- render_confidence_threshold=DEFAULT_RENDERING_CONFIDENCE_THRESHOLD,
391
- crop_images=False,
392
- box_thickness=DEFAULT_BOX_THICKNESS,
393
- box_expansion=DEFAULT_BOX_EXPANSION,
394
- image_size=None,
395
- label_font_size=DEFAULT_LABEL_FONT_SIZE
396
- ):
397
- r"""
398
- Loads and runs a detector on target images, and visualizes the results.
399
-
400
- Args:
401
- model_file (str): model filename, e.g. c:/x/z/md_v5a.0.0.pt, or a known model
402
- string, e.g. "MDV5A"
403
- image_file_names (list): list of absolute paths to process
404
- output_dir (str): folder to write visualized images to
405
- render_confidence_threshold (float, optional): only render boxes for detections
406
- above this threshold
407
- crop_images (bool, optional): whether to crop detected objects to individual images
408
- (default is to render images with boxes, rather than cropping)
409
- box_thickness (float, optional): thickness in pixels for box rendering
410
- box_expansion (float, optional): box expansion in pixels
411
- image_size (tuple, optional): image size to use for inference, only mess with this
412
- if (a) you're using a model other than MegaDetector or (b) you know what you're
413
- doing
414
- label_font_size (float, optional): font size to use for displaying class names
415
- and confidence values in the rendered images
416
- """
417
-
418
- if len(image_file_names) == 0:
419
- print('Warning: no files available')
420
- return
421
-
422
- # Possibly automatically download the model
423
- model_file = try_download_known_detector(model_file)
424
-
425
- print('GPU available: {}'.format(is_gpu_available(model_file)))
426
-
427
- detector = load_detector(model_file)
428
-
429
- detection_results = []
430
- time_load = []
431
- time_infer = []
432
-
433
- # Dictionary mapping output file names to a collision-avoidance count.
434
- #
435
- # Since we'll be writing a bunch of files to the same folder, we rename
436
- # as necessary to avoid collisions.
437
- output_filename_collision_counts = {}
438
-
439
- def input_file_to_detection_file(fn, crop_index=-1):
440
- """
441
- Creates unique file names for output files.
442
-
443
- This function does 3 things:
444
- 1) If the --crop flag is used, then each input image may produce several output
445
- crops. For example, if foo.jpg has 3 detections, then this function should
446
- get called 3 times, with crop_index taking on 0, 1, then 2. Each time, this
447
- function appends crop_index to the filename, resulting in
448
- foo_crop00_detections.jpg
449
- foo_crop01_detections.jpg
450
- foo_crop02_detections.jpg
451
-
452
- 2) If the --recursive flag is used, then the same file (base)name may appear
453
- multiple times. However, we output into a single flat folder. To avoid
454
- filename collisions, we prepend an integer prefix to duplicate filenames:
455
- foo_crop00_detections.jpg
456
- 0000_foo_crop00_detections.jpg
457
- 0001_foo_crop00_detections.jpg
458
-
459
- 3) Prepends the output directory:
460
- out_dir/foo_crop00_detections.jpg
461
-
462
- Args:
463
- fn: str, filename
464
- crop_index: int, crop number
465
-
466
- Returns: output file path
467
- """
468
-
469
- fn = os.path.basename(fn).lower()
470
- name, ext = os.path.splitext(fn)
471
- if crop_index >= 0:
472
- name += '_crop{:0>2d}'.format(crop_index)
473
- fn = '{}{}{}'.format(name, DETECTION_FILENAME_INSERT, '.jpg')
474
- if fn in output_filename_collision_counts:
475
- n_collisions = output_filename_collision_counts[fn]
476
- fn = '{:0>4d}'.format(n_collisions) + '_' + fn
477
- output_filename_collision_counts[fn] += 1
478
- else:
479
- output_filename_collision_counts[fn] = 0
480
- fn = os.path.join(output_dir, fn)
481
- return fn
482
-
483
- # ...def input_file_to_detection_file()
484
-
485
- for im_file in tqdm(image_file_names):
486
-
487
- try:
488
- start_time = time.time()
489
-
490
- image = vis_utils.load_image(im_file)
491
-
492
- elapsed = time.time() - start_time
493
- time_load.append(elapsed)
494
-
495
- except Exception as e:
496
- print('Image {} cannot be loaded. Exception: {}'.format(im_file, e))
497
- result = {
498
- 'file': im_file,
499
- 'failure': FAILURE_IMAGE_OPEN
500
- }
501
- detection_results.append(result)
502
- continue
503
-
504
- try:
505
- start_time = time.time()
506
-
507
- result = detector.generate_detections_one_image(image, im_file,
508
- detection_threshold=DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD,
509
- image_size=image_size)
510
- detection_results.append(result)
511
-
512
- elapsed = time.time() - start_time
513
- time_infer.append(elapsed)
514
-
515
- except Exception as e:
516
- print('An error occurred while running the detector on image {}. Exception: {}'.format(im_file, e))
517
- continue
518
-
519
- try:
520
- if crop_images:
521
-
522
- images_cropped = vis_utils.crop_image(result['detections'], image,
523
- confidence_threshold=render_confidence_threshold,
524
- expansion=box_expansion)
525
-
526
- for i_crop, cropped_image in enumerate(images_cropped):
527
- output_full_path = input_file_to_detection_file(im_file, i_crop)
528
- cropped_image.save(output_full_path)
529
-
530
- else:
531
-
532
- # Image is modified in place
533
- vis_utils.render_detection_bounding_boxes(result['detections'], image,
534
- label_map=DEFAULT_DETECTOR_LABEL_MAP,
535
- confidence_threshold=render_confidence_threshold,
536
- thickness=box_thickness, expansion=box_expansion,
537
- label_font_size=label_font_size)
538
- output_full_path = input_file_to_detection_file(im_file)
539
- image.save(output_full_path)
540
-
541
- except Exception as e:
542
- print('Visualizing results on the image {} failed. Exception: {}'.format(im_file, e))
543
- continue
544
-
545
- # ...for each image
546
-
547
- ave_time_load = statistics.mean(time_load)
548
- ave_time_infer = statistics.mean(time_infer)
549
- if len(time_load) > 1 and len(time_infer) > 1:
550
- std_dev_time_load = humanfriendly.format_timespan(statistics.stdev(time_load))
551
- std_dev_time_infer = humanfriendly.format_timespan(statistics.stdev(time_infer))
552
- else:
553
- std_dev_time_load = 'not available'
554
- std_dev_time_infer = 'not available'
555
- print('On average, for each image,')
556
- print('- loading took {}, std dev is {}'.format(humanfriendly.format_timespan(ave_time_load),
557
- std_dev_time_load))
558
- print('- inference took {}, std dev is {}'.format(humanfriendly.format_timespan(ave_time_infer),
559
- std_dev_time_infer))
560
-
561
- # ...def load_and_run_detector()
562
-
563
-
564
- def download_model(model_name,force_download=False):
565
- """
566
- Downloads one of the known models to local temp space if it hasn't already been downloaded.
567
-
568
- Args:
569
- model_name (str): a known model string, e.g. "MDV5A"
570
- force_download (bool, optional): whether download the model even if the local target
571
- file already exists
572
- """
573
-
574
- import tempfile
575
- from md_utils.url_utils import download_url
576
- model_tempdir = os.path.join(tempfile.gettempdir(), 'megadetector_models')
577
- os.makedirs(model_tempdir,exist_ok=True)
578
-
579
- # This is a lazy fix to an issue... if multiple users run this script, the
580
- # "megadetector_models" folder is owned by the first person who creates it, and others
581
- # can't write to it. I could create uniquely-named folders, but I philosophically prefer
582
- # to put all the individual UUID-named folders within a larger folder, so as to be a
583
- # good tempdir citizen. So, the lazy fix is to make this world-writable.
584
- try:
585
- os.chmod(model_tempdir,0o777)
586
- except Exception:
587
- pass
588
- if model_name not in downloadable_models:
589
- print('Unrecognized downloadable model {}'.format(model_name))
590
- return None
591
- url = downloadable_models[model_name]
592
- destination_filename = os.path.join(model_tempdir,url.split('/')[-1])
593
- local_file = download_url(url, destination_filename=destination_filename, progress_updater=None,
594
- force_download=force_download, verbose=True)
595
- return local_file
596
-
597
-
598
- def try_download_known_detector(detector_file):
599
- """
600
- Checks whether detector_file is really the name of a known model, in which case we will
601
- either read the actual filename from the corresponding environment variable or download
602
- (if necessary) to local temp space. Otherwise just returns the input string.
603
-
604
- Args:
605
- detector_file (str): a known model string (e.g. "MDV5A"), or any other string (in which
606
- case this function is a no-op)
607
-
608
- Returns:
609
- str: the local filename to which the model was downloaded, or the same string that
610
- was passed in, if it's not recognized as a well-known model name
611
- """
612
-
613
- if detector_file in downloadable_models:
614
- if detector_file in os.environ:
615
- fn = os.environ[detector_file]
616
- print('Reading MD location from environment variable {}: {}'.format(
617
- detector_file,fn))
618
- detector_file = fn
619
- else:
620
- print('Downloading model {}'.format(detector_file))
621
- detector_file = download_model(detector_file)
622
- return detector_file
623
-
624
-
625
-
626
- #%% Command-line driver
627
-
628
- def main():
629
-
630
- parser = argparse.ArgumentParser(
631
- description='Module to run an animal detection model on images')
632
-
633
- parser.add_argument(
634
- 'detector_file',
635
- help='Path detector model file (.pb or .pt). Can also be MDV4, MDV5A, or MDV5B to request automatic download.')
636
-
637
- # Must specify either an image file or a directory
638
- group = parser.add_mutually_exclusive_group(required=True)
639
- group.add_argument(
640
- '--image_file',
641
- type=str,
642
- default=None,
643
- help='Single file to process, mutually exclusive with --image_dir')
644
- group.add_argument(
645
- '--image_dir',
646
- type=str,
647
- default=None,
648
- help='Directory to search for images, with optional recursion by adding --recursive')
649
-
650
- parser.add_argument(
651
- '--recursive',
652
- action='store_true',
653
- help='Recurse into directories, only meaningful if using --image_dir')
654
-
655
- parser.add_argument(
656
- '--output_dir',
657
- type=str,
658
- default=None,
659
- help='Directory for output images (defaults to same as input)')
660
-
661
- parser.add_argument(
662
- '--image_size',
663
- type=int,
664
- default=None,
665
- help=('Force image resizing to a (square) integer size (not recommended to change this)'))
666
-
667
- parser.add_argument(
668
- '--threshold',
669
- type=float,
670
- default=DEFAULT_RENDERING_CONFIDENCE_THRESHOLD,
671
- help=('Confidence threshold between 0 and 1.0; only render' +
672
- ' boxes above this confidence (defaults to {})'.format(
673
- DEFAULT_RENDERING_CONFIDENCE_THRESHOLD)))
674
-
675
- parser.add_argument(
676
- '--crop',
677
- default=False,
678
- action='store_true',
679
- help=('If set, produces separate output images for each crop, '
680
- 'rather than adding bounding boxes to the original image'))
681
-
682
- parser.add_argument(
683
- '--box_thickness',
684
- type=int,
685
- default=DEFAULT_BOX_THICKNESS,
686
- help=('Line width (in pixels) for box rendering (defaults to {})'.format(
687
- DEFAULT_BOX_THICKNESS)))
688
-
689
- parser.add_argument(
690
- '--box_expansion',
691
- type=int,
692
- default=DEFAULT_BOX_EXPANSION,
693
- help=('Number of pixels to expand boxes by (defaults to {})'.format(
694
- DEFAULT_BOX_EXPANSION)))
695
-
696
- parser.add_argument(
697
- '--label_font_size',
698
- type=int,
699
- default=DEFAULT_LABEL_FONT_SIZE,
700
- help=('Label font size (defaults to {})'.format(
701
- DEFAULT_LABEL_FONT_SIZE)))
702
-
703
- parser.add_argument(
704
- '--process_likely_output_images',
705
- action='store_true',
706
- help=('By default, we skip images that end in {}, because they probably came from this script. '\
707
- .format(DETECTION_FILENAME_INSERT) + \
708
- 'This option disables that behavior.'))
709
-
710
- if len(sys.argv[1:]) == 0:
711
- parser.print_help()
712
- parser.exit()
713
-
714
- args = parser.parse_args()
715
-
716
- # If the specified detector file is really the name of a known model, find
717
- # (and possibly download) that model
718
- args.detector_file = try_download_known_detector(args.detector_file)
719
-
720
- assert os.path.exists(args.detector_file), 'detector file {} does not exist'.format(
721
- args.detector_file)
722
- assert 0.0 < args.threshold <= 1.0, 'Confidence threshold needs to be between 0 and 1'
723
-
724
- if args.image_file:
725
- image_file_names = [args.image_file]
726
- else:
727
- image_file_names = path_utils.find_images(args.image_dir, args.recursive)
728
-
729
- # Optionally skip images that were probably generated by this script
730
- if not args.process_likely_output_images:
731
- image_file_names_valid = []
732
- for fn in image_file_names:
733
- if os.path.splitext(fn)[0].endswith(DETECTION_FILENAME_INSERT):
734
- print('Skipping likely output image {}'.format(fn))
735
- else:
736
- image_file_names_valid.append(fn)
737
- image_file_names = image_file_names_valid
738
-
739
- print('Running detector on {} images...'.format(len(image_file_names)))
740
-
741
- if args.output_dir:
742
- os.makedirs(args.output_dir, exist_ok=True)
743
- else:
744
- if args.image_dir:
745
- args.output_dir = args.image_dir
746
- else:
747
- # but for a single image, args.image_dir is also None
748
- args.output_dir = os.path.dirname(args.image_file)
749
-
750
- load_and_run_detector(model_file=args.detector_file,
751
- image_file_names=image_file_names,
752
- output_dir=args.output_dir,
753
- render_confidence_threshold=args.threshold,
754
- box_thickness=args.box_thickness,
755
- box_expansion=args.box_expansion,
756
- crop_images=args.crop,
757
- image_size=args.image_size,
758
- label_font_size=args.label_font_size)
759
-
760
- if __name__ == '__main__':
761
- main()
762
-
763
-
764
- #%% Interactive driver
765
-
766
- if False:
767
-
768
- #%%
769
- model_file = r'c:\temp\models\md_v4.1.0.pb'
770
- image_file_names = path_utils.find_images(r'c:\temp\demo_images\ssverymini')
771
- output_dir = r'c:\temp\demo_images\ssverymini'
772
- render_confidence_threshold = 0.8
773
- crop_images = True
774
-
775
- load_and_run_detector(model_file=model_file,
776
- image_file_names=image_file_names,
777
- output_dir=output_dir,
778
- render_confidence_threshold=render_confidence_threshold,
779
- crop_images=crop_images)