megadetector 5.0.8__py3-none-any.whl → 5.0.10__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 (190) hide show
  1. api/__init__.py +0 -0
  2. api/batch_processing/__init__.py +0 -0
  3. api/batch_processing/api_core/__init__.py +0 -0
  4. api/batch_processing/api_core/batch_service/__init__.py +0 -0
  5. api/batch_processing/api_core/batch_service/score.py +0 -1
  6. api/batch_processing/api_core/server_job_status_table.py +0 -1
  7. api/batch_processing/api_core_support/__init__.py +0 -0
  8. api/batch_processing/api_core_support/aggregate_results_manually.py +0 -1
  9. api/batch_processing/api_support/__init__.py +0 -0
  10. api/batch_processing/api_support/summarize_daily_activity.py +0 -1
  11. api/batch_processing/data_preparation/__init__.py +0 -0
  12. api/batch_processing/data_preparation/manage_local_batch.py +65 -65
  13. api/batch_processing/data_preparation/manage_video_batch.py +8 -8
  14. api/batch_processing/integration/digiKam/xmp_integration.py +0 -1
  15. api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -1
  16. api/batch_processing/postprocessing/__init__.py +0 -0
  17. api/batch_processing/postprocessing/add_max_conf.py +12 -12
  18. api/batch_processing/postprocessing/categorize_detections_by_size.py +32 -14
  19. api/batch_processing/postprocessing/combine_api_outputs.py +68 -54
  20. api/batch_processing/postprocessing/compare_batch_results.py +113 -43
  21. api/batch_processing/postprocessing/convert_output_format.py +41 -16
  22. api/batch_processing/postprocessing/load_api_results.py +16 -17
  23. api/batch_processing/postprocessing/md_to_coco.py +31 -21
  24. api/batch_processing/postprocessing/md_to_labelme.py +52 -22
  25. api/batch_processing/postprocessing/merge_detections.py +14 -14
  26. api/batch_processing/postprocessing/postprocess_batch_results.py +246 -174
  27. api/batch_processing/postprocessing/remap_detection_categories.py +32 -25
  28. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +60 -27
  29. api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +53 -44
  30. api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +25 -14
  31. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +242 -158
  32. api/batch_processing/postprocessing/separate_detections_into_folders.py +159 -114
  33. api/batch_processing/postprocessing/subset_json_detector_output.py +146 -169
  34. api/batch_processing/postprocessing/top_folders_to_bottom.py +77 -43
  35. api/synchronous/__init__.py +0 -0
  36. api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  37. api/synchronous/api_core/animal_detection_api/api_backend.py +0 -2
  38. api/synchronous/api_core/animal_detection_api/api_frontend.py +266 -268
  39. api/synchronous/api_core/animal_detection_api/config.py +35 -35
  40. api/synchronous/api_core/tests/__init__.py +0 -0
  41. api/synchronous/api_core/tests/load_test.py +109 -109
  42. classification/__init__.py +0 -0
  43. classification/aggregate_classifier_probs.py +21 -24
  44. classification/analyze_failed_images.py +11 -13
  45. classification/cache_batchapi_outputs.py +51 -51
  46. classification/create_classification_dataset.py +69 -68
  47. classification/crop_detections.py +54 -53
  48. classification/csv_to_json.py +97 -100
  49. classification/detect_and_crop.py +105 -105
  50. classification/evaluate_model.py +43 -42
  51. classification/identify_mislabeled_candidates.py +47 -46
  52. classification/json_to_azcopy_list.py +10 -10
  53. classification/json_validator.py +72 -71
  54. classification/map_classification_categories.py +44 -43
  55. classification/merge_classification_detection_output.py +68 -68
  56. classification/prepare_classification_script.py +157 -154
  57. classification/prepare_classification_script_mc.py +228 -228
  58. classification/run_classifier.py +27 -26
  59. classification/save_mislabeled.py +30 -30
  60. classification/train_classifier.py +20 -20
  61. classification/train_classifier_tf.py +21 -22
  62. classification/train_utils.py +10 -10
  63. data_management/__init__.py +0 -0
  64. data_management/annotations/__init__.py +0 -0
  65. data_management/annotations/annotation_constants.py +18 -31
  66. data_management/camtrap_dp_to_coco.py +238 -0
  67. data_management/cct_json_utils.py +102 -59
  68. data_management/cct_to_md.py +176 -158
  69. data_management/cct_to_wi.py +247 -219
  70. data_management/coco_to_labelme.py +272 -263
  71. data_management/coco_to_yolo.py +79 -58
  72. data_management/databases/__init__.py +0 -0
  73. data_management/databases/add_width_and_height_to_db.py +20 -16
  74. data_management/databases/combine_coco_camera_traps_files.py +35 -31
  75. data_management/databases/integrity_check_json_db.py +62 -24
  76. data_management/databases/subset_json_db.py +24 -15
  77. data_management/generate_crops_from_cct.py +27 -45
  78. data_management/get_image_sizes.py +188 -162
  79. data_management/importers/add_nacti_sizes.py +8 -8
  80. data_management/importers/add_timestamps_to_icct.py +78 -78
  81. data_management/importers/animl_results_to_md_results.py +158 -158
  82. data_management/importers/auckland_doc_test_to_json.py +9 -9
  83. data_management/importers/auckland_doc_to_json.py +8 -8
  84. data_management/importers/awc_to_json.py +7 -7
  85. data_management/importers/bellevue_to_json.py +15 -15
  86. data_management/importers/cacophony-thermal-importer.py +13 -13
  87. data_management/importers/carrizo_shrubfree_2018.py +8 -8
  88. data_management/importers/carrizo_trail_cam_2017.py +8 -8
  89. data_management/importers/cct_field_adjustments.py +9 -9
  90. data_management/importers/channel_islands_to_cct.py +10 -10
  91. data_management/importers/eMammal/copy_and_unzip_emammal.py +1 -0
  92. data_management/importers/ena24_to_json.py +7 -7
  93. data_management/importers/filenames_to_json.py +8 -8
  94. data_management/importers/helena_to_cct.py +7 -7
  95. data_management/importers/idaho-camera-traps.py +7 -7
  96. data_management/importers/idfg_iwildcam_lila_prep.py +10 -10
  97. data_management/importers/jb_csv_to_json.py +9 -9
  98. data_management/importers/mcgill_to_json.py +8 -8
  99. data_management/importers/missouri_to_json.py +18 -18
  100. data_management/importers/nacti_fieldname_adjustments.py +10 -10
  101. data_management/importers/noaa_seals_2019.py +7 -7
  102. data_management/importers/pc_to_json.py +7 -7
  103. data_management/importers/plot_wni_giraffes.py +7 -7
  104. data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -359
  105. data_management/importers/prepare_zsl_imerit.py +7 -7
  106. data_management/importers/rspb_to_json.py +8 -8
  107. data_management/importers/save_the_elephants_survey_A.py +8 -8
  108. data_management/importers/save_the_elephants_survey_B.py +9 -9
  109. data_management/importers/snapshot_safari_importer.py +26 -26
  110. data_management/importers/snapshot_safari_importer_reprise.py +665 -665
  111. data_management/importers/snapshot_serengeti_lila.py +14 -14
  112. data_management/importers/sulross_get_exif.py +8 -9
  113. data_management/importers/timelapse_csv_set_to_json.py +11 -11
  114. data_management/importers/ubc_to_json.py +13 -13
  115. data_management/importers/umn_to_json.py +7 -7
  116. data_management/importers/wellington_to_json.py +8 -8
  117. data_management/importers/wi_to_json.py +9 -9
  118. data_management/importers/zamba_results_to_md_results.py +181 -181
  119. data_management/labelme_to_coco.py +65 -24
  120. data_management/labelme_to_yolo.py +8 -8
  121. data_management/lila/__init__.py +0 -0
  122. data_management/lila/add_locations_to_island_camera_traps.py +9 -9
  123. data_management/lila/add_locations_to_nacti.py +147 -147
  124. data_management/lila/create_lila_blank_set.py +13 -13
  125. data_management/lila/create_lila_test_set.py +8 -8
  126. data_management/lila/create_links_to_md_results_files.py +106 -106
  127. data_management/lila/download_lila_subset.py +44 -110
  128. data_management/lila/generate_lila_per_image_labels.py +55 -42
  129. data_management/lila/get_lila_annotation_counts.py +18 -15
  130. data_management/lila/get_lila_image_counts.py +11 -11
  131. data_management/lila/lila_common.py +96 -33
  132. data_management/lila/test_lila_metadata_urls.py +132 -116
  133. data_management/ocr_tools.py +173 -128
  134. data_management/read_exif.py +110 -97
  135. data_management/remap_coco_categories.py +83 -83
  136. data_management/remove_exif.py +58 -62
  137. data_management/resize_coco_dataset.py +30 -23
  138. data_management/wi_download_csv_to_coco.py +246 -239
  139. data_management/yolo_output_to_md_output.py +86 -73
  140. data_management/yolo_to_coco.py +300 -60
  141. detection/__init__.py +0 -0
  142. detection/detector_training/__init__.py +0 -0
  143. detection/process_video.py +85 -33
  144. detection/pytorch_detector.py +43 -25
  145. detection/run_detector.py +157 -72
  146. detection/run_detector_batch.py +179 -113
  147. detection/run_inference_with_yolov5_val.py +108 -48
  148. detection/run_tiled_inference.py +111 -40
  149. detection/tf_detector.py +51 -29
  150. detection/video_utils.py +606 -521
  151. docs/source/conf.py +43 -0
  152. md_utils/__init__.py +0 -0
  153. md_utils/azure_utils.py +9 -9
  154. md_utils/ct_utils.py +228 -68
  155. md_utils/directory_listing.py +59 -64
  156. md_utils/md_tests.py +968 -871
  157. md_utils/path_utils.py +460 -134
  158. md_utils/process_utils.py +157 -133
  159. md_utils/sas_blob_utils.py +20 -20
  160. md_utils/split_locations_into_train_val.py +45 -32
  161. md_utils/string_utils.py +33 -10
  162. md_utils/url_utils.py +176 -60
  163. md_utils/write_html_image_list.py +40 -33
  164. md_visualization/__init__.py +0 -0
  165. md_visualization/plot_utils.py +102 -109
  166. md_visualization/render_images_with_thumbnails.py +34 -34
  167. md_visualization/visualization_utils.py +597 -291
  168. md_visualization/visualize_db.py +76 -48
  169. md_visualization/visualize_detector_output.py +61 -42
  170. {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/METADATA +13 -7
  171. megadetector-5.0.10.dist-info/RECORD +224 -0
  172. {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/top_level.txt +1 -0
  173. taxonomy_mapping/__init__.py +0 -0
  174. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +342 -335
  175. taxonomy_mapping/map_new_lila_datasets.py +154 -154
  176. taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -134
  177. taxonomy_mapping/preview_lila_taxonomy.py +591 -591
  178. taxonomy_mapping/retrieve_sample_image.py +12 -12
  179. taxonomy_mapping/simple_image_download.py +11 -11
  180. taxonomy_mapping/species_lookup.py +10 -10
  181. taxonomy_mapping/taxonomy_csv_checker.py +18 -18
  182. taxonomy_mapping/taxonomy_graph.py +47 -47
  183. taxonomy_mapping/validate_lila_category_mappings.py +83 -76
  184. data_management/cct_json_to_filename_json.py +0 -89
  185. data_management/cct_to_csv.py +0 -140
  186. data_management/databases/remove_corrupted_images_from_db.py +0 -191
  187. detection/detector_training/copy_checkpoints.py +0 -43
  188. megadetector-5.0.8.dist-info/RECORD +0 -205
  189. {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/LICENSE +0 -0
  190. {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/WHEEL +0 -0
@@ -1,39 +1,47 @@
1
- ########
2
- #
3
- # run_inference_with_yolov5_val.py
4
- #
5
- # Runs a folder of images through MegaDetector (or another YOLOv5 model) with YOLOv5's
6
- # val.py, converting the output to the standard MD format. The main goal is to leverage
7
- # YOLO's test-time augmentation tools.
8
- #
9
- # YOLOv5's val.py uses each file's base name as a unique identifier, which doesn't work
10
- # when you have typical camera trap images like:
11
- #
12
- # a/b/c/RECONYX0001.JPG
13
- # d/e/f/RECONYX0001.JPG
14
- #
15
- # ...so this script jumps through a bunch of hoops to put a symlinks in a flat
16
- # folder, run YOLOv5 on that folder, and map the results back to the real files.
17
- #
18
- # Currently requires the user to supply the path where a working YOLOv5 install lives,
19
- # and assumes that the current conda environment is all set up for YOLOv5.
20
- #
21
- # By default, this script uses symlinks to format the input images in a way that YOLOv5's
22
- # val.py likes. This requires admin privileges on Windows... actually technically this only
23
- # requires permissions to create symbolic links, but I've never seen a case where someone has
24
- # that permission and *doesn't* have admin privileges. If you are running this script on
25
- # Windows and you don't have admin privileges, use --no_use_symlinks.
26
- #
27
- # TODO:
28
- #
29
- # * Multiple GPU support
30
- #
31
- # * Checkpointing
32
- #
33
- # * Support alternative class names at the command line (currently defaults to MD classes,
34
- # though other class names can be supplied programmatically)
35
- #
36
- ########
1
+ """
2
+
3
+ run_inference_with_yolov5_val.py
4
+
5
+ Runs a folder of images through MegaDetector (or another YOLOv5/YOLOv8 model) with YOLO's
6
+ val.py, converting the output to the standard MD format. The reasons this script exists,
7
+ as an alternative to the standard run_detector_batch.py are:
8
+
9
+ * This script provides access to YOLO's test-time augmentation tools.
10
+ * This script serves a reference implementation: by any reasonable definition, YOLOv5's
11
+ val.py produces the "correct" result for any image, since it matches what was used in
12
+ training.
13
+ * This script works for any Ultralytics detection model, including YOLOv8 models
14
+
15
+ YOLOv5's val.py uses each file's base name as a unique identifier, which doesn't work
16
+ when you have typical camera trap images like:
17
+
18
+ * a/b/c/RECONYX0001.JPG
19
+ * d/e/f/RECONYX0001.JPG
20
+
21
+ ...both of which would just be "RECONYX0001.JPG". So this script jumps through a bunch of
22
+ hoops to put a symlinks in a flat folder, run YOLOv5 on that folder, and map the results back
23
+ to the real files.
24
+
25
+ If you are running a YOLOv5 model, this script currently requires the caller to supply the path
26
+ where a working YOLOv5 install lives, and assumes that the current conda environment is all set up for
27
+ YOLOv5. If you are running a YOLOv8 model, the folder doesn't matter, but it assumes that ultralytics
28
+ tools are available in the current environment.
29
+
30
+ By default, this script uses symlinks to format the input images in a way that YOLO's
31
+ val.py likes, as per above. This requires admin privileges on Windows... actually technically this
32
+ only requires permissions to create symbolic links, but I've never seen a case where someone has
33
+ that permission and *doesn't* have admin privileges. If you are running this script on
34
+ Windows and you don't have admin privileges, use --no_use_symlinks, which will make copies of images,
35
+ rather than using symlinks.
36
+
37
+ TODO:
38
+
39
+ * Multiple GPU support
40
+ * Checkpointing
41
+ * Support alternative class names at the command line (currently defaults to MD classes,
42
+ though other class names can be supplied programmatically)
43
+
44
+ """
37
45
 
38
46
  #%% Imports
39
47
 
@@ -60,59 +68,112 @@ default_image_size_with_no_augmentation = 1280
60
68
  #%% Options class
61
69
 
62
70
  class YoloInferenceOptions:
63
-
71
+ """
72
+ Parameters that control the behavior of run_inference_with_yolov5_val(), including
73
+ the input/output filenames.
74
+ """
75
+
64
76
  ## Required ##
65
77
 
78
+ #: Folder of images to process
66
79
  input_folder = None
80
+
81
+ #: Model filename (ending in .pt), or a well-known model name (e.g. "MDV5A")
67
82
  model_filename = None
83
+
84
+ #: .json output file, in MD results format
68
85
  output_file = None
69
86
 
87
+
70
88
  ## Optional ##
71
89
 
72
- # Required for older YOLOv5 inference, not for newer ulytralytics inference
90
+ #: Required for older YOLOv5 inference, not for newer ulytralytics/YOLOv8 inference
73
91
  yolo_working_folder = None
74
92
 
75
- # Currently 'yolov5' and 'ultralytics' are supported, and really these are proxies for
76
- # "the yolov5 repo" and "the ultralytics repo" (typically YOLOv8).
93
+ #: Currently 'yolov5' and 'ultralytics' are supported, and really these are proxies for
94
+ #: "the yolov5 repo" and "the ultralytics repo".
77
95
  model_type = 'yolov5'
78
96
 
97
+ #: Image size to use; this is a single int, which in ultralytics's terminology means
98
+ #: "scale the long side of the image to this size, and preserve aspect ratio".
79
99
  image_size = default_image_size_with_augmentation
100
+
101
+ #: Detections below this threshold will not be included in the output file
80
102
  conf_thres = '0.001'
103
+
104
+ #: Batch size... has no impact on results, but may create memory issues if you set
105
+ #: this to large values
81
106
  batch_size = 1
107
+
108
+ #: Device string: typically '0' for GPU 0, '1' for GPU 1, etc., or 'cpu'
82
109
  device_string = '0'
110
+
111
+ #: Should we enable test-time augmentation?
83
112
  augment = True
113
+
114
+ #: Should we enable half-precision inference?
84
115
  half_precision_enabled = None
85
116
 
117
+ #: Where should we stash the temporary symlinks used to give unique identifiers to image files?
118
+ #:
119
+ #: If this is None, we'll create a folder in system temp space.
86
120
  symlink_folder = None
121
+
122
+ #: Should we use symlinks to give unique identifiers to image files (vs. copies)?
87
123
  use_symlinks = True
88
124
 
125
+ #: Temporary folder to stash intermediate YOLO results.
126
+ #:
127
+ #: If this is None, we'll create a folder in system temp space.
89
128
  yolo_results_folder = None
90
129
 
130
+ #: Should we remove the symlink folder when we're done?
91
131
  remove_symlink_folder = True
132
+
133
+ #: Should we remove the intermediate results folder when we're done?
92
134
  remove_yolo_results_folder = True
93
135
 
94
- # These are deliberately offset from the standard MD categories; YOLOv5
95
- # needs categories IDs to start at 0.
96
- #
97
- # This can also be a string that points to a YOLOv5 dataset.yaml file.
136
+ #: These are deliberately offset from the standard MD categories; YOLOv5
137
+ #: needs categories IDs to start at 0.
138
+ #:
139
+ #: This can also be a string that points to a YOLO dataset.yaml file.
98
140
  yolo_category_id_to_name = {0:'animal',1:'person',2:'vehicle'}
99
141
 
100
- # 'error','skip','overwrite'
142
+ #: What should we do if the output file already exists?
143
+ #:
144
+ #: Can be 'error', 'skip', or 'overwrite'.
101
145
  overwrite_handling = 'skip'
102
146
 
147
+ #: If True, we'll do a dry run that lets you preview the YOLO val command, without
148
+ #: actually running it.
103
149
  preview_yolo_command_only = False
104
150
 
151
+ #: By default, if any errors occur while we're copying images or creating symlinks, it's
152
+ #: game over. If this is True, those errors become warnings, and we plow ahead.
105
153
  treat_copy_failures_as_warnings = False
106
154
 
155
+ #: Save YOLO console output
107
156
  save_yolo_debug_output = False
108
157
 
158
+ #: Whether to search for images recursively within [input_folder]
109
159
  recursive = True
110
160
 
111
161
 
162
+ # ...YoloInferenceOptions()
163
+
164
+
112
165
  #%% Main function
113
166
 
114
167
  def run_inference_with_yolo_val(options):
115
-
168
+ """
169
+ Runs a folder of images through MegaDetector (or another YOLOv5/YOLOv8 model) with YOLO's
170
+ val.py, converting the output to the standard MD format.
171
+
172
+ Args:
173
+ options (YoloInferenceOptions): all the parameters used to control this process,
174
+ including filenames; see YoloInferenceOptions for details
175
+ """
176
+
116
177
  ##%% Input and path handling
117
178
 
118
179
  if options.model_type == 'yolov8':
@@ -606,8 +667,7 @@ def main():
606
667
 
607
668
  print(options.__dict__)
608
669
 
609
- run_inference_with_yolo_val(options)
610
-
670
+ run_inference_with_yolo_val(options)
611
671
 
612
672
  if __name__ == '__main__':
613
673
  main()
@@ -1,24 +1,26 @@
1
- ########
2
- #
3
- # run_tiled_inference.py
4
- #
5
- # Run inference on a folder, fist splitting each image up into tiles of size
6
- # MxN (typically the native inference size of your detector), writing those
7
- # tiles out to a temporary folder, then de-duplicating the results before merging
8
- # them back into a set of detections that make sense on the original images.
9
- #
10
- # This approach will likely fail to detect very large animals, so if you expect both large
11
- # and small animals (in terms of pixel size), this script is best used in
12
- # conjunction with a traditional inference pass that looks at whole images.
13
- #
14
- # Currently requires temporary storage at least as large as the input data, generally
15
- # a lot more than that (depending on the overlap between adjacent tiles). This is
16
- # inefficient, but easy to debug.
17
- #
18
- # Programmatic invocation supports using YOLOv5's inference scripts (and test-time
19
- # augmentation); the command-line interface only supports standard inference right now.
20
- #
21
- ########
1
+ """
2
+
3
+ run_tiled_inference.py
4
+
5
+ **This script is experimental, YMMV.**
6
+
7
+ Runs inference on a folder, fist splitting each image up into tiles of size
8
+ MxN (typically the native inference size of your detector), writing those
9
+ tiles out to a temporary folder, then de-duplicating the resulting detections before
10
+ merging them back into a set of detections that make sense on the original images.
11
+
12
+ This approach will likely fail to detect very large animals, so if you expect both large
13
+ and small animals (in terms of pixel size), this script is best used in
14
+ conjunction with a traditional inference pass that looks at whole images.
15
+
16
+ Currently requires temporary storage at least as large as the input data, generally
17
+ a lot more than that (depending on the overlap between adjacent tiles). This is
18
+ inefficient, but easy to debug.
19
+
20
+ Programmatic invocation supports using YOLOv5's inference scripts (and test-time
21
+ augmentation); the command-line interface only supports standard inference right now.
22
+
23
+ """
22
24
 
23
25
  #%% Imports and constants
24
26
 
@@ -54,17 +56,24 @@ parallelization_uses_threads = False
54
56
 
55
57
  def get_patch_boundaries(image_size,patch_size,patch_stride=None):
56
58
  """
57
- Get a list of patch starting coordinates (x,y) given an image size (w,h)
58
- and a stride (x,y). Stride defaults to half the patch size.
59
+ Computes a list of patch starting coordinates (x,y) given an image size (w,h)
60
+ and a stride (x,y)
59
61
 
60
- patch_stride can also be a single float, in which case that is interpreted
61
- as the stride relative to the patch size (0.1 == 10% stride).
62
-
63
- Patch size is guaranteed, stride may deviate to make sure all pixels are covered.
62
+ Patch size is guaranteed, but the stride may deviate to make sure all pixels are covered.
64
63
  I.e., we move by regular strides until the current patch walks off the right/bottom,
65
64
  at which point it backs up to one patch from the end. So if your image is 15
66
65
  pixels wide and you have a stride of 10 pixels, you will get starting positions
67
66
  of 0 (from 0 to 9) and 5 (from 5 to 14).
67
+
68
+ Args:
69
+ image_size (tuple): size of the image you want to divide into patches, as a length-2 tuple (w,h)
70
+ patch_size (tuple): patch size into which you want to divide an image, as a length-2 tuple (w,h)
71
+ patch_stride (tuple or float, optional): stride between patches, as a length-2 tuple (x,y), or a
72
+ float; if this is a float, it's interpreted as the stride relative to the patch size
73
+ (0.1 == 10% stride). Defaults to half the patch size.
74
+
75
+ Returns:
76
+ list: list of length-2 tuples, each representing the x/y start position of a patch
68
77
  """
69
78
 
70
79
  if patch_stride is None:
@@ -163,23 +172,50 @@ def get_patch_boundaries(image_size,patch_size,patch_stride=None):
163
172
 
164
173
 
165
174
  def patch_info_to_patch_name(image_name,patch_x_min,patch_y_min):
175
+ """
176
+ Gives a unique string name to an x/y coordinate, e.g. turns ("a.jpg",10,20) into
177
+ "a.jpg_0010_0020".
166
178
 
179
+ Args:
180
+ image_name (str): image identifier
181
+ patch_x_min (int): x coordinate
182
+ patch_y_min (int): y coordinate
183
+
184
+ Returns:
185
+ str: name for this patch, e.g. "a.jpg_0010_0020"
186
+ """
167
187
  patch_name = image_name + '_' + \
168
188
  str(patch_x_min).zfill(4) + '_' + str(patch_y_min).zfill(4)
169
189
  return patch_name
170
190
 
171
191
 
172
- def extract_patch_from_image(im,patch_xy,patch_size,
173
- patch_image_fn=None,patch_folder=None,image_name=None,overwrite=True):
192
+ def extract_patch_from_image(im,
193
+ patch_xy,
194
+ patch_size,
195
+ patch_image_fn=None,
196
+ patch_folder=None,
197
+ image_name=None,
198
+ overwrite=True):
174
199
  """
175
- Extracts a patch from the provided image, writing the patch out to patch_image_fn.
176
- [im] can be a string or a PIL image.
177
-
178
- patch_xy is a length-2 tuple specifying the upper-left corner of the patch.
179
-
180
- image_name and patch_folder are only required if patch_image_fn is None.
181
-
182
- Returns a dictionary with fields xmin,xmax,ymin,ymax,patch_fn.
200
+ Extracts a patch from the provided image, and writes that patch out to a new file.
201
+
202
+ Args:
203
+ im (str or Image): image from which we should extract a patch, can be a filename or
204
+ a PIL Image object.
205
+ patch_xy (tuple): length-2 tuple of ints (x,y) representing the upper-left corner
206
+ of the patch to extract
207
+ patch_size (tuple): length-2 tuple of ints (w,h) representing the size of the
208
+ patch to extract
209
+ patch_image_fn (str, optional): image filename to write the patch to; if this is None
210
+ the filename will be generated from [image_name] and the patch coordinates
211
+ patch_folder (str, optional): folder in which the image lives; only used to generate
212
+ a patch filename, so only required if [patch_image_fn] is None
213
+ image_name (str, optional): the identifier of the source image; only used to generate
214
+ a patch filename, so only required if [patch_image_fn] is None
215
+ overwrite (bool, optional): whether to overwrite an existing patch image
216
+
217
+ Returns:
218
+ dict: a dictionary with fields xmin,xmax,ymin,ymax,patch_fn
183
219
  """
184
220
 
185
221
  if isinstance(im,str):
@@ -223,10 +259,20 @@ def extract_patch_from_image(im,patch_xy,patch_size,
223
259
 
224
260
  return patch_info
225
261
 
262
+ # ...def extract_patch_from_image(...)
263
+
226
264
 
227
265
  def in_place_nms(md_results, iou_thres=0.45, verbose=True):
228
266
  """
229
- Run torch.ops.nms in-place on MD-formatted detection results
267
+ Run torch.ops.nms in-place on MD-formatted detection results.
268
+
269
+ Args:
270
+ md_results (dict): detection results for a list of images, in MD results format (i.e.,
271
+ containing a list of image dicts with the key 'images', each of which has a list
272
+ of detections with the key 'detections')
273
+ iou_thres (float, optional): IoU threshold above which we will treat two detections as
274
+ redundant
275
+ verbose (bool, optional): enable additional debug console output
230
276
  """
231
277
 
232
278
  n_detections_before = 0
@@ -343,7 +389,7 @@ def run_tiled_inference(model_file, image_folder, tiling_folder, output_file,
343
389
  overwrite_tiles=True,
344
390
  image_list=None):
345
391
  """
346
- Run inference using [model_file] on the images in [image_folder], fist splitting each image up
392
+ Runs inference using [model_file] on the images in [image_folder], fist splitting each image up
347
393
  into tiles of size [tile_size_x] x [tile_size_y], writing those tiles to [tiling_folder],
348
394
  then de-duplicating the results before merging them back into a set of detections that make
349
395
  sense on the original images and writing those results to [output_file].
@@ -360,7 +406,32 @@ def run_tiled_inference(model_file, image_folder, tiling_folder, output_file,
360
406
 
361
407
  if yolo_inference_options is supplied, it should be an instance of YoloInferenceOptions; in
362
408
  this case the model will be run with run_inference_with_yolov5_val. This is typically used to
363
- run the model with test-time augmentation.
409
+ run the model with test-time augmentation.
410
+
411
+ Args:
412
+ model_file (str): model filename (ending in .pt), or a well-known model name (e.g. "MDV5A")
413
+ image_folder (str): the folder of images to proess (always recursive)
414
+ tiling_folder (str): folder for temporary tile storage; see caveats above
415
+ output_file (str): .json file to which we should write MD-formatted results
416
+ tile_size_x (int, optional): tile width
417
+ tile_size_y (int, optional): tile height
418
+ tile_overlap (float, optional): overlap between adjacenet tiles, as a fraction of the
419
+ tile size
420
+ checkpoint_path (str, optional): checkpoint path; passed directly to run_detector_batch; see
421
+ run_detector_batch for details
422
+ checkpoint_frequency (int, optional): checkpoint frequency; passed directly to run_detector_batch; see
423
+ run_detector_batch for details
424
+ remove_tiles (bool, optional): whether to delete the tiles when we're done
425
+ yolo_inference_options (YoloInferenceOptions, optional): if not None, will run inference with
426
+ run_inference_with_yolov5_val.py, rather than with run_detector_batch.py, using these options
427
+ n_patch_extraction_workers (int, optional): number of workers to use for patch extraction;
428
+ set to <= 1 to disable parallelization
429
+ image_list (list, optional): .json file containing a list of specific images to process. If
430
+ this is supplied, and the paths are absolute, [image_folder] will be ignored. If this is supplied,
431
+ and the paths are relative, they should be relative to [image_folder].
432
+
433
+ Returns:
434
+ dict: MD-formatted results dictionary, identical to what's written to [output_file]
364
435
  """
365
436
 
366
437
  ##%% Validate arguments
detection/tf_detector.py CHANGED
@@ -1,11 +1,12 @@
1
- ########
2
- #
3
- # tf_detector.py
4
- #
5
- # Module containing the class TFDetector for loading a TensorFlow detection model and
6
- # running inference.
7
- #
8
- ########
1
+ """
2
+
3
+ tf_detector.py
4
+
5
+ Module containing the class TFDetector, for loading and running a TensorFlow detection model.
6
+
7
+ """
8
+
9
+ #%% Imports and constants
9
10
 
10
11
  import numpy as np
11
12
 
@@ -18,36 +19,41 @@ print('TensorFlow version:', tf.__version__)
18
19
  print('Is GPU available? tf.test.is_gpu_available:', tf.test.is_gpu_available())
19
20
 
20
21
 
22
+ #%% Classes
23
+
21
24
  class TFDetector:
22
25
  """
23
26
  A detector model loaded at the time of initialization. It is intended to be used with
24
- the MegaDetector (TF). The inference batch size is set to 1; code needs to be modified
25
- to support larger batch sizes, including resizing appropriately.
27
+ TensorFlow-based versions of MegaDetector (v2, v3, or v4). If someone can find v1, I
28
+ suppose you could use this class for v1 also.
26
29
  """
27
30
 
28
- # MegaDetector was trained with batch size of 1, and the resizing function is a part
29
- # of the inference graph
31
+ #: TF versions of MD were trained with batch size of 1, and the resizing function is a
32
+ #: part of the inference graph, so this is fixed.
33
+ #:
34
+ #: :meta private:
30
35
  BATCH_SIZE = 1
31
36
 
32
37
 
33
38
  def __init__(self, model_path):
34
39
  """
35
- Loads model from model_path and starts a tf.Session with this graph. Obtains
40
+ Loads a model from [model_path] and starts a tf.Session with this graph. Obtains
36
41
  input and output tensor handles.
37
42
  """
38
43
 
39
44
  detection_graph = TFDetector.__load_model(model_path)
40
45
  self.tf_session = tf.Session(graph=detection_graph)
41
-
42
46
  self.image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')
43
47
  self.box_tensor = detection_graph.get_tensor_by_name('detection_boxes:0')
44
48
  self.score_tensor = detection_graph.get_tensor_by_name('detection_scores:0')
45
49
  self.class_tensor = detection_graph.get_tensor_by_name('detection_classes:0')
46
50
 
51
+
47
52
  @staticmethod
48
- def round_and_make_float(d, precision=4):
53
+ def __round_and_make_float(d, precision=4):
49
54
  return truncate_float(float(d), precision=precision)
50
55
 
56
+
51
57
  @staticmethod
52
58
  def __convert_coords(tf_coords):
53
59
  """
@@ -70,9 +76,10 @@ class TFDetector:
70
76
 
71
77
  # convert numpy floats to Python floats
72
78
  for i, d in enumerate(new):
73
- new[i] = TFDetector.round_and_make_float(d, precision=COORD_DIGITS)
79
+ new[i] = TFDetector.__round_and_make_float(d, precision=COORD_DIGITS)
74
80
  return new
75
81
 
82
+
76
83
  @staticmethod
77
84
  def __load_model(model_path):
78
85
  """
@@ -96,7 +103,12 @@ class TFDetector:
96
103
 
97
104
  return detection_graph
98
105
 
106
+
99
107
  def _generate_detections_one_image(self, image):
108
+ """
109
+ Runs the detector on a single image.
110
+ """
111
+
100
112
  np_im = np.asarray(image, np.uint8)
101
113
  im_w_batch_dim = np.expand_dims(np_im, axis=0)
102
114
 
@@ -111,30 +123,36 @@ class TFDetector:
111
123
 
112
124
  return box_tensor_out, score_tensor_out, class_tensor_out
113
125
 
126
+
114
127
  def generate_detections_one_image(self, image, image_id, detection_threshold, image_size=None,
115
128
  skip_image_resizing=False):
116
129
  """
117
- Apply the detector to an image.
130
+ Runs the detector on an image.
118
131
 
119
132
  Args:
120
- image: the PIL Image object
121
- image_id: a path to identify the image; will be in the "file" field of the output object
122
- detection_threshold: confidence above which to include the detection proposal
133
+ image (Image): the PIL Image object on which we should run the detector
134
+ image_id (str): a path to identify the image; will be in the "file" field of the output object
135
+ detection_threshold (float): only detections above this threshold will be included in the return
136
+ value
137
+ image_size (tuple, optional): image size to use for inference, only mess with this
138
+ if (a) you're using a model other than MegaDetector or (b) you know what you're
139
+ doing
140
+ skip_image_resizing (bool, optional): whether to skip internal image resizing (and rely on external
141
+ resizing)... not currently supported, but included here for compatibility with PTDetector.
123
142
 
124
143
  Returns:
125
- A dict with the following fields, see the 'images' key in:
126
- https://github.com/agentmorris/MegaDetector/tree/master/api/batch_processing#batch-processing-api-output-format
127
- - 'file' (always present)
128
- - 'max_detection_conf'
129
- - 'detections', which is a list of detection objects containing keys 'category', 'conf' and 'bbox'
130
- - 'failure'
144
+ dict: a dictionary with the following fields:
145
+ - 'file' (filename, always present)
146
+ - 'max_detection_conf' (removed from MegaDetector output files by default, but generated here)
147
+ - 'detections' (a list of detection objects containing keys 'category', 'conf', and 'bbox')
148
+ - 'failure' (a failure string, or None if everything went fine)
131
149
  """
132
150
 
133
151
  assert image_size is None, 'Image sizing not supported for TF detectors'
134
152
  assert not skip_image_resizing, 'Image sizing not supported for TF detectors'
135
- result = {
136
- 'file': image_id
137
- }
153
+
154
+ result = { 'file': image_id }
155
+
138
156
  try:
139
157
  b_box, b_score, b_class = self._generate_detections_one_image(image)
140
158
 
@@ -164,3 +182,7 @@ class TFDetector:
164
182
  print('TFDetector: image {} failed during inference: {}'.format(image_id, str(e)))
165
183
 
166
184
  return result
185
+
186
+ # ...def generate_detections_one_image(...)
187
+
188
+ # ...class TFDetector