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