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.
- megadetector/data_management/animl_to_md.py +158 -0
- megadetector/data_management/zamba_to_md.py +188 -0
- megadetector/detection/process_video.py +165 -946
- megadetector/detection/pytorch_detector.py +575 -276
- megadetector/detection/run_detector_batch.py +629 -202
- megadetector/detection/run_md_and_speciesnet.py +1319 -0
- megadetector/detection/video_utils.py +243 -107
- megadetector/postprocessing/classification_postprocessing.py +12 -1
- megadetector/postprocessing/combine_batch_outputs.py +2 -0
- megadetector/postprocessing/compare_batch_results.py +21 -2
- megadetector/postprocessing/merge_detections.py +16 -12
- megadetector/postprocessing/separate_detections_into_folders.py +1 -1
- megadetector/postprocessing/subset_json_detector_output.py +1 -3
- 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 +69 -5
- megadetector/utils/extract_frames_from_video.py +303 -0
- megadetector/utils/md_tests.py +583 -524
- megadetector/utils/path_utils.py +4 -15
- megadetector/utils/wi_utils.py +20 -4
- megadetector/visualization/visualization_utils.py +1 -1
- megadetector/visualization/visualize_db.py +8 -22
- megadetector/visualization/visualize_detector_output.py +7 -5
- megadetector/visualization/visualize_video_output.py +607 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/METADATA +134 -135
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/RECORD +30 -23
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/licenses/LICENSE +0 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/top_level.txt +0 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/WHEEL +0 -0
megadetector/utils/md_tests.py
CHANGED
|
@@ -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
|
|
57
|
-
self.
|
|
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
|
-
#:
|
|
145
|
-
self.
|
|
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 (
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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,
|
|
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
|
-
|
|
588
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
-
|
|
828
|
+
if not options.skip_image_tests:
|
|
816
829
|
|
|
817
|
-
|
|
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
|
-
|
|
828
|
-
|
|
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
|
-
|
|
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
|
-
|
|
836
|
-
from megadetector.utils import path_utils # noqa
|
|
853
|
+
## Run inference on a folder
|
|
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
|
+
print('\n** Running MD on a folder of images (module) **\n')
|
|
850
856
|
|
|
851
|
-
|
|
857
|
+
from megadetector.detection.run_detector_batch import load_and_run_detector_batch,write_results_to_file
|
|
852
858
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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
|
-
|
|
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
|
-
|
|
864
|
-
|
|
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
|
-
|
|
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
|
-
|
|
887
|
+
## Run again with a batch size > 1
|
|
875
888
|
|
|
876
|
-
|
|
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
|
-
|
|
888
|
-
|
|
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
|
-
|
|
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
|
-
|
|
912
|
+
## Run and verify again with augmentation enabled
|
|
896
913
|
|
|
897
|
-
|
|
898
|
-
PostProcessingOptions,process_batch_results
|
|
899
|
-
postprocessing_options = PostProcessingOptions()
|
|
914
|
+
print('\n** Running MD on images with augmentation (module) **\n')
|
|
900
915
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
-
|
|
933
|
+
## Postprocess results
|
|
911
934
|
|
|
912
|
-
|
|
935
|
+
print('\n** Post-processing results (module) **\n')
|
|
913
936
|
|
|
914
|
-
|
|
915
|
-
|
|
937
|
+
from megadetector.postprocessing.postprocess_batch_results import \
|
|
938
|
+
PostProcessingOptions,process_batch_results
|
|
939
|
+
postprocessing_options = PostProcessingOptions()
|
|
916
940
|
|
|
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)
|
|
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
|
-
|
|
950
|
+
## Partial RDE test
|
|
932
951
|
|
|
933
|
-
print('
|
|
952
|
+
print('\n** Testing RDE (module) **\n')
|
|
934
953
|
|
|
935
|
-
|
|
954
|
+
from megadetector.postprocessing.repeat_detection_elimination.repeat_detections_core import \
|
|
955
|
+
RepeatDetectionOptions, find_repeat_detections
|
|
936
956
|
|
|
937
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
983
|
+
inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
|
|
956
984
|
|
|
957
|
-
|
|
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
|
-
|
|
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
|
-
|
|
997
|
+
## Confirm this matches the standard inference path
|
|
966
998
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
1005
|
+
# Run again, without symlinks this time
|
|
974
1006
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
1013
|
+
# Run again, with chunked inference and symlinks
|
|
983
1014
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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
|
-
|
|
1022
|
+
# Run again, with chunked inference and no symlinks
|
|
992
1023
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
-
|
|
1000
|
-
assert output_files_are_identical(fn1, fn2, verbose=True)
|
|
1031
|
+
fn1 = inference_output_file_yolo_val
|
|
1001
1032
|
|
|
1002
|
-
|
|
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,
|
|
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
|
-
_ =
|
|
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,
|
|
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.
|
|
1074
|
-
|
|
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(
|
|
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
|
-
|
|
1161
|
+
if not options.skip_image_tests:
|
|
1186
1162
|
|
|
1187
|
-
|
|
1163
|
+
## Run inference on an image
|
|
1188
1164
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1190
|
+
print('\n** Running MD on a single image (CLI) (with symbolic model name) **\n')
|
|
1218
1191
|
|
|
1219
|
-
|
|
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
|
-
|
|
1198
|
+
## Run inference on a folder
|
|
1236
1199
|
|
|
1200
|
+
print('\n** Running MD on a folder (CLI) **\n')
|
|
1237
1201
|
|
|
1238
|
-
|
|
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
|
-
|
|
1214
|
+
base_cmd = cmd
|
|
1241
1215
|
|
|
1242
|
-
|
|
1243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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
|
-
|
|
1256
|
+
## Run again with the image queue and worker-side preprocessing enabled
|
|
1282
1257
|
|
|
1283
|
-
|
|
1258
|
+
print('\n** Running MD on a folder (with image queue and worker-side preprocessing) (CLI) **\n')
|
|
1284
1259
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
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
|
-
|
|
1272
|
+
## Run again with the image queue and worker-side preprocessing
|
|
1298
1273
|
|
|
1299
|
-
|
|
1274
|
+
print('\n** Running MD on a folder (with image queue and preprocessing) (CLI) **\n')
|
|
1300
1275
|
|
|
1301
|
-
|
|
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
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
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
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1307
|
+
## Run again with checkpointing enabled
|
|
1329
1308
|
|
|
1309
|
+
print('\n** Running MD on a folder (with checkpoints) (CLI) **\n')
|
|
1330
1310
|
|
|
1331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
1382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1360
|
+
if not options.skip_cpu_tests:
|
|
1393
1361
|
|
|
1394
|
-
|
|
1362
|
+
## Run again on multiple cores
|
|
1395
1363
|
|
|
1396
|
-
|
|
1397
|
-
|
|
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.
|
|
1417
|
+
cmd = 'python -m megadetector.postprocessing.postprocess_batch_results'
|
|
1404
1418
|
else:
|
|
1405
|
-
cmd = 'python megadetector/
|
|
1406
|
-
cmd += ' "{}" "{}"
|
|
1407
|
-
|
|
1408
|
-
cmd += ' --
|
|
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
|
-
|
|
1430
|
+
rde_output_dir = os.path.join(options.scratch_dir,'rde_output_cli')
|
|
1416
1431
|
|
|
1417
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1449
|
+
print('Using RDE filtering folder {}'.format(filtering_output_dir))
|
|
1422
1450
|
|
|
1423
|
-
|
|
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.
|
|
1454
|
+
cmd = 'python -m megadetector.postprocessing.repeat_detection_elimination.remove_repeat_detections'
|
|
1431
1455
|
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'
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
1464
|
+
## Run inference on a folder (tiled)
|
|
1456
1465
|
|
|
1457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1542
|
+
cmd = 'python -m megadetector.detection.run_detector_batch'
|
|
1472
1543
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
1576
|
+
if not options.skip_video_tests:
|
|
1495
1577
|
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1735
|
-
options.skip_python_tests =
|
|
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 =
|
|
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
|
|
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))
|