megadetector 5.0.28__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 (197) hide show
  1. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
  2. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
  3. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
  4. megadetector/classification/aggregate_classifier_probs.py +3 -3
  5. megadetector/classification/analyze_failed_images.py +5 -5
  6. megadetector/classification/cache_batchapi_outputs.py +5 -5
  7. megadetector/classification/create_classification_dataset.py +11 -12
  8. megadetector/classification/crop_detections.py +10 -10
  9. megadetector/classification/csv_to_json.py +8 -8
  10. megadetector/classification/detect_and_crop.py +13 -15
  11. megadetector/classification/efficientnet/model.py +8 -8
  12. megadetector/classification/efficientnet/utils.py +6 -5
  13. megadetector/classification/evaluate_model.py +7 -7
  14. megadetector/classification/identify_mislabeled_candidates.py +6 -6
  15. megadetector/classification/json_to_azcopy_list.py +1 -1
  16. megadetector/classification/json_validator.py +29 -32
  17. megadetector/classification/map_classification_categories.py +9 -9
  18. megadetector/classification/merge_classification_detection_output.py +12 -9
  19. megadetector/classification/prepare_classification_script.py +19 -19
  20. megadetector/classification/prepare_classification_script_mc.py +26 -26
  21. megadetector/classification/run_classifier.py +4 -4
  22. megadetector/classification/save_mislabeled.py +6 -6
  23. megadetector/classification/train_classifier.py +1 -1
  24. megadetector/classification/train_classifier_tf.py +9 -9
  25. megadetector/classification/train_utils.py +10 -10
  26. megadetector/data_management/annotations/annotation_constants.py +1 -2
  27. megadetector/data_management/camtrap_dp_to_coco.py +79 -46
  28. megadetector/data_management/cct_json_utils.py +103 -103
  29. megadetector/data_management/cct_to_md.py +49 -49
  30. megadetector/data_management/cct_to_wi.py +33 -33
  31. megadetector/data_management/coco_to_labelme.py +75 -75
  32. megadetector/data_management/coco_to_yolo.py +210 -193
  33. megadetector/data_management/databases/add_width_and_height_to_db.py +86 -12
  34. megadetector/data_management/databases/combine_coco_camera_traps_files.py +40 -40
  35. megadetector/data_management/databases/integrity_check_json_db.py +228 -200
  36. megadetector/data_management/databases/subset_json_db.py +33 -33
  37. megadetector/data_management/generate_crops_from_cct.py +88 -39
  38. megadetector/data_management/get_image_sizes.py +54 -49
  39. megadetector/data_management/labelme_to_coco.py +133 -125
  40. megadetector/data_management/labelme_to_yolo.py +159 -73
  41. megadetector/data_management/lila/create_lila_blank_set.py +81 -83
  42. megadetector/data_management/lila/create_lila_test_set.py +32 -31
  43. megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
  44. megadetector/data_management/lila/download_lila_subset.py +21 -24
  45. megadetector/data_management/lila/generate_lila_per_image_labels.py +365 -107
  46. megadetector/data_management/lila/get_lila_annotation_counts.py +35 -33
  47. megadetector/data_management/lila/get_lila_image_counts.py +22 -22
  48. megadetector/data_management/lila/lila_common.py +73 -70
  49. megadetector/data_management/lila/test_lila_metadata_urls.py +28 -19
  50. megadetector/data_management/mewc_to_md.py +344 -340
  51. megadetector/data_management/ocr_tools.py +262 -255
  52. megadetector/data_management/read_exif.py +249 -227
  53. megadetector/data_management/remap_coco_categories.py +90 -28
  54. megadetector/data_management/remove_exif.py +81 -21
  55. megadetector/data_management/rename_images.py +187 -187
  56. megadetector/data_management/resize_coco_dataset.py +588 -120
  57. megadetector/data_management/speciesnet_to_md.py +41 -41
  58. megadetector/data_management/wi_download_csv_to_coco.py +55 -55
  59. megadetector/data_management/yolo_output_to_md_output.py +248 -122
  60. megadetector/data_management/yolo_to_coco.py +333 -191
  61. megadetector/detection/change_detection.py +832 -0
  62. megadetector/detection/process_video.py +340 -337
  63. megadetector/detection/pytorch_detector.py +358 -278
  64. megadetector/detection/run_detector.py +399 -186
  65. megadetector/detection/run_detector_batch.py +404 -377
  66. megadetector/detection/run_inference_with_yolov5_val.py +340 -327
  67. megadetector/detection/run_tiled_inference.py +257 -249
  68. megadetector/detection/tf_detector.py +24 -24
  69. megadetector/detection/video_utils.py +332 -295
  70. megadetector/postprocessing/add_max_conf.py +19 -11
  71. megadetector/postprocessing/categorize_detections_by_size.py +45 -45
  72. megadetector/postprocessing/classification_postprocessing.py +468 -433
  73. megadetector/postprocessing/combine_batch_outputs.py +23 -23
  74. megadetector/postprocessing/compare_batch_results.py +590 -525
  75. megadetector/postprocessing/convert_output_format.py +106 -102
  76. megadetector/postprocessing/create_crop_folder.py +347 -147
  77. megadetector/postprocessing/detector_calibration.py +173 -168
  78. megadetector/postprocessing/generate_csv_report.py +508 -499
  79. megadetector/postprocessing/load_api_results.py +48 -27
  80. megadetector/postprocessing/md_to_coco.py +133 -102
  81. megadetector/postprocessing/md_to_labelme.py +107 -90
  82. megadetector/postprocessing/md_to_wi.py +40 -40
  83. megadetector/postprocessing/merge_detections.py +92 -114
  84. megadetector/postprocessing/postprocess_batch_results.py +319 -301
  85. megadetector/postprocessing/remap_detection_categories.py +91 -38
  86. megadetector/postprocessing/render_detection_confusion_matrix.py +214 -205
  87. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
  88. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
  89. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +704 -679
  90. megadetector/postprocessing/separate_detections_into_folders.py +226 -211
  91. megadetector/postprocessing/subset_json_detector_output.py +265 -262
  92. megadetector/postprocessing/top_folders_to_bottom.py +45 -45
  93. megadetector/postprocessing/validate_batch_results.py +70 -70
  94. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
  95. megadetector/taxonomy_mapping/map_new_lila_datasets.py +18 -19
  96. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +54 -33
  97. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +67 -67
  98. megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
  99. megadetector/taxonomy_mapping/simple_image_download.py +8 -8
  100. megadetector/taxonomy_mapping/species_lookup.py +156 -74
  101. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
  102. megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
  103. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
  104. megadetector/utils/ct_utils.py +1049 -211
  105. megadetector/utils/directory_listing.py +21 -77
  106. megadetector/utils/gpu_test.py +22 -22
  107. megadetector/utils/md_tests.py +632 -529
  108. megadetector/utils/path_utils.py +1520 -431
  109. megadetector/utils/process_utils.py +41 -41
  110. megadetector/utils/split_locations_into_train_val.py +62 -62
  111. megadetector/utils/string_utils.py +148 -27
  112. megadetector/utils/url_utils.py +489 -176
  113. megadetector/utils/wi_utils.py +2658 -2526
  114. megadetector/utils/write_html_image_list.py +137 -137
  115. megadetector/visualization/plot_utils.py +34 -30
  116. megadetector/visualization/render_images_with_thumbnails.py +39 -74
  117. megadetector/visualization/visualization_utils.py +487 -435
  118. megadetector/visualization/visualize_db.py +232 -198
  119. megadetector/visualization/visualize_detector_output.py +82 -76
  120. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/METADATA +5 -2
  121. megadetector-10.0.0.dist-info/RECORD +139 -0
  122. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/WHEEL +1 -1
  123. megadetector/api/batch_processing/api_core/__init__.py +0 -0
  124. megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  125. megadetector/api/batch_processing/api_core/batch_service/score.py +0 -439
  126. megadetector/api/batch_processing/api_core/server.py +0 -294
  127. megadetector/api/batch_processing/api_core/server_api_config.py +0 -97
  128. megadetector/api/batch_processing/api_core/server_app_config.py +0 -55
  129. megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -220
  130. megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -149
  131. megadetector/api/batch_processing/api_core/server_orchestration.py +0 -360
  132. megadetector/api/batch_processing/api_core/server_utils.py +0 -88
  133. megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  134. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
  135. megadetector/api/batch_processing/api_support/__init__.py +0 -0
  136. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -152
  137. megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  138. megadetector/api/synchronous/__init__.py +0 -0
  139. megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  140. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -151
  141. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -263
  142. megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -35
  143. megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  144. megadetector/api/synchronous/api_core/tests/load_test.py +0 -110
  145. megadetector/data_management/importers/add_nacti_sizes.py +0 -52
  146. megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
  147. megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
  148. megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
  149. megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
  150. megadetector/data_management/importers/awc_to_json.py +0 -191
  151. megadetector/data_management/importers/bellevue_to_json.py +0 -272
  152. megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
  153. megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
  154. megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
  155. megadetector/data_management/importers/cct_field_adjustments.py +0 -58
  156. megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
  157. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  158. megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
  159. megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
  160. megadetector/data_management/importers/ena24_to_json.py +0 -276
  161. megadetector/data_management/importers/filenames_to_json.py +0 -386
  162. megadetector/data_management/importers/helena_to_cct.py +0 -283
  163. megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
  164. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  165. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
  166. megadetector/data_management/importers/jb_csv_to_json.py +0 -150
  167. megadetector/data_management/importers/mcgill_to_json.py +0 -250
  168. megadetector/data_management/importers/missouri_to_json.py +0 -490
  169. megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
  170. megadetector/data_management/importers/noaa_seals_2019.py +0 -181
  171. megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
  172. megadetector/data_management/importers/pc_to_json.py +0 -365
  173. megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
  174. megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
  175. megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
  176. megadetector/data_management/importers/rspb_to_json.py +0 -356
  177. megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
  178. megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
  179. megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
  180. megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
  181. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  182. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  183. megadetector/data_management/importers/sulross_get_exif.py +0 -65
  184. megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
  185. megadetector/data_management/importers/ubc_to_json.py +0 -399
  186. megadetector/data_management/importers/umn_to_json.py +0 -507
  187. megadetector/data_management/importers/wellington_to_json.py +0 -263
  188. megadetector/data_management/importers/wi_to_json.py +0 -442
  189. megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
  190. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
  191. megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
  192. megadetector/utils/azure_utils.py +0 -178
  193. megadetector/utils/sas_blob_utils.py +0 -509
  194. megadetector-5.0.28.dist-info/RECORD +0 -209
  195. /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
  196. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
  197. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/top_level.txt +0 -0
@@ -44,55 +44,55 @@ class TopFoldersToBottomOptions:
44
44
  """
45
45
  Options used to parameterize top_folders_to_bottom()
46
46
  """
47
-
47
+
48
48
  def __init__(self,input_folder,output_folder,copy=True,n_threads=1):
49
-
49
+
50
50
  #: Whether to copy (True) vs. move (False) false when re-organizing
51
51
  self.copy = copy
52
-
52
+
53
53
  #: Number of worker threads to use, or <1 to disable parallelization
54
54
  self.n_threads = n_threads
55
-
55
+
56
56
  #: Input folder
57
57
  self.input_folder = input_folder
58
-
58
+
59
59
  #: Output folder
60
60
  self.output_folder = output_folder
61
-
61
+
62
62
  #: If this is False and an output file exists, throw an error
63
63
  self.overwrite = False
64
-
64
+
65
65
 
66
66
  #%% Main functions
67
67
 
68
68
  def _process_file(relative_filename,options,execute=True):
69
-
69
+
70
70
  assert ('/' in relative_filename) and \
71
71
  ('\\' not in relative_filename) and \
72
72
  (not path_is_abs(relative_filename))
73
-
73
+
74
74
  # Find top-level folder
75
75
  tokens = relative_filename.split('/')
76
76
  topmost_folder = tokens.pop(0)
77
77
  tokens.insert(len(tokens)-1,topmost_folder)
78
-
78
+
79
79
  # Find file/folder names
80
80
  output_relative_path = '/'.join(tokens)
81
81
  output_relative_folder = '/'.join(tokens[0:-1])
82
-
82
+
83
83
  output_absolute_folder = os.path.join(options.output_folder,output_relative_folder)
84
84
  output_absolute_path = os.path.join(options.output_folder,output_relative_path)
85
85
 
86
86
  if execute:
87
-
87
+
88
88
  os.makedirs(output_absolute_folder,exist_ok=True)
89
-
89
+
90
90
  input_absolute_path = os.path.join(options.input_folder,relative_filename)
91
-
91
+
92
92
  if not options.overwrite:
93
93
  assert not os.path.isfile(output_absolute_path), \
94
94
  'Error: output file {} exists'.format(output_absolute_path)
95
-
95
+
96
96
  # Move or copy
97
97
  if options.copy:
98
98
  shutil.copy(input_absolute_path, output_absolute_path)
@@ -100,7 +100,7 @@ def _process_file(relative_filename,options,execute=True):
100
100
  shutil.move(input_absolute_path, output_absolute_path)
101
101
 
102
102
  return output_absolute_path
103
-
103
+
104
104
  # ...def _process_file()
105
105
 
106
106
 
@@ -131,93 +131,93 @@ def top_folders_to_bottom(options):
131
131
 
132
132
  """
133
133
  os.makedirs(options.output_folder,exist_ok=True)
134
-
134
+
135
135
  # Enumerate input folder
136
136
  print('Enumerating files...')
137
137
  files = list(Path(options.input_folder).rglob('*'))
138
138
  files = [p for p in files if not p.is_dir()]
139
139
  files = [str(s) for s in files]
140
140
  print('Enumerated {} files'.format(len(files)))
141
-
141
+
142
142
  # Convert absolute paths to relative paths
143
143
  relative_files = [os.path.relpath(s,options.input_folder) for s in files]
144
-
144
+
145
145
  # Standardize delimiters
146
146
  relative_files = [s.replace('\\','/') for s in relative_files]
147
-
147
+
148
148
  base_files = [s for s in relative_files if '/' not in s]
149
149
  if len(base_files) > 0:
150
150
  print('Warning: ignoring {} files in the base folder'.format(len(base_files)))
151
151
  relative_files = [s for s in relative_files if '/' in s]
152
-
152
+
153
153
  # Make sure each input file maps to a unique output file
154
154
  absolute_output_files = [_process_file(s, options, execute=False) for s in relative_files]
155
155
  assert len(absolute_output_files) == len(set(absolute_output_files)),\
156
156
  "Error: input filenames don't map to unique output filenames"
157
-
157
+
158
158
  # relative_filename = relative_files[0]
159
-
159
+
160
160
  # Loop
161
161
  if options.n_threads <= 1:
162
-
162
+
163
163
  for relative_filename in tqdm(relative_files):
164
164
  _process_file(relative_filename,options)
165
-
165
+
166
166
  else:
167
-
167
+
168
168
  print('Starting a pool with {} threads'.format(options.n_threads))
169
169
  pool = ThreadPool(options.n_threads)
170
170
  process_file_with_options = partial(_process_file, options=options)
171
171
  _ = list(tqdm(pool.imap(process_file_with_options, relative_files), total=len(relative_files)))
172
172
 
173
- # ...def top_folders_to_bottom()
174
-
173
+ # ...def top_folders_to_bottom()
174
+
175
175
 
176
176
  #%% Interactive driver
177
-
177
+
178
178
  if False:
179
179
 
180
180
  pass
181
181
 
182
182
  #%%
183
-
183
+
184
184
  input_folder = r"G:\temp\output"
185
- output_folder = r"G:\temp\output-inverted"
185
+ output_folder = r"G:\temp\output-inverted"
186
186
  options = TopFoldersToBottomOptions(input_folder,output_folder,copy=True,n_threads=10)
187
-
187
+
188
188
  #%%
189
-
189
+
190
190
  top_folders_to_bottom(options)
191
191
 
192
192
 
193
- #%% Command-line driver
193
+ #%% Command-line driver
194
194
 
195
195
  # python top_folders_to_bottom.py "g:\temp\separated_images" "g:\temp\separated_images_inverted" --n_threads 100
196
196
 
197
- def main():
198
-
197
+ def main(): # noqa
198
+
199
199
  parser = argparse.ArgumentParser()
200
200
  parser.add_argument('input_folder', type=str, help='Input image folder')
201
201
  parser.add_argument('output_folder', type=str, help='Output image folder')
202
202
 
203
- parser.add_argument('--copy', action='store_true',
203
+ parser.add_argument('--copy', action='store_true',
204
204
  help='Copy images, instead of moving (moving is the default)')
205
- parser.add_argument('--overwrite', action='store_true',
205
+ parser.add_argument('--overwrite', action='store_true',
206
206
  help='Allow image overwrite (default=False)')
207
207
  parser.add_argument('--n_threads', type=int, default=1,
208
208
  help='Number of threads to use for parallel operation (default=1)')
209
-
209
+
210
210
  if len(sys.argv[1:])==0:
211
211
  parser.print_help()
212
212
  parser.exit()
213
-
214
- args = parser.parse_args()
215
-
213
+
214
+ args = parser.parse_args()
215
+
216
216
  # Convert to an options object
217
217
  options = TopFoldersToBottomOptions(
218
218
  args.input_folder,args.output_folder,copy=args.copy,n_threads=args.n_threads)
219
-
219
+
220
220
  top_folders_to_bottom(options)
221
-
222
- if __name__ == '__main__':
221
+
222
+ if __name__ == '__main__':
223
223
  main()
@@ -41,27 +41,27 @@ class ValidateBatchResultsOptions:
41
41
  """
42
42
  Options controlling the behavior of validate_bach_results()
43
43
  """
44
-
44
+
45
45
  def __init__(self):
46
-
46
+
47
47
  #: Should we verify that images exist? If this is True, and the .json
48
48
  #: file contains relative paths, relative_path_base needs to be specified.
49
49
  self.check_image_existence = False
50
-
50
+
51
51
  #: If check_image_existence is True, where do the images live?
52
52
  #:
53
53
  #: If None, assumes absolute paths.
54
54
  self.relative_path_base = None
55
-
55
+
56
56
  #: Should we return the loaded data, or just the validation results?
57
57
  self.return_data = False
58
-
58
+
59
59
  #: Enable additional debug output
60
60
  self.verbose = False
61
-
61
+
62
62
  #: Should we raise errors immediately (vs. just catching and reporting)?
63
63
  self.raise_errors = False
64
-
64
+
65
65
  # ...class ValidateBatchResultsOptions
66
66
 
67
67
 
@@ -70,67 +70,67 @@ class ValidateBatchResultsOptions:
70
70
  def validate_batch_results(json_filename,options=None):
71
71
  """
72
72
  Verify that [json_filename] is a valid MD output file. Currently errors on invalid files.
73
-
74
- Args:
73
+
74
+ Args:
75
75
  json_filename (str): the filename to validate
76
- options (ValidateBatchResultsOptions, optional): all the parameters used to control this
76
+ options (ValidateBatchResultsOptions, optional): all the parameters used to control this
77
77
  process, see ValidateBatchResultsOptions for details
78
-
78
+
79
79
  Returns:
80
80
  dict: a dict with a field called "validation_results", which is itself a dict. The reason
81
- it's a dict inside a dict is that if return_data is True, the outer dict also contains all
81
+ it's a dict inside a dict is that if return_data is True, the outer dict also contains all
82
82
  the loaded data. The "validation_results" dict contains fields called "errors", "warnings",
83
83
  and "filename". "errors" and "warnings" are lists of strings, although "errors" will never
84
84
  be longer than N=1, since validation fails at the first error.
85
-
85
+
86
86
  """
87
-
87
+
88
88
  if options is None:
89
89
  options = ValidateBatchResultsOptions()
90
-
90
+
91
91
  if options.verbose:
92
92
  print('Loading results from {}'.format(json_filename))
93
-
93
+
94
94
  with open(json_filename,'r') as f:
95
95
  d = json.load(f)
96
-
96
+
97
97
  validation_results = {}
98
98
  validation_results['filename'] = json_filename
99
99
  validation_results['warnings'] = []
100
100
  validation_results['errors'] = []
101
-
101
+
102
102
  if not isinstance(d,dict):
103
-
103
+
104
104
  validation_results['errors'].append('Input data is not a dict')
105
105
  to_return = {}
106
106
  to_return['validation_results'] = validation_results
107
107
  return to_return
108
-
108
+
109
109
  try:
110
-
110
+
111
111
  ## Info validation
112
-
113
- if not 'info' in d:
112
+
113
+ if 'info' not in d:
114
114
  raise ValueError('Input does not contain info field')
115
-
115
+
116
116
  info = d['info']
117
-
117
+
118
118
  if not isinstance(info,dict):
119
119
  raise ValueError('Input contains invalid info field')
120
-
120
+
121
121
  if 'format_version' not in info :
122
122
  raise ValueError('Input does not specify format version')
123
-
123
+
124
124
  format_version = float(info['format_version'])
125
125
  if format_version < 1.3:
126
126
  raise ValueError('This validator can only be used with format version 1.3 or later')
127
-
128
-
127
+
128
+
129
129
  ## Category validation
130
-
130
+
131
131
  if 'detection_categories' not in d:
132
132
  raise ValueError('Input does not contain detection_categories field')
133
-
133
+
134
134
  for k in d['detection_categories'].keys():
135
135
  # Category ID should be string-formatted ints
136
136
  if not isinstance(k,str):
@@ -139,7 +139,7 @@ def validate_batch_results(json_filename,options=None):
139
139
  if not isinstance(d['detection_categories'][k],str):
140
140
  raise ValueError('Invalid detection category name: {}'.format(
141
141
  d['detection_categories'][k]))
142
-
142
+
143
143
  if 'classification_categories' in d:
144
144
  for k in d['classification_categories'].keys():
145
145
  # Categories should be string-formatted ints
@@ -149,28 +149,28 @@ def validate_batch_results(json_filename,options=None):
149
149
  if not isinstance(d['classification_categories'][k],str):
150
150
  raise ValueError('Invalid classification category name: {}'.format(
151
151
  d['classification_categories'][k]))
152
-
153
-
152
+
153
+
154
154
  ## Image validation
155
-
155
+
156
156
  if 'images' not in d:
157
157
  raise ValueError('images field not present')
158
158
  if not isinstance(d['images'],list):
159
159
  raise ValueError('Invalid images field')
160
-
160
+
161
161
  if options.verbose:
162
162
  print('Validating images')
163
-
163
+
164
164
  # im = d['images'][0]
165
165
  for i_im,im in tqdm(enumerate(d['images']),total=len(d['images']),disable=(not options.verbose)):
166
-
166
+
167
167
  if not isinstance(im,dict):
168
168
  raise ValueError('Invalid image at index {}'.format(i_im))
169
169
  if 'file' not in im:
170
170
  raise ValueError('Image without filename at index {}'.format(i_im))
171
-
171
+
172
172
  file = im['file']
173
-
173
+
174
174
  if 'detections' in im and im['detections'] is not None:
175
175
  for det in im['detections']:
176
176
  assert 'category' in det, 'Image {} has a detection with no category'.format(file)
@@ -180,17 +180,17 @@ def validate_batch_results(json_filename,options=None):
180
180
  assert 'bbox' in det, 'Image {} has a detection with no box'.format(file)
181
181
  assert det['category'] in d['detection_categories'], \
182
182
  'Image {} has a detection with an unmapped category {}'.format(
183
- file,det['category'])
184
-
183
+ file,det['category'])
184
+
185
185
  if options.check_image_existence:
186
-
186
+
187
187
  if options.relative_path_base is None:
188
188
  file_abs = file
189
189
  else:
190
190
  file_abs = os.path.join(options.relative_path_base,file)
191
191
  if not os.path.isfile(file_abs):
192
192
  raise ValueError('Cannot find file {}'.format(file_abs))
193
-
193
+
194
194
  if 'failure' in im:
195
195
  if im['failure'] is not None:
196
196
  if not isinstance(im['failure'],str):
@@ -206,9 +206,9 @@ def validate_batch_results(json_filename,options=None):
206
206
  else:
207
207
  if not isinstance(im['detections'],list):
208
208
  raise ValueError('Invalid detections list for image {}'.format(im['file']))
209
-
209
+
210
210
  if is_video_file(im['file']) and (format_version >= 1.4):
211
-
211
+
212
212
  if 'frame_rate' not in im:
213
213
  raise ValueError('Video without frame rate: {}'.format(im['file']))
214
214
  if im['frame_rate'] < 0:
@@ -220,34 +220,34 @@ def validate_batch_results(json_filename,options=None):
220
220
  raise ValueError('Frame without frame number in video {}'.format(
221
221
  im['file']))
222
222
  frame_numbers = [det['frame_number'] for det in im['detections']] # noqa
223
- # assert is_list_sorted(frame_numbers)
224
-
223
+ # assert is_list_sorted(frame_numbers)
224
+
225
225
  # ...for each image
226
-
227
-
226
+
227
+
228
228
  ## Validation of other keys
229
-
230
- for k in d.keys():
229
+
230
+ for k in d.keys():
231
231
  if (k not in typical_keys) and (k not in required_keys):
232
232
  validation_results['warnings'].append(
233
- 'Warning: non-standard key {} present at file level'.format(k))
234
-
233
+ 'Warning: non-standard key {} present at file level'.format(k))
234
+
235
235
  except Exception as e:
236
-
236
+
237
237
  if options.raise_errors:
238
238
  raise
239
239
  else:
240
240
  validation_results['errors'].append(str(e))
241
-
241
+
242
242
  # ...try/except
243
-
243
+
244
244
  if options.return_data:
245
245
  to_return = d
246
246
  else:
247
247
  to_return = {}
248
-
248
+
249
249
  to_return['validation_results'] = validation_results
250
-
250
+
251
251
  return to_return
252
252
 
253
253
  # ...def validate_batch_results(...)
@@ -258,16 +258,16 @@ def validate_batch_results(json_filename,options=None):
258
258
  if False:
259
259
 
260
260
  #%% Validate all .json files in the MD test suite
261
-
261
+
262
262
  from megadetector.utils.path_utils import recursive_file_list
263
263
  filenames = recursive_file_list(os.path.expanduser('~/AppData/Local/Temp/md-tests'))
264
264
  filenames = [fn for fn in filenames if fn.endswith('.json')]
265
265
  filenames = [fn for fn in filenames if 'detectionIndex' not in fn]
266
-
266
+
267
267
  options = ValidateBatchResultsOptions()
268
268
  options.check_image_existence = False
269
269
  options.relative_path_base = None # r'g:\temp\test-videos'
270
-
270
+
271
271
  for json_filename in filenames:
272
272
  results = validate_batch_results(json_filename,options)
273
273
  if len(results['validation_results']['warnings']) > 0:
@@ -275,13 +275,13 @@ if False:
275
275
  for s in results['validation_results']['warnings']:
276
276
  print(s)
277
277
  print('')
278
- assert len(results['validation_results']['errors']) == 0
279
-
278
+ assert len(results['validation_results']['errors']) == 0
279
+
280
280
 
281
281
  #%% Command-line driver
282
282
 
283
- def main():
284
-
283
+ def main(): # noqa
284
+
285
285
  options = ValidateBatchResultsOptions()
286
286
 
287
287
  parser = argparse.ArgumentParser()
@@ -299,11 +299,11 @@ def main():
299
299
  parser.exit()
300
300
 
301
301
  args = parser.parse_args()
302
-
303
- args_to_object(args, options)
302
+
303
+ args_to_object(args, options)
304
304
 
305
305
  validate_batch_results(args.json_filename,options)
306
-
307
-
306
+
307
+
308
308
  if __name__ == '__main__':
309
309
  main()