megadetector 5.0.7__py3-none-any.whl → 5.0.9__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 (191) 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 +93 -79
  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 +69 -55
  20. api/batch_processing/postprocessing/compare_batch_results.py +114 -44
  21. api/batch_processing/postprocessing/convert_output_format.py +62 -19
  22. api/batch_processing/postprocessing/load_api_results.py +17 -20
  23. api/batch_processing/postprocessing/md_to_coco.py +31 -21
  24. api/batch_processing/postprocessing/md_to_labelme.py +165 -68
  25. api/batch_processing/postprocessing/merge_detections.py +40 -15
  26. api/batch_processing/postprocessing/postprocess_batch_results.py +270 -186
  27. api/batch_processing/postprocessing/remap_detection_categories.py +170 -0
  28. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +75 -39
  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 +244 -160
  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 +107 -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 -0
  71. data_management/coco_to_yolo.py +86 -62
  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 +130 -83
  76. data_management/databases/subset_json_db.py +25 -16
  77. data_management/generate_crops_from_cct.py +27 -45
  78. data_management/get_image_sizes.py +188 -144
  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 -160
  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 +8 -8
  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 +309 -159
  120. data_management/labelme_to_yolo.py +103 -60
  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 +114 -31
  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 +92 -90
  128. data_management/lila/generate_lila_per_image_labels.py +56 -43
  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 +103 -70
  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 +161 -99
  135. data_management/remap_coco_categories.py +84 -0
  136. data_management/remove_exif.py +58 -62
  137. data_management/resize_coco_dataset.py +32 -44
  138. data_management/wi_download_csv_to_coco.py +246 -0
  139. data_management/yolo_output_to_md_output.py +86 -73
  140. data_management/yolo_to_coco.py +535 -95
  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 +189 -114
  147. detection/run_inference_with_yolov5_val.py +118 -51
  148. detection/run_tiled_inference.py +113 -42
  149. detection/tf_detector.py +51 -28
  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 +249 -70
  155. md_utils/directory_listing.py +59 -64
  156. md_utils/md_tests.py +968 -862
  157. md_utils/path_utils.py +655 -155
  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 +208 -27
  163. md_utils/write_html_image_list.py +51 -35
  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 +908 -311
  168. md_visualization/visualize_db.py +109 -58
  169. md_visualization/visualize_detector_output.py +61 -42
  170. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/METADATA +21 -17
  171. megadetector-5.0.9.dist-info/RECORD +224 -0
  172. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/WHEEL +1 -1
  173. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/top_level.txt +1 -0
  174. taxonomy_mapping/__init__.py +0 -0
  175. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +342 -335
  176. taxonomy_mapping/map_new_lila_datasets.py +154 -154
  177. taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -134
  178. taxonomy_mapping/preview_lila_taxonomy.py +591 -591
  179. taxonomy_mapping/retrieve_sample_image.py +12 -12
  180. taxonomy_mapping/simple_image_download.py +11 -11
  181. taxonomy_mapping/species_lookup.py +10 -10
  182. taxonomy_mapping/taxonomy_csv_checker.py +18 -18
  183. taxonomy_mapping/taxonomy_graph.py +47 -47
  184. taxonomy_mapping/validate_lila_category_mappings.py +83 -76
  185. data_management/cct_json_to_filename_json.py +0 -89
  186. data_management/cct_to_csv.py +0 -140
  187. data_management/databases/remove_corrupted_images_from_db.py +0 -191
  188. detection/detector_training/copy_checkpoints.py +0 -43
  189. md_visualization/visualize_megadb.py +0 -183
  190. megadetector-5.0.7.dist-info/RECORD +0 -202
  191. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/LICENSE +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,57 +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
157
+
158
+ #: Whether to search for images recursively within [input_folder]
159
+ recursive = True
108
160
 
109
161
 
162
+ # ...YoloInferenceOptions()
163
+
164
+
110
165
  #%% Main function
111
166
 
112
167
  def run_inference_with_yolo_val(options):
113
-
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
+
114
177
  ##%% Input and path handling
115
178
 
116
179
  if options.model_type == 'yolov8':
@@ -203,7 +266,7 @@ def run_inference_with_yolo_val(options):
203
266
  ##%% Enumerate images
204
267
 
205
268
  if os.path.isdir(options.input_folder):
206
- image_files_absolute = path_utils.find_images(options.input_folder,recursive=True)
269
+ image_files_absolute = path_utils.find_images(options.input_folder,recursive=options.recursive)
207
270
  else:
208
271
  assert os.path.isfile(options.input_folder)
209
272
  with open(options.input_folder,'r') as f:
@@ -381,7 +444,7 @@ def run_inference_with_yolo_val(options):
381
444
  # YOLO console output contains lots of ANSI escape codes, remove them for easier parsing
382
445
  yolo_console_output = [string_utils.remove_ansi_codes(s) for s in yolo_console_output]
383
446
 
384
- # Find errors that occrred during the initial corruption check; these will not be included in the
447
+ # Find errors that occurred during the initial corruption check; these will not be included in the
385
448
  # output. Errors that occur during inference will be handled separately.
386
449
  yolo_read_failures = []
387
450
 
@@ -518,7 +581,7 @@ def main():
518
581
  help='inference batch size (default {})'.format(options.batch_size))
519
582
  parser.add_argument(
520
583
  '--half_precision_enabled', default=None, type=int,
521
- help='use half-precision-inference (1 or 0) (default is the underlying model\'s default, probably half for YOLOv8 and full for YOLOv8')
584
+ help='use half-precision-inference (1 or 0) (default is the underlying model\'s default, probably full for YOLOv8 and half for YOLOv5')
522
585
  parser.add_argument(
523
586
  '--device_string', default=options.device_string, type=str,
524
587
  help='CUDA device specifier, typically "0" or "1" for CUDA devices, "mps" for M1/M2 devices, or "cpu" (default {})'.format(options.device_string))
@@ -553,6 +616,10 @@ def main():
553
616
  '--save_yolo_debug_output', action='store_true',
554
617
  help='write yolo console output to a text file in the results folder, along with additional debug files')
555
618
 
619
+ parser.add_argument(
620
+ '--nonrecursive', action='store_true',
621
+ help='Disable recursive folder processing')
622
+
556
623
  parser.add_argument(
557
624
  '--preview_yolo_command_only', action='store_true',
558
625
  help='don\'t run inference, just preview the YOLO inference command (still creates symlinks)')
@@ -592,6 +659,7 @@ def main():
592
659
  if args.yolo_dataset_file is not None:
593
660
  options.yolo_category_id_to_name = args.yolo_dataset_file
594
661
 
662
+ options.recursive = (not options.nonrecursive)
595
663
  options.remove_symlink_folder = (not options.no_remove_symlink_folder)
596
664
  options.remove_yolo_results_folder = (not options.no_remove_yolo_results_folder)
597
665
  options.use_symlinks = (not options.no_use_symlinks)
@@ -599,8 +667,7 @@ def main():
599
667
 
600
668
  print(options.__dict__)
601
669
 
602
- run_inference_with_yolo_val(options)
603
-
670
+ run_inference_with_yolo_val(options)
604
671
 
605
672
  if __name__ == '__main__':
606
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
@@ -823,12 +894,12 @@ def main():
823
894
  '--overwrite_handling',
824
895
  type=str,
825
896
  default='skip',
826
- help=('behavior when the targt file exists (skip/overwrite/error) (default skip)'))
897
+ help=('Behavior when the target file exists (skip/overwrite/error) (default skip)'))
827
898
  parser.add_argument(
828
899
  '--image_list',
829
900
  type=str,
830
901
  default=None,
831
- help=('a .json list of relative filenames (or absolute paths contained within image_folder) to include'))
902
+ help=('A .json list of relative filenames (or absolute paths contained within image_folder) to include'))
832
903
 
833
904
  if len(sys.argv[1:]) == 0:
834
905
  parser.print_help()
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,29 +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 https://github.com/agentmorris/MegaDetector/tree/master/api/batch_processing#batch-processing-api-output-format
126
- - 'file' (always present)
127
- - 'max_detection_conf'
128
- - 'detections', which is a list of detection objects containing keys 'category', 'conf' and 'bbox'
129
- - '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)
130
149
  """
131
150
 
132
151
  assert image_size is None, 'Image sizing not supported for TF detectors'
133
152
  assert not skip_image_resizing, 'Image sizing not supported for TF detectors'
134
- result = {
135
- 'file': image_id
136
- }
153
+
154
+ result = { 'file': image_id }
155
+
137
156
  try:
138
157
  b_box, b_score, b_class = self._generate_detections_one_image(image)
139
158
 
@@ -163,3 +182,7 @@ class TFDetector:
163
182
  print('TFDetector: image {} failed during inference: {}'.format(image_id, str(e)))
164
183
 
165
184
  return result
185
+
186
+ # ...def generate_detections_one_image(...)
187
+
188
+ # ...class TFDetector