megadetector 5.0.22__py3-none-any.whl → 5.0.24__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/api/synchronous/api_core/animal_detection_api/api_backend.py +2 -3
- megadetector/classification/merge_classification_detection_output.py +2 -2
- megadetector/data_management/coco_to_labelme.py +2 -1
- megadetector/data_management/databases/integrity_check_json_db.py +15 -14
- megadetector/data_management/databases/subset_json_db.py +49 -21
- megadetector/data_management/mewc_to_md.py +340 -0
- megadetector/data_management/wi_to_md.py +41 -0
- megadetector/data_management/yolo_output_to_md_output.py +15 -8
- megadetector/detection/process_video.py +24 -7
- megadetector/detection/pytorch_detector.py +841 -160
- megadetector/detection/run_detector.py +340 -146
- megadetector/detection/run_detector_batch.py +306 -70
- megadetector/detection/run_inference_with_yolov5_val.py +61 -4
- megadetector/detection/tf_detector.py +6 -1
- megadetector/postprocessing/{combine_api_outputs.py → combine_batch_outputs.py} +10 -13
- megadetector/postprocessing/compare_batch_results.py +68 -6
- megadetector/postprocessing/md_to_labelme.py +7 -7
- megadetector/postprocessing/md_to_wi.py +40 -0
- megadetector/postprocessing/merge_detections.py +1 -1
- megadetector/postprocessing/postprocess_batch_results.py +10 -3
- megadetector/postprocessing/separate_detections_into_folders.py +32 -4
- megadetector/postprocessing/validate_batch_results.py +9 -4
- megadetector/utils/ct_utils.py +172 -57
- megadetector/utils/gpu_test.py +107 -0
- megadetector/utils/md_tests.py +363 -108
- megadetector/utils/path_utils.py +9 -2
- megadetector/utils/wi_utils.py +1794 -0
- megadetector/visualization/visualization_utils.py +82 -16
- megadetector/visualization/visualize_db.py +25 -7
- megadetector/visualization/visualize_detector_output.py +60 -13
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/LICENSE +0 -0
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/METADATA +129 -143
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/RECORD +35 -33
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/top_level.txt +0 -0
- megadetector/detection/detector_training/__init__.py +0 -0
- megadetector/detection/detector_training/model_main_tf2.py +0 -114
- megadetector/utils/torch_test.py +0 -32
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/WHEEL +0 -0
megadetector/utils/md_tests.py
CHANGED
|
@@ -29,6 +29,8 @@ import subprocess
|
|
|
29
29
|
import argparse
|
|
30
30
|
import inspect
|
|
31
31
|
|
|
32
|
+
from copy import copy
|
|
33
|
+
|
|
32
34
|
|
|
33
35
|
#%% Classes
|
|
34
36
|
|
|
@@ -50,12 +52,21 @@ class MDTestOptions:
|
|
|
50
52
|
#: Skip tests related to video processing
|
|
51
53
|
self.skip_video_tests = False
|
|
52
54
|
|
|
55
|
+
#: Skip tests related to video rendering
|
|
56
|
+
self.skip_video_rendering_tests = False
|
|
57
|
+
|
|
53
58
|
#: Skip tests launched via Python functions (as opposed to CLIs)
|
|
54
59
|
self.skip_python_tests = False
|
|
55
60
|
|
|
56
61
|
#: Skip CLI tests
|
|
57
62
|
self.skip_cli_tests = False
|
|
58
63
|
|
|
64
|
+
#: Skip download tests
|
|
65
|
+
self.skip_download_tests = False
|
|
66
|
+
|
|
67
|
+
#: Skip force-CPU tests
|
|
68
|
+
self.skip_cpu_tests = False
|
|
69
|
+
|
|
59
70
|
#: Force a specific folder for temporary input/output
|
|
60
71
|
self.scratch_dir = None
|
|
61
72
|
|
|
@@ -106,7 +117,22 @@ class MDTestOptions:
|
|
|
106
117
|
#: IoU threshold used to determine whether boxes in two detection files likely correspond
|
|
107
118
|
#: to the same box.
|
|
108
119
|
self.iou_threshold_for_file_comparison = 0.85
|
|
109
|
-
|
|
120
|
+
|
|
121
|
+
#: Detector options passed to PTDetector
|
|
122
|
+
self.detector_options = {'compatibility_mode':'classic-test'}
|
|
123
|
+
|
|
124
|
+
#: Used to drive a series of tests (typically with a low value for
|
|
125
|
+
#: python_test_depth) over a folder of models.
|
|
126
|
+
self.model_folder = None
|
|
127
|
+
|
|
128
|
+
#: Used as a knob to control the level of Python tests, typically used when
|
|
129
|
+
#: we want to run a series of simple tests on a small number of models, rather
|
|
130
|
+
#: than a deep test of tests on a small number of models. The gestalt is that
|
|
131
|
+
#: this is a range from 0-100.
|
|
132
|
+
self.python_test_depth = 100
|
|
133
|
+
|
|
134
|
+
# ...def __init__()
|
|
135
|
+
|
|
110
136
|
# ...class MDTestOptions()
|
|
111
137
|
|
|
112
138
|
|
|
@@ -171,7 +197,7 @@ def get_expected_results_filename(gpu_is_available,
|
|
|
171
197
|
|
|
172
198
|
if options is not None and options.scratch_dir is not None:
|
|
173
199
|
fn = os.path.join(options.scratch_dir,fn)
|
|
174
|
-
|
|
200
|
+
|
|
175
201
|
return fn
|
|
176
202
|
|
|
177
203
|
|
|
@@ -268,7 +294,7 @@ def download_test_data(options=None):
|
|
|
268
294
|
os.path.isfile(os.path.join(scratch_dir,fn))]
|
|
269
295
|
|
|
270
296
|
print('Finished unzipping and enumerating test data')
|
|
271
|
-
|
|
297
|
+
|
|
272
298
|
return options
|
|
273
299
|
|
|
274
300
|
# ...def download_test_data(...)
|
|
@@ -276,7 +302,7 @@ def download_test_data(options=None):
|
|
|
276
302
|
|
|
277
303
|
def is_gpu_available(verbose=True):
|
|
278
304
|
"""
|
|
279
|
-
Checks whether a GPU (including M1/M2 MPS) is available.
|
|
305
|
+
Checks whether a GPU (including M1/M2 MPS) is available, according to PyTorch.
|
|
280
306
|
|
|
281
307
|
Args:
|
|
282
308
|
verbose (bool, optional): enable additional debug console output
|
|
@@ -384,15 +410,21 @@ def compare_detection_lists(detections_a,detections_b,options,bidirectional_comp
|
|
|
384
410
|
|
|
385
411
|
max_conf_error = 0
|
|
386
412
|
max_coord_error = 0
|
|
387
|
-
|
|
413
|
+
|
|
414
|
+
max_conf_error_det_a = None
|
|
415
|
+
max_conf_error_det_b = None
|
|
416
|
+
|
|
417
|
+
max_coord_error_det_a = None
|
|
418
|
+
max_coord_error_det_b = None
|
|
419
|
+
|
|
388
420
|
# i_det_a = 0
|
|
389
421
|
for i_det_a in range(0,len(detections_a)):
|
|
390
422
|
|
|
391
423
|
det_a = detections_a[i_det_a]
|
|
392
424
|
|
|
393
425
|
# Don't process very-low-confidence boxes
|
|
394
|
-
if det_a['conf'] < options.max_conf_error:
|
|
395
|
-
|
|
426
|
+
# if det_a['conf'] < options.max_conf_error:
|
|
427
|
+
# continue
|
|
396
428
|
|
|
397
429
|
matching_det_b = None
|
|
398
430
|
highest_iou = -1
|
|
@@ -402,22 +434,23 @@ def compare_detection_lists(detections_a,detections_b,options,bidirectional_comp
|
|
|
402
434
|
# i_det_b = 0
|
|
403
435
|
for i_det_b in range(0,len(detections_b)):
|
|
404
436
|
|
|
405
|
-
|
|
437
|
+
det_b = detections_b[i_det_b]
|
|
406
438
|
|
|
407
|
-
if
|
|
439
|
+
if det_b['category'] != det_a['category']:
|
|
408
440
|
continue
|
|
409
441
|
|
|
410
|
-
iou = get_iou(det_a['bbox'],
|
|
442
|
+
iou = get_iou(det_a['bbox'],det_b['bbox'])
|
|
411
443
|
|
|
412
444
|
# Is this likely the same detection as det_a?
|
|
413
445
|
if iou >= options.iou_threshold_for_file_comparison and iou > highest_iou:
|
|
414
|
-
matching_det_b =
|
|
446
|
+
matching_det_b = det_b
|
|
415
447
|
highest_iou = iou
|
|
416
448
|
|
|
417
449
|
# If there are no detections in this category in detections_b
|
|
418
450
|
if matching_det_b is None:
|
|
419
451
|
if det_a['conf'] > max_conf_error:
|
|
420
452
|
max_conf_error = det_a['conf']
|
|
453
|
+
max_conf_error_det_a = det_a
|
|
421
454
|
# max_coord_error = 1.0
|
|
422
455
|
continue
|
|
423
456
|
|
|
@@ -427,13 +460,17 @@ def compare_detection_lists(detections_a,detections_b,options,bidirectional_comp
|
|
|
427
460
|
for i_coord in range(0,4):
|
|
428
461
|
coord_differences.append(abs(det_a['bbox'][i_coord]-\
|
|
429
462
|
matching_det_b['bbox'][i_coord]))
|
|
430
|
-
coord_err = max(coord_differences)
|
|
463
|
+
coord_err = max(coord_differences)
|
|
431
464
|
|
|
432
465
|
if conf_err >= max_conf_error:
|
|
433
466
|
max_conf_error = conf_err
|
|
467
|
+
max_conf_error_det_a = det_a
|
|
468
|
+
max_conf_error_det_b = det_b
|
|
434
469
|
|
|
435
470
|
if coord_err >= max_coord_error:
|
|
436
|
-
max_coord_error = coord_err
|
|
471
|
+
max_coord_error = coord_err
|
|
472
|
+
max_coord_error_det_a = det_a
|
|
473
|
+
max_coord_error_det_b = det_b
|
|
437
474
|
|
|
438
475
|
# ...for each detection in detections_a
|
|
439
476
|
|
|
@@ -446,19 +483,32 @@ def compare_detection_lists(detections_a,detections_b,options,bidirectional_comp
|
|
|
446
483
|
|
|
447
484
|
if reverse_comparison_results['max_conf_error'] > max_conf_error:
|
|
448
485
|
max_conf_error = reverse_comparison_results['max_conf_error']
|
|
486
|
+
max_conf_error_det_a = reverse_comparison_results['max_conf_error_det_b']
|
|
487
|
+
max_conf_error_det_b = reverse_comparison_results['max_conf_error_det_a']
|
|
449
488
|
if reverse_comparison_results['max_coord_error'] > max_coord_error:
|
|
450
489
|
max_coord_error = reverse_comparison_results['max_coord_error']
|
|
490
|
+
max_coord_error_det_a = reverse_comparison_results['max_coord_error_det_b']
|
|
491
|
+
max_coord_error_det_b = reverse_comparison_results['max_coord_error_det_a']
|
|
451
492
|
|
|
452
493
|
list_comparison_results = {}
|
|
494
|
+
|
|
453
495
|
list_comparison_results['max_coord_error'] = max_coord_error
|
|
496
|
+
list_comparison_results['max_coord_error_det_a'] = max_coord_error_det_a
|
|
497
|
+
list_comparison_results['max_coord_error_det_b'] = max_coord_error_det_b
|
|
498
|
+
|
|
454
499
|
list_comparison_results['max_conf_error'] = max_conf_error
|
|
500
|
+
list_comparison_results['max_conf_error_det_a'] = max_conf_error_det_a
|
|
501
|
+
list_comparison_results['max_conf_error_det_b'] = max_conf_error_det_b
|
|
455
502
|
|
|
456
503
|
return list_comparison_results
|
|
457
504
|
|
|
458
505
|
# ...def compare_detection_lists(...)
|
|
459
506
|
|
|
460
507
|
|
|
461
|
-
def compare_results(inference_output_file,
|
|
508
|
+
def compare_results(inference_output_file,
|
|
509
|
+
expected_results_file,
|
|
510
|
+
options,
|
|
511
|
+
expected_results_file_is_absolute=False):
|
|
462
512
|
"""
|
|
463
513
|
Compare two MD-formatted output files that should be nearly identical, allowing small
|
|
464
514
|
changes (e.g. rounding differences). Generally used to compare a new results file to
|
|
@@ -468,6 +518,9 @@ def compare_results(inference_output_file,expected_results_file,options):
|
|
|
468
518
|
inference_output_file (str): the first results file to compare
|
|
469
519
|
expected_results_file (str): the second results file to compare
|
|
470
520
|
options (MDTestOptions): options that determine tolerable differences between files
|
|
521
|
+
expected_results_file_is_absolute (str, optional): by default,
|
|
522
|
+
expected_results_file is appended to options.scratch_dir; this option
|
|
523
|
+
specifies that it's an absolute path.
|
|
471
524
|
|
|
472
525
|
Returns:
|
|
473
526
|
dict: dictionary with keys 'max_coord_error' and 'max_conf_error'
|
|
@@ -477,7 +530,10 @@ def compare_results(inference_output_file,expected_results_file,options):
|
|
|
477
530
|
with open(inference_output_file,'r') as f:
|
|
478
531
|
results_from_file = json.load(f) # noqa
|
|
479
532
|
|
|
480
|
-
|
|
533
|
+
if not expected_results_file_is_absolute:
|
|
534
|
+
expected_results_file= os.path.join(options.scratch_dir,expected_results_file)
|
|
535
|
+
|
|
536
|
+
with open(expected_results_file,'r') as f:
|
|
481
537
|
expected_results = json.load(f)
|
|
482
538
|
|
|
483
539
|
filename_to_results = {im['file'].replace('\\','/'):im for im in results_from_file['images']}
|
|
@@ -488,11 +544,13 @@ def compare_results(inference_output_file,expected_results_file,options):
|
|
|
488
544
|
len(filename_to_results_expected),
|
|
489
545
|
len(filename_to_results))
|
|
490
546
|
|
|
491
|
-
max_conf_error =
|
|
547
|
+
max_conf_error = -1
|
|
492
548
|
max_conf_error_file = None
|
|
549
|
+
max_conf_error_comparison_results = None
|
|
493
550
|
|
|
494
|
-
max_coord_error =
|
|
551
|
+
max_coord_error = -1
|
|
495
552
|
max_coord_error_file = None
|
|
553
|
+
max_coord_error_comparison_results = None
|
|
496
554
|
|
|
497
555
|
# fn = next(iter(filename_to_results.keys()))
|
|
498
556
|
for fn in filename_to_results.keys():
|
|
@@ -518,10 +576,12 @@ def compare_results(inference_output_file,expected_results_file,options):
|
|
|
518
576
|
|
|
519
577
|
if comparison_results_this_image['max_conf_error'] > max_conf_error:
|
|
520
578
|
max_conf_error = comparison_results_this_image['max_conf_error']
|
|
579
|
+
max_conf_error_comparison_results = comparison_results_this_image
|
|
521
580
|
max_conf_error_file = fn
|
|
522
581
|
|
|
523
582
|
if comparison_results_this_image['max_coord_error'] > max_coord_error:
|
|
524
583
|
max_coord_error = comparison_results_this_image['max_coord_error']
|
|
584
|
+
max_coord_error_comparison_results = comparison_results_this_image
|
|
525
585
|
max_coord_error_file = fn
|
|
526
586
|
|
|
527
587
|
# ...for each image
|
|
@@ -537,7 +597,7 @@ def compare_results(inference_output_file,expected_results_file,options):
|
|
|
537
597
|
'Coord error {} is greater than allowable ({}), on file:\n{} ({},{})'.format(
|
|
538
598
|
max_coord_error,options.max_coord_error,max_coord_error_file,
|
|
539
599
|
inference_output_file,expected_results_file)
|
|
540
|
-
|
|
600
|
+
|
|
541
601
|
print('Max conf error: {} (file {})'.format(
|
|
542
602
|
max_conf_error,max_conf_error_file))
|
|
543
603
|
print('Max coord error: {} (file {})'.format(
|
|
@@ -545,7 +605,9 @@ def compare_results(inference_output_file,expected_results_file,options):
|
|
|
545
605
|
|
|
546
606
|
comparison_results = {}
|
|
547
607
|
comparison_results['max_conf_error'] = max_conf_error
|
|
608
|
+
comparison_results['max_conf_error_comparison_results'] = max_conf_error_comparison_results
|
|
548
609
|
comparison_results['max_coord_error'] = max_coord_error
|
|
610
|
+
comparison_results['max_coord_error_comparison_results'] = max_coord_error_comparison_results
|
|
549
611
|
|
|
550
612
|
return comparison_results
|
|
551
613
|
|
|
@@ -580,6 +642,10 @@ def _args_to_object(args, obj):
|
|
|
580
642
|
|
|
581
643
|
os.environ["PYTHONUNBUFFERED"] = "1"
|
|
582
644
|
|
|
645
|
+
# In some circumstances I want to allow CLI tests to "succeed" even when they return
|
|
646
|
+
# specific non-zero output values.
|
|
647
|
+
allowable_process_return_codes = [0]
|
|
648
|
+
|
|
583
649
|
def execute(cmd):
|
|
584
650
|
"""
|
|
585
651
|
Runs [cmd] (a single string) in a shell, yielding each line of output to the caller.
|
|
@@ -598,7 +664,7 @@ def execute(cmd):
|
|
|
598
664
|
yield stdout_line
|
|
599
665
|
popen.stdout.close()
|
|
600
666
|
return_code = popen.wait()
|
|
601
|
-
if return_code:
|
|
667
|
+
if return_code not in allowable_process_return_codes:
|
|
602
668
|
raise subprocess.CalledProcessError(return_code, cmd)
|
|
603
669
|
return return_code
|
|
604
670
|
|
|
@@ -628,7 +694,7 @@ def execute_and_print(cmd,print_output=True,catch_exceptions=False,echo_command=
|
|
|
628
694
|
print(s,end='',flush=True)
|
|
629
695
|
to_return['status'] = 0
|
|
630
696
|
except subprocess.CalledProcessError as cpe:
|
|
631
|
-
if not catch_exceptions:
|
|
697
|
+
if not catch_exceptions:
|
|
632
698
|
raise
|
|
633
699
|
print('execute_and_print caught error: {}'.format(cpe.output))
|
|
634
700
|
to_return['status'] = cpe.returncode
|
|
@@ -648,23 +714,41 @@ def run_python_tests(options):
|
|
|
648
714
|
"""
|
|
649
715
|
|
|
650
716
|
print('\n*** Starting module tests ***\n')
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
## Make sure our tests are doing what we think they're doing
|
|
720
|
+
|
|
721
|
+
from megadetector.detection import pytorch_detector
|
|
722
|
+
pytorch_detector.require_non_default_compatibility_mode = True
|
|
651
723
|
|
|
652
724
|
## Prepare data
|
|
653
725
|
|
|
654
726
|
download_test_data(options)
|
|
655
727
|
|
|
656
728
|
|
|
657
|
-
##
|
|
729
|
+
## Miscellaneous utility tests
|
|
730
|
+
|
|
731
|
+
print('\n** Running ct_utils module test **\n')
|
|
732
|
+
|
|
733
|
+
from megadetector.utils.ct_utils import __module_test__ as ct_utils_test
|
|
734
|
+
ct_utils_test()
|
|
658
735
|
|
|
736
|
+
|
|
737
|
+
## Run inference on an image
|
|
738
|
+
|
|
659
739
|
print('\n** Running MD on a single image (module) **\n')
|
|
660
740
|
|
|
661
741
|
from megadetector.detection import run_detector
|
|
662
742
|
from megadetector.visualization import visualization_utils as vis_utils
|
|
663
743
|
image_fn = os.path.join(options.scratch_dir,options.test_images[0])
|
|
664
|
-
model = run_detector.load_detector(options.default_model
|
|
744
|
+
model = run_detector.load_detector(options.default_model,
|
|
745
|
+
detector_options=copy(options.detector_options))
|
|
665
746
|
pil_im = vis_utils.load_image(image_fn)
|
|
666
747
|
result = model.generate_detections_one_image(pil_im) # noqa
|
|
667
|
-
|
|
748
|
+
|
|
749
|
+
if options.python_test_depth <= 1:
|
|
750
|
+
return
|
|
751
|
+
|
|
668
752
|
|
|
669
753
|
## Run inference on a folder
|
|
670
754
|
|
|
@@ -677,13 +761,15 @@ def run_python_tests(options):
|
|
|
677
761
|
assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
|
|
678
762
|
inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
|
|
679
763
|
image_file_names = path_utils.find_images(image_folder,recursive=True)
|
|
680
|
-
results = load_and_run_detector_batch(options.default_model,
|
|
764
|
+
results = load_and_run_detector_batch(options.default_model,
|
|
765
|
+
image_file_names,
|
|
766
|
+
quiet=True,
|
|
767
|
+
detector_options=copy(options.detector_options))
|
|
681
768
|
_ = write_results_to_file(results,
|
|
682
769
|
inference_output_file,
|
|
683
770
|
relative_path_base=image_folder,
|
|
684
771
|
detector_file=options.default_model)
|
|
685
772
|
|
|
686
|
-
|
|
687
773
|
## Verify results
|
|
688
774
|
|
|
689
775
|
# Verify format correctness
|
|
@@ -698,6 +784,9 @@ def run_python_tests(options):
|
|
|
698
784
|
|
|
699
785
|
# Make note of this filename, we will use it again later
|
|
700
786
|
inference_output_file_standard_inference = inference_output_file
|
|
787
|
+
|
|
788
|
+
if options.python_test_depth <= 2:
|
|
789
|
+
return
|
|
701
790
|
|
|
702
791
|
|
|
703
792
|
## Run and verify again with augmentation enabled
|
|
@@ -707,7 +796,11 @@ def run_python_tests(options):
|
|
|
707
796
|
from megadetector.utils.path_utils import insert_before_extension
|
|
708
797
|
|
|
709
798
|
inference_output_file_augmented = insert_before_extension(inference_output_file,'augmented')
|
|
710
|
-
results = load_and_run_detector_batch(options.default_model,
|
|
799
|
+
results = load_and_run_detector_batch(options.default_model,
|
|
800
|
+
image_file_names,
|
|
801
|
+
quiet=True,
|
|
802
|
+
augment=True,
|
|
803
|
+
detector_options=copy(options.detector_options))
|
|
711
804
|
_ = write_results_to_file(results,
|
|
712
805
|
inference_output_file_augmented,
|
|
713
806
|
relative_path_base=image_folder,
|
|
@@ -847,7 +940,9 @@ def run_python_tests(options):
|
|
|
847
940
|
video_options.output_video_file = os.path.join(options.scratch_dir,'video_scratch/rendered_video.mp4')
|
|
848
941
|
video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
|
|
849
942
|
video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
|
|
850
|
-
|
|
943
|
+
|
|
944
|
+
video_options.render_output_video = (not options.skip_video_rendering_tests)
|
|
945
|
+
|
|
851
946
|
# video_options.keep_rendered_frames = False
|
|
852
947
|
# video_options.keep_extracted_frames = False
|
|
853
948
|
video_options.force_extracted_frame_folder_deletion = True
|
|
@@ -863,6 +958,7 @@ def run_python_tests(options):
|
|
|
863
958
|
video_options.n_cores = 5
|
|
864
959
|
# video_options.debug_max_frames = -1
|
|
865
960
|
# video_options.class_mapping_filename = None
|
|
961
|
+
video_options.detector_options = copy(options.detector_options)
|
|
866
962
|
|
|
867
963
|
_ = process_video(video_options)
|
|
868
964
|
|
|
@@ -900,7 +996,7 @@ def run_python_tests(options):
|
|
|
900
996
|
# video_options.rendering_confidence_threshold = None
|
|
901
997
|
# video_options.json_confidence_threshold = 0.005
|
|
902
998
|
video_options.frame_sample = 10
|
|
903
|
-
video_options.n_cores = 5
|
|
999
|
+
video_options.n_cores = 5
|
|
904
1000
|
|
|
905
1001
|
# Force frame extraction to disk, since that's how we generated our expected results file
|
|
906
1002
|
video_options.force_on_disk_frame_extraction = True
|
|
@@ -910,13 +1006,15 @@ def run_python_tests(options):
|
|
|
910
1006
|
# Use quality == None, because we can't control whether YOLOv5 has patched cm2.imread,
|
|
911
1007
|
# and therefore can't rely on using the quality parameter
|
|
912
1008
|
video_options.quality = None
|
|
913
|
-
video_options.max_width = None
|
|
1009
|
+
video_options.max_width = None
|
|
1010
|
+
video_options.detector_options = copy(options.detector_options)
|
|
914
1011
|
|
|
1012
|
+
video_options.keep_extracted_frames = True
|
|
915
1013
|
_ = process_video_folder(video_options)
|
|
916
1014
|
|
|
917
1015
|
assert os.path.isfile(video_options.output_json_file), \
|
|
918
1016
|
'Python video test failed to render output .json file'
|
|
919
|
-
|
|
1017
|
+
|
|
920
1018
|
frame_output_file = insert_before_extension(video_options.output_json_file,'frames')
|
|
921
1019
|
assert os.path.isfile(frame_output_file)
|
|
922
1020
|
|
|
@@ -926,6 +1024,7 @@ def run_python_tests(options):
|
|
|
926
1024
|
expected_results_file = \
|
|
927
1025
|
get_expected_results_filename(is_gpu_available(verbose=False),test_type='video',options=options)
|
|
928
1026
|
assert os.path.isfile(expected_results_file)
|
|
1027
|
+
|
|
929
1028
|
compare_results(frame_output_file,expected_results_file,options)
|
|
930
1029
|
|
|
931
1030
|
|
|
@@ -970,7 +1069,6 @@ def run_cli_tests(options):
|
|
|
970
1069
|
|
|
971
1070
|
print('\n*** Starting CLI tests ***\n')
|
|
972
1071
|
|
|
973
|
-
|
|
974
1072
|
## Environment management
|
|
975
1073
|
|
|
976
1074
|
if options.cli_test_pythonpath is not None:
|
|
@@ -988,6 +1086,12 @@ def run_cli_tests(options):
|
|
|
988
1086
|
download_test_data(options)
|
|
989
1087
|
|
|
990
1088
|
|
|
1089
|
+
## Utility imports
|
|
1090
|
+
|
|
1091
|
+
from megadetector.utils.ct_utils import dict_to_kvp_list
|
|
1092
|
+
from megadetector.utils.path_utils import insert_before_extension
|
|
1093
|
+
|
|
1094
|
+
|
|
991
1095
|
## Run inference on an image
|
|
992
1096
|
|
|
993
1097
|
print('\n** Running MD on a single image (CLI) **\n')
|
|
@@ -1000,6 +1104,7 @@ def run_cli_tests(options):
|
|
|
1000
1104
|
cmd = 'python megadetector/detection/run_detector.py'
|
|
1001
1105
|
cmd += ' "{}" --image_file "{}" --output_dir "{}"'.format(
|
|
1002
1106
|
options.default_model,image_fn,output_dir)
|
|
1107
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1003
1108
|
cmd_results = execute_and_print(cmd)
|
|
1004
1109
|
|
|
1005
1110
|
if options.cpu_execution_is_error:
|
|
@@ -1011,6 +1116,13 @@ def run_cli_tests(options):
|
|
|
1011
1116
|
if not gpu_available_via_cli:
|
|
1012
1117
|
raise Exception('GPU execution is required, but not available')
|
|
1013
1118
|
|
|
1119
|
+
# Make sure we can also pass an absolute path to a model file, instead of, e.g. "MDV5A"
|
|
1120
|
+
|
|
1121
|
+
from megadetector.detection.run_detector import try_download_known_detector
|
|
1122
|
+
model_file = try_download_known_detector(options.default_model,force_download=False,verbose=False)
|
|
1123
|
+
cmd = cmd.replace(options.default_model,model_file)
|
|
1124
|
+
cmd_results = execute_and_print(cmd)
|
|
1125
|
+
|
|
1014
1126
|
|
|
1015
1127
|
## Run inference on a folder
|
|
1016
1128
|
|
|
@@ -1027,6 +1139,7 @@ def run_cli_tests(options):
|
|
|
1027
1139
|
options.default_model,image_folder,inference_output_file)
|
|
1028
1140
|
cmd += ' --output_relative_filenames --quiet --include_image_size'
|
|
1029
1141
|
cmd += ' --include_image_timestamp --include_exif_data'
|
|
1142
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1030
1143
|
cmd_results = execute_and_print(cmd)
|
|
1031
1144
|
|
|
1032
1145
|
base_cmd = cmd
|
|
@@ -1035,13 +1148,12 @@ def run_cli_tests(options):
|
|
|
1035
1148
|
## Run again with checkpointing enabled, make sure the results are the same
|
|
1036
1149
|
|
|
1037
1150
|
print('\n** Running MD on a folder (with checkpoints) (CLI) **\n')
|
|
1038
|
-
|
|
1039
|
-
from megadetector.utils.path_utils import insert_before_extension
|
|
1040
1151
|
|
|
1041
1152
|
checkpoint_string = ' --checkpoint_frequency 5'
|
|
1042
1153
|
cmd = base_cmd + checkpoint_string
|
|
1043
1154
|
inference_output_file_checkpoint = insert_before_extension(inference_output_file,'_checkpoint')
|
|
1044
1155
|
cmd = cmd.replace(inference_output_file,inference_output_file_checkpoint)
|
|
1156
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1045
1157
|
cmd_results = execute_and_print(cmd)
|
|
1046
1158
|
|
|
1047
1159
|
assert output_files_are_identical(fn1=inference_output_file,
|
|
@@ -1051,12 +1163,12 @@ def run_cli_tests(options):
|
|
|
1051
1163
|
|
|
1052
1164
|
## Run again with the image queue enabled, make sure the results are the same
|
|
1053
1165
|
|
|
1054
|
-
print('\n** Running MD on a folder (with image queue) (CLI) **\n')
|
|
1166
|
+
print('\n** Running MD on a folder (with image queue but no preprocessing) (CLI) **\n')
|
|
1055
1167
|
|
|
1056
1168
|
cmd = base_cmd + ' --use_image_queue'
|
|
1057
|
-
from megadetector.utils.path_utils import insert_before_extension
|
|
1058
1169
|
inference_output_file_queue = insert_before_extension(inference_output_file,'_queue')
|
|
1059
1170
|
cmd = cmd.replace(inference_output_file,inference_output_file_queue)
|
|
1171
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1060
1172
|
cmd_results = execute_and_print(cmd)
|
|
1061
1173
|
|
|
1062
1174
|
assert output_files_are_identical(fn1=inference_output_file,
|
|
@@ -1064,48 +1176,66 @@ def run_cli_tests(options):
|
|
|
1064
1176
|
verbose=True)
|
|
1065
1177
|
|
|
1066
1178
|
|
|
1067
|
-
|
|
1179
|
+
print('\n** Running MD on a folder (with image queue and preprocessing) (CLI) **\n')
|
|
1180
|
+
|
|
1181
|
+
cmd = base_cmd + ' --use_image_queue --preprocess_on_image_queue'
|
|
1182
|
+
inference_output_file_queue = insert_before_extension(inference_output_file,'_queue')
|
|
1183
|
+
cmd = cmd.replace(inference_output_file,inference_output_file_queue)
|
|
1184
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1185
|
+
cmd_results = execute_and_print(cmd)
|
|
1068
1186
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1187
|
+
assert output_files_are_identical(fn1=inference_output_file,
|
|
1188
|
+
fn2=inference_output_file_queue,
|
|
1189
|
+
verbose=True)
|
|
1071
1190
|
|
|
1072
|
-
|
|
1191
|
+
## Run again on multiple cores, make sure the results are the same
|
|
1073
1192
|
|
|
1074
|
-
|
|
1075
|
-
if 'CUDA_VISIBLE_DEVICES' in os.environ:
|
|
1076
|
-
cuda_visible_devices = os.environ['CUDA_VISIBLE_DEVICES']
|
|
1077
|
-
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
|
1193
|
+
if not options.skip_cpu_tests:
|
|
1078
1194
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
else:
|
|
1195
|
+
# First run again on the CPU on a single thread if necessary, so we get a file that
|
|
1196
|
+
# *should* be identical to the multicore version.
|
|
1197
|
+
gpu_available = is_gpu_available(verbose=False)
|
|
1083
1198
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1199
|
+
cuda_visible_devices = None
|
|
1200
|
+
if 'CUDA_VISIBLE_DEVICES' in os.environ:
|
|
1201
|
+
cuda_visible_devices = os.environ['CUDA_VISIBLE_DEVICES']
|
|
1202
|
+
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
|
1203
|
+
|
|
1204
|
+
# If we already ran on the CPU, no need to run again
|
|
1205
|
+
if not gpu_available:
|
|
1206
|
+
|
|
1207
|
+
inference_output_file_cpu = inference_output_file
|
|
1208
|
+
|
|
1209
|
+
else:
|
|
1210
|
+
|
|
1211
|
+
print('\n** Running MD on a folder (single CPU) (CLI) **\n')
|
|
1212
|
+
|
|
1213
|
+
inference_output_file_cpu = insert_before_extension(inference_output_file,'cpu')
|
|
1214
|
+
cmd = base_cmd
|
|
1215
|
+
cmd = cmd.replace(inference_output_file,inference_output_file_cpu)
|
|
1216
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1217
|
+
cmd_results = execute_and_print(cmd)
|
|
1218
|
+
|
|
1219
|
+
print('\n** Running MD on a folder (multiple CPUs) (CLI) **\n')
|
|
1220
|
+
|
|
1221
|
+
cpu_string = ' --ncores 4'
|
|
1222
|
+
cmd = base_cmd + cpu_string
|
|
1223
|
+
inference_output_file_cpu_multicore = insert_before_extension(inference_output_file,'multicore')
|
|
1224
|
+
cmd = cmd.replace(inference_output_file,inference_output_file_cpu_multicore)
|
|
1225
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1089
1226
|
cmd_results = execute_and_print(cmd)
|
|
1090
1227
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
if cuda_visible_devices is not None:
|
|
1101
|
-
print('Restoring CUDA_VISIBLE_DEVICES')
|
|
1102
|
-
os.environ['CUDA_VISIBLE_DEVICES'] = cuda_visible_devices
|
|
1103
|
-
else:
|
|
1104
|
-
del os.environ['CUDA_VISIBLE_DEVICES']
|
|
1228
|
+
if cuda_visible_devices is not None:
|
|
1229
|
+
print('Restoring CUDA_VISIBLE_DEVICES')
|
|
1230
|
+
os.environ['CUDA_VISIBLE_DEVICES'] = cuda_visible_devices
|
|
1231
|
+
else:
|
|
1232
|
+
del os.environ['CUDA_VISIBLE_DEVICES']
|
|
1233
|
+
|
|
1234
|
+
assert output_files_are_identical(fn1=inference_output_file_cpu,
|
|
1235
|
+
fn2=inference_output_file_cpu_multicore,
|
|
1236
|
+
verbose=True)
|
|
1105
1237
|
|
|
1106
|
-
|
|
1107
|
-
fn2=inference_output_file_cpu_multicore,
|
|
1108
|
-
verbose=True)
|
|
1238
|
+
# ...if we're not skipping the force-cpu tests
|
|
1109
1239
|
|
|
1110
1240
|
|
|
1111
1241
|
## Postprocessing
|
|
@@ -1164,23 +1294,33 @@ def run_cli_tests(options):
|
|
|
1164
1294
|
|
|
1165
1295
|
## Run inference on a folder (tiled)
|
|
1166
1296
|
|
|
1167
|
-
|
|
1297
|
+
# This is a rather esoteric code path that I turn off when I'm testing some
|
|
1298
|
+
# features that it doesn't include yet, particularly compatibility mode
|
|
1299
|
+
# control.
|
|
1300
|
+
skip_tiling_tests = True
|
|
1168
1301
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
cmd = 'python -m megadetector.detection.run_tiled_inference'
|
|
1302
|
+
if skip_tiling_tests:
|
|
1303
|
+
|
|
1304
|
+
print('### DEBUG: skipping tiling tests ###')
|
|
1305
|
+
|
|
1174
1306
|
else:
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
options.
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1307
|
+
print('\n** Running tiled inference (CLI) **\n')
|
|
1308
|
+
|
|
1309
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
1310
|
+
tiling_folder = os.path.join(options.scratch_dir,'tiling-folder')
|
|
1311
|
+
inference_output_file_tiled = os.path.join(options.scratch_dir,'folder_inference_output_tiled.json')
|
|
1312
|
+
if options.cli_working_dir is None:
|
|
1313
|
+
cmd = 'python -m megadetector.detection.run_tiled_inference'
|
|
1314
|
+
else:
|
|
1315
|
+
cmd = 'python megadetector/detection/run_tiled_inference.py'
|
|
1316
|
+
cmd += ' "{}" "{}" "{}" "{}"'.format(
|
|
1317
|
+
options.default_model,image_folder,tiling_folder,inference_output_file_tiled)
|
|
1318
|
+
cmd += ' --overwrite_handling overwrite'
|
|
1319
|
+
cmd_results = execute_and_print(cmd)
|
|
1183
1320
|
|
|
1321
|
+
with open(inference_output_file_tiled,'r') as f:
|
|
1322
|
+
results_from_file = json.load(f) # noqa
|
|
1323
|
+
|
|
1184
1324
|
|
|
1185
1325
|
## Run inference on a folder (augmented, w/YOLOv5 val script)
|
|
1186
1326
|
|
|
@@ -1244,9 +1384,14 @@ def run_cli_tests(options):
|
|
|
1244
1384
|
cmd += ' "{}" "{}"'.format(options.default_model,video_fn)
|
|
1245
1385
|
cmd += ' --frame_folder "{}" --frame_rendering_folder "{}" --output_json_file "{}" --output_video_file "{}"'.format(
|
|
1246
1386
|
frame_folder,frame_rendering_folder,video_inference_output_file,output_video_file)
|
|
1247
|
-
cmd += ' --
|
|
1387
|
+
cmd += ' --fourcc {}'.format(options.video_fourcc)
|
|
1248
1388
|
cmd += ' --force_extracted_frame_folder_deletion --force_rendered_frame_folder_deletion --n_cores 5 --frame_sample 3'
|
|
1249
1389
|
cmd += ' --verbose'
|
|
1390
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1391
|
+
|
|
1392
|
+
if not options.skip_video_rendering_tests:
|
|
1393
|
+
cmd += ' --render_output_video'
|
|
1394
|
+
|
|
1250
1395
|
cmd_results = execute_and_print(cmd)
|
|
1251
1396
|
|
|
1252
1397
|
# ...if we're not skipping video tests
|
|
@@ -1266,6 +1411,7 @@ def run_cli_tests(options):
|
|
|
1266
1411
|
options.alt_model,image_folder,inference_output_file_alt)
|
|
1267
1412
|
cmd += ' --output_relative_filenames --quiet --include_image_size'
|
|
1268
1413
|
cmd += ' --include_image_timestamp --include_exif_data'
|
|
1414
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1269
1415
|
cmd_results = execute_and_print(cmd)
|
|
1270
1416
|
|
|
1271
1417
|
with open(inference_output_file_alt,'r') as f:
|
|
@@ -1294,6 +1440,50 @@ def run_cli_tests(options):
|
|
|
1294
1440
|
# ...def run_cli_tests(...)
|
|
1295
1441
|
|
|
1296
1442
|
|
|
1443
|
+
def run_download_tests(options):
|
|
1444
|
+
"""
|
|
1445
|
+
Args:
|
|
1446
|
+
options (MDTestOptions): see MDTestOptions for details
|
|
1447
|
+
"""
|
|
1448
|
+
|
|
1449
|
+
if not options.skip_download_tests:
|
|
1450
|
+
|
|
1451
|
+
from megadetector.detection.run_detector import known_models, \
|
|
1452
|
+
try_download_known_detector, \
|
|
1453
|
+
get_detector_version_from_model_file, \
|
|
1454
|
+
model_string_to_model_version
|
|
1455
|
+
|
|
1456
|
+
# Make sure we can download models based on canonical version numbers,
|
|
1457
|
+
# e.g. "v5a.0.0"
|
|
1458
|
+
for model_name in known_models:
|
|
1459
|
+
url = known_models[model_name]['url']
|
|
1460
|
+
if 'localhost' in url:
|
|
1461
|
+
continue
|
|
1462
|
+
print('Testing download for known model {}'.format(model_name))
|
|
1463
|
+
fn = try_download_known_detector(model_name,
|
|
1464
|
+
force_download=False,
|
|
1465
|
+
verbose=False)
|
|
1466
|
+
version_string = get_detector_version_from_model_file(fn, verbose=False)
|
|
1467
|
+
assert version_string == model_name
|
|
1468
|
+
|
|
1469
|
+
# Make sure we can download models based on short names, e.g. "MDV5A"
|
|
1470
|
+
for model_name in model_string_to_model_version:
|
|
1471
|
+
model_version = model_string_to_model_version[model_name]
|
|
1472
|
+
assert model_version in known_models
|
|
1473
|
+
url = known_models[model_version]['url']
|
|
1474
|
+
if 'localhost' in url:
|
|
1475
|
+
continue
|
|
1476
|
+
print('Testing download for model short name {}'.format(model_name))
|
|
1477
|
+
fn = try_download_known_detector(model_name,
|
|
1478
|
+
force_download=False,
|
|
1479
|
+
verbose=False)
|
|
1480
|
+
assert fn != model_name
|
|
1481
|
+
|
|
1482
|
+
# ...if we need to test model downloads
|
|
1483
|
+
|
|
1484
|
+
# ...def run_download_tests()
|
|
1485
|
+
|
|
1486
|
+
|
|
1297
1487
|
#%% Main test wrapper
|
|
1298
1488
|
|
|
1299
1489
|
def run_tests(options):
|
|
@@ -1307,6 +1497,9 @@ def run_tests(options):
|
|
|
1307
1497
|
# Prepare data folder
|
|
1308
1498
|
download_test_data(options)
|
|
1309
1499
|
|
|
1500
|
+
# Run download tests if necessary
|
|
1501
|
+
run_download_tests(options)
|
|
1502
|
+
|
|
1310
1503
|
if options.disable_gpu:
|
|
1311
1504
|
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
|
1312
1505
|
|
|
@@ -1323,8 +1516,32 @@ def run_tests(options):
|
|
|
1323
1516
|
|
|
1324
1517
|
# Run python tests
|
|
1325
1518
|
if not options.skip_python_tests:
|
|
1326
|
-
|
|
1327
|
-
|
|
1519
|
+
|
|
1520
|
+
if options.model_folder is not None:
|
|
1521
|
+
|
|
1522
|
+
assert os.path.isdir(options.model_folder), \
|
|
1523
|
+
'Could not find model folder {}'.format(options.model_folder)
|
|
1524
|
+
|
|
1525
|
+
model_files = os.listdir(options.model_folder)
|
|
1526
|
+
model_files = [fn for fn in model_files if fn.endswith('.pt')]
|
|
1527
|
+
model_files = [os.path.join(options.model_folder,fn) for fn in model_files]
|
|
1528
|
+
|
|
1529
|
+
assert len(model_files) > 0, \
|
|
1530
|
+
'Could not find any models in folder {}'.format(options.model_folder)
|
|
1531
|
+
|
|
1532
|
+
original_default_model = options.default_model
|
|
1533
|
+
|
|
1534
|
+
for model_file in model_files:
|
|
1535
|
+
print('Running Python tests for model {}'.format(model_file))
|
|
1536
|
+
options.default_model = model_file
|
|
1537
|
+
run_python_tests(options)
|
|
1538
|
+
|
|
1539
|
+
options.default_model = original_default_model
|
|
1540
|
+
|
|
1541
|
+
else:
|
|
1542
|
+
|
|
1543
|
+
run_python_tests(options)
|
|
1544
|
+
|
|
1328
1545
|
# Run CLI tests
|
|
1329
1546
|
if not options.skip_cli_tests:
|
|
1330
1547
|
run_cli_tests(options)
|
|
@@ -1352,16 +1569,26 @@ if False:
|
|
|
1352
1569
|
options.warning_mode = False
|
|
1353
1570
|
options.max_coord_error = 0.01 # 0.001
|
|
1354
1571
|
options.max_conf_error = 0.01 # 0.005
|
|
1355
|
-
|
|
1572
|
+
options.skip_video_rendering_tests = True
|
|
1573
|
+
# options.iou_threshold_for_file_comparison = 0.7
|
|
1574
|
+
|
|
1575
|
+
options.cli_working_dir = r'c:\git\MegaDetector'
|
|
1576
|
+
# When running in the cameratraps-detector environment
|
|
1577
|
+
# options.cli_test_pythonpath = r'c:\git\MegaDetector;c:\git\yolov5-md'
|
|
1578
|
+
|
|
1579
|
+
# When running in the MegaDetector environment
|
|
1580
|
+
options.cli_test_pythonpath = r'c:\git\MegaDetector'
|
|
1581
|
+
|
|
1582
|
+
# options.cli_working_dir = os.path.expanduser('~')
|
|
1356
1583
|
# options.yolo_working_dir = r'c:\git\yolov5-md'
|
|
1357
|
-
options.cli_working_dir = os.path.expanduser('~')
|
|
1358
1584
|
# options.yolo_working_dir = '/mnt/c/git/yolov5-md'
|
|
1359
1585
|
options = download_test_data(options)
|
|
1360
1586
|
|
|
1361
1587
|
#%%
|
|
1362
1588
|
|
|
1363
1589
|
import os
|
|
1364
|
-
if 'PYTHONPATH' not in os.environ or
|
|
1590
|
+
if ('PYTHONPATH' not in os.environ) or \
|
|
1591
|
+
(options.yolo_working_dir is not None and options.yolo_working_dir not in os.environ['PYTHONPATH']):
|
|
1365
1592
|
os.environ['PYTHONPATH'] += ';' + options.yolo_working_dir
|
|
1366
1593
|
|
|
1367
1594
|
#%%
|
|
@@ -1440,6 +1667,11 @@ def main():
|
|
|
1440
1667
|
action='store_true',
|
|
1441
1668
|
help='Skip tests related to video (which can be slow)')
|
|
1442
1669
|
|
|
1670
|
+
parser.add_argument(
|
|
1671
|
+
'--skip_video_rendering_tests',
|
|
1672
|
+
action='store_true',
|
|
1673
|
+
help='Skip tests related to *rendering* video')
|
|
1674
|
+
|
|
1443
1675
|
parser.add_argument(
|
|
1444
1676
|
'--skip_python_tests',
|
|
1445
1677
|
action='store_true',
|
|
@@ -1450,6 +1682,16 @@ def main():
|
|
|
1450
1682
|
action='store_true',
|
|
1451
1683
|
help='Skip CLI tests')
|
|
1452
1684
|
|
|
1685
|
+
parser.add_argument(
|
|
1686
|
+
'--skip_download_tests',
|
|
1687
|
+
action='store_true',
|
|
1688
|
+
help='Skip model download tests')
|
|
1689
|
+
|
|
1690
|
+
parser.add_argument(
|
|
1691
|
+
'--skip_cpu_tests',
|
|
1692
|
+
action='store_true',
|
|
1693
|
+
help='Skip force-CPU tests')
|
|
1694
|
+
|
|
1453
1695
|
parser.add_argument(
|
|
1454
1696
|
'--force_data_download',
|
|
1455
1697
|
action='store_true',
|
|
@@ -1498,13 +1740,43 @@ def main():
|
|
|
1498
1740
|
help='PYTHONPATH to set for CLI tests; if None, inherits from the parent process'
|
|
1499
1741
|
)
|
|
1500
1742
|
|
|
1501
|
-
|
|
1743
|
+
parser.add_argument(
|
|
1744
|
+
'--python_test_depth',
|
|
1745
|
+
type=int,
|
|
1746
|
+
default=options.python_test_depth,
|
|
1747
|
+
help='Used as a knob to control the level of Python tests (0-100)'
|
|
1748
|
+
)
|
|
1749
|
+
|
|
1750
|
+
parser.add_argument(
|
|
1751
|
+
'--model_folder',
|
|
1752
|
+
type=str,
|
|
1753
|
+
default=None,
|
|
1754
|
+
help='Run Python tests on every model in this folder'
|
|
1755
|
+
)
|
|
1756
|
+
|
|
1757
|
+
parser.add_argument(
|
|
1758
|
+
'--detector_options',
|
|
1759
|
+
nargs='*',
|
|
1760
|
+
metavar='KEY=VALUE',
|
|
1761
|
+
default='',
|
|
1762
|
+
help='Detector-specific options, as a space-separated list of key-value pairs')
|
|
1763
|
+
|
|
1764
|
+
parser.add_argument(
|
|
1765
|
+
'--default_model',
|
|
1766
|
+
type=str,
|
|
1767
|
+
default=options.default_model,
|
|
1768
|
+
help='Default model file or well-known model name (used for most tests)')
|
|
1769
|
+
|
|
1770
|
+
# The following token is used for linting, do not remove.
|
|
1502
1771
|
#
|
|
1503
1772
|
# no_arguments_required
|
|
1504
|
-
|
|
1773
|
+
|
|
1505
1774
|
args = parser.parse_args()
|
|
1506
1775
|
|
|
1776
|
+
initial_detector_options = options.detector_options
|
|
1507
1777
|
_args_to_object(args,options)
|
|
1778
|
+
from megadetector.utils.ct_utils import parse_kvp_list
|
|
1779
|
+
options.detector_options = parse_kvp_list(args.detector_options,d=initial_detector_options)
|
|
1508
1780
|
|
|
1509
1781
|
run_tests(options)
|
|
1510
1782
|
|
|
@@ -1512,23 +1784,6 @@ if __name__ == '__main__':
|
|
|
1512
1784
|
main()
|
|
1513
1785
|
|
|
1514
1786
|
|
|
1515
|
-
#%% Sample invocations
|
|
1516
|
-
|
|
1517
|
-
r"""
|
|
1518
|
-
# Windows
|
|
1519
|
-
set PYTHONPATH=c:\git\MegaDetector;c:\git\yolov5-md
|
|
1520
|
-
cd c:\git\MegaDetector\megadetector\utils
|
|
1521
|
-
python md_tests.py --cli_working_dir "c:\git\MegaDetector" --yolo_working_dir "c:\git\yolov5-md" --cli_test_pythonpath "c:\git\MegaDetector;c:\git\yolov5-md"
|
|
1522
|
-
|
|
1523
|
-
# Linux
|
|
1524
|
-
export PYTHONPATH=/mnt/c/git/MegaDetector:/mnt/c/git/yolov5-md
|
|
1525
|
-
cd /mnt/c/git/MegaDetector/megadetector/utils
|
|
1526
|
-
python md_tests.py --cli_working_dir "/mnt/c/git/MegaDetector" --yolo_working_dir "/mnt/c/git/yolov5-md" --cli_test_pythonpath "/mnt/c/git/MegaDetector:/mnt/c/git/yolov5-md"
|
|
1527
|
-
|
|
1528
|
-
python -c "import md_tests; print(md_tests.get_expected_results_filename(True))"
|
|
1529
|
-
"""
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
1787
|
#%% Scrap
|
|
1533
1788
|
|
|
1534
1789
|
if False:
|