megadetector 5.0.23__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 +304 -68
- 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 +165 -45
- megadetector/utils/gpu_test.py +107 -0
- megadetector/utils/md_tests.py +355 -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.23.dist-info → megadetector-5.0.24.dist-info}/METADATA +10 -24
- {megadetector-5.0.23.dist-info → megadetector-5.0.24.dist-info}/RECORD +35 -33
- 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.23.dist-info → megadetector-5.0.24.dist-info}/LICENSE +0 -0
- {megadetector-5.0.23.dist-info → megadetector-5.0.24.dist-info}/WHEEL +0 -0
- {megadetector-5.0.23.dist-info → megadetector-5.0.24.dist-info}/top_level.txt +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,6 +714,12 @@ 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
|
|
|
@@ -663,16 +735,20 @@ def run_python_tests(options):
|
|
|
663
735
|
|
|
664
736
|
|
|
665
737
|
## Run inference on an image
|
|
666
|
-
|
|
738
|
+
|
|
667
739
|
print('\n** Running MD on a single image (module) **\n')
|
|
668
740
|
|
|
669
741
|
from megadetector.detection import run_detector
|
|
670
742
|
from megadetector.visualization import visualization_utils as vis_utils
|
|
671
743
|
image_fn = os.path.join(options.scratch_dir,options.test_images[0])
|
|
672
|
-
model = run_detector.load_detector(options.default_model
|
|
744
|
+
model = run_detector.load_detector(options.default_model,
|
|
745
|
+
detector_options=copy(options.detector_options))
|
|
673
746
|
pil_im = vis_utils.load_image(image_fn)
|
|
674
747
|
result = model.generate_detections_one_image(pil_im) # noqa
|
|
675
|
-
|
|
748
|
+
|
|
749
|
+
if options.python_test_depth <= 1:
|
|
750
|
+
return
|
|
751
|
+
|
|
676
752
|
|
|
677
753
|
## Run inference on a folder
|
|
678
754
|
|
|
@@ -685,13 +761,15 @@ def run_python_tests(options):
|
|
|
685
761
|
assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
|
|
686
762
|
inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
|
|
687
763
|
image_file_names = path_utils.find_images(image_folder,recursive=True)
|
|
688
|
-
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))
|
|
689
768
|
_ = write_results_to_file(results,
|
|
690
769
|
inference_output_file,
|
|
691
770
|
relative_path_base=image_folder,
|
|
692
771
|
detector_file=options.default_model)
|
|
693
772
|
|
|
694
|
-
|
|
695
773
|
## Verify results
|
|
696
774
|
|
|
697
775
|
# Verify format correctness
|
|
@@ -706,6 +784,9 @@ def run_python_tests(options):
|
|
|
706
784
|
|
|
707
785
|
# Make note of this filename, we will use it again later
|
|
708
786
|
inference_output_file_standard_inference = inference_output_file
|
|
787
|
+
|
|
788
|
+
if options.python_test_depth <= 2:
|
|
789
|
+
return
|
|
709
790
|
|
|
710
791
|
|
|
711
792
|
## Run and verify again with augmentation enabled
|
|
@@ -715,7 +796,11 @@ def run_python_tests(options):
|
|
|
715
796
|
from megadetector.utils.path_utils import insert_before_extension
|
|
716
797
|
|
|
717
798
|
inference_output_file_augmented = insert_before_extension(inference_output_file,'augmented')
|
|
718
|
-
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))
|
|
719
804
|
_ = write_results_to_file(results,
|
|
720
805
|
inference_output_file_augmented,
|
|
721
806
|
relative_path_base=image_folder,
|
|
@@ -855,7 +940,9 @@ def run_python_tests(options):
|
|
|
855
940
|
video_options.output_video_file = os.path.join(options.scratch_dir,'video_scratch/rendered_video.mp4')
|
|
856
941
|
video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
|
|
857
942
|
video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
|
|
858
|
-
|
|
943
|
+
|
|
944
|
+
video_options.render_output_video = (not options.skip_video_rendering_tests)
|
|
945
|
+
|
|
859
946
|
# video_options.keep_rendered_frames = False
|
|
860
947
|
# video_options.keep_extracted_frames = False
|
|
861
948
|
video_options.force_extracted_frame_folder_deletion = True
|
|
@@ -871,6 +958,7 @@ def run_python_tests(options):
|
|
|
871
958
|
video_options.n_cores = 5
|
|
872
959
|
# video_options.debug_max_frames = -1
|
|
873
960
|
# video_options.class_mapping_filename = None
|
|
961
|
+
video_options.detector_options = copy(options.detector_options)
|
|
874
962
|
|
|
875
963
|
_ = process_video(video_options)
|
|
876
964
|
|
|
@@ -908,7 +996,7 @@ def run_python_tests(options):
|
|
|
908
996
|
# video_options.rendering_confidence_threshold = None
|
|
909
997
|
# video_options.json_confidence_threshold = 0.005
|
|
910
998
|
video_options.frame_sample = 10
|
|
911
|
-
video_options.n_cores = 5
|
|
999
|
+
video_options.n_cores = 5
|
|
912
1000
|
|
|
913
1001
|
# Force frame extraction to disk, since that's how we generated our expected results file
|
|
914
1002
|
video_options.force_on_disk_frame_extraction = True
|
|
@@ -918,13 +1006,15 @@ def run_python_tests(options):
|
|
|
918
1006
|
# Use quality == None, because we can't control whether YOLOv5 has patched cm2.imread,
|
|
919
1007
|
# and therefore can't rely on using the quality parameter
|
|
920
1008
|
video_options.quality = None
|
|
921
|
-
video_options.max_width = None
|
|
1009
|
+
video_options.max_width = None
|
|
1010
|
+
video_options.detector_options = copy(options.detector_options)
|
|
922
1011
|
|
|
1012
|
+
video_options.keep_extracted_frames = True
|
|
923
1013
|
_ = process_video_folder(video_options)
|
|
924
1014
|
|
|
925
1015
|
assert os.path.isfile(video_options.output_json_file), \
|
|
926
1016
|
'Python video test failed to render output .json file'
|
|
927
|
-
|
|
1017
|
+
|
|
928
1018
|
frame_output_file = insert_before_extension(video_options.output_json_file,'frames')
|
|
929
1019
|
assert os.path.isfile(frame_output_file)
|
|
930
1020
|
|
|
@@ -934,6 +1024,7 @@ def run_python_tests(options):
|
|
|
934
1024
|
expected_results_file = \
|
|
935
1025
|
get_expected_results_filename(is_gpu_available(verbose=False),test_type='video',options=options)
|
|
936
1026
|
assert os.path.isfile(expected_results_file)
|
|
1027
|
+
|
|
937
1028
|
compare_results(frame_output_file,expected_results_file,options)
|
|
938
1029
|
|
|
939
1030
|
|
|
@@ -978,7 +1069,6 @@ def run_cli_tests(options):
|
|
|
978
1069
|
|
|
979
1070
|
print('\n*** Starting CLI tests ***\n')
|
|
980
1071
|
|
|
981
|
-
|
|
982
1072
|
## Environment management
|
|
983
1073
|
|
|
984
1074
|
if options.cli_test_pythonpath is not None:
|
|
@@ -996,6 +1086,12 @@ def run_cli_tests(options):
|
|
|
996
1086
|
download_test_data(options)
|
|
997
1087
|
|
|
998
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
|
+
|
|
999
1095
|
## Run inference on an image
|
|
1000
1096
|
|
|
1001
1097
|
print('\n** Running MD on a single image (CLI) **\n')
|
|
@@ -1008,6 +1104,7 @@ def run_cli_tests(options):
|
|
|
1008
1104
|
cmd = 'python megadetector/detection/run_detector.py'
|
|
1009
1105
|
cmd += ' "{}" --image_file "{}" --output_dir "{}"'.format(
|
|
1010
1106
|
options.default_model,image_fn,output_dir)
|
|
1107
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1011
1108
|
cmd_results = execute_and_print(cmd)
|
|
1012
1109
|
|
|
1013
1110
|
if options.cpu_execution_is_error:
|
|
@@ -1019,6 +1116,13 @@ def run_cli_tests(options):
|
|
|
1019
1116
|
if not gpu_available_via_cli:
|
|
1020
1117
|
raise Exception('GPU execution is required, but not available')
|
|
1021
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
|
+
|
|
1022
1126
|
|
|
1023
1127
|
## Run inference on a folder
|
|
1024
1128
|
|
|
@@ -1035,6 +1139,7 @@ def run_cli_tests(options):
|
|
|
1035
1139
|
options.default_model,image_folder,inference_output_file)
|
|
1036
1140
|
cmd += ' --output_relative_filenames --quiet --include_image_size'
|
|
1037
1141
|
cmd += ' --include_image_timestamp --include_exif_data'
|
|
1142
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1038
1143
|
cmd_results = execute_and_print(cmd)
|
|
1039
1144
|
|
|
1040
1145
|
base_cmd = cmd
|
|
@@ -1043,13 +1148,12 @@ def run_cli_tests(options):
|
|
|
1043
1148
|
## Run again with checkpointing enabled, make sure the results are the same
|
|
1044
1149
|
|
|
1045
1150
|
print('\n** Running MD on a folder (with checkpoints) (CLI) **\n')
|
|
1046
|
-
|
|
1047
|
-
from megadetector.utils.path_utils import insert_before_extension
|
|
1048
1151
|
|
|
1049
1152
|
checkpoint_string = ' --checkpoint_frequency 5'
|
|
1050
1153
|
cmd = base_cmd + checkpoint_string
|
|
1051
1154
|
inference_output_file_checkpoint = insert_before_extension(inference_output_file,'_checkpoint')
|
|
1052
1155
|
cmd = cmd.replace(inference_output_file,inference_output_file_checkpoint)
|
|
1156
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1053
1157
|
cmd_results = execute_and_print(cmd)
|
|
1054
1158
|
|
|
1055
1159
|
assert output_files_are_identical(fn1=inference_output_file,
|
|
@@ -1059,12 +1163,12 @@ def run_cli_tests(options):
|
|
|
1059
1163
|
|
|
1060
1164
|
## Run again with the image queue enabled, make sure the results are the same
|
|
1061
1165
|
|
|
1062
|
-
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')
|
|
1063
1167
|
|
|
1064
1168
|
cmd = base_cmd + ' --use_image_queue'
|
|
1065
|
-
from megadetector.utils.path_utils import insert_before_extension
|
|
1066
1169
|
inference_output_file_queue = insert_before_extension(inference_output_file,'_queue')
|
|
1067
1170
|
cmd = cmd.replace(inference_output_file,inference_output_file_queue)
|
|
1171
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1068
1172
|
cmd_results = execute_and_print(cmd)
|
|
1069
1173
|
|
|
1070
1174
|
assert output_files_are_identical(fn1=inference_output_file,
|
|
@@ -1072,48 +1176,66 @@ def run_cli_tests(options):
|
|
|
1072
1176
|
verbose=True)
|
|
1073
1177
|
|
|
1074
1178
|
|
|
1075
|
-
|
|
1179
|
+
print('\n** Running MD on a folder (with image queue and preprocessing) (CLI) **\n')
|
|
1076
1180
|
|
|
1077
|
-
|
|
1078
|
-
|
|
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)
|
|
1079
1186
|
|
|
1080
|
-
|
|
1187
|
+
assert output_files_are_identical(fn1=inference_output_file,
|
|
1188
|
+
fn2=inference_output_file_queue,
|
|
1189
|
+
verbose=True)
|
|
1081
1190
|
|
|
1082
|
-
|
|
1083
|
-
if 'CUDA_VISIBLE_DEVICES' in os.environ:
|
|
1084
|
-
cuda_visible_devices = os.environ['CUDA_VISIBLE_DEVICES']
|
|
1085
|
-
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
|
1191
|
+
## Run again on multiple cores, make sure the results are the same
|
|
1086
1192
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1193
|
+
if not options.skip_cpu_tests:
|
|
1194
|
+
|
|
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)
|
|
1091
1198
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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))
|
|
1097
1226
|
cmd_results = execute_and_print(cmd)
|
|
1098
1227
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if cuda_visible_devices is not None:
|
|
1109
|
-
print('Restoring CUDA_VISIBLE_DEVICES')
|
|
1110
|
-
os.environ['CUDA_VISIBLE_DEVICES'] = cuda_visible_devices
|
|
1111
|
-
else:
|
|
1112
|
-
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)
|
|
1113
1237
|
|
|
1114
|
-
|
|
1115
|
-
fn2=inference_output_file_cpu_multicore,
|
|
1116
|
-
verbose=True)
|
|
1238
|
+
# ...if we're not skipping the force-cpu tests
|
|
1117
1239
|
|
|
1118
1240
|
|
|
1119
1241
|
## Postprocessing
|
|
@@ -1172,23 +1294,33 @@ def run_cli_tests(options):
|
|
|
1172
1294
|
|
|
1173
1295
|
## Run inference on a folder (tiled)
|
|
1174
1296
|
|
|
1175
|
-
|
|
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
|
|
1176
1301
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
cmd = 'python -m megadetector.detection.run_tiled_inference'
|
|
1302
|
+
if skip_tiling_tests:
|
|
1303
|
+
|
|
1304
|
+
print('### DEBUG: skipping tiling tests ###')
|
|
1305
|
+
|
|
1182
1306
|
else:
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
options.
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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)
|
|
1191
1320
|
|
|
1321
|
+
with open(inference_output_file_tiled,'r') as f:
|
|
1322
|
+
results_from_file = json.load(f) # noqa
|
|
1323
|
+
|
|
1192
1324
|
|
|
1193
1325
|
## Run inference on a folder (augmented, w/YOLOv5 val script)
|
|
1194
1326
|
|
|
@@ -1252,9 +1384,14 @@ def run_cli_tests(options):
|
|
|
1252
1384
|
cmd += ' "{}" "{}"'.format(options.default_model,video_fn)
|
|
1253
1385
|
cmd += ' --frame_folder "{}" --frame_rendering_folder "{}" --output_json_file "{}" --output_video_file "{}"'.format(
|
|
1254
1386
|
frame_folder,frame_rendering_folder,video_inference_output_file,output_video_file)
|
|
1255
|
-
cmd += ' --
|
|
1387
|
+
cmd += ' --fourcc {}'.format(options.video_fourcc)
|
|
1256
1388
|
cmd += ' --force_extracted_frame_folder_deletion --force_rendered_frame_folder_deletion --n_cores 5 --frame_sample 3'
|
|
1257
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
|
+
|
|
1258
1395
|
cmd_results = execute_and_print(cmd)
|
|
1259
1396
|
|
|
1260
1397
|
# ...if we're not skipping video tests
|
|
@@ -1274,6 +1411,7 @@ def run_cli_tests(options):
|
|
|
1274
1411
|
options.alt_model,image_folder,inference_output_file_alt)
|
|
1275
1412
|
cmd += ' --output_relative_filenames --quiet --include_image_size'
|
|
1276
1413
|
cmd += ' --include_image_timestamp --include_exif_data'
|
|
1414
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1277
1415
|
cmd_results = execute_and_print(cmd)
|
|
1278
1416
|
|
|
1279
1417
|
with open(inference_output_file_alt,'r') as f:
|
|
@@ -1302,6 +1440,50 @@ def run_cli_tests(options):
|
|
|
1302
1440
|
# ...def run_cli_tests(...)
|
|
1303
1441
|
|
|
1304
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
|
+
|
|
1305
1487
|
#%% Main test wrapper
|
|
1306
1488
|
|
|
1307
1489
|
def run_tests(options):
|
|
@@ -1315,6 +1497,9 @@ def run_tests(options):
|
|
|
1315
1497
|
# Prepare data folder
|
|
1316
1498
|
download_test_data(options)
|
|
1317
1499
|
|
|
1500
|
+
# Run download tests if necessary
|
|
1501
|
+
run_download_tests(options)
|
|
1502
|
+
|
|
1318
1503
|
if options.disable_gpu:
|
|
1319
1504
|
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
|
1320
1505
|
|
|
@@ -1331,8 +1516,32 @@ def run_tests(options):
|
|
|
1331
1516
|
|
|
1332
1517
|
# Run python tests
|
|
1333
1518
|
if not options.skip_python_tests:
|
|
1334
|
-
|
|
1335
|
-
|
|
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
|
+
|
|
1336
1545
|
# Run CLI tests
|
|
1337
1546
|
if not options.skip_cli_tests:
|
|
1338
1547
|
run_cli_tests(options)
|
|
@@ -1360,16 +1569,26 @@ if False:
|
|
|
1360
1569
|
options.warning_mode = False
|
|
1361
1570
|
options.max_coord_error = 0.01 # 0.001
|
|
1362
1571
|
options.max_conf_error = 0.01 # 0.005
|
|
1363
|
-
|
|
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('~')
|
|
1364
1583
|
# options.yolo_working_dir = r'c:\git\yolov5-md'
|
|
1365
|
-
options.cli_working_dir = os.path.expanduser('~')
|
|
1366
1584
|
# options.yolo_working_dir = '/mnt/c/git/yolov5-md'
|
|
1367
1585
|
options = download_test_data(options)
|
|
1368
1586
|
|
|
1369
1587
|
#%%
|
|
1370
1588
|
|
|
1371
1589
|
import os
|
|
1372
|
-
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']):
|
|
1373
1592
|
os.environ['PYTHONPATH'] += ';' + options.yolo_working_dir
|
|
1374
1593
|
|
|
1375
1594
|
#%%
|
|
@@ -1448,6 +1667,11 @@ def main():
|
|
|
1448
1667
|
action='store_true',
|
|
1449
1668
|
help='Skip tests related to video (which can be slow)')
|
|
1450
1669
|
|
|
1670
|
+
parser.add_argument(
|
|
1671
|
+
'--skip_video_rendering_tests',
|
|
1672
|
+
action='store_true',
|
|
1673
|
+
help='Skip tests related to *rendering* video')
|
|
1674
|
+
|
|
1451
1675
|
parser.add_argument(
|
|
1452
1676
|
'--skip_python_tests',
|
|
1453
1677
|
action='store_true',
|
|
@@ -1458,6 +1682,16 @@ def main():
|
|
|
1458
1682
|
action='store_true',
|
|
1459
1683
|
help='Skip CLI tests')
|
|
1460
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
|
+
|
|
1461
1695
|
parser.add_argument(
|
|
1462
1696
|
'--force_data_download',
|
|
1463
1697
|
action='store_true',
|
|
@@ -1506,13 +1740,43 @@ def main():
|
|
|
1506
1740
|
help='PYTHONPATH to set for CLI tests; if None, inherits from the parent process'
|
|
1507
1741
|
)
|
|
1508
1742
|
|
|
1509
|
-
|
|
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.
|
|
1510
1771
|
#
|
|
1511
1772
|
# no_arguments_required
|
|
1512
|
-
|
|
1773
|
+
|
|
1513
1774
|
args = parser.parse_args()
|
|
1514
1775
|
|
|
1776
|
+
initial_detector_options = options.detector_options
|
|
1515
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)
|
|
1516
1780
|
|
|
1517
1781
|
run_tests(options)
|
|
1518
1782
|
|
|
@@ -1520,23 +1784,6 @@ if __name__ == '__main__':
|
|
|
1520
1784
|
main()
|
|
1521
1785
|
|
|
1522
1786
|
|
|
1523
|
-
#%% Sample invocations
|
|
1524
|
-
|
|
1525
|
-
r"""
|
|
1526
|
-
# Windows
|
|
1527
|
-
set PYTHONPATH=c:\git\MegaDetector;c:\git\yolov5-md
|
|
1528
|
-
cd c:\git\MegaDetector\megadetector\utils
|
|
1529
|
-
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"
|
|
1530
|
-
|
|
1531
|
-
# Linux
|
|
1532
|
-
export PYTHONPATH=/mnt/c/git/MegaDetector:/mnt/c/git/yolov5-md
|
|
1533
|
-
cd /mnt/c/git/MegaDetector/megadetector/utils
|
|
1534
|
-
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"
|
|
1535
|
-
|
|
1536
|
-
python -c "import md_tests; print(md_tests.get_expected_results_filename(True))"
|
|
1537
|
-
"""
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
1787
|
#%% Scrap
|
|
1541
1788
|
|
|
1542
1789
|
if False:
|