megadetector 10.0.2__py3-none-any.whl → 10.0.4__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 (30) hide show
  1. megadetector/data_management/animl_to_md.py +158 -0
  2. megadetector/data_management/zamba_to_md.py +188 -0
  3. megadetector/detection/process_video.py +165 -946
  4. megadetector/detection/pytorch_detector.py +575 -276
  5. megadetector/detection/run_detector_batch.py +629 -202
  6. megadetector/detection/run_md_and_speciesnet.py +1319 -0
  7. megadetector/detection/video_utils.py +243 -107
  8. megadetector/postprocessing/classification_postprocessing.py +12 -1
  9. megadetector/postprocessing/combine_batch_outputs.py +2 -0
  10. megadetector/postprocessing/compare_batch_results.py +21 -2
  11. megadetector/postprocessing/merge_detections.py +16 -12
  12. megadetector/postprocessing/separate_detections_into_folders.py +1 -1
  13. megadetector/postprocessing/subset_json_detector_output.py +1 -3
  14. megadetector/postprocessing/validate_batch_results.py +25 -2
  15. megadetector/tests/__init__.py +0 -0
  16. megadetector/tests/test_nms_synthetic.py +335 -0
  17. megadetector/utils/ct_utils.py +69 -5
  18. megadetector/utils/extract_frames_from_video.py +303 -0
  19. megadetector/utils/md_tests.py +583 -524
  20. megadetector/utils/path_utils.py +4 -15
  21. megadetector/utils/wi_utils.py +20 -4
  22. megadetector/visualization/visualization_utils.py +1 -1
  23. megadetector/visualization/visualize_db.py +8 -22
  24. megadetector/visualization/visualize_detector_output.py +7 -5
  25. megadetector/visualization/visualize_video_output.py +607 -0
  26. {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/METADATA +134 -135
  27. {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/RECORD +30 -23
  28. {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/licenses/LICENSE +0 -0
  29. {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/top_level.txt +0 -0
  30. {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/WHEEL +0 -0
@@ -21,6 +21,7 @@ since much of what it tries to test is, e.g., imports.
21
21
  import os
22
22
  import json
23
23
  import glob
24
+ import sys
24
25
  import tempfile
25
26
  import urllib
26
27
  import urllib.request
@@ -53,12 +54,15 @@ class MDTestOptions:
53
54
  #: Skip tests related to video processing
54
55
  self.skip_video_tests = False
55
56
 
56
- #: Skip tests related to video rendering
57
- self.skip_video_rendering_tests = False
57
+ #: Skip tests related to still image processing
58
+ self.skip_image_tests = False
58
59
 
59
60
  #: Skip tests launched via Python functions (as opposed to CLIs)
60
61
  self.skip_python_tests = False
61
62
 
63
+ #: Skip module import tests
64
+ self.skip_import_tests = False
65
+
62
66
  #: Skip CLI tests
63
67
  self.skip_cli_tests = False
64
68
 
@@ -105,9 +109,6 @@ class MDTestOptions:
105
109
  #: If this is None, we'll skip that test.
106
110
  self.yolo_working_dir = None
107
111
 
108
- #: fourcc code to use for video tests that involve rendering video
109
- self.video_fourcc = 'mp4v'
110
-
111
112
  #: Default model to use for testing (filename, URL, or well-known model string)
112
113
  self.default_model = 'MDV5A'
113
114
 
@@ -141,8 +142,8 @@ class MDTestOptions:
141
142
  #: Number of cores to use for multi-CPU inference tests
142
143
  self.n_cores_for_multiprocessing_tests = 2
143
144
 
144
- #: Number of cores to use for multi-CPU video tests
145
- self.n_cores_for_video_tests = 2
145
+ #: Batch size to use when testing batches of size > 1
146
+ self.alternative_batch_size = 3
146
147
 
147
148
  # ...def __init__()
148
149
 
@@ -169,7 +170,7 @@ def get_expected_results_filename(gpu_is_available,
169
170
  model_string (str, optional): the model for which we're retrieving expected results
170
171
  test_type (str, optional): the test type we're running ("image" or "video")
171
172
  augment (bool, optional): whether we're running this test with image augmentation
172
- options (MDTestOptiosn, optional): additional control flow options
173
+ options (MDTestOptions, optional): additional control flow options
173
174
 
174
175
  Returns:
175
176
  str: relative filename of the results file we should use (within the test
@@ -205,12 +206,11 @@ def get_expected_results_filename(gpu_is_available,
205
206
  if augment:
206
207
  aug_string = 'augment-'
207
208
 
208
- fn = '{}-{}{}-{}-{}.json'.format(model_string,aug_string,test_type,hw_string,pt_string)
209
-
210
- from megadetector.utils.path_utils import insert_before_extension
211
-
212
- if test_type == 'video':
213
- fn = insert_before_extension(fn,'frames')
209
+ # We only have a single set of video results
210
+ if test_type == 'image':
211
+ fn = '{}-{}{}-{}-{}.json'.format(model_string,aug_string,test_type,hw_string,pt_string)
212
+ else:
213
+ fn = '{}-{}.json'.format(model_string,test_type)
214
214
 
215
215
  if options is not None and options.scratch_dir is not None:
216
216
  fn = os.path.join(options.scratch_dir,fn)
@@ -306,7 +306,6 @@ def download_test_data(options=None):
306
306
  options.all_test_files = test_files
307
307
  options.test_images = [fn for fn in test_files if os.path.splitext(fn.lower())[1] in ('.jpg','.jpeg','.png')]
308
308
  options.test_videos = [fn for fn in test_files if os.path.splitext(fn.lower())[1] in ('.mp4','.avi')]
309
- options.test_videos = [fn for fn in options.test_videos if 'rendered' not in fn]
310
309
  options.test_videos = [fn for fn in options.test_videos if \
311
310
  os.path.isfile(os.path.join(scratch_dir,fn))]
312
311
 
@@ -401,7 +400,9 @@ def output_files_are_identical(fn1,fn2,verbose=False):
401
400
 
402
401
  if fn1_image['file'] != fn2_image['file']:
403
402
  if verbose:
404
- print('Filename difference at {}: {} vs {} '.format(i_image,fn1_image['file'],fn1_image['file']))
403
+ print('Filename difference at {}: {} vs {} '.format(i_image,
404
+ fn1_image['file'],
405
+ fn2_image['file']))
405
406
  return False
406
407
 
407
408
  if fn1_image != fn2_image:
@@ -430,6 +431,7 @@ def compare_detection_lists(detections_a,detections_b,options,bidirectional_comp
430
431
  Returns:
431
432
  dict: a dictionary with keys 'max_conf_error' and 'max_coord_error'.
432
433
  """
434
+
433
435
  from megadetector.utils.ct_utils import get_iou
434
436
 
435
437
  max_conf_error = 0
@@ -564,7 +566,9 @@ def compare_results(inference_output_file,
564
566
  filename_to_results_expected = {im['file'].replace('\\','/'):im for im in expected_results['images']}
565
567
 
566
568
  assert len(filename_to_results) == len(filename_to_results_expected), \
567
- 'Error: expected {} files in results, found {}'.format(
569
+ 'Error: comparing expected file {} to actual file {}, expected {} files in results, found {}'.format(
570
+ expected_results_file,
571
+ inference_output_file,
568
572
  len(filename_to_results_expected),
569
573
  len(filename_to_results))
570
574
 
@@ -583,9 +587,16 @@ def compare_results(inference_output_file,
583
587
  expected_image_results = filename_to_results_expected[fn]
584
588
 
585
589
  if 'failure' in actual_image_results:
590
+ # We allow some variation in how failures are represented
586
591
  assert 'failure' in expected_image_results and \
587
- 'detections' not in actual_image_results and \
588
- 'detections' not in expected_image_results
592
+ (
593
+ ('detections' not in actual_image_results) or \
594
+ (actual_image_results['detections'] is None)
595
+ ) and \
596
+ (
597
+ ('detections' not in expected_image_results) or \
598
+ (expected_image_results['detections'] is None)
599
+ )
589
600
  continue
590
601
  assert 'failure' not in expected_image_results
591
602
 
@@ -792,12 +803,14 @@ def run_python_tests(options):
792
803
 
793
804
  ## Import tests
794
805
 
795
- print('\n** Running package import tests **\n')
796
- test_package_imports('megadetector.visualization')
797
- test_package_imports('megadetector.postprocessing')
798
- test_package_imports('megadetector.postprocessing.repeat_detection_elimination')
799
- test_package_imports('megadetector.utils',exceptions=['md_tests'])
800
- test_package_imports('megadetector.data_management',exceptions=['lila','ocr_tools'])
806
+ if not options.skip_import_tests:
807
+
808
+ print('\n** Running package import tests **\n')
809
+ test_package_imports('megadetector.visualization')
810
+ test_package_imports('megadetector.postprocessing')
811
+ test_package_imports('megadetector.postprocessing.repeat_detection_elimination')
812
+ test_package_imports('megadetector.utils',exceptions=['md_tests'])
813
+ test_package_imports('megadetector.data_management',exceptions=['lila','ocr_tools'])
801
814
 
802
815
 
803
816
  ## Return early if we're not running torch-related tests
@@ -812,236 +825,245 @@ def run_python_tests(options):
812
825
  pytorch_detector.require_non_default_compatibility_mode = True
813
826
 
814
827
 
815
- ## Run inference on an image
828
+ if not options.skip_image_tests:
816
829
 
817
- print('\n** Running MD on a single image (module) **\n')
830
+ from megadetector.utils import path_utils # noqa
831
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
832
+ assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
833
+ inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
834
+ image_file_names = path_utils.find_images(image_folder,recursive=True)
818
835
 
819
- from megadetector.detection import run_detector
820
- from megadetector.visualization import visualization_utils as vis_utils # noqa
821
- image_fn = os.path.join(options.scratch_dir,options.test_images[0])
822
- model = run_detector.load_detector(options.default_model,
823
- detector_options=copy(options.detector_options))
824
- pil_im = vis_utils.load_image(image_fn)
825
- result = model.generate_detections_one_image(pil_im) # noqa
826
836
 
827
- if options.python_test_depth <= 1:
828
- return
837
+ ## Run inference on an image
838
+
839
+ print('\n** Running MD on a single image (module) **\n')
829
840
 
841
+ from megadetector.detection import run_detector
842
+ from megadetector.visualization import visualization_utils as vis_utils # noqa
843
+ image_fn = os.path.join(options.scratch_dir,options.test_images[0])
844
+ model = run_detector.load_detector(options.default_model,
845
+ detector_options=copy(options.detector_options))
846
+ pil_im = vis_utils.load_image(image_fn)
847
+ result = model.generate_detections_one_image(pil_im) # noqa
830
848
 
831
- ## Run inference on a folder
849
+ if options.python_test_depth <= 1:
850
+ return
832
851
 
833
- print('\n** Running MD on a folder of images (module) **\n')
834
852
 
835
- from megadetector.detection.run_detector_batch import load_and_run_detector_batch,write_results_to_file
836
- from megadetector.utils import path_utils # noqa
853
+ ## Run inference on a folder
837
854
 
838
- image_folder = os.path.join(options.scratch_dir,'md-test-images')
839
- assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
840
- inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
841
- image_file_names = path_utils.find_images(image_folder,recursive=True)
842
- results = load_and_run_detector_batch(options.default_model,
843
- image_file_names,
844
- quiet=True,
845
- detector_options=copy(options.detector_options))
846
- _ = write_results_to_file(results,
847
- inference_output_file,
848
- relative_path_base=image_folder,
849
- detector_file=options.default_model)
855
+ print('\n** Running MD on a folder of images (module) **\n')
850
856
 
851
- ## Verify results
857
+ from megadetector.detection.run_detector_batch import load_and_run_detector_batch,write_results_to_file
852
858
 
853
- # Verify format correctness
854
- from megadetector.postprocessing.validate_batch_results import validate_batch_results #noqa
855
- validate_batch_results(inference_output_file)
859
+ results = load_and_run_detector_batch(options.default_model,
860
+ image_file_names,
861
+ quiet=True,
862
+ detector_options=copy(options.detector_options))
863
+ _ = write_results_to_file(results,
864
+ inference_output_file,
865
+ relative_path_base=image_folder,
866
+ detector_file=options.default_model)
856
867
 
857
- # Verify value correctness
858
- expected_results_file = get_expected_results_filename(is_gpu_available(verbose=False),
859
- options=options)
860
- compare_results(inference_output_file,expected_results_file,options)
868
+ ## Verify results
861
869
 
870
+ # Verify format correctness
871
+ from megadetector.postprocessing.validate_batch_results import validate_batch_results #noqa
872
+ validate_batch_results(inference_output_file)
862
873
 
863
- # Make note of this filename, we will use it again later
864
- inference_output_file_standard_inference = inference_output_file
874
+ # Verify value correctness
875
+ expected_results_file = get_expected_results_filename(is_gpu_available(verbose=False),
876
+ options=options)
877
+ compare_results(inference_output_file,expected_results_file,options)
865
878
 
866
- if options.python_test_depth <= 2:
867
- return
868
879
 
880
+ # Make note of this filename, we will use it again later
881
+ inference_output_file_standard_inference = inference_output_file
869
882
 
870
- ## Run and verify again with augmentation enabled
883
+ if options.python_test_depth <= 2:
884
+ return
871
885
 
872
- print('\n** Running MD on images with augmentation (module) **\n')
873
886
 
874
- from megadetector.utils.path_utils import insert_before_extension
887
+ ## Run again with a batch size > 1
875
888
 
876
- inference_output_file_augmented = insert_before_extension(inference_output_file,'augmented')
877
- results = load_and_run_detector_batch(options.default_model,
878
- image_file_names,
879
- quiet=True,
880
- augment=True,
881
- detector_options=copy(options.detector_options))
882
- _ = write_results_to_file(results,
883
- inference_output_file_augmented,
884
- relative_path_base=image_folder,
885
- detector_file=options.default_model)
889
+ print('\n** Running MD on a folder of images with batch size > 1 (module) **\n')
886
890
 
887
- expected_results_file_augmented = \
888
- get_expected_results_filename(is_gpu_available(verbose=False),
889
- augment=True,options=options)
890
- compare_results(inference_output_file_augmented,expected_results_file_augmented,options)
891
+ from megadetector.detection.run_detector_batch import load_and_run_detector_batch,write_results_to_file
892
+ from megadetector.utils.path_utils import insert_before_extension
891
893
 
894
+ inference_output_file_batch = insert_before_extension(inference_output_file,'batch')
895
+ from megadetector.detection import run_detector_batch
896
+ run_detector_batch.verbose = True
897
+ results = load_and_run_detector_batch(options.default_model,
898
+ image_file_names,
899
+ quiet=True,
900
+ batch_size=options.alternative_batch_size,
901
+ detector_options=copy(options.detector_options))
902
+ run_detector_batch.verbose = False
903
+ _ = write_results_to_file(results,
904
+ inference_output_file_batch,
905
+ relative_path_base=image_folder,
906
+ detector_file=options.default_model)
892
907
 
893
- ## Postprocess results
908
+ expected_results_file = get_expected_results_filename(is_gpu_available(verbose=False),
909
+ options=options)
910
+ compare_results(inference_output_file_batch,expected_results_file,options)
894
911
 
895
- print('\n** Post-processing results (module) **\n')
912
+ ## Run and verify again with augmentation enabled
896
913
 
897
- from megadetector.postprocessing.postprocess_batch_results import \
898
- PostProcessingOptions,process_batch_results
899
- postprocessing_options = PostProcessingOptions()
914
+ print('\n** Running MD on images with augmentation (module) **\n')
900
915
 
901
- postprocessing_options.md_results_file = inference_output_file
902
- postprocessing_options.output_dir = os.path.join(options.scratch_dir,'postprocessing_output')
903
- postprocessing_options.image_base_dir = image_folder
916
+ inference_output_file_augmented = insert_before_extension(inference_output_file,'augmented')
917
+ results = load_and_run_detector_batch(options.default_model,
918
+ image_file_names,
919
+ quiet=True,
920
+ augment=True,
921
+ detector_options=copy(options.detector_options))
922
+ _ = write_results_to_file(results,
923
+ inference_output_file_augmented,
924
+ relative_path_base=image_folder,
925
+ detector_file=options.default_model)
904
926
 
905
- postprocessing_results = process_batch_results(postprocessing_options)
906
- assert os.path.isfile(postprocessing_results.output_html_file), \
907
- 'Postprocessing output file {} not found'.format(postprocessing_results.output_html_file)
927
+ expected_results_file_augmented = \
928
+ get_expected_results_filename(is_gpu_available(verbose=False),
929
+ augment=True,options=options)
930
+ compare_results(inference_output_file_augmented,expected_results_file_augmented,options)
908
931
 
909
932
 
910
- ## Partial RDE test
933
+ ## Postprocess results
911
934
 
912
- print('\n** Testing RDE (module) **\n')
935
+ print('\n** Post-processing results (module) **\n')
913
936
 
914
- from megadetector.postprocessing.repeat_detection_elimination.repeat_detections_core import \
915
- RepeatDetectionOptions, find_repeat_detections
937
+ from megadetector.postprocessing.postprocess_batch_results import \
938
+ PostProcessingOptions,process_batch_results
939
+ postprocessing_options = PostProcessingOptions()
916
940
 
917
- rde_options = RepeatDetectionOptions()
918
- rde_options.occurrenceThreshold = 2
919
- rde_options.confidenceMin = 0.001
920
- rde_options.outputBase = os.path.join(options.scratch_dir,'rde_working_dir')
921
- rde_options.imageBase = image_folder
922
- rde_output_file = inference_output_file.replace('.json','_filtered.json')
923
- assert rde_output_file != inference_output_file
924
- rde_results = find_repeat_detections(inference_output_file, rde_output_file, rde_options)
925
- assert os.path.isfile(rde_results.filterFile),\
926
- 'Could not find RDE output file {}'.format(rde_results.filterFile)
941
+ postprocessing_options.md_results_file = inference_output_file
942
+ postprocessing_options.output_dir = os.path.join(options.scratch_dir,'postprocessing_output')
943
+ postprocessing_options.image_base_dir = image_folder
927
944
 
945
+ postprocessing_results = process_batch_results(postprocessing_options)
946
+ assert os.path.isfile(postprocessing_results.output_html_file), \
947
+ 'Postprocessing output file {} not found'.format(postprocessing_results.output_html_file)
928
948
 
929
- ## Run inference on a folder (with YOLOv5 val script)
930
949
 
931
- if options.yolo_working_dir is None:
950
+ ## Partial RDE test
932
951
 
933
- print('Skipping YOLO val inference tests, no YOLO folder supplied')
952
+ print('\n** Testing RDE (module) **\n')
934
953
 
935
- else:
954
+ from megadetector.postprocessing.repeat_detection_elimination.repeat_detections_core import \
955
+ RepeatDetectionOptions, find_repeat_detections
936
956
 
937
- print('\n** Running YOLO val inference test (module) **\n')
957
+ rde_options = RepeatDetectionOptions()
958
+ rde_options.occurrenceThreshold = 2
959
+ rde_options.confidenceMin = 0.001
960
+ rde_options.outputBase = os.path.join(options.scratch_dir,'rde_working_dir')
961
+ rde_options.imageBase = image_folder
962
+ rde_output_file = inference_output_file.replace('.json','_filtered.json')
963
+ assert rde_output_file != inference_output_file
964
+ rde_results = find_repeat_detections(inference_output_file, rde_output_file, rde_options)
965
+ assert os.path.isfile(rde_results.filterFile),\
966
+ 'Could not find RDE output file {}'.format(rde_results.filterFile)
938
967
 
939
- from megadetector.detection.run_inference_with_yolov5_val import \
940
- YoloInferenceOptions, run_inference_with_yolo_val
941
- from megadetector.utils.path_utils import insert_before_extension
942
968
 
943
- inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
969
+ ## Run inference on a folder (with YOLOv5 val script)
970
+
971
+ if options.yolo_working_dir is None:
972
+
973
+ print('Skipping YOLO val inference tests, no YOLO folder supplied')
974
+
975
+ else:
976
+
977
+ print('\n** Running YOLO val inference test (module) **\n')
944
978
 
945
- yolo_inference_options = YoloInferenceOptions()
946
- yolo_inference_options.input_folder = os.path.join(options.scratch_dir,'md-test-images')
947
- yolo_inference_options.output_file = inference_output_file_yolo_val
948
- yolo_inference_options.yolo_working_folder = options.yolo_working_dir
949
- yolo_inference_options.model_filename = options.default_model
950
- yolo_inference_options.augment = False
951
- yolo_inference_options.overwrite_handling = 'overwrite'
952
- from megadetector.detection.run_detector import DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD
953
- yolo_inference_options.conf_thres = DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD
979
+ from megadetector.detection.run_inference_with_yolov5_val import \
980
+ YoloInferenceOptions, run_inference_with_yolo_val
981
+ from megadetector.utils.path_utils import insert_before_extension
954
982
 
955
- run_inference_with_yolo_val(yolo_inference_options)
983
+ inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
956
984
 
957
- ## Confirm this matches the standard inference path
985
+ yolo_inference_options = YoloInferenceOptions()
986
+ yolo_inference_options.input_folder = os.path.join(options.scratch_dir,'md-test-images')
987
+ yolo_inference_options.output_file = inference_output_file_yolo_val
988
+ yolo_inference_options.yolo_working_folder = options.yolo_working_dir
989
+ yolo_inference_options.model_filename = options.default_model
990
+ yolo_inference_options.augment = False
991
+ yolo_inference_options.overwrite_handling = 'overwrite'
992
+ from megadetector.detection.run_detector import DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD
993
+ yolo_inference_options.conf_thres = DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD
958
994
 
959
- if False:
960
- # TODO: compare_results() isn't quite ready for this yet
961
- compare_results(inference_output_file=inference_output_file_yolo_val,
962
- expected_results_file=inference_output_file_standard_inference,
963
- options=options)
995
+ run_inference_with_yolo_val(yolo_inference_options)
964
996
 
965
- # Run again, without symlinks this time
997
+ ## Confirm this matches the standard inference path
966
998
 
967
- inference_output_file_yolo_val_no_links = insert_before_extension(inference_output_file_yolo_val,
968
- 'no-links')
969
- yolo_inference_options.output_file = inference_output_file_yolo_val_no_links
970
- yolo_inference_options.use_symlinks = False
971
- run_inference_with_yolo_val(yolo_inference_options)
999
+ if False:
1000
+ # TODO: compare_results() isn't quite ready for this yet
1001
+ compare_results(inference_output_file=inference_output_file_yolo_val,
1002
+ expected_results_file=inference_output_file_standard_inference,
1003
+ options=options)
972
1004
 
973
- # Run again, with chunked inference and symlinks
1005
+ # Run again, without symlinks this time
974
1006
 
975
- inference_output_file_yolo_val_checkpoints = insert_before_extension(inference_output_file_yolo_val,
976
- 'checkpoints')
977
- yolo_inference_options.output_file = inference_output_file_yolo_val_checkpoints
978
- yolo_inference_options.use_symlinks = True
979
- yolo_inference_options.checkpoint_frequency = 5
980
- run_inference_with_yolo_val(yolo_inference_options)
1007
+ inference_output_file_yolo_val_no_links = insert_before_extension(inference_output_file_yolo_val,
1008
+ 'no-links')
1009
+ yolo_inference_options.output_file = inference_output_file_yolo_val_no_links
1010
+ yolo_inference_options.use_symlinks = False
1011
+ run_inference_with_yolo_val(yolo_inference_options)
981
1012
 
982
- # Run again, with chunked inference and no symlinks
1013
+ # Run again, with chunked inference and symlinks
983
1014
 
984
- inference_output_file_yolo_val_checkpoints_no_links = \
985
- insert_before_extension(inference_output_file_yolo_val,'checkpoints-no-links')
986
- yolo_inference_options.output_file = inference_output_file_yolo_val_checkpoints_no_links
987
- yolo_inference_options.use_symlinks = False
988
- yolo_inference_options.checkpoint_frequency = 5
989
- run_inference_with_yolo_val(yolo_inference_options)
1015
+ inference_output_file_yolo_val_checkpoints = insert_before_extension(inference_output_file_yolo_val,
1016
+ 'checkpoints')
1017
+ yolo_inference_options.output_file = inference_output_file_yolo_val_checkpoints
1018
+ yolo_inference_options.use_symlinks = True
1019
+ yolo_inference_options.checkpoint_frequency = 5
1020
+ run_inference_with_yolo_val(yolo_inference_options)
990
1021
 
991
- fn1 = inference_output_file_yolo_val
1022
+ # Run again, with chunked inference and no symlinks
992
1023
 
993
- output_files_to_compare = [
994
- inference_output_file_yolo_val_no_links,
995
- inference_output_file_yolo_val_checkpoints,
996
- inference_output_file_yolo_val_checkpoints_no_links
997
- ]
1024
+ inference_output_file_yolo_val_checkpoints_no_links = \
1025
+ insert_before_extension(inference_output_file_yolo_val,'checkpoints-no-links')
1026
+ yolo_inference_options.output_file = inference_output_file_yolo_val_checkpoints_no_links
1027
+ yolo_inference_options.use_symlinks = False
1028
+ yolo_inference_options.checkpoint_frequency = 5
1029
+ run_inference_with_yolo_val(yolo_inference_options)
998
1030
 
999
- for fn2 in output_files_to_compare:
1000
- assert output_files_are_identical(fn1, fn2, verbose=True)
1031
+ fn1 = inference_output_file_yolo_val
1001
1032
 
1002
- # ...if we need to run the YOLO val inference tests
1033
+ output_files_to_compare = [
1034
+ inference_output_file_yolo_val_no_links,
1035
+ inference_output_file_yolo_val_checkpoints,
1036
+ inference_output_file_yolo_val_checkpoints_no_links
1037
+ ]
1003
1038
 
1039
+ for fn2 in output_files_to_compare:
1040
+ assert output_files_are_identical(fn1, fn2, verbose=True)
1041
+
1042
+ # ...if we need to run the YOLO val inference tests
1043
+
1044
+ # ...if we're not skipping image tests
1004
1045
 
1005
1046
  if not options.skip_video_tests:
1006
1047
 
1007
1048
  ## Video test (single video)
1008
1049
 
1050
+ # This test just checks non-crashing-ness; we will test correctness in the next
1051
+ # test (which runs a folder of videos)
1052
+
1009
1053
  print('\n** Running MD on a single video (module) **\n')
1010
1054
 
1011
- from megadetector.detection.process_video import ProcessVideoOptions, process_video
1055
+ from megadetector.detection.process_video import ProcessVideoOptions, process_videos
1012
1056
  from megadetector.utils.path_utils import insert_before_extension
1013
1057
 
1014
1058
  video_options = ProcessVideoOptions()
1015
1059
  video_options.model_file = options.default_model
1016
1060
  video_options.input_video_file = os.path.join(options.scratch_dir,options.test_videos[0])
1017
1061
  video_options.output_json_file = os.path.join(options.scratch_dir,'single_video_output.json')
1018
- video_options.output_video_file = os.path.join(options.scratch_dir,'video_scratch/rendered_video.mp4')
1019
- video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
1020
- video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
1021
-
1022
- video_options.render_output_video = (not options.skip_video_rendering_tests)
1023
-
1024
- # video_options.keep_rendered_frames = False
1025
- # video_options.keep_extracted_frames = False
1026
- video_options.force_extracted_frame_folder_deletion = True
1027
- video_options.force_rendered_frame_folder_deletion = True
1028
- # video_options.reuse_results_if_available = False
1029
- # video_options.reuse_frames_if_available = False
1030
- video_options.recursive = True
1031
- video_options.verbose = False
1032
- video_options.fourcc = options.video_fourcc
1033
- # video_options.rendering_confidence_threshold = None
1034
- # video_options.json_confidence_threshold = 0.005
1035
1062
  video_options.frame_sample = 10
1036
- video_options.n_cores = options.n_cores_for_video_tests
1037
- # video_options.debug_max_frames = -1
1038
- # video_options.class_mapping_filename = None
1039
1063
  video_options.detector_options = copy(options.detector_options)
1040
1064
 
1041
- _ = process_video(video_options)
1065
+ _ = process_videos(video_options)
1042
1066
 
1043
- assert os.path.isfile(video_options.output_video_file), \
1044
- 'Python video test failed to render output video file'
1045
1067
  assert os.path.isfile(video_options.output_json_file), \
1046
1068
  'Python video test failed to render output .json file'
1047
1069
 
@@ -1050,7 +1072,7 @@ def run_python_tests(options):
1050
1072
 
1051
1073
  print('\n** Running MD on a folder of videos (module) **\n')
1052
1074
 
1053
- from megadetector.detection.process_video import ProcessVideoOptions, process_video_folder
1075
+ from megadetector.detection.process_video import ProcessVideoOptions, process_videos
1054
1076
  from megadetector.utils.path_utils import insert_before_extension
1055
1077
 
1056
1078
  video_options = ProcessVideoOptions()
@@ -1059,74 +1081,28 @@ def run_python_tests(options):
1059
1081
  os.path.dirname(options.test_videos[0]))
1060
1082
  video_options.output_json_file = os.path.join(options.scratch_dir,'video_folder_output.json')
1061
1083
  video_options.output_video_file = None
1062
- video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
1063
- video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
1064
- video_options.render_output_video = False
1065
- video_options.keep_rendered_frames = False
1066
- video_options.keep_extracted_frames = False
1067
- video_options.force_extracted_frame_folder_deletion = True
1068
- video_options.force_rendered_frame_folder_deletion = True
1069
- video_options.reuse_results_if_available = False
1070
- video_options.reuse_frames_if_available = False
1071
1084
  video_options.recursive = True
1072
1085
  video_options.verbose = True
1073
- video_options.fourcc = options.video_fourcc
1074
- # video_options.rendering_confidence_threshold = None
1075
- # video_options.json_confidence_threshold = 0.005
1076
- video_options.frame_sample = 10
1077
- video_options.n_cores = options.n_cores_for_video_tests
1078
-
1079
- # Force frame extraction to disk, since that's how we generated our expected results file
1080
- video_options.force_on_disk_frame_extraction = True
1081
- # video_options.debug_max_frames = -1
1082
- # video_options.class_mapping_filename = None
1083
-
1084
- # Use quality == None, because we can't control whether YOLOv5 has patched cm2.imread,
1085
- # and therefore can't rely on using the quality parameter
1086
- video_options.quality = None
1087
- video_options.max_width = None
1086
+ video_options.json_confidence_threshold = 0.05
1087
+ video_options.time_sample = 2
1088
1088
  video_options.detector_options = copy(options.detector_options)
1089
-
1090
- video_options.keep_extracted_frames = True
1091
- _ = process_video_folder(video_options)
1089
+ _ = process_videos(video_options)
1092
1090
 
1093
1091
  assert os.path.isfile(video_options.output_json_file), \
1094
1092
  'Python video test failed to render output .json file'
1095
1093
 
1096
- frame_output_file = insert_before_extension(video_options.output_json_file,'frames')
1097
- assert os.path.isfile(frame_output_file)
1098
-
1099
-
1100
1094
  ## Verify results
1101
1095
 
1102
1096
  expected_results_file = \
1103
1097
  get_expected_results_filename(is_gpu_available(verbose=False),test_type='video',options=options)
1104
1098
  assert os.path.isfile(expected_results_file)
1105
1099
 
1106
- compare_results(frame_output_file,expected_results_file,options)
1107
-
1108
-
1109
- ## Run again, this time in memory, and make sure the results are *almost* the same
1110
-
1111
- # They won't be quite the same, because the on-disk path goes through a jpeg intermediate
1112
-
1113
- print('\n** Running MD on a folder of videos (in memory) (module) **\n')
1114
-
1115
- video_options.output_json_file = insert_before_extension(video_options.output_json_file,'in-memory')
1116
- video_options.force_on_disk_frame_extraction = False
1117
- _ = process_video_folder(video_options)
1118
-
1119
- frame_output_file_in_memory = insert_before_extension(video_options.output_json_file,'frames')
1120
- assert os.path.isfile(frame_output_file_in_memory)
1121
-
1122
1100
  from copy import deepcopy
1123
1101
  options_loose = deepcopy(options)
1124
1102
  options_loose.max_conf_error = 0.05
1125
1103
  options_loose.max_coord_error = 0.01
1126
1104
 
1127
- compare_results(inference_output_file=frame_output_file,
1128
- expected_results_file=frame_output_file_in_memory,
1129
- options=options_loose)
1105
+ compare_results(video_options.output_json_file,expected_results_file,options_loose)
1130
1106
 
1131
1107
  # ...if we're not skipping video tests
1132
1108
 
@@ -1182,350 +1158,443 @@ def run_cli_tests(options):
1182
1158
  return
1183
1159
 
1184
1160
 
1185
- ## Run inference on an image
1161
+ if not options.skip_image_tests:
1186
1162
 
1187
- print('\n** Running MD on a single image (CLI) **\n')
1163
+ ## Run inference on an image
1188
1164
 
1189
- image_fn = os.path.join(options.scratch_dir,options.test_images[0])
1190
- output_dir = os.path.join(options.scratch_dir,'single_image_test')
1191
- if options.cli_working_dir is None:
1192
- cmd = 'python -m megadetector.detection.run_detector'
1193
- else:
1194
- cmd = 'python megadetector/detection/run_detector.py'
1195
- cmd += ' "{}" --image_file "{}" --output_dir "{}"'.format(
1196
- options.default_model,image_fn,output_dir)
1197
- cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1198
- cmd_results = execute_and_print(cmd)
1199
-
1200
- if options.cpu_execution_is_error:
1201
- gpu_available_via_cli = False
1202
- for s in cmd_results['output']:
1203
- if 'GPU available: True' in s:
1204
- gpu_available_via_cli = True
1205
- break
1206
- if not gpu_available_via_cli:
1207
- raise Exception('GPU execution is required, but not available')
1165
+ print('\n** Running MD on a single image (CLI) **\n')
1166
+
1167
+ image_fn = os.path.join(options.scratch_dir,options.test_images[0])
1168
+ output_dir = os.path.join(options.scratch_dir,'single_image_test')
1169
+ if options.cli_working_dir is None:
1170
+ cmd = 'python -m megadetector.detection.run_detector'
1171
+ else:
1172
+ cmd = 'python megadetector/detection/run_detector.py'
1173
+ cmd += ' "{}" --image_file "{}" --output_dir "{}"'.format(
1174
+ options.default_model,image_fn,output_dir)
1175
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1176
+ cmd_results = execute_and_print(cmd)
1208
1177
 
1209
- # Make sure we can also pass an absolute path to a model file, instead of, e.g. "MDV5A"
1178
+ if options.cpu_execution_is_error:
1179
+ gpu_available_via_cli = False
1180
+ for s in cmd_results['output']:
1181
+ if 'GPU available: True' in s:
1182
+ gpu_available_via_cli = True
1183
+ break
1184
+ if not gpu_available_via_cli:
1185
+ raise Exception('GPU execution is required, but not available')
1210
1186
 
1211
- from megadetector.detection.run_detector import try_download_known_detector
1212
- model_file = try_download_known_detector(options.default_model,force_download=False,verbose=False)
1213
- cmd = cmd.replace(options.default_model,model_file)
1214
- cmd_results = execute_and_print(cmd)
1215
1187
 
1188
+ ## Make sure we can also pass an absolute path to a model file, instead of, e.g. "MDV5A"
1216
1189
 
1217
- ## Run inference on a folder
1190
+ print('\n** Running MD on a single image (CLI) (with symbolic model name) **\n')
1218
1191
 
1219
- print('\n** Running MD on a folder (CLI) **\n')
1192
+ from megadetector.detection.run_detector import try_download_known_detector
1193
+ model_file = try_download_known_detector(options.default_model,force_download=False,verbose=False)
1194
+ cmd = cmd.replace(options.default_model,model_file)
1195
+ cmd_results = execute_and_print(cmd)
1220
1196
 
1221
- image_folder = os.path.join(options.scratch_dir,'md-test-images')
1222
- assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
1223
- inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
1224
- if options.cli_working_dir is None:
1225
- cmd = 'python -m megadetector.detection.run_detector_batch'
1226
- else:
1227
- cmd = 'python megadetector/detection/run_detector_batch.py'
1228
- cmd += ' "{}" "{}" "{}" --recursive'.format(
1229
- options.default_model,image_folder,inference_output_file)
1230
- cmd += ' --output_relative_filenames --quiet --include_image_size'
1231
- cmd += ' --include_image_timestamp --include_exif_data'
1232
- cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1233
- cmd_results = execute_and_print(cmd)
1234
1197
 
1235
- base_cmd = cmd
1198
+ ## Run inference on a folder
1236
1199
 
1200
+ print('\n** Running MD on a folder (CLI) **\n')
1237
1201
 
1238
- ## Run again with checkpointing enabled, make sure the results are the same
1202
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
1203
+ assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
1204
+ inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
1205
+ if options.cli_working_dir is None:
1206
+ cmd = 'python -m megadetector.detection.run_detector_batch'
1207
+ else:
1208
+ cmd = 'python megadetector/detection/run_detector_batch.py'
1209
+ cmd += ' "{}" "{}" "{}" --recursive'.format(
1210
+ options.default_model,image_folder,inference_output_file)
1211
+ cmd += ' --output_relative_filenames --quiet --include_image_size'
1212
+ cmd += ' --include_image_timestamp --include_exif_data'
1239
1213
 
1240
- print('\n** Running MD on a folder (with checkpoints) (CLI) **\n')
1214
+ base_cmd = cmd
1241
1215
 
1242
- checkpoint_string = ' --checkpoint_frequency 5'
1243
- cmd = base_cmd + checkpoint_string
1244
- inference_output_file_checkpoint = insert_before_extension(inference_output_file,'_checkpoint')
1245
- cmd = cmd.replace(inference_output_file,inference_output_file_checkpoint)
1246
- cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1247
- cmd_results = execute_and_print(cmd)
1216
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1217
+ cmd_results = execute_and_print(cmd)
1248
1218
 
1249
- assert output_files_are_identical(fn1=inference_output_file,
1250
- fn2=inference_output_file_checkpoint,
1251
- verbose=True)
1252
1219
 
1220
+ ## Run again with a batch size > 1
1253
1221
 
1254
- ## Run again with the image queue enabled, make sure the results are the same
1222
+ print('\n** Running MD on a folder (with a batch size > 1) (CLI) **\n')
1223
+
1224
+ batch_string = ' --batch_size {}'.format(options.alternative_batch_size)
1225
+ cmd = base_cmd + batch_string
1226
+ inference_output_file_batch = insert_before_extension(inference_output_file,'batch')
1227
+ cmd = cmd.replace(inference_output_file,inference_output_file_batch)
1228
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1229
+ cmd_results = execute_and_print(cmd)
1255
1230
 
1256
- print('\n** Running MD on a folder (with image queue but no preprocessing) (CLI) **\n')
1231
+ # Use compare_results() here rather than output_files_are_identical(), because
1232
+ # batch inference may introduce very small differences. Override the default tolerance,
1233
+ # though, because these differences should be very small compared to, e.g., differences
1234
+ # across library versions.
1235
+ batch_options = copy(options)
1236
+ batch_options.max_coord_error = 0.01
1237
+ batch_options.max_conf_error = 0.01
1238
+ compare_results(inference_output_file,inference_output_file_batch,batch_options)
1257
1239
 
1258
- cmd = base_cmd + ' --use_image_queue'
1259
- inference_output_file_queue = insert_before_extension(inference_output_file,'_queue')
1260
- cmd = cmd.replace(inference_output_file,inference_output_file_queue)
1261
- cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1262
- cmd_results = execute_and_print(cmd)
1263
1240
 
1264
- assert output_files_are_identical(fn1=inference_output_file,
1265
- fn2=inference_output_file_queue,
1266
- verbose=True)
1241
+ ## Run again with the image queue enabled
1267
1242
 
1243
+ print('\n** Running MD on a folder (with image queue but consumer-side preprocessing) (CLI) **\n')
1268
1244
 
1269
- print('\n** Running MD on a folder (with image queue and preprocessing) (CLI) **\n')
1245
+ cmd = base_cmd + ' --use_image_queue'
1246
+ inference_output_file_queue = insert_before_extension(inference_output_file,'queue')
1247
+ cmd = cmd.replace(inference_output_file,inference_output_file_queue)
1248
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1249
+ cmd_results = execute_and_print(cmd)
1270
1250
 
1271
- cmd = base_cmd + ' --use_image_queue --preprocess_on_image_queue'
1272
- inference_output_file_queue = insert_before_extension(inference_output_file,'_queue')
1273
- cmd = cmd.replace(inference_output_file,inference_output_file_queue)
1274
- cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1275
- cmd_results = execute_and_print(cmd)
1251
+ assert output_files_are_identical(fn1=inference_output_file,
1252
+ fn2=inference_output_file_queue,
1253
+ verbose=True)
1276
1254
 
1277
- assert output_files_are_identical(fn1=inference_output_file,
1278
- fn2=inference_output_file_queue,
1279
- verbose=True)
1280
1255
 
1281
- ## Run again on multiple cores, make sure the results are the same
1256
+ ## Run again with the image queue and worker-side preprocessing enabled
1282
1257
 
1283
- if not options.skip_cpu_tests:
1258
+ print('\n** Running MD on a folder (with image queue and worker-side preprocessing) (CLI) **\n')
1284
1259
 
1285
- # First run again on the CPU on a single thread if necessary, so we get a file that
1286
- # *should* be identical to the multicore version.
1287
- gpu_available = is_gpu_available(verbose=False)
1260
+ cmd = base_cmd + ' --use_image_queue --preprocess_on_image_queue'
1261
+ inference_output_file_preprocess_queue = \
1262
+ insert_before_extension(inference_output_file,'preprocess_queue')
1263
+ cmd = cmd.replace(inference_output_file,inference_output_file_preprocess_queue)
1264
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1265
+ cmd_results = execute_and_print(cmd)
1288
1266
 
1289
- cuda_visible_devices = None
1290
- if 'CUDA_VISIBLE_DEVICES' in os.environ:
1291
- cuda_visible_devices = os.environ['CUDA_VISIBLE_DEVICES']
1292
- os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
1267
+ assert output_files_are_identical(fn1=inference_output_file,
1268
+ fn2=inference_output_file_preprocess_queue,
1269
+ verbose=True)
1293
1270
 
1294
- # If we already ran on the CPU, no need to run again
1295
- if not gpu_available:
1296
1271
 
1297
- inference_output_file_cpu = inference_output_file
1272
+ ## Run again with the image queue and worker-side preprocessing
1298
1273
 
1299
- else:
1274
+ print('\n** Running MD on a folder (with image queue and preprocessing) (CLI) **\n')
1300
1275
 
1301
- print('\n** Running MD on a folder (single CPU) (CLI) **\n')
1276
+ cmd = base_cmd + ' --use_image_queue --preprocess_on_image_queue'
1277
+ inference_output_file_preprocess_queue = \
1278
+ insert_before_extension(inference_output_file,'preprocess_queue')
1279
+ cmd = cmd.replace(inference_output_file,inference_output_file_preprocess_queue)
1280
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1281
+ cmd_results = execute_and_print(cmd)
1302
1282
 
1303
- inference_output_file_cpu = insert_before_extension(inference_output_file,'cpu')
1304
- cmd = base_cmd
1305
- cmd = cmd.replace(inference_output_file,inference_output_file_cpu)
1306
- cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1307
- cmd_results = execute_and_print(cmd)
1283
+ assert output_files_are_identical(fn1=inference_output_file,
1284
+ fn2=inference_output_file_preprocess_queue,
1285
+ verbose=True)
1308
1286
 
1309
- print('\n** Running MD on a folder (multiple CPUs) (CLI) **\n')
1310
1287
 
1311
- cpu_string = ' --ncores {}'.format(options.n_cores_for_multiprocessing_tests)
1312
- cmd = base_cmd + cpu_string
1313
- inference_output_file_cpu_multicore = insert_before_extension(inference_output_file,'multicore')
1314
- cmd = cmd.replace(inference_output_file,inference_output_file_cpu_multicore)
1288
+ ## Run again with the worker-side preprocessing and an alternative batch size
1289
+
1290
+ print('\n** Running MD on a folder (with worker-side preprocessing and batched inference) (CLI) **\n')
1291
+
1292
+ batch_string = ' --batch_size {}'.format(options.alternative_batch_size)
1293
+
1294
+ # I reduce the number of loader workers here to force batching to actually append; with a small
1295
+ # number of images and a few that are intentionally corrupt, with the default number of loader
1296
+ # workers we end up with batches that are mostly just one image.
1297
+ cmd = base_cmd + ' --use_image_queue --preprocess_on_image_queue --loader_workers 2' + batch_string
1298
+ inference_output_file_queue_batch = \
1299
+ insert_before_extension(inference_output_file,'preprocess_queue_batch')
1300
+ cmd = cmd.replace(inference_output_file,inference_output_file_queue_batch)
1315
1301
  cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1316
1302
  cmd_results = execute_and_print(cmd)
1317
1303
 
1318
- if cuda_visible_devices is not None:
1319
- print('Restoring CUDA_VISIBLE_DEVICES')
1320
- os.environ['CUDA_VISIBLE_DEVICES'] = cuda_visible_devices
1321
- else:
1322
- del os.environ['CUDA_VISIBLE_DEVICES']
1304
+ compare_results(inference_output_file,inference_output_file_queue_batch,batch_options)
1323
1305
 
1324
- assert output_files_are_identical(fn1=inference_output_file_cpu,
1325
- fn2=inference_output_file_cpu_multicore,
1326
- verbose=True)
1327
1306
 
1328
- # ...if we're not skipping the force-cpu tests
1307
+ ## Run again with checkpointing enabled
1329
1308
 
1309
+ print('\n** Running MD on a folder (with checkpoints) (CLI) **\n')
1330
1310
 
1331
- ## Postprocessing
1311
+ checkpoint_string = ' --checkpoint_frequency 5'
1312
+ cmd = base_cmd + checkpoint_string
1313
+ inference_output_file_checkpoint = insert_before_extension(inference_output_file,'checkpoint')
1314
+ cmd = cmd.replace(inference_output_file,inference_output_file_checkpoint)
1315
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1316
+ cmd_results = execute_and_print(cmd)
1332
1317
 
1333
- print('\n** Testing post-processing (CLI) **\n')
1318
+ assert output_files_are_identical(fn1=inference_output_file,
1319
+ fn2=inference_output_file_checkpoint,
1320
+ verbose=True)
1334
1321
 
1335
- postprocessing_output_dir = os.path.join(options.scratch_dir,'postprocessing_output_cli')
1336
1322
 
1337
- if options.cli_working_dir is None:
1338
- cmd = 'python -m megadetector.postprocessing.postprocess_batch_results'
1339
- else:
1340
- cmd = 'python megadetector/postprocessing/postprocess_batch_results.py'
1341
- cmd += ' "{}" "{}"'.format(
1342
- inference_output_file,postprocessing_output_dir)
1343
- cmd += ' --image_base_dir "{}"'.format(image_folder)
1344
- cmd_results = execute_and_print(cmd)
1323
+ ## Run again with "modern" postprocessing, make sure the results are *not* the same as classic
1345
1324
 
1325
+ print('\n** Running MD on a folder (with modern preprocessing) (CLI) **\n')
1346
1326
 
1347
- ## RDE
1327
+ inference_output_file_modern = insert_before_extension(inference_output_file,'modern')
1328
+ cmd = base_cmd
1329
+ cmd = cmd.replace(inference_output_file,inference_output_file_modern)
1330
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list({'compatibility_mode':'modern'}))
1331
+ cmd_results = execute_and_print(cmd)
1348
1332
 
1349
- print('\n** Running RDE (CLI) **\n')
1333
+ assert not output_files_are_identical(fn1=inference_output_file,
1334
+ fn2=inference_output_file_modern,
1335
+ verbose=True)
1350
1336
 
1351
- rde_output_dir = os.path.join(options.scratch_dir,'rde_output_cli')
1352
1337
 
1353
- if options.cli_working_dir is None:
1354
- cmd = 'python -m megadetector.postprocessing.repeat_detection_elimination.find_repeat_detections'
1355
- else:
1356
- cmd = 'python megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py'
1357
- cmd += ' "{}"'.format(inference_output_file)
1358
- cmd += ' --imageBase "{}"'.format(image_folder)
1359
- cmd += ' --outputBase "{}"'.format(rde_output_dir)
1360
- cmd += ' --occurrenceThreshold 1' # Use an absurd number here to make sure we get some suspicious detections
1361
- cmd_results = execute_and_print(cmd)
1362
-
1363
- # Find the latest filtering folder
1364
- filtering_output_dir = os.listdir(rde_output_dir)
1365
- filtering_output_dir = [fn for fn in filtering_output_dir if fn.startswith('filtering_')]
1366
- filtering_output_dir = [os.path.join(rde_output_dir,fn) for fn in filtering_output_dir]
1367
- filtering_output_dir = [fn for fn in filtering_output_dir if os.path.isdir(fn)]
1368
- filtering_output_dir = sorted(filtering_output_dir)[-1]
1369
-
1370
- print('Using RDE filtering folder {}'.format(filtering_output_dir))
1371
-
1372
- filtered_output_file = inference_output_file.replace('.json','_filtered.json')
1373
-
1374
- if options.cli_working_dir is None:
1375
- cmd = 'python -m megadetector.postprocessing.repeat_detection_elimination.remove_repeat_detections'
1376
- else:
1377
- cmd = 'python megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py'
1378
- cmd += ' "{}" "{}" "{}"'.format(inference_output_file,filtered_output_file,filtering_output_dir)
1379
- cmd_results = execute_and_print(cmd)
1338
+ ## Run again with "modern" postprocessing and worker-side preprocessing,
1339
+ ## make sure the results are the same as modern.
1340
+
1341
+ print('\n** Running MD on a folder (with worker-side modern preprocessing) (CLI) **\n')
1380
1342
 
1381
- assert os.path.isfile(filtered_output_file), \
1382
- 'Could not find RDE output file {}'.format(filtered_output_file)
1343
+ inference_output_file_modern_worker_preprocessing = insert_before_extension(inference_output_file,'modern')
1344
+ cmd = base_cmd + ' --use_image_queue --preprocess_on_image_queue'
1345
+ cmd = cmd.replace(inference_output_file,inference_output_file_modern_worker_preprocessing)
1346
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list({'compatibility_mode':'modern'}))
1347
+ cmd_results = execute_and_print(cmd)
1383
1348
 
1349
+ # This should not be the same as the "classic" results
1350
+ assert not output_files_are_identical(fn1=inference_output_file,
1351
+ fn2=inference_output_file_modern_worker_preprocessing,
1352
+ verbose=True)
1384
1353
 
1385
- ## Run inference on a folder (tiled)
1354
+ # ...but it should be the same as the single-threaded "modern" results
1355
+ assert output_files_are_identical(fn1=inference_output_file_modern,
1356
+ fn2=inference_output_file_modern_worker_preprocessing,
1357
+ verbose=True)
1386
1358
 
1387
- # This is a rather esoteric code path that I turn off when I'm testing some
1388
- # features that it doesn't include yet, particularly compatibility mode
1389
- # control.
1390
- skip_tiling_tests = True
1391
1359
 
1392
- if skip_tiling_tests:
1360
+ if not options.skip_cpu_tests:
1393
1361
 
1394
- print('### DEBUG: skipping tiling tests ###')
1362
+ ## Run again on multiple cores
1395
1363
 
1396
- else:
1397
- print('\n** Running tiled inference (CLI) **\n')
1364
+ # First run again on the CPU on a single thread if necessary, so we get a file that
1365
+ # *should* be identical to the multicore version.
1366
+ gpu_available = is_gpu_available(verbose=False)
1367
+
1368
+ cuda_visible_devices = None
1369
+ if 'CUDA_VISIBLE_DEVICES' in os.environ:
1370
+ cuda_visible_devices = os.environ['CUDA_VISIBLE_DEVICES']
1371
+ os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
1372
+
1373
+ # If we already ran on the CPU, no need to run again
1374
+ if not gpu_available:
1375
+
1376
+ inference_output_file_cpu = inference_output_file
1377
+
1378
+ else:
1379
+
1380
+ print('\n** Running MD on a folder (single CPU) (CLI) **\n')
1381
+
1382
+ inference_output_file_cpu = insert_before_extension(inference_output_file,'cpu')
1383
+ cmd = base_cmd
1384
+ cmd = cmd.replace(inference_output_file,inference_output_file_cpu)
1385
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1386
+ cmd_results = execute_and_print(cmd)
1387
+
1388
+ print('\n** Running MD on a folder (multiple CPUs) (CLI) **\n')
1389
+
1390
+ cpu_string = ' --ncores {}'.format(options.n_cores_for_multiprocessing_tests)
1391
+ cmd = base_cmd + cpu_string
1392
+ inference_output_file_cpu_multicore = insert_before_extension(inference_output_file,'multicore')
1393
+ cmd = cmd.replace(inference_output_file,inference_output_file_cpu_multicore)
1394
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1395
+ cmd_results = execute_and_print(cmd)
1396
+
1397
+ if cuda_visible_devices is not None:
1398
+ print('Restoring CUDA_VISIBLE_DEVICES')
1399
+ os.environ['CUDA_VISIBLE_DEVICES'] = cuda_visible_devices
1400
+ else:
1401
+ del os.environ['CUDA_VISIBLE_DEVICES']
1402
+
1403
+ assert output_files_are_identical(fn1=inference_output_file_cpu,
1404
+ fn2=inference_output_file_cpu_multicore,
1405
+ verbose=True)
1406
+
1407
+ # ...if we're not skipping the force-cpu tests
1408
+
1409
+
1410
+ ## Postprocessing
1411
+
1412
+ print('\n** Testing post-processing (CLI) **\n')
1413
+
1414
+ postprocessing_output_dir = os.path.join(options.scratch_dir,'postprocessing_output_cli')
1398
1415
 
1399
- image_folder = os.path.join(options.scratch_dir,'md-test-images')
1400
- tiling_folder = os.path.join(options.scratch_dir,'tiling-folder')
1401
- inference_output_file_tiled = os.path.join(options.scratch_dir,'folder_inference_output_tiled.json')
1402
1416
  if options.cli_working_dir is None:
1403
- cmd = 'python -m megadetector.detection.run_tiled_inference'
1417
+ cmd = 'python -m megadetector.postprocessing.postprocess_batch_results'
1404
1418
  else:
1405
- cmd = 'python megadetector/detection/run_tiled_inference.py'
1406
- cmd += ' "{}" "{}" "{}" "{}"'.format(
1407
- options.default_model,image_folder,tiling_folder,inference_output_file_tiled)
1408
- cmd += ' --overwrite_handling overwrite'
1419
+ cmd = 'python megadetector/postprocessing/postprocess_batch_results.py'
1420
+ cmd += ' "{}" "{}"'.format(
1421
+ inference_output_file,postprocessing_output_dir)
1422
+ cmd += ' --image_base_dir "{}"'.format(image_folder)
1409
1423
  cmd_results = execute_and_print(cmd)
1410
1424
 
1411
- with open(inference_output_file_tiled,'r') as f:
1412
- results_from_file = json.load(f) # noqa
1413
1425
 
1426
+ ## RDE
1427
+
1428
+ print('\n** Running RDE (CLI) **\n')
1414
1429
 
1415
- ## Run inference on a folder (augmented, w/YOLOv5 val script)
1430
+ rde_output_dir = os.path.join(options.scratch_dir,'rde_output_cli')
1416
1431
 
1417
- if options.yolo_working_dir is None:
1432
+ if options.cli_working_dir is None:
1433
+ cmd = 'python -m megadetector.postprocessing.repeat_detection_elimination.find_repeat_detections'
1434
+ else:
1435
+ cmd = 'python megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py'
1436
+ cmd += ' "{}"'.format(inference_output_file)
1437
+ cmd += ' --imageBase "{}"'.format(image_folder)
1438
+ cmd += ' --outputBase "{}"'.format(rde_output_dir)
1439
+ cmd += ' --occurrenceThreshold 1' # Use an absurd number here to make sure we get some suspicious detections
1440
+ cmd_results = execute_and_print(cmd)
1418
1441
 
1419
- print('Bypassing YOLOv5 val tests, no yolo folder supplied')
1442
+ # Find the latest filtering folder
1443
+ filtering_output_dir = os.listdir(rde_output_dir)
1444
+ filtering_output_dir = [fn for fn in filtering_output_dir if fn.startswith('filtering_')]
1445
+ filtering_output_dir = [os.path.join(rde_output_dir,fn) for fn in filtering_output_dir]
1446
+ filtering_output_dir = [fn for fn in filtering_output_dir if os.path.isdir(fn)]
1447
+ filtering_output_dir = sorted(filtering_output_dir)[-1]
1420
1448
 
1421
- else:
1449
+ print('Using RDE filtering folder {}'.format(filtering_output_dir))
1422
1450
 
1423
- print('\n** Running YOLOv5 val tests (CLI) **\n')
1451
+ filtered_output_file = inference_output_file.replace('.json','_filtered.json')
1424
1452
 
1425
- image_folder = os.path.join(options.scratch_dir,'md-test-images')
1426
- yolo_results_folder = os.path.join(options.scratch_dir,'yolo-output-folder')
1427
- yolo_symlink_folder = os.path.join(options.scratch_dir,'yolo-symlink_folder')
1428
- inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
1429
1453
  if options.cli_working_dir is None:
1430
- cmd = 'python -m megadetector.detection.run_inference_with_yolov5_val'
1454
+ cmd = 'python -m megadetector.postprocessing.repeat_detection_elimination.remove_repeat_detections'
1431
1455
  else:
1432
- cmd = 'python megadetector/detection/run_inference_with_yolov5_val.py'
1433
- cmd += ' "{}" "{}" "{}"'.format(
1434
- options.default_model,image_folder,inference_output_file_yolo_val)
1435
- cmd += ' --yolo_working_folder "{}"'.format(options.yolo_working_dir)
1436
- cmd += ' --yolo_results_folder "{}"'.format(yolo_results_folder)
1437
- cmd += ' --symlink_folder "{}"'.format(yolo_symlink_folder)
1438
- cmd += ' --augment_enabled 1'
1439
- # cmd += ' --no_use_symlinks'
1440
- cmd += ' --overwrite_handling overwrite'
1456
+ cmd = 'python megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py'
1457
+ cmd += ' "{}" "{}" "{}"'.format(inference_output_file,filtered_output_file,filtering_output_dir)
1441
1458
  cmd_results = execute_and_print(cmd)
1442
1459
 
1443
- # Run again with checkpointing, make sure the outputs are identical
1444
- cmd += ' --checkpoint_frequency 5'
1445
- inference_output_file_yolo_val_checkpoint = \
1446
- os.path.join(options.scratch_dir,'folder_inference_output_yolo_val_checkpoint.json')
1447
- assert inference_output_file_yolo_val_checkpoint != inference_output_file_yolo_val
1448
- cmd = cmd.replace(inference_output_file_yolo_val,inference_output_file_yolo_val_checkpoint)
1449
- cmd_results = execute_and_print(cmd)
1460
+ assert os.path.isfile(filtered_output_file), \
1461
+ 'Could not find RDE output file {}'.format(filtered_output_file)
1450
1462
 
1451
- assert output_files_are_identical(fn1=inference_output_file_yolo_val,
1452
- fn2=inference_output_file_yolo_val_checkpoint,
1453
- verbose=True)
1454
1463
 
1455
- if not options.skip_video_tests:
1464
+ ## Run inference on a folder (tiled)
1456
1465
 
1457
- ## Video test
1466
+ # This is a rather esoteric code path that I turn off when I'm testing some
1467
+ # features that it doesn't include yet, particularly compatibility mode
1468
+ # control.
1469
+ skip_tiling_tests = True
1470
+
1471
+ if skip_tiling_tests:
1472
+
1473
+ print('### DEBUG: skipping tiling tests ###')
1474
+
1475
+ else:
1476
+ print('\n** Running tiled inference (CLI) **\n')
1477
+
1478
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
1479
+ tiling_folder = os.path.join(options.scratch_dir,'tiling-folder')
1480
+ inference_output_file_tiled = os.path.join(options.scratch_dir,'folder_inference_output_tiled.json')
1481
+ if options.cli_working_dir is None:
1482
+ cmd = 'python -m megadetector.detection.run_tiled_inference'
1483
+ else:
1484
+ cmd = 'python megadetector/detection/run_tiled_inference.py'
1485
+ cmd += ' "{}" "{}" "{}" "{}"'.format(
1486
+ options.default_model,image_folder,tiling_folder,inference_output_file_tiled)
1487
+ cmd += ' --overwrite_handling overwrite'
1488
+ cmd_results = execute_and_print(cmd)
1489
+
1490
+ with open(inference_output_file_tiled,'r') as f:
1491
+ results_from_file = json.load(f) # noqa
1492
+
1493
+
1494
+ ## Run inference on a folder (augmented, w/YOLOv5 val script)
1495
+
1496
+ if options.yolo_working_dir is None:
1497
+
1498
+ print('Bypassing YOLOv5 val tests, no yolo folder supplied')
1499
+
1500
+ else:
1501
+
1502
+ print('\n** Running YOLOv5 val tests (CLI) **\n')
1503
+
1504
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
1505
+ yolo_results_folder = os.path.join(options.scratch_dir,'yolo-output-folder')
1506
+ yolo_symlink_folder = os.path.join(options.scratch_dir,'yolo-symlink_folder')
1507
+ inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
1508
+ if options.cli_working_dir is None:
1509
+ cmd = 'python -m megadetector.detection.run_inference_with_yolov5_val'
1510
+ else:
1511
+ cmd = 'python megadetector/detection/run_inference_with_yolov5_val.py'
1512
+ cmd += ' "{}" "{}" "{}"'.format(
1513
+ options.default_model,image_folder,inference_output_file_yolo_val)
1514
+ cmd += ' --yolo_working_folder "{}"'.format(options.yolo_working_dir)
1515
+ cmd += ' --yolo_results_folder "{}"'.format(yolo_results_folder)
1516
+ cmd += ' --symlink_folder "{}"'.format(yolo_symlink_folder)
1517
+ cmd += ' --augment_enabled 1'
1518
+ # cmd += ' --no_use_symlinks'
1519
+ cmd += ' --overwrite_handling overwrite'
1520
+ cmd_results = execute_and_print(cmd)
1521
+
1522
+ # Run again with checkpointing, make sure the outputs are identical
1523
+ cmd += ' --checkpoint_frequency 5'
1524
+ inference_output_file_yolo_val_checkpoint = \
1525
+ os.path.join(options.scratch_dir,'folder_inference_output_yolo_val_checkpoint.json')
1526
+ assert inference_output_file_yolo_val_checkpoint != inference_output_file_yolo_val
1527
+ cmd = cmd.replace(inference_output_file_yolo_val,inference_output_file_yolo_val_checkpoint)
1528
+ cmd_results = execute_and_print(cmd)
1529
+
1530
+ assert output_files_are_identical(fn1=inference_output_file_yolo_val,
1531
+ fn2=inference_output_file_yolo_val_checkpoint,
1532
+ verbose=True)
1458
1533
 
1459
- print('\n** Testing video rendering (CLI) **\n')
1460
1534
 
1461
- video_inference_output_file = os.path.join(options.scratch_dir,'video_inference_output.json')
1462
- output_video_file = os.path.join(options.scratch_dir,'video_scratch/cli_rendered_video.mp4')
1463
- frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder_cli')
1464
- frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder_cli')
1535
+ ## Run inference on a folder (with MDV5B, so we can do a comparison)
1465
1536
 
1466
- video_fn = os.path.join(options.scratch_dir,options.test_videos[-1])
1467
- assert os.path.isfile(video_fn), 'Could not find video file {}'.format(video_fn)
1537
+ print('\n** Running MDv5b (CLI) **\n')
1468
1538
 
1469
- output_dir = os.path.join(options.scratch_dir,'single_video_test_cli')
1539
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
1540
+ inference_output_file_alt = os.path.join(options.scratch_dir,'folder_inference_output_alt.json')
1470
1541
  if options.cli_working_dir is None:
1471
- cmd = 'python -m megadetector.detection.process_video'
1542
+ cmd = 'python -m megadetector.detection.run_detector_batch'
1472
1543
  else:
1473
- cmd = 'python megadetector/detection/process_video.py'
1474
- cmd += ' "{}" "{}"'.format(options.default_model,video_fn)
1475
- cmd += ' --frame_folder "{}" --frame_rendering_folder "{}" --output_json_file "{}" --output_video_file "{}"'.format( #noqa
1476
- frame_folder,frame_rendering_folder,video_inference_output_file,output_video_file)
1477
- cmd += ' --fourcc {}'.format(options.video_fourcc)
1478
- cmd += ' --force_extracted_frame_folder_deletion --force_rendered_frame_folder_deletion'
1479
- cmd += ' --n_cores {}'.format(options.n_cores_for_video_tests)
1480
- cmd += ' --frame_sample 4'
1481
- cmd += ' --verbose'
1544
+ cmd = 'python megadetector/detection/run_detector_batch.py'
1545
+ cmd += ' "{}" "{}" "{}" --recursive'.format(
1546
+ options.alt_model,image_folder,inference_output_file_alt)
1547
+ cmd += ' --output_relative_filenames --quiet --include_image_size'
1548
+ cmd += ' --include_image_timestamp --include_exif_data'
1482
1549
  cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1550
+ cmd_results = execute_and_print(cmd)
1551
+
1552
+ with open(inference_output_file_alt,'r') as f:
1553
+ results_from_file = json.load(f) # noqa
1483
1554
 
1484
- if not options.skip_video_rendering_tests:
1485
- cmd += ' --render_output_video'
1486
1555
 
1556
+ ## Compare the two files
1557
+
1558
+ comparison_output_folder = os.path.join(options.scratch_dir,'results_comparison')
1559
+ image_folder = os.path.join(options.scratch_dir,'md-test-images')
1560
+ results_files_string = '"{}" "{}"'.format(
1561
+ inference_output_file,inference_output_file_alt)
1562
+ if options.cli_working_dir is None:
1563
+ cmd = 'python -m megadetector.postprocessing.compare_batch_results'
1564
+ else:
1565
+ cmd = 'python megadetector/postprocessing/compare_batch_results.py'
1566
+ cmd += ' "{}" "{}" {}'.format(comparison_output_folder,image_folder,results_files_string)
1487
1567
  cmd_results = execute_and_print(cmd)
1488
1568
 
1489
- # ...if we're not skipping video tests
1569
+ assert cmd_results['status'] == 0, 'Error generating comparison HTML'
1570
+ assert os.path.isfile(os.path.join(comparison_output_folder,'index.html')), \
1571
+ 'Failed to generate comparison HTML'
1490
1572
 
1573
+ # ...if we're not skipping image tests
1491
1574
 
1492
- ## Run inference on a folder (with MDV5B, so we can do a comparison)
1493
1575
 
1494
- print('\n** Running MDv5b (CLI) **\n')
1576
+ if not options.skip_video_tests:
1495
1577
 
1496
- image_folder = os.path.join(options.scratch_dir,'md-test-images')
1497
- inference_output_file_alt = os.path.join(options.scratch_dir,'folder_inference_output_alt.json')
1498
- if options.cli_working_dir is None:
1499
- cmd = 'python -m megadetector.detection.run_detector_batch'
1500
- else:
1501
- cmd = 'python megadetector/detection/run_detector_batch.py'
1502
- cmd += ' "{}" "{}" "{}" --recursive'.format(
1503
- options.alt_model,image_folder,inference_output_file_alt)
1504
- cmd += ' --output_relative_filenames --quiet --include_image_size'
1505
- cmd += ' --include_image_timestamp --include_exif_data'
1506
- cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1507
- cmd_results = execute_and_print(cmd)
1508
-
1509
- with open(inference_output_file_alt,'r') as f:
1510
- results_from_file = json.load(f) # noqa
1578
+ ## Video test
1511
1579
 
1580
+ print('\n** Testing video processing (CLI) **\n')
1581
+
1582
+ video_inference_output_file = os.path.join(options.scratch_dir,'video_folder_output_cli.json')
1583
+ if options.cli_working_dir is None:
1584
+ cmd = 'python -m megadetector.detection.process_video'
1585
+ else:
1586
+ cmd = 'python megadetector/detection/process_video.py'
1512
1587
 
1513
- ## Compare the two files
1588
+ cmd += ' "{}" "{}"'.format(options.default_model,options.scratch_dir)
1589
+ cmd += ' --output_json_file "{}"'.format(video_inference_output_file)
1590
+ cmd += ' --frame_sample 4'
1591
+ cmd += ' --verbose'
1592
+ cmd += ' --recursive'
1593
+ cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1514
1594
 
1515
- comparison_output_folder = os.path.join(options.scratch_dir,'results_comparison')
1516
- image_folder = os.path.join(options.scratch_dir,'md-test-images')
1517
- results_files_string = '"{}" "{}"'.format(
1518
- inference_output_file,inference_output_file_alt)
1519
- if options.cli_working_dir is None:
1520
- cmd = 'python -m megadetector.postprocessing.compare_batch_results'
1521
- else:
1522
- cmd = 'python megadetector/postprocessing/compare_batch_results.py'
1523
- cmd += ' "{}" "{}" {}'.format(comparison_output_folder,image_folder,results_files_string)
1524
- cmd_results = execute_and_print(cmd)
1595
+ cmd_results = execute_and_print(cmd)
1525
1596
 
1526
- assert cmd_results['status'] == 0, 'Error generating comparison HTML'
1527
- assert os.path.isfile(os.path.join(comparison_output_folder,'index.html')), \
1528
- 'Failed to generate comparison HTML'
1597
+ # ...if we're not skipping video tests
1529
1598
 
1530
1599
  print('\n*** Finished CLI tests ***\n')
1531
1600
 
@@ -1692,7 +1761,8 @@ def run_tests(options):
1692
1761
 
1693
1762
  def test_suite_entry_point():
1694
1763
  """
1695
- Main entry point for the numerical test suite.
1764
+ This is the entry point when running tests via pytest; we run a subset of
1765
+ tests in this environment, e.g. we don't run CLI or video tests.
1696
1766
  """
1697
1767
 
1698
1768
  options = MDTestOptions()
@@ -1713,6 +1783,12 @@ def test_suite_entry_point():
1713
1783
  options.cli_test_pythonpath = None
1714
1784
  options.skip_download_tests = True
1715
1785
  options.skip_localhost_downloads = True
1786
+ options.skip_import_tests = False
1787
+
1788
+ if sys.platform == 'darwin':
1789
+ print('Detected a Mac environment, widening tolerance')
1790
+ options.max_coord_error = 0.05
1791
+ options.max_conf_error = 0.05
1716
1792
 
1717
1793
  options = download_test_data(options)
1718
1794
 
@@ -1727,12 +1803,14 @@ if False:
1727
1803
 
1728
1804
  #%% Test Prep
1729
1805
 
1806
+ from megadetector.utils.md_tests import MDTestOptions, download_test_data
1807
+
1730
1808
  options = MDTestOptions()
1731
1809
 
1732
1810
  options.disable_gpu = False
1733
1811
  options.cpu_execution_is_error = False
1734
- options.skip_video_tests = False
1735
- options.skip_python_tests = False
1812
+ options.skip_video_tests = True
1813
+ options.skip_python_tests = True
1736
1814
  options.skip_cli_tests = False
1737
1815
  options.scratch_dir = None
1738
1816
  options.test_data_url = 'https://lila.science/public/md-test-package.zip'
@@ -1741,18 +1819,19 @@ if False:
1741
1819
  options.warning_mode = False
1742
1820
  options.max_coord_error = 0.01 # 0.001
1743
1821
  options.max_conf_error = 0.01 # 0.005
1822
+ options.skip_cpu_tests = True
1744
1823
  options.skip_video_rendering_tests = True
1745
- options.skip_download_tests = False
1824
+ options.skip_download_tests = True
1746
1825
  options.skip_localhost_downloads = False
1747
1826
 
1748
1827
  # options.iou_threshold_for_file_comparison = 0.7
1749
1828
 
1750
- options.cli_working_dir = r'c:\git\MegaDetector'
1829
+ # options.cli_working_dir = r'c:\git\MegaDetector'
1751
1830
  # When running in the cameratraps-detector environment
1752
1831
  # options.cli_test_pythonpath = r'c:\git\MegaDetector;c:\git\yolov5-md'
1753
1832
 
1754
1833
  # When running in the MegaDetector environment
1755
- options.cli_test_pythonpath = r'c:\git\MegaDetector'
1834
+ # options.cli_test_pythonpath = r'c:\git\MegaDetector'
1756
1835
 
1757
1836
  # options.cli_working_dir = os.path.expanduser('~')
1758
1837
  # options.yolo_working_dir = r'c:\git\yolov5-md'
@@ -1774,11 +1853,13 @@ if False:
1774
1853
 
1775
1854
  #%% Run download tests
1776
1855
 
1856
+ from megadetector.utils.md_tests import run_download_tests
1777
1857
  run_download_tests(options=options)
1778
1858
 
1779
1859
 
1780
1860
  #%% Run all tests
1781
1861
 
1862
+ from megadetector.utils.md_tests import run_tests
1782
1863
  run_tests(options)
1783
1864
 
1784
1865
 
@@ -1849,10 +1930,15 @@ def main(): # noqa
1849
1930
  type=str,
1850
1931
  help='Directory for temporary storage (defaults to system temp dir)')
1851
1932
 
1933
+ parser.add_argument(
1934
+ '--skip_image_tests',
1935
+ action='store_true',
1936
+ help='Skip tests related to still images')
1937
+
1852
1938
  parser.add_argument(
1853
1939
  '--skip_video_tests',
1854
1940
  action='store_true',
1855
- help='Skip tests related to video (which can be slow)')
1941
+ help='Skip tests related to video')
1856
1942
 
1857
1943
  parser.add_argument(
1858
1944
  '--skip_video_rendering_tests',
@@ -1874,6 +1960,11 @@ def main(): # noqa
1874
1960
  action='store_true',
1875
1961
  help='Skip model download tests')
1876
1962
 
1963
+ parser.add_argument(
1964
+ '--skip_import_tests',
1965
+ action='store_true',
1966
+ help='Skip module import tests')
1967
+
1877
1968
  parser.add_argument(
1878
1969
  '--skip_cpu_tests',
1879
1970
  action='store_true',
@@ -1979,35 +2070,3 @@ def main(): # noqa
1979
2070
  if __name__ == '__main__':
1980
2071
  main()
1981
2072
 
1982
-
1983
- #%% Scrap
1984
-
1985
- if False:
1986
-
1987
- pass
1988
-
1989
- #%%
1990
-
1991
- import sys; sys.path.append(r'c:\git\yolov5-md')
1992
-
1993
- #%%
1994
-
1995
- fn1 = r"G:\temp\md-test-package\mdv5a-video-cpu-pt1.10.1.frames.json"
1996
- fn2 = r"G:\temp\md-test-package\mdv5a-video-gpu-pt1.10.1.frames.json"
1997
- fn3 = r"G:\temp\md-test-package\mdv5a-video-cpu-pt2.x.frames.json"
1998
- fn4 = r"G:\temp\md-test-package\mdv5a-video-gpu-pt2.x.frames.json"
1999
-
2000
- assert all([os.path.isfile(fn) for fn in [fn1,fn2,fn3,fn4]])
2001
- print(output_files_are_identical(fn1,fn1,verbose=False))
2002
- print(output_files_are_identical(fn1,fn2,verbose=False))
2003
- print(output_files_are_identical(fn1,fn3,verbose=False))
2004
-
2005
- #%%
2006
-
2007
- fn1 = r"G:\temp\md-test-package\mdv5a-image-gpu-pt1.10.1.json"
2008
- fn2 = r"G:\temp\md-test-package\mdv5a-augment-image-gpu-pt1.10.1.json"
2009
- print(output_files_are_identical(fn1,fn2,verbose=True))
2010
-
2011
- fn1 = r"G:\temp\md-test-package\mdv5a-image-cpu-pt1.10.1.json"
2012
- fn2 = r"G:\temp\md-test-package\mdv5a-augment-image-cpu-pt1.10.1.json"
2013
- print(output_files_are_identical(fn1,fn2,verbose=True))