megadetector 5.0.29__py3-none-any.whl → 10.0.0__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 (95) hide show
  1. megadetector/classification/efficientnet/model.py +8 -8
  2. megadetector/classification/efficientnet/utils.py +6 -5
  3. megadetector/classification/prepare_classification_script_mc.py +3 -3
  4. megadetector/data_management/annotations/annotation_constants.py +0 -1
  5. megadetector/data_management/camtrap_dp_to_coco.py +34 -1
  6. megadetector/data_management/cct_json_utils.py +2 -2
  7. megadetector/data_management/coco_to_yolo.py +22 -5
  8. megadetector/data_management/databases/add_width_and_height_to_db.py +85 -12
  9. megadetector/data_management/databases/combine_coco_camera_traps_files.py +2 -2
  10. megadetector/data_management/databases/integrity_check_json_db.py +29 -15
  11. megadetector/data_management/generate_crops_from_cct.py +50 -1
  12. megadetector/data_management/labelme_to_coco.py +4 -2
  13. megadetector/data_management/labelme_to_yolo.py +82 -2
  14. megadetector/data_management/lila/generate_lila_per_image_labels.py +276 -18
  15. megadetector/data_management/lila/get_lila_annotation_counts.py +5 -3
  16. megadetector/data_management/lila/lila_common.py +3 -0
  17. megadetector/data_management/lila/test_lila_metadata_urls.py +15 -5
  18. megadetector/data_management/mewc_to_md.py +5 -0
  19. megadetector/data_management/ocr_tools.py +4 -3
  20. megadetector/data_management/read_exif.py +20 -5
  21. megadetector/data_management/remap_coco_categories.py +66 -4
  22. megadetector/data_management/remove_exif.py +50 -1
  23. megadetector/data_management/rename_images.py +3 -3
  24. megadetector/data_management/resize_coco_dataset.py +563 -95
  25. megadetector/data_management/yolo_output_to_md_output.py +131 -2
  26. megadetector/data_management/yolo_to_coco.py +140 -5
  27. megadetector/detection/change_detection.py +4 -3
  28. megadetector/detection/pytorch_detector.py +60 -22
  29. megadetector/detection/run_detector.py +225 -25
  30. megadetector/detection/run_detector_batch.py +42 -16
  31. megadetector/detection/run_inference_with_yolov5_val.py +12 -2
  32. megadetector/detection/run_tiled_inference.py +1 -0
  33. megadetector/detection/video_utils.py +53 -24
  34. megadetector/postprocessing/add_max_conf.py +4 -0
  35. megadetector/postprocessing/categorize_detections_by_size.py +1 -1
  36. megadetector/postprocessing/classification_postprocessing.py +55 -20
  37. megadetector/postprocessing/combine_batch_outputs.py +3 -2
  38. megadetector/postprocessing/compare_batch_results.py +64 -10
  39. megadetector/postprocessing/convert_output_format.py +12 -8
  40. megadetector/postprocessing/create_crop_folder.py +137 -10
  41. megadetector/postprocessing/load_api_results.py +26 -8
  42. megadetector/postprocessing/md_to_coco.py +4 -4
  43. megadetector/postprocessing/md_to_labelme.py +18 -7
  44. megadetector/postprocessing/merge_detections.py +5 -0
  45. megadetector/postprocessing/postprocess_batch_results.py +6 -3
  46. megadetector/postprocessing/remap_detection_categories.py +55 -2
  47. megadetector/postprocessing/render_detection_confusion_matrix.py +9 -6
  48. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +2 -2
  49. megadetector/taxonomy_mapping/map_new_lila_datasets.py +3 -4
  50. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +40 -19
  51. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +1 -1
  52. megadetector/taxonomy_mapping/species_lookup.py +123 -41
  53. megadetector/utils/ct_utils.py +133 -113
  54. megadetector/utils/md_tests.py +93 -13
  55. megadetector/utils/path_utils.py +137 -107
  56. megadetector/utils/split_locations_into_train_val.py +2 -2
  57. megadetector/utils/string_utils.py +7 -7
  58. megadetector/utils/url_utils.py +81 -58
  59. megadetector/utils/wi_utils.py +46 -17
  60. megadetector/visualization/plot_utils.py +13 -9
  61. megadetector/visualization/render_images_with_thumbnails.py +2 -1
  62. megadetector/visualization/visualization_utils.py +94 -46
  63. megadetector/visualization/visualize_db.py +36 -9
  64. megadetector/visualization/visualize_detector_output.py +4 -4
  65. {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/METADATA +135 -135
  66. megadetector-10.0.0.dist-info/RECORD +139 -0
  67. {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
  68. {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/top_level.txt +0 -0
  69. megadetector/api/batch_processing/api_core/__init__.py +0 -0
  70. megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  71. megadetector/api/batch_processing/api_core/batch_service/score.py +0 -438
  72. megadetector/api/batch_processing/api_core/server.py +0 -294
  73. megadetector/api/batch_processing/api_core/server_api_config.py +0 -97
  74. megadetector/api/batch_processing/api_core/server_app_config.py +0 -55
  75. megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -220
  76. megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -149
  77. megadetector/api/batch_processing/api_core/server_orchestration.py +0 -360
  78. megadetector/api/batch_processing/api_core/server_utils.py +0 -88
  79. megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  80. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
  81. megadetector/api/batch_processing/api_support/__init__.py +0 -0
  82. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -152
  83. megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  84. megadetector/api/synchronous/__init__.py +0 -0
  85. megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  86. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -151
  87. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -263
  88. megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -35
  89. megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  90. megadetector/api/synchronous/api_core/tests/load_test.py +0 -109
  91. megadetector/utils/azure_utils.py +0 -178
  92. megadetector/utils/sas_blob_utils.py +0 -513
  93. megadetector-5.0.29.dist-info/RECORD +0 -163
  94. /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
  95. {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/WHEEL +0 -0
@@ -32,6 +32,7 @@ import time
32
32
  import json
33
33
  import warnings
34
34
  import tempfile
35
+ import zipfile
35
36
 
36
37
  import humanfriendly
37
38
  from tqdm import tqdm
@@ -40,6 +41,7 @@ from megadetector.utils import path_utils as path_utils
40
41
  from megadetector.visualization import visualization_utils as vis_utils
41
42
  from megadetector.utils.url_utils import download_url
42
43
  from megadetector.utils.ct_utils import parse_kvp_list
44
+ from megadetector.utils.path_utils import compute_file_hash
43
45
 
44
46
  # ignoring all "PIL cannot read EXIF metainfo for the images" warnings
45
47
  warnings.filterwarnings('ignore', '(Possibly )?corrupt EXIF data', UserWarning)
@@ -81,29 +83,66 @@ USE_MODEL_NATIVE_CLASSES = False
81
83
  #
82
84
  # Order matters here.
83
85
  model_string_to_model_version = {
86
+
87
+ # Specific model versions that might be expressed in a variety of ways
84
88
  'mdv2':'v2.0.0',
85
89
  'mdv3':'v3.0.0',
86
90
  'mdv4':'v4.1.0',
87
- 'mdv5a':'v5a.0.0',
88
- 'mdv5b':'v5b.0.0',
91
+ 'mdv5a':'v5a.0.1',
92
+ 'mdv5b':'v5b.0.1',
93
+
89
94
  'v2':'v2.0.0',
90
95
  'v3':'v3.0.0',
91
96
  'v4':'v4.1.0',
92
97
  'v4.1':'v4.1.0',
93
- 'v5a.0.0':'v5a.0.0',
94
- 'v5b.0.0':'v5b.0.0',
98
+ 'v5a.0.0':'v5a.0.1',
99
+ 'v5b.0.0':'v5b.0.1',
100
+
101
+ 'md1000-redwood':'v1000.0.0-redwood',
102
+ 'md1000-cedar':'v1000.0.0-cedar',
103
+ 'md1000-larch':'v1000.0.0-larch',
104
+ 'md1000-sorrel':'v1000.0.0-sorrel',
105
+ 'md1000-spruce':'v1000.0.0-spruce',
106
+
107
+ 'mdv1000-redwood':'v1000.0.0-redwood',
108
+ 'mdv1000-cedar':'v1000.0.0-cedar',
109
+ 'mdv1000-larch':'v1000.0.0-larch',
110
+ 'mdv1000-sorrel':'v1000.0.0-sorrel',
111
+ 'mdv1000-spruce':'v1000.0.0-spruce',
112
+
113
+ 'v1000-redwood':'v1000.0.0-redwood',
114
+ 'v1000-cedar':'v1000.0.0-cedar',
115
+ 'v1000-larch':'v1000.0.0-larch',
116
+ 'v1000-sorrel':'v1000.0.0-sorrel',
117
+ 'v1000-spruce':'v1000.0.0-spruce',
118
+
119
+ # Arguably less specific model versions
95
120
  'redwood':'v1000.0.0-redwood',
96
121
  'spruce':'v1000.0.0-spruce',
97
122
  'cedar':'v1000.0.0-cedar',
98
123
  'larch':'v1000.0.0-larch',
99
- 'default':'v5a.0.0',
100
- 'default-model':'v5a.0.0',
101
- 'megadetector':'v5a.0.0'
124
+
125
+ # Opinionated defaults
126
+ 'mdv5':'v5a.0.1',
127
+ 'md5':'v5a.0.1',
128
+ 'mdv1000':'v1000.0.0-redwood',
129
+ 'md1000':'v1000.0.0-redwood',
130
+ 'default':'v5a.0.1',
131
+ 'megadetector':'v5a.0.1',
102
132
  }
103
133
 
104
- model_url_base = 'http://localhost:8181/'
134
+ # python -m http.server 8181
135
+ model_url_base = 'https://github.com/agentmorris/MegaDetector/releases/download/v1000.0/'
105
136
  assert model_url_base.endswith('/')
106
137
 
138
+ if os.environ.get('MD_MODEL_URL_BASE') is not None:
139
+ model_url_base = os.environ['MD_MODEL_URL_BASE']
140
+ print('Model URL base provided via environment variable: {}'.format(
141
+ model_url_base
142
+ ))
143
+ if not model_url_base.endswith('/'):
144
+ model_url_base += '/'
145
+
107
146
  # Maps canonical model version numbers to metadata
108
147
  known_models = {
109
148
  'v2.0.0':
@@ -137,7 +176,8 @@ known_models = {
137
176
  'conservative_detection_threshold':0.05,
138
177
  'image_size':1280,
139
178
  'model_type':'yolov5',
140
- 'normalized_typical_inference_speed':1.0
179
+ 'normalized_typical_inference_speed':1.0,
180
+ 'md5':'ec1d7603ec8cf642d6e0cd008ba2be8c'
141
181
  },
142
182
  'v5b.0.0':
143
183
  {
@@ -146,29 +186,58 @@ known_models = {
146
186
  'conservative_detection_threshold':0.05,
147
187
  'image_size':1280,
148
188
  'model_type':'yolov5',
149
- 'normalized_typical_inference_speed':1.0
189
+ 'normalized_typical_inference_speed':1.0,
190
+ 'md5':'bc235e73f53c5c95e66ea0d1b2cbf542'
191
+ },
192
+ 'v5a.0.1':
193
+ {
194
+ 'url':'https://github.com/agentmorris/MegaDetector/releases/download/v5.0/md_v5a.0.1.pt',
195
+ 'typical_detection_threshold':0.2,
196
+ 'conservative_detection_threshold':0.05,
197
+ 'image_size':1280,
198
+ 'model_type':'yolov5',
199
+ 'normalized_typical_inference_speed':1.0,
200
+ 'md5':'60f8e7ec1308554df258ed1f4040bc4f'
201
+ },
202
+ 'v5b.0.1':
203
+ {
204
+ 'url':'https://github.com/agentmorris/MegaDetector/releases/download/v5.0/md_v5b.0.1.pt',
205
+ 'typical_detection_threshold':0.2,
206
+ 'conservative_detection_threshold':0.05,
207
+ 'image_size':1280,
208
+ 'model_type':'yolov5',
209
+ 'normalized_typical_inference_speed':1.0,
210
+ 'md5':'f17ed6fedfac2e403606a08c89984905'
150
211
  },
151
-
152
- # Fake values for testing
153
212
  'v1000.0.0-redwood':
154
213
  {
155
- 'normalized_typical_inference_speed':2.0,
156
- 'url':model_url_base + 'md_v1000.0.0-redwood.pt'
214
+ 'url':model_url_base + 'md_v1000.0.0-redwood.pt',
215
+ 'normalized_typical_inference_speed':1.0,
216
+ 'md5':'74474b3aec9cf1a990da38b37ddf9197'
157
217
  },
158
218
  'v1000.0.0-spruce':
159
219
  {
160
- 'normalized_typical_inference_speed':3.0,
161
- 'url':model_url_base + 'md_v1000.0.0-spruce.pt'
220
+ 'url':model_url_base + 'md_v1000.0.0-spruce.pt',
221
+ 'normalized_typical_inference_speed':12.7,
222
+ 'md5':'1c9d1d2b3ba54931881471fdd508e6f2'
162
223
  },
163
224
  'v1000.0.0-larch':
164
225
  {
165
- 'normalized_typical_inference_speed':4.0,
166
- 'url':model_url_base + 'md_v1000.0.0-larch.pt'
226
+ 'url':model_url_base + 'md_v1000.0.0-larch.pt',
227
+ 'normalized_typical_inference_speed':2.4,
228
+ 'md5':'cab94ebd190c2278e12fb70ffd548b6d'
167
229
  },
168
230
  'v1000.0.0-cedar':
169
231
  {
170
- 'normalized_typical_inference_speed':5.0,
171
- 'url':model_url_base + 'md_v1000.0.0-cedar.pt'
232
+ 'url':model_url_base + 'md_v1000.0.0-cedar.pt',
233
+ 'normalized_typical_inference_speed':2.0,
234
+ 'md5':'3d6472c9b95ba687b59ebe255f7c576b'
235
+ },
236
+ 'v1000.0.0-sorrel':
237
+ {
238
+ 'url':model_url_base + 'md_v1000.0.0-sorrel.pt',
239
+ 'normalized_typical_inference_speed':7.0,
240
+ 'md5':'4339a2c8af7a381f18ded7ac2a4df03e'
172
241
  }
173
242
  }
174
243
 
@@ -337,7 +406,8 @@ def get_detector_version_from_model_file(detector_filename,verbose=False):
337
406
 
338
407
  if version_string_based_on_filename != version_string_based_on_model_file:
339
408
  print(
340
- 'Warning: model version string in file:\n\n{}\n\n...is:\n\n{}\n\n...but the filename implies:\n\n{}'.format(
409
+ 'Warning: model version string in file:' + \
410
+ '\n\n{}\n\n...is:\n\n{}\n\n...but the filename implies:\n\n{}'.format(
341
411
  os.path.basename(detector_filename),
342
412
  version_string_based_on_model_file,
343
413
  version_string_based_on_filename))
@@ -602,6 +672,7 @@ def load_and_run_detector(model_file,
602
672
  exists
603
673
  detector_options (dict, optional): key/value pairs that are interpreted differently
604
674
  by different detectors
675
+ verbose (bool, optional): enable additional debug output
605
676
  """
606
677
 
607
678
  if len(image_file_names) == 0:
@@ -707,7 +778,8 @@ def load_and_run_detector(model_file,
707
778
  time_infer.append(elapsed)
708
779
 
709
780
  except Exception as e:
710
- print('An error occurred while running the detector on image {}: {}'.format(im_file, str(e)))
781
+ print('An error occurred while running the detector on image {}: {}'.format(
782
+ im_file, str(e)))
711
783
  continue
712
784
 
713
785
  try:
@@ -756,6 +828,55 @@ def load_and_run_detector(model_file,
756
828
  # ...def load_and_run_detector()
757
829
 
758
830
 
831
+ def _validate_zip_file(file_path, file_description='file'):
832
+ """
833
+ Validates that a .pt file is a valid zip file.
834
+
835
+ Args:
836
+ file_path (str): path to the file to validate
837
+ file_description (str): descriptive string for error messages
838
+
839
+ Returns:
840
+ bool: True if valid, False otherwise
841
+ """
842
+ try:
843
+ with zipfile.ZipFile(file_path, 'r') as zipf:
844
+ zipf.testzip()
845
+ return True
846
+ except (zipfile.BadZipFile, zipfile.LargeZipFile) as e:
847
+ print('{} {} appears to be corrupted (bad zip): {}'.format(
848
+ file_description.capitalize(), file_path, str(e)))
849
+ return False
850
+ except Exception as e:
851
+ print('Error validating {}: {}'.format(file_description, str(e)))
852
+ return False
853
+
854
+
855
+ def _validate_md5_hash(file_path, expected_hash, file_description='file'):
856
+ """
857
+ Validates that a file has the expected MD5 hash.
858
+
859
+ Args:
860
+ file_path (str): path to the file to validate
861
+ expected_hash (str): expected MD5 hash
862
+ file_description (str): descriptive string for error messages
863
+
864
+ Returns:
865
+ bool: True if hash matches, False otherwise
866
+ """
867
+ try:
868
+ actual_hash = compute_file_hash(file_path, algorithm='md5').lower()
869
+ expected_hash = expected_hash.lower()
870
+ if actual_hash != expected_hash:
871
+ print('{} {} has incorrect hash. Expected: {}, Actual: {}'.format(
872
+ file_description.capitalize(), file_path, expected_hash, actual_hash))
873
+ return False
874
+ return True
875
+ except Exception as e:
876
+ print('Error computing hash for {}: {}'.format(file_description, str(e)))
877
+ return False
878
+
879
+
759
880
  def _download_model(model_name,force_download=False):
760
881
  """
761
882
  Downloads one of the known models to local temp space if it hasn't already been downloaded.
@@ -782,13 +903,92 @@ def _download_model(model_name,force_download=False):
782
903
  if model_name.lower() not in known_models:
783
904
  print('Unrecognized downloadable model {}'.format(model_name))
784
905
  return None
785
- url = known_models[model_name.lower()]['url']
906
+
907
+ model_info = known_models[model_name.lower()]
908
+ url = model_info['url']
786
909
  destination_filename = os.path.join(model_tempdir,url.split('/')[-1])
787
- local_file = download_url(url, destination_filename=destination_filename, progress_updater=None,
788
- force_download=force_download, verbose=True)
910
+
911
+ # Check whether the file already exists, in which case we want to validate it
912
+ if os.path.exists(destination_filename) and not force_download:
913
+
914
+ # Only validate .pt files, not .pb files
915
+ if destination_filename.endswith('.pt'):
916
+
917
+ is_valid = True
918
+
919
+ # Check whether the file is a valid zip file (.pt files are zip files in disguise)
920
+ if not _validate_zip_file(destination_filename,
921
+ 'existing model file'):
922
+ is_valid = False
923
+
924
+ # Check MD5 hash if available
925
+ if is_valid and \
926
+ ('md5' in model_info) and \
927
+ (model_info['md5'] is not None) and \
928
+ (len(model_info['md5'].strip()) > 0):
929
+
930
+ if not _validate_md5_hash(destination_filename, model_info['md5'],
931
+ 'existing model file'):
932
+ is_valid = False
933
+
934
+ # If validation failed, delete the corrupted file and re-download
935
+ if not is_valid:
936
+ print('Deleting corrupted model file and re-downloading: {}'.format(
937
+ destination_filename))
938
+ try:
939
+ os.remove(destination_filename)
940
+ # This should be a no-op at this point, but it can't hurt
941
+ force_download = True
942
+ except Exception as e:
943
+ print('Warning: failed to delete corrupted file {}: {}'.format(
944
+ destination_filename, str(e)))
945
+ # Continue with download attempt anyway, setting force_download to True
946
+ force_download = True
947
+ else:
948
+ print('Model {} already exists and is valid at {}'.format(
949
+ model_name, destination_filename))
950
+ return destination_filename
951
+
952
+ # Download the model
953
+ try:
954
+ local_file = download_url(url,
955
+ destination_filename=destination_filename,
956
+ progress_updater=None,
957
+ force_download=force_download,
958
+ verbose=True)
959
+ except Exception as e:
960
+ print('Error downloading model {} from {}: {}'.format(model_name, url, str(e)))
961
+ raise
962
+
963
+ # Validate the downloaded file if it's a .pt file
964
+ if local_file and local_file.endswith('.pt'):
965
+
966
+ # Check if the downloaded file is a valid zip file
967
+ if not _validate_zip_file(local_file, "downloaded model file"):
968
+ # Clean up the corrupted download
969
+ try:
970
+ os.remove(local_file)
971
+ except Exception:
972
+ pass
973
+ return None
974
+
975
+ # Check MD5 hash if available
976
+ if ('md5' in model_info) and \
977
+ (model_info['md5'] is not None) and \
978
+ (len(model_info['md5'].strip()) > 0):
979
+
980
+ if not _validate_md5_hash(local_file, model_info['md5'], "downloaded model file"):
981
+ # Clean up the corrupted download
982
+ try:
983
+ os.remove(local_file)
984
+ except Exception:
985
+ pass
986
+ return None
987
+
789
988
  print('Model {} available at {}'.format(model_name,local_file))
790
989
  return local_file
791
990
 
991
+ # ...def _download_model(...)
792
992
 
793
993
  def try_download_known_detector(detector_file,force_download=False,verbose=False):
794
994
  """
@@ -135,7 +135,9 @@ def _producer_func(q,
135
135
  assert isinstance(preprocessor,str)
136
136
  detector_options = deepcopy(detector_options)
137
137
  detector_options['preprocess_only'] = True
138
- preprocessor = load_detector(preprocessor,detector_options=detector_options,verbose=verbose)
138
+ preprocessor = load_detector(preprocessor,
139
+ detector_options=detector_options,
140
+ verbose=verbose)
139
141
 
140
142
  for im_file in image_files:
141
143
 
@@ -209,7 +211,9 @@ def _consumer_func(q,
209
211
  start_time = time.time()
210
212
 
211
213
  if isinstance(model_file,str):
212
- detector = load_detector(model_file,detector_options=detector_options,verbose=verbose)
214
+ detector = load_detector(model_file,
215
+ detector_options=detector_options,
216
+ verbose=verbose)
213
217
  elapsed = time.time() - start_time
214
218
  print('Loaded model (before queueing) in {}, printing updates every {} images'.format(
215
219
  humanfriendly.format_timespan(elapsed),n_queue_print))
@@ -323,7 +327,7 @@ def run_detector_with_image_queue(image_files,
323
327
  confidence_threshold (float): minimum confidence detection to include in
324
328
  output
325
329
  quiet (bool, optional): suppress per-image console printouts
326
- image_size (tuple, optional): image size to use for inference, only mess with this
330
+ image_size (int, optional): image size to use for inference, only mess with this
327
331
  if (a) you're using a model other than MegaDetector or (b) you know what you're
328
332
  doing
329
333
  include_image_size (bool, optional): should we include image size in the output for each image?
@@ -333,6 +337,8 @@ def run_detector_with_image_queue(image_files,
333
337
  detector_options (dict, optional): key/value pairs that are interpreted differently
334
338
  by different detectors
335
339
  loader_workers (int, optional): number of loaders to use
340
+ preprocess_on_image_queue (bool, optional): if the image queue is enabled, should it handle
341
+ image loading and preprocessing (True), or just image loading (False)?
336
342
 
337
343
  Returns:
338
344
  list: list of dicts in the format returned by process_image()
@@ -344,6 +350,9 @@ def run_detector_with_image_queue(image_files,
344
350
  if loader_workers <= 0:
345
351
  loader_workers = 1
346
352
 
353
+ if detector_options is None:
354
+ detector_options = {}
355
+
347
356
  q = multiprocessing.JoinableQueue(max_queue_size)
348
357
  return_queue = multiprocessing.Queue(1)
349
358
 
@@ -492,13 +501,13 @@ def process_images(im_files,
492
501
  Runs a detector (typically MegaDetector) over a list of image files on a single thread.
493
502
 
494
503
  Args:
495
- im_files (list: paths to image files
504
+ im_files (list): paths to image files
496
505
  detector (str or detector object): loaded model or str; if this is a string, it can be a
497
506
  path to a .pb/.pt model file or a known model identifier (e.g. "MDV5A")
498
507
  confidence_threshold (float): only detections above this threshold are returned
499
508
  use_image_queue (bool, optional): separate image loading onto a dedicated worker process
500
509
  quiet (bool, optional): suppress per-image printouts
501
- image_size (tuple, optional): image size to use for inference, only mess with this
510
+ image_size (int, optional): image size to use for inference, only mess with this
502
511
  if (a) you're using a model other than MegaDetector or (b) you know what you're
503
512
  doing
504
513
  checkpoint_queue (Queue, optional): internal parameter used to pass image queues around
@@ -509,6 +518,8 @@ def process_images(im_files,
509
518
  detector_options (dict, optional): key/value pairs that are interpreted differently
510
519
  by different detectors
511
520
  loader_workers (int, optional): number of loaders to use (only relevant when using image queue)
521
+ preprocess_on_image_queue (bool, optional): if the image queue is enabled, should it handle
522
+ image loading and preprocessing (True), or just image loading (False)?
512
523
 
513
524
  Returns:
514
525
  list: list of dicts, in which each dict represents detections on one image,
@@ -518,10 +529,15 @@ def process_images(im_files,
518
529
  if isinstance(detector, str):
519
530
 
520
531
  start_time = time.time()
521
- detector = load_detector(detector,detector_options=detector_options,verbose=verbose)
532
+ detector = load_detector(detector,
533
+ detector_options=detector_options,
534
+ verbose=verbose)
522
535
  elapsed = time.time() - start_time
523
536
  print('Loaded model (batch level) in {}'.format(humanfriendly.format_timespan(elapsed)))
524
537
 
538
+ if detector_options is None:
539
+ detector_options = {}
540
+
525
541
  if use_image_queue:
526
542
 
527
543
  run_detector_with_image_queue(im_files,
@@ -582,7 +598,7 @@ def process_image(im_file,
582
598
  image (Image, optional): previously-loaded image, if available, used when a worker
583
599
  thread is handling image loads
584
600
  quiet (bool, optional): suppress per-image printouts
585
- image_size (tuple, optional): image size to use for inference, only mess with this
601
+ image_size (int, optional): image size to use for inference, only mess with this
586
602
  if (a) you're using a model other than MegaDetector or (b) you know what you're
587
603
  doing
588
604
  include_image_size (bool, optional): should we include image size in the output for each image?
@@ -705,7 +721,7 @@ def load_and_run_detector_batch(model_file,
705
721
  image_file_names (list or str): list of strings (image filenames), a single image filename,
706
722
  a folder to recursively search for images in, or a .json or .txt file containing a list
707
723
  of images.
708
- checkpoint_path (str, optional), path to use for checkpoints (if None, checkpointing
724
+ checkpoint_path (str, optional): path to use for checkpoints (if None, checkpointing
709
725
  is disabled)
710
726
  confidence_threshold (float, optional): only detections above this threshold are returned
711
727
  checkpoint_frequency (int, optional): int, write results to JSON checkpoint file every N
@@ -715,10 +731,10 @@ def load_and_run_detector_batch(model_file,
715
731
  n_cores (int, optional): number of parallel worker to use, ignored if we're running on a GPU
716
732
  use_image_queue (bool, optional): use a dedicated worker for image loading
717
733
  quiet (bool, optional): disable per-image console output
718
- image_size (tuple, optional): image size to use for inference, only mess with this
734
+ image_size (int, optional): image size to use for inference, only mess with this
719
735
  if (a) you're using a model other than MegaDetector or (b) you know what you're
720
736
  doing
721
- class_mapping_filename (str, optional), use a non-default class mapping supplied in a .json
737
+ class_mapping_filename (str, optional): use a non-default class mapping supplied in a .json
722
738
  file or YOLOv5 dataset.yaml file
723
739
  include_image_size (bool, optional): should we include image size in the output for each image?
724
740
  include_image_timestamp (bool, optional): should we include image timestamps in the output for each image?
@@ -730,6 +746,8 @@ def load_and_run_detector_batch(model_file,
730
746
  detector_options (dict, optional): key/value pairs that are interpreted differently
731
747
  by different detectors
732
748
  loader_workers (int, optional): number of loaders to use, only relevant when use_image_queue is True
749
+ preprocess_on_image_queue (bool, optional): if the image queue is enabled, should it handle
750
+ image loading and preprocessing (True), or just image loading (False)?
733
751
 
734
752
  Returns:
735
753
  results: list of dicts; each dict represents detections on one image
@@ -739,6 +757,9 @@ def load_and_run_detector_batch(model_file,
739
757
  if n_cores is None or n_cores <= 0:
740
758
  n_cores = 1
741
759
 
760
+ if detector_options is None:
761
+ detector_options = {}
762
+
742
763
  if confidence_threshold is None:
743
764
  confidence_threshold=run_detector.DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD
744
765
 
@@ -790,17 +811,19 @@ def load_and_run_detector_batch(model_file,
790
811
 
791
812
  already_processed = set([i['file'] for i in results])
792
813
 
793
- model_file = try_download_known_detector(model_file, force_download=force_model_download)
814
+ model_file = try_download_known_detector(model_file,
815
+ force_download=force_model_download,
816
+ verbose=verbose)
794
817
 
795
818
  print('GPU available: {}'.format(is_gpu_available(model_file)))
796
819
 
797
- if n_cores > 1 and is_gpu_available(model_file):
820
+ if (n_cores > 1) and is_gpu_available(model_file):
798
821
 
799
822
  print('Warning: multiple cores requested, but a GPU is available; parallelization across ' + \
800
823
  'GPUs is not currently supported, defaulting to one GPU')
801
824
  n_cores = 1
802
825
 
803
- if n_cores > 1 and use_image_queue:
826
+ if (n_cores > 1) and use_image_queue:
804
827
 
805
828
  print('Warning: multiple cores requested, but the image queue is enabled; parallelization ' + \
806
829
  'with the image queue is not currently supported, defaulting to one worker')
@@ -830,7 +853,9 @@ def load_and_run_detector_batch(model_file,
830
853
 
831
854
  # Load the detector
832
855
  start_time = time.time()
833
- detector = load_detector(model_file,detector_options=detector_options,verbose=verbose)
856
+ detector = load_detector(model_file,
857
+ detector_options=detector_options,
858
+ verbose=verbose)
834
859
  elapsed = time.time() - start_time
835
860
  print('Loaded model in {}'.format(humanfriendly.format_timespan(elapsed)))
836
861
 
@@ -1089,7 +1114,7 @@ def write_results_to_file(results,
1089
1114
 
1090
1115
  if detector_file is not None:
1091
1116
  detector_filename = os.path.basename(detector_file)
1092
- detector_version = get_detector_version_from_filename(detector_filename)
1117
+ detector_version = get_detector_version_from_filename(detector_filename,verbose=True)
1093
1118
  detector_metadata = get_detector_metadata_from_version_string(detector_version)
1094
1119
  info['detector'] = detector_filename
1095
1120
  info['detector_metadata'] = detector_metadata
@@ -1409,7 +1434,8 @@ def main(): # noqa
1409
1434
  # If the specified detector file is really the name of a known model, find
1410
1435
  # (and possibly download) that model
1411
1436
  args.detector_file = try_download_known_detector(args.detector_file,
1412
- force_download=args.force_model_download)
1437
+ force_download=args.force_model_download,
1438
+ verbose=verbose)
1413
1439
 
1414
1440
  assert os.path.exists(args.detector_file), \
1415
1441
  'detector file {} does not exist'.format(args.detector_file)
@@ -191,6 +191,10 @@ class YoloInferenceOptions:
191
191
  #: across calls, this can be set to False.
192
192
  self.append_job_id_to_symlink_folder = True
193
193
 
194
+ #: By default, we turn category ID 0 coming out of the YOLO .json file
195
+ #: into category 1 in the MD-formatted .json file.
196
+ self.offset_yolo_category_ids = True
197
+
194
198
  # ...def __init__()
195
199
 
196
200
  # ...YoloInferenceOptions()
@@ -225,7 +229,7 @@ def get_stats_for_category(filename,category='all'):
225
229
 
226
230
  Args:
227
231
  filename (str): a text file containing console output from a YOLO val run
228
- category (optional, str): a category name
232
+ category (str, optional): a category name
229
233
 
230
234
  Returns:
231
235
  dict: a dict with fields n_images, n_labels, P, R, mAP50, and mAP50-95
@@ -957,7 +961,8 @@ def run_inference_with_yolo_val(options):
957
961
  yolo_category_id_to_name=options.yolo_category_id_to_name,
958
962
  detector_name=os.path.basename(model_filename),
959
963
  image_id_to_relative_path=image_id_to_relative_path,
960
- image_id_to_error=image_id_to_error)
964
+ image_id_to_error=image_id_to_error,
965
+ offset_yolo_class_ids=options.offset_yolo_category_ids)
961
966
 
962
967
 
963
968
  ##%% Clean up
@@ -1058,6 +1063,9 @@ def main(): # noqa
1058
1063
  parser.add_argument(
1059
1064
  '--nonrecursive', action='store_true',
1060
1065
  help='disable recursive folder processing')
1066
+ parser.add_argument(
1067
+ '--no_offset_class_ids', action='store_true',
1068
+ help='disable class ID offsetting')
1061
1069
 
1062
1070
  parser.add_argument(
1063
1071
  '--preview_yolo_command_only', action='store_true',
@@ -1113,6 +1121,7 @@ def main(): # noqa
1113
1121
  options.remove_yolo_results_folder = (not options.no_remove_yolo_results_folder)
1114
1122
  options.use_symlinks = (not options.no_use_symlinks)
1115
1123
  options.augment = (options.augment_enabled > 0)
1124
+ options.offset_yolo_category_ids = (not options.no_offset_class_ids)
1116
1125
 
1117
1126
  del options.nonrecursive
1118
1127
  del options.no_remove_symlink_folder
@@ -1120,6 +1129,7 @@ def main(): # noqa
1120
1129
  del options.no_use_symlinks
1121
1130
  del options.augment_enabled
1122
1131
  del options.yolo_dataset_file
1132
+ del options.no_offset_class_ids
1123
1133
 
1124
1134
  print(options.__dict__)
1125
1135
 
@@ -447,6 +447,7 @@ def run_tiled_inference(model_file,
447
447
  run_inference_with_yolov5_val.py, rather than with run_detector_batch.py, using these options
448
448
  n_patch_extraction_workers (int, optional): number of workers to use for patch extraction;
449
449
  set to <= 1 to disable parallelization
450
+ overwrite_tiles (bool, optional): whether to overwrite image files for individual tiles if they exist
450
451
  image_list (list, optional): .json file containing a list of specific images to process. If
451
452
  this is supplied, and the paths are absolute, [image_folder] will be ignored. If this is supplied,
452
453
  and the paths are relative, they should be relative to [image_folder]