megadetector 5.0.5__py3-none-any.whl → 5.0.7__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 (132) hide show
  1. api/batch_processing/data_preparation/manage_local_batch.py +302 -263
  2. api/batch_processing/data_preparation/manage_video_batch.py +81 -2
  3. api/batch_processing/postprocessing/add_max_conf.py +1 -0
  4. api/batch_processing/postprocessing/categorize_detections_by_size.py +50 -19
  5. api/batch_processing/postprocessing/compare_batch_results.py +110 -60
  6. api/batch_processing/postprocessing/load_api_results.py +56 -70
  7. api/batch_processing/postprocessing/md_to_coco.py +1 -1
  8. api/batch_processing/postprocessing/md_to_labelme.py +2 -1
  9. api/batch_processing/postprocessing/postprocess_batch_results.py +240 -81
  10. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +625 -0
  11. api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +71 -23
  12. api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +1 -1
  13. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +227 -75
  14. api/batch_processing/postprocessing/subset_json_detector_output.py +132 -5
  15. api/batch_processing/postprocessing/top_folders_to_bottom.py +1 -1
  16. api/synchronous/api_core/animal_detection_api/detection/run_detector_batch.py +2 -2
  17. classification/prepare_classification_script.py +191 -191
  18. data_management/coco_to_yolo.py +68 -45
  19. data_management/databases/integrity_check_json_db.py +7 -5
  20. data_management/generate_crops_from_cct.py +3 -3
  21. data_management/get_image_sizes.py +8 -6
  22. data_management/importers/add_timestamps_to_icct.py +79 -0
  23. data_management/importers/animl_results_to_md_results.py +160 -0
  24. data_management/importers/auckland_doc_test_to_json.py +4 -4
  25. data_management/importers/auckland_doc_to_json.py +1 -1
  26. data_management/importers/awc_to_json.py +5 -5
  27. data_management/importers/bellevue_to_json.py +5 -5
  28. data_management/importers/carrizo_shrubfree_2018.py +5 -5
  29. data_management/importers/carrizo_trail_cam_2017.py +5 -5
  30. data_management/importers/cct_field_adjustments.py +2 -3
  31. data_management/importers/channel_islands_to_cct.py +4 -4
  32. data_management/importers/ena24_to_json.py +5 -5
  33. data_management/importers/helena_to_cct.py +10 -10
  34. data_management/importers/idaho-camera-traps.py +12 -12
  35. data_management/importers/idfg_iwildcam_lila_prep.py +8 -8
  36. data_management/importers/jb_csv_to_json.py +4 -4
  37. data_management/importers/missouri_to_json.py +1 -1
  38. data_management/importers/noaa_seals_2019.py +1 -1
  39. data_management/importers/pc_to_json.py +5 -5
  40. data_management/importers/prepare-noaa-fish-data-for-lila.py +4 -4
  41. data_management/importers/prepare_zsl_imerit.py +5 -5
  42. data_management/importers/rspb_to_json.py +4 -4
  43. data_management/importers/save_the_elephants_survey_A.py +5 -5
  44. data_management/importers/save_the_elephants_survey_B.py +6 -6
  45. data_management/importers/snapshot_safari_importer.py +9 -9
  46. data_management/importers/snapshot_serengeti_lila.py +9 -9
  47. data_management/importers/timelapse_csv_set_to_json.py +5 -7
  48. data_management/importers/ubc_to_json.py +4 -4
  49. data_management/importers/umn_to_json.py +4 -4
  50. data_management/importers/wellington_to_json.py +1 -1
  51. data_management/importers/wi_to_json.py +2 -2
  52. data_management/importers/zamba_results_to_md_results.py +181 -0
  53. data_management/labelme_to_coco.py +35 -7
  54. data_management/labelme_to_yolo.py +229 -0
  55. data_management/lila/add_locations_to_island_camera_traps.py +1 -1
  56. data_management/lila/add_locations_to_nacti.py +147 -0
  57. data_management/lila/create_lila_blank_set.py +474 -0
  58. data_management/lila/create_lila_test_set.py +2 -1
  59. data_management/lila/create_links_to_md_results_files.py +106 -0
  60. data_management/lila/download_lila_subset.py +46 -21
  61. data_management/lila/generate_lila_per_image_labels.py +23 -14
  62. data_management/lila/get_lila_annotation_counts.py +17 -11
  63. data_management/lila/lila_common.py +14 -11
  64. data_management/lila/test_lila_metadata_urls.py +116 -0
  65. data_management/ocr_tools.py +829 -0
  66. data_management/resize_coco_dataset.py +13 -11
  67. data_management/yolo_output_to_md_output.py +84 -12
  68. data_management/yolo_to_coco.py +38 -20
  69. detection/process_video.py +36 -14
  70. detection/pytorch_detector.py +23 -8
  71. detection/run_detector.py +76 -19
  72. detection/run_detector_batch.py +178 -63
  73. detection/run_inference_with_yolov5_val.py +326 -57
  74. detection/run_tiled_inference.py +153 -43
  75. detection/video_utils.py +34 -8
  76. md_utils/ct_utils.py +172 -1
  77. md_utils/md_tests.py +372 -51
  78. md_utils/path_utils.py +167 -39
  79. md_utils/process_utils.py +26 -7
  80. md_utils/split_locations_into_train_val.py +215 -0
  81. md_utils/string_utils.py +10 -0
  82. md_utils/url_utils.py +0 -2
  83. md_utils/write_html_image_list.py +9 -26
  84. md_visualization/plot_utils.py +12 -8
  85. md_visualization/visualization_utils.py +106 -7
  86. md_visualization/visualize_db.py +16 -8
  87. md_visualization/visualize_detector_output.py +208 -97
  88. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/METADATA +3 -6
  89. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/RECORD +98 -121
  90. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/WHEEL +1 -1
  91. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +1 -1
  92. taxonomy_mapping/map_new_lila_datasets.py +43 -39
  93. taxonomy_mapping/prepare_lila_taxonomy_release.py +5 -2
  94. taxonomy_mapping/preview_lila_taxonomy.py +27 -27
  95. taxonomy_mapping/species_lookup.py +33 -13
  96. taxonomy_mapping/taxonomy_csv_checker.py +7 -5
  97. api/synchronous/api_core/yolov5/detect.py +0 -252
  98. api/synchronous/api_core/yolov5/export.py +0 -607
  99. api/synchronous/api_core/yolov5/hubconf.py +0 -146
  100. api/synchronous/api_core/yolov5/models/__init__.py +0 -0
  101. api/synchronous/api_core/yolov5/models/common.py +0 -738
  102. api/synchronous/api_core/yolov5/models/experimental.py +0 -104
  103. api/synchronous/api_core/yolov5/models/tf.py +0 -574
  104. api/synchronous/api_core/yolov5/models/yolo.py +0 -338
  105. api/synchronous/api_core/yolov5/train.py +0 -670
  106. api/synchronous/api_core/yolov5/utils/__init__.py +0 -36
  107. api/synchronous/api_core/yolov5/utils/activations.py +0 -103
  108. api/synchronous/api_core/yolov5/utils/augmentations.py +0 -284
  109. api/synchronous/api_core/yolov5/utils/autoanchor.py +0 -170
  110. api/synchronous/api_core/yolov5/utils/autobatch.py +0 -66
  111. api/synchronous/api_core/yolov5/utils/aws/__init__.py +0 -0
  112. api/synchronous/api_core/yolov5/utils/aws/resume.py +0 -40
  113. api/synchronous/api_core/yolov5/utils/benchmarks.py +0 -148
  114. api/synchronous/api_core/yolov5/utils/callbacks.py +0 -71
  115. api/synchronous/api_core/yolov5/utils/dataloaders.py +0 -1087
  116. api/synchronous/api_core/yolov5/utils/downloads.py +0 -178
  117. api/synchronous/api_core/yolov5/utils/flask_rest_api/example_request.py +0 -19
  118. api/synchronous/api_core/yolov5/utils/flask_rest_api/restapi.py +0 -46
  119. api/synchronous/api_core/yolov5/utils/general.py +0 -1018
  120. api/synchronous/api_core/yolov5/utils/loggers/__init__.py +0 -187
  121. api/synchronous/api_core/yolov5/utils/loggers/wandb/__init__.py +0 -0
  122. api/synchronous/api_core/yolov5/utils/loggers/wandb/log_dataset.py +0 -27
  123. api/synchronous/api_core/yolov5/utils/loggers/wandb/sweep.py +0 -41
  124. api/synchronous/api_core/yolov5/utils/loggers/wandb/wandb_utils.py +0 -577
  125. api/synchronous/api_core/yolov5/utils/loss.py +0 -234
  126. api/synchronous/api_core/yolov5/utils/metrics.py +0 -355
  127. api/synchronous/api_core/yolov5/utils/plots.py +0 -489
  128. api/synchronous/api_core/yolov5/utils/torch_utils.py +0 -314
  129. api/synchronous/api_core/yolov5/val.py +0 -394
  130. md_utils/matlab_porting_tools.py +0 -97
  131. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/LICENSE +0 -0
  132. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/top_level.txt +0 -0
md_utils/md_tests.py CHANGED
@@ -1,24 +1,17 @@
1
1
  ########
2
2
  #
3
- # md-tests.py
3
+ # md_tests.py
4
4
  #
5
5
  # A series of tests to validate basic repo functionality and verify either "correct"
6
6
  # inference behavior, or - when operating in environments other than the training
7
7
  # environment - acceptable deviation from the correct results.
8
8
  #
9
- # This module should not depend on anything else in this repo outside of the,
10
- # tests themselves, even if it means some duplicated code (e.g. for downloading files), since
11
- # much of what it tries to test is, e.g., imports.
9
+ # This module should not depend on anything else in this repo outside of the
10
+ # tests themselves, even if it means some duplicated code (e.g. for downloading files),
11
+ # since much of what it tries to test is, e.g., imports.
12
12
  #
13
13
  ########
14
14
 
15
- #%% TODO
16
-
17
- # Video tests
18
- # Augmented inference tests
19
- # Checkpoint tests
20
-
21
-
22
15
  #%% Imports and constants
23
16
 
24
17
  ### Only standard imports belong here, not MD-specific imports ###
@@ -42,28 +35,61 @@ class MDTestOptions:
42
35
 
43
36
  disable_gpu = False
44
37
  cpu_execution_is_error = False
45
- disable_video_tests = False
38
+ skip_video_tests = False
39
+ skip_python_tests = False
40
+ skip_cli_tests = False
46
41
  scratch_dir = None
47
42
  test_data_url = 'https://lila.science/public/md-test-package.zip'
48
43
  force_data_download = False
49
44
  force_data_unzip = False
45
+ warning_mode = False
50
46
  test_image_subdir = 'md-test-images'
51
47
  max_coord_error = 0.001
52
48
  max_conf_error = 0.005
53
49
  cli_working_dir = None
50
+ yolo_working_folder = None
54
51
 
55
52
 
56
53
  #%% Support functions
57
54
 
58
55
  def get_expected_results_filename(gpu_is_available):
56
+ """
57
+ Expected results vary just a little across inference environments, particularly
58
+ between PT 1.x and 2.x, so when making sure things are working acceptably, we
59
+ compare to a reference file that matches the current environment.
60
+ """
59
61
 
60
62
  if gpu_is_available:
61
- return 'md-test-results-gpu-pt1.10.1.json'
63
+ hw_string = 'gpu'
64
+ else:
65
+ hw_string = 'cpu'
66
+ import torch
67
+ torch_version = str(torch.__version__)
68
+ if torch_version.startswith('1'):
69
+ assert torch_version == '1.10.1', 'Only tested against PT 1.10.1 and PT 2.x'
70
+ pt_string = 'pt1.10.1'
62
71
  else:
63
- return 'md-test-results-cpu-pt1.10.1.json'
72
+ assert torch_version.startswith('2'), 'Unknown torch version: {}'.format(torch_version)
73
+ pt_string = 'pt2.x'
74
+
75
+ # A hack for now to account for the fact that even with acceleration enabled and PT2
76
+ # installed, Apple silicon appears to provide the same results as CPU/PT1 inference
77
+ try:
78
+ import torch
79
+ m1_inference = torch.backends.mps.is_built and torch.backends.mps.is_available()
80
+ if m1_inference:
81
+ hw_string = 'cpu'
82
+ pt_string = 'pt1.10.1'
83
+ except Exception:
84
+ pass
85
+
86
+ return 'md-test-results-{}-{}.json'.format(hw_string,pt_string)
64
87
 
65
88
 
66
89
  def download_test_data(options):
90
+ """
91
+ Download the test zipfile if necessary, unzip if necessary.
92
+ """
67
93
 
68
94
  if options.scratch_dir is None:
69
95
  tempdir_base = tempfile.gettempdir()
@@ -87,9 +113,9 @@ def download_test_data(options):
87
113
  if download_zipfile:
88
114
  print('Downloading test data zipfile')
89
115
  urllib.request.urlretrieve(options.test_data_url, local_zipfile)
90
- print('Finished download')
116
+ print('Finished download to {}'.format(local_zipfile))
91
117
  else:
92
- print('Bypassing test data zipfile download')
118
+ print('Bypassing test data zipfile download for {}'.format(local_zipfile))
93
119
 
94
120
 
95
121
  ## Unzip data
@@ -132,23 +158,38 @@ def download_test_data(options):
132
158
  options.all_test_files = test_files
133
159
  options.test_images = [fn for fn in test_files if os.path.splitext(fn.lower())[1] in ('.jpg','.jpeg','.png')]
134
160
  options.test_videos = [fn for fn in test_files if os.path.splitext(fn.lower())[1] in ('.mp4','.avi')]
161
+ options.test_videos = [fn for fn in options.test_videos if 'rendered' not in fn]
135
162
 
136
163
  # ...def download_test_data(...)
137
164
 
138
165
 
139
166
  def is_gpu_available(verbose=True):
140
-
167
+ """
168
+ Check whether a GPU (including M1/M2 MPS) is available.
169
+ """
170
+
141
171
  # Import torch inside this function, so we have a chance to set CUDA_VISIBLE_DEVICES
142
172
  # before checking GPU availability.
143
173
  import torch
144
174
  gpu_available = torch.cuda.is_available()
145
175
 
146
- if verbose:
147
- print('CUDA available: {}'.format(gpu_available))
148
- device_ids = list(range(torch.cuda.device_count()))
149
- if len(device_ids) > 1:
150
- print('Found multiple devices: {}'.format(str(device_ids)))
151
-
176
+ if gpu_available:
177
+ if verbose:
178
+ print('CUDA available: {}'.format(gpu_available))
179
+ device_ids = list(range(torch.cuda.device_count()))
180
+ if len(device_ids) > 1:
181
+ print('Found multiple devices: {}'.format(str(device_ids)))
182
+ else:
183
+ try:
184
+ gpu_available = torch.backends.mps.is_built and torch.backends.mps.is_available()
185
+ except AttributeError:
186
+ pass
187
+ if gpu_available:
188
+ print('Metal performance shaders available')
189
+
190
+ if not gpu_available:
191
+ print('No GPU available')
192
+
152
193
  return gpu_available
153
194
 
154
195
 
@@ -201,6 +242,8 @@ def execute_and_print(cmd,print_output=True):
201
242
 
202
243
  def run_python_tests(options):
203
244
 
245
+ print('\n*** Starting module tests ***\n')
246
+
204
247
  ## Prepare data
205
248
 
206
249
  download_test_data(options)
@@ -237,8 +280,6 @@ def run_python_tests(options):
237
280
 
238
281
  ## Verify results
239
282
 
240
- #%%
241
-
242
283
  # Read expected results
243
284
  expected_results_filename = get_expected_results_filename(is_gpu_available(verbose=False))
244
285
 
@@ -272,9 +313,17 @@ def run_python_tests(options):
272
313
  actual_detections = actual_image_results['detections']
273
314
  expected_detections = expected_image_results['detections']
274
315
 
316
+ s = 'expected {} detections for file {}, found {}'.format(
317
+ len(expected_detections),fn,len(actual_detections))
318
+ s += '\nExpected results file: {}\nActual results file: {}'.format(
319
+ expected_results_filename,inference_output_file)
320
+
321
+ if options.warning_mode:
322
+ if len(actual_detections) != len(expected_detections):
323
+ print('Warning: {}'.format(s))
324
+ continue
275
325
  assert len(actual_detections) == len(expected_detections), \
276
- 'Error: expected {} detections for file {}, found {}'.format(
277
- len(expected_detections),fn,len(actual_detections))
326
+ 'Error: {}'.format(s)
278
327
 
279
328
  # i_det = 0
280
329
  for i_det in range(0,len(actual_detections)):
@@ -296,16 +345,16 @@ def run_python_tests(options):
296
345
 
297
346
  # ...for each image
298
347
 
299
- #%%
300
-
301
- assert max_conf_error <= options.max_conf_error, \
302
- 'Confidence error {} is greater than allowable ({})'.format(
303
- max_conf_error,options.max_conf_error)
304
-
305
- assert max_coord_error <= options.max_coord_error, \
306
- 'Coord error {} is greater than allowable ({})'.format(
307
- max_coord_error,options.max_coord_error)
308
-
348
+ if not options.warning_mode:
349
+
350
+ assert max_conf_error <= options.max_conf_error, \
351
+ 'Confidence error {} is greater than allowable ({})'.format(
352
+ max_conf_error,options.max_conf_error)
353
+
354
+ assert max_coord_error <= options.max_coord_error, \
355
+ 'Coord error {} is greater than allowable ({})'.format(
356
+ max_coord_error,options.max_coord_error)
357
+
309
358
  print('Max conf error: {}'.format(max_conf_error))
310
359
  print('Max coord error: {}'.format(max_coord_error))
311
360
 
@@ -341,7 +390,84 @@ def run_python_tests(options):
341
390
  assert os.path.isfile(rde_results.filterFile),\
342
391
  'Could not find RDE output file {}'.format(rde_results.filterFile)
343
392
 
344
- print('Finished running Python tests')
393
+
394
+ # TODO: add remove_repeat_detections test here
395
+ #
396
+ # It's already tested in the CLI tests, so this is not urgent.
397
+
398
+
399
+ ## Video test (single video)
400
+
401
+ from detection.process_video import ProcessVideoOptions, process_video
402
+
403
+ video_options = ProcessVideoOptions()
404
+ video_options.model_file = 'MDV5A'
405
+ video_options.input_video_file = os.path.join(options.scratch_dir,options.test_videos[0])
406
+ video_options.output_json_file = os.path.join(options.scratch_dir,'single_video_output.json')
407
+ video_options.output_video_file = os.path.join(options.scratch_dir,'video_scratch/rendered_video.mp4')
408
+ video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
409
+ video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
410
+ video_options.render_output_video = True
411
+ # video_options.keep_rendered_frames = False
412
+ # video_options.keep_rendered_frames = False
413
+ video_options.force_extracted_frame_folder_deletion = True
414
+ video_options.force_rendered_frame_folder_deletion = True
415
+ # video_options.reuse_results_if_available = False
416
+ # video_options.reuse_frames_if_available = False
417
+ video_options.recursive = True
418
+ video_options.verbose = False
419
+ video_options.fourcc = 'mp4v'
420
+ # video_options.rendering_confidence_threshold = None
421
+ # video_options.json_confidence_threshold = 0.005
422
+ video_options.frame_sample = 5
423
+ video_options.n_cores = 5
424
+ # video_options.debug_max_frames = -1
425
+ # video_options.class_mapping_filename = None
426
+
427
+ _ = process_video(video_options)
428
+
429
+ assert os.path.isfile(video_options.output_video_file), \
430
+ 'Python video test failed to render output video file'
431
+ assert os.path.isfile(video_options.output_json_file), \
432
+ 'Python video test failed to render output .json file'
433
+
434
+
435
+ ## Video test (folder)
436
+
437
+ from detection.process_video import ProcessVideoOptions, process_video_folder
438
+
439
+ video_options = ProcessVideoOptions()
440
+ video_options.model_file = 'MDV5A'
441
+ video_options.input_video_file = os.path.join(options.scratch_dir,
442
+ os.path.dirname(options.test_videos[0]))
443
+ video_options.output_json_file = os.path.join(options.scratch_dir,'video_folder_output.json')
444
+ # video_options.output_video_file = None
445
+ video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
446
+ video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
447
+ video_options.render_output_video = False
448
+ # video_options.keep_rendered_frames = False
449
+ # video_options.keep_rendered_frames = False
450
+ video_options.force_extracted_frame_folder_deletion = True
451
+ video_options.force_rendered_frame_folder_deletion = True
452
+ # video_options.reuse_results_if_available = False
453
+ # video_options.reuse_frames_if_available = False
454
+ video_options.recursive = True
455
+ video_options.verbose = False
456
+ # video_options.fourcc = None
457
+ # video_options.rendering_confidence_threshold = None
458
+ # video_options.json_confidence_threshold = 0.005
459
+ video_options.frame_sample = 5
460
+ video_options.n_cores = 5
461
+ # video_options.debug_max_frames = -1
462
+ # video_options.class_mapping_filename = None
463
+
464
+ _ = process_video_folder(video_options)
465
+
466
+ assert os.path.isfile(video_options.output_json_file), \
467
+ 'Python video test failed to render output .json file'
468
+
469
+
470
+ print('\n*** Finished module tests ***\n')
345
471
 
346
472
  # ...def run_python_tests(...)
347
473
 
@@ -350,6 +476,8 @@ def run_python_tests(options):
350
476
 
351
477
  def run_cli_tests(options):
352
478
 
479
+ print('\n*** Starting CLI tests ***\n')
480
+
353
481
  ## chdir if necessary
354
482
 
355
483
  if options.cli_working_dir is not None:
@@ -366,7 +494,11 @@ def run_cli_tests(options):
366
494
  model_file = 'MDV5A'
367
495
  image_fn = os.path.join(options.scratch_dir,options.test_images[0])
368
496
  output_dir = os.path.join(options.scratch_dir,'single_image_test')
369
- cmd = 'python detection/run_detector.py {} --image_file {} --output_dir {}'.format(
497
+ if options.cli_working_dir is None:
498
+ cmd = 'python -m detection.run_detector'
499
+ else:
500
+ cmd = 'python detection/run_detector.py'
501
+ cmd += ' {} --image_file {} --output_dir {}'.format(
370
502
  model_file,image_fn,output_dir)
371
503
  print('Running: {}'.format(cmd))
372
504
  cmd_results = execute_and_print(cmd)
@@ -386,9 +518,14 @@ def run_cli_tests(options):
386
518
  image_folder = os.path.join(options.scratch_dir,'md-test-images')
387
519
  assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
388
520
  inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
389
- cmd = 'python detection/run_detector_batch.py {} {} {} --recursive'.format(
521
+ if options.cli_working_dir is None:
522
+ cmd = 'python -m detection.run_detector_batch'
523
+ else:
524
+ cmd = 'python detection/run_detector_batch.py'
525
+ cmd += ' {} {} {} --recursive'.format(
390
526
  model_file,image_folder,inference_output_file)
391
- cmd += ' --output_relative_filenames --quiet --include_image_size --include_image_timestamp --include_exif_data'
527
+ cmd += ' --output_relative_filenames --quiet --include_image_size'
528
+ cmd += ' --include_image_timestamp --include_exif_data'
392
529
  print('Running: {}'.format(cmd))
393
530
  cmd_results = execute_and_print(cmd)
394
531
 
@@ -402,16 +539,171 @@ def run_cli_tests(options):
402
539
 
403
540
  postprocessing_output_dir = os.path.join(options.scratch_dir,'postprocessing_output_cli')
404
541
 
405
- cmd = 'python api/batch_processing/postprocessing/postprocess_batch_results.py {} {}'.format(
542
+ if options.cli_working_dir is None:
543
+ cmd = 'python -m api.batch_processing.postprocessing.postprocess_batch_results'
544
+ else:
545
+ cmd = 'python api/batch_processing/postprocessing/postprocess_batch_results.py'
546
+ cmd += ' {} {}'.format(
406
547
  inference_output_file,postprocessing_output_dir)
407
548
  cmd += ' --image_base_dir {}'.format(image_folder)
408
549
  print('Running: {}'.format(cmd))
409
550
  cmd_results = execute_and_print(cmd)
410
551
 
552
+
553
+ ## RDE
554
+
555
+ rde_output_dir = os.path.join(options.scratch_dir,'rde_output_cli')
556
+
557
+ if options.cli_working_dir is None:
558
+ cmd = 'python -m api.batch_processing.postprocessing.repeat_detection_elimination.find_repeat_detections'
559
+ else:
560
+ cmd = 'python api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py'
561
+ cmd += ' {}'.format(inference_output_file)
562
+ cmd += ' --imageBase {}'.format(image_folder)
563
+ cmd += ' --outputBase {}'.format(rde_output_dir)
564
+ cmd += ' --occurrenceThreshold 1' # Use an absurd number here to make sure we get some suspicious detections
565
+ print('Running: {}'.format(cmd))
566
+ cmd_results = execute_and_print(cmd)
567
+
568
+ # Find the latest filtering folder
569
+ filtering_output_dir = os.listdir(rde_output_dir)
570
+ filtering_output_dir = [fn for fn in filtering_output_dir if fn.startswith('filtering_')]
571
+ filtering_output_dir = [os.path.join(rde_output_dir,fn) for fn in filtering_output_dir]
572
+ filtering_output_dir = [fn for fn in filtering_output_dir if os.path.isdir(fn)]
573
+ filtering_output_dir = sorted(filtering_output_dir)[-1]
574
+
575
+ print('Using RDE filtering folder {}'.format(filtering_output_dir))
576
+
577
+ filtered_output_file = inference_output_file.replace('.json','_filtered.json')
578
+
579
+ if options.cli_working_dir is None:
580
+ cmd = 'python -m api.batch_processing.postprocessing.repeat_detection_elimination.remove_repeat_detections'
581
+ else:
582
+ cmd = 'python api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py'
583
+ cmd += ' {} {} {}'.format(inference_output_file,filtered_output_file,filtering_output_dir)
584
+ print('Running: {}'.format(cmd))
585
+ cmd_results = execute_and_print(cmd)
586
+
587
+ assert os.path.isfile(filtered_output_file), \
588
+ 'Could not find RDE output file {}'.format(filtered_output_file)
589
+
590
+
591
+ ## Run inference on a folder (tiled)
592
+
593
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
594
+ tiling_folder = os.path.join(options.scratch_dir,'tiling-folder')
595
+ inference_output_file_tiled = os.path.join(options.scratch_dir,'folder_inference_output_tiled.json')
596
+ if options.cli_working_dir is None:
597
+ cmd = 'python -m detection.run_tiled_inference'
598
+ else:
599
+ cmd = 'python detection/run_tiled_inference.py'
600
+ cmd += ' {} {} {} {}'.format(
601
+ model_file,image_folder,tiling_folder,inference_output_file_tiled)
602
+ cmd += ' --overwrite_handling overwrite'
603
+ print('Running: {}'.format(cmd))
604
+ cmd_results = execute_and_print(cmd)
605
+
606
+ with open(inference_output_file_tiled,'r') as f:
607
+ results_from_file = json.load(f) # noqa
608
+
609
+
610
+ ## Run inference on a folder (augmented)
611
+
612
+ if options.yolo_working_folder is None:
613
+
614
+ print('Bypassing YOLOv5 val tests, no yolo folder supplied')
615
+
616
+ else:
617
+
618
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
619
+ yolo_results_folder = os.path.join(options.scratch_dir,'yolo-output-folder')
620
+ yolo_symlink_folder = os.path.join(options.scratch_dir,'yolo-symlink_folder')
621
+ inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
622
+ if options.cli_working_dir is None:
623
+ cmd = 'python -m detection.run_inference_with_yolov5_val'
624
+ else:
625
+ cmd = 'python detection/run_inference_with_yolov5_val.py'
626
+ cmd += ' {} {} {}'.format(
627
+ model_file,image_folder,inference_output_file_yolo_val)
628
+ cmd += ' --yolo_working_folder {}'.format(options.yolo_working_folder)
629
+ cmd += ' --yolo_results_folder {}'.format(yolo_results_folder)
630
+ cmd += ' --symlink_folder {}'.format(yolo_symlink_folder)
631
+ cmd += ' --augment_enabled 1'
632
+ # cmd += ' --no_use_symlinks'
633
+ cmd += ' --overwrite_handling overwrite'
634
+ print('Running: {}'.format(cmd))
635
+ cmd_results = execute_and_print(cmd)
636
+
637
+ with open(inference_output_file_yolo_val,'r') as f:
638
+ results_from_file = json.load(f) # noqa
639
+
640
+
641
+ ## Video test
642
+
643
+ model_file = 'MDV5A'
644
+ video_inference_output_file = os.path.join(options.scratch_dir,'video_inference_output.json')
645
+ output_video_file = os.path.join(options.scratch_dir,'video_scratch/cli_rendered_video.mp4')
646
+ frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder_cli')
647
+ frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder_cli')
648
+
649
+ video_fn = os.path.join(options.scratch_dir,options.test_videos[-1])
650
+ output_dir = os.path.join(options.scratch_dir,'single_video_test_cli')
651
+ if options.cli_working_dir is None:
652
+ cmd = 'python -m detection.process_video'
653
+ else:
654
+ cmd = 'python detection/process_video.py'
655
+ cmd += ' {} {}'.format(model_file,video_fn)
656
+ cmd += ' --frame_folder {} --frame_rendering_folder {} --output_json_file {} --output_video_file {}'.format(
657
+ frame_folder,frame_rendering_folder,video_inference_output_file,output_video_file)
658
+ cmd += ' --render_output_video --fourcc mp4v'
659
+ cmd += ' --force_extracted_frame_folder_deletion --force_rendered_frame_folder_deletion --n_cores 5 --frame_sample 3'
660
+ print('Running: {}'.format(cmd))
661
+ cmd_results = execute_and_print(cmd)
662
+
663
+
664
+ ## Run inference on a folder (again, so we can do a comparison)
665
+
666
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
667
+ model_file = 'MDV5B'
668
+ inference_output_file_alt = os.path.join(options.scratch_dir,'folder_inference_output_alt.json')
669
+ if options.cli_working_dir is None:
670
+ cmd = 'python -m detection.run_detector_batch'
671
+ else:
672
+ cmd = 'python detection/run_detector_batch.py'
673
+ cmd += ' {} {} {} --recursive'.format(
674
+ model_file,image_folder,inference_output_file_alt)
675
+ cmd += ' --output_relative_filenames --quiet --include_image_size'
676
+ cmd += ' --include_image_timestamp --include_exif_data'
677
+ print('Running: {}'.format(cmd))
678
+ cmd_results = execute_and_print(cmd)
679
+
680
+ with open(inference_output_file_alt,'r') as f:
681
+ results_from_file = json.load(f) # noqa
682
+
683
+
684
+ ## Compare the two files
685
+
686
+ comparison_output_folder = os.path.join(options.scratch_dir,'results_comparison')
687
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
688
+ results_files_string = '"{}" "{}"'.format(
689
+ inference_output_file,inference_output_file_alt)
690
+ if options.cli_working_dir is None:
691
+ cmd = 'python -m api.batch_processing.postprocessing.compare_batch_results'
692
+ else:
693
+ cmd = 'python api/batch_processing/postprocessing/compare_batch_results.py'
694
+ cmd += ' {} {} {}'.format(comparison_output_folder,image_folder,results_files_string)
695
+ print('Running: {}'.format(cmd))
696
+ cmd_results = execute_and_print(cmd)
697
+
698
+ assert cmd_results['status'] == 0, 'Error generating comparison HTML'
699
+ assert os.path.isfile(os.path.join(comparison_output_folder,'index.html')), \
700
+ 'Failed to generate comparison HTML'
701
+
702
+ print('\n*** Finished CLI tests ***\n')
703
+
411
704
  # ...def run_cli_tests(...)
412
705
 
413
706
 
414
-
415
707
  #%% Main test wrapper
416
708
 
417
709
  def run_tests(options):
@@ -431,13 +723,15 @@ def run_tests(options):
431
723
 
432
724
  # If the GPU should be disabled, verify that it is
433
725
  if options.disable_gpu:
434
- assert (not gpu_available), 'Oops, CPU execution specified, but the GPU appears to be available'
726
+ assert (not gpu_available), 'CPU execution specified, but the GPU appears to be available'
435
727
 
436
728
  # Run python tests
437
- run_python_tests(options)
729
+ if not options.skip_python_tests:
730
+ run_python_tests(options)
438
731
 
439
732
  # Run CLI tests
440
- run_cli_tests(options)
733
+ if not options.skip_cli_tests:
734
+ run_cli_tests(options)
441
735
 
442
736
 
443
737
  #%% Interactive driver
@@ -452,9 +746,19 @@ if False:
452
746
 
453
747
  options.disable_gpu = False
454
748
  options.cpu_execution_is_error = False
455
- options.disable_video_tests = False
749
+ options.skip_video_tests = False
750
+ options.skip_python_tests = False
751
+ options.skip_cli_tests = False
456
752
  options.scratch_dir = None
753
+ options.test_data_url = 'https://lila.science/public/md-test-package.zip'
754
+ options.force_data_download = False
755
+ options.force_data_unzip = False
756
+ options.warning_mode = True
757
+ options.test_image_subdir = 'md-test-images'
758
+ options.max_coord_error = 0.001
759
+ options.max_conf_error = 0.005
457
760
  options.cli_working_dir = r'c:\git\MegaDetector'
761
+ options.yolo_working_folder = r'c:\git\yolov5'
458
762
 
459
763
 
460
764
  #%%
@@ -488,9 +792,19 @@ def main():
488
792
  help='Directory for temporary storage (defaults to system temp dir)')
489
793
 
490
794
  parser.add_argument(
491
- '--disable_video_tests',
795
+ '--skip_video_tests',
492
796
  action='store_true',
493
- help='Disable tests related to video (which can be slow)')
797
+ help='Skip tests related to video (which can be slow)')
798
+
799
+ parser.add_argument(
800
+ '--skip_python_tests',
801
+ action='store_true',
802
+ help='Skip python tests')
803
+
804
+ parser.add_argument(
805
+ '--skip_cli_tests',
806
+ action='store_true',
807
+ help='Skip CLI tests')
494
808
 
495
809
  parser.add_argument(
496
810
  '--force_data_download',
@@ -502,6 +816,11 @@ def main():
502
816
  action='store_true',
503
817
  help='Force extraction of all files in the test data file, even if they\'re already available')
504
818
 
819
+ parser.add_argument(
820
+ '--warning_mode',
821
+ action='store_true',
822
+ help='Turns numeric/content errors into warnings')
823
+
505
824
  parser.add_argument(
506
825
  '--max_conf_error',
507
826
  type=float,
@@ -526,8 +845,11 @@ def main():
526
845
 
527
846
  options.disable_gpu = args.disable_gpu
528
847
  options.cpu_execution_is_error = args.cpu_execution_is_error
529
- options.disable_video_tests = args.disable_video_tests
848
+ options.skip_video_tests = args.skip_video_tests
849
+ options.skip_python_tests = args.skip_python_tests
850
+ options.skip_cli_tests = args.skip_cli_tests
530
851
  options.scratch_dir = args.scratch_dir
852
+ options.warning_mode = args.warning_mode
531
853
  options.force_data_download = args.force_data_download
532
854
  options.max_conf_error = args.max_conf_error
533
855
  options.max_coord_error = args.max_coord_error
@@ -538,4 +860,3 @@ def main():
538
860
 
539
861
  if __name__ == '__main__':
540
862
  main()
541
-