megadetector 5.0.5__py3-none-any.whl → 5.0.7__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.
- api/batch_processing/data_preparation/manage_local_batch.py +302 -263
- api/batch_processing/data_preparation/manage_video_batch.py +81 -2
- api/batch_processing/postprocessing/add_max_conf.py +1 -0
- api/batch_processing/postprocessing/categorize_detections_by_size.py +50 -19
- api/batch_processing/postprocessing/compare_batch_results.py +110 -60
- api/batch_processing/postprocessing/load_api_results.py +56 -70
- api/batch_processing/postprocessing/md_to_coco.py +1 -1
- api/batch_processing/postprocessing/md_to_labelme.py +2 -1
- api/batch_processing/postprocessing/postprocess_batch_results.py +240 -81
- api/batch_processing/postprocessing/render_detection_confusion_matrix.py +625 -0
- api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +71 -23
- api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +1 -1
- api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +227 -75
- api/batch_processing/postprocessing/subset_json_detector_output.py +132 -5
- api/batch_processing/postprocessing/top_folders_to_bottom.py +1 -1
- api/synchronous/api_core/animal_detection_api/detection/run_detector_batch.py +2 -2
- classification/prepare_classification_script.py +191 -191
- data_management/coco_to_yolo.py +68 -45
- data_management/databases/integrity_check_json_db.py +7 -5
- data_management/generate_crops_from_cct.py +3 -3
- data_management/get_image_sizes.py +8 -6
- data_management/importers/add_timestamps_to_icct.py +79 -0
- data_management/importers/animl_results_to_md_results.py +160 -0
- data_management/importers/auckland_doc_test_to_json.py +4 -4
- data_management/importers/auckland_doc_to_json.py +1 -1
- data_management/importers/awc_to_json.py +5 -5
- data_management/importers/bellevue_to_json.py +5 -5
- data_management/importers/carrizo_shrubfree_2018.py +5 -5
- data_management/importers/carrizo_trail_cam_2017.py +5 -5
- data_management/importers/cct_field_adjustments.py +2 -3
- data_management/importers/channel_islands_to_cct.py +4 -4
- data_management/importers/ena24_to_json.py +5 -5
- data_management/importers/helena_to_cct.py +10 -10
- data_management/importers/idaho-camera-traps.py +12 -12
- data_management/importers/idfg_iwildcam_lila_prep.py +8 -8
- data_management/importers/jb_csv_to_json.py +4 -4
- data_management/importers/missouri_to_json.py +1 -1
- data_management/importers/noaa_seals_2019.py +1 -1
- data_management/importers/pc_to_json.py +5 -5
- data_management/importers/prepare-noaa-fish-data-for-lila.py +4 -4
- data_management/importers/prepare_zsl_imerit.py +5 -5
- data_management/importers/rspb_to_json.py +4 -4
- data_management/importers/save_the_elephants_survey_A.py +5 -5
- data_management/importers/save_the_elephants_survey_B.py +6 -6
- data_management/importers/snapshot_safari_importer.py +9 -9
- data_management/importers/snapshot_serengeti_lila.py +9 -9
- data_management/importers/timelapse_csv_set_to_json.py +5 -7
- data_management/importers/ubc_to_json.py +4 -4
- data_management/importers/umn_to_json.py +4 -4
- data_management/importers/wellington_to_json.py +1 -1
- data_management/importers/wi_to_json.py +2 -2
- data_management/importers/zamba_results_to_md_results.py +181 -0
- data_management/labelme_to_coco.py +35 -7
- data_management/labelme_to_yolo.py +229 -0
- data_management/lila/add_locations_to_island_camera_traps.py +1 -1
- data_management/lila/add_locations_to_nacti.py +147 -0
- data_management/lila/create_lila_blank_set.py +474 -0
- data_management/lila/create_lila_test_set.py +2 -1
- data_management/lila/create_links_to_md_results_files.py +106 -0
- data_management/lila/download_lila_subset.py +46 -21
- data_management/lila/generate_lila_per_image_labels.py +23 -14
- data_management/lila/get_lila_annotation_counts.py +17 -11
- data_management/lila/lila_common.py +14 -11
- data_management/lila/test_lila_metadata_urls.py +116 -0
- data_management/ocr_tools.py +829 -0
- data_management/resize_coco_dataset.py +13 -11
- data_management/yolo_output_to_md_output.py +84 -12
- data_management/yolo_to_coco.py +38 -20
- detection/process_video.py +36 -14
- detection/pytorch_detector.py +23 -8
- detection/run_detector.py +76 -19
- detection/run_detector_batch.py +178 -63
- detection/run_inference_with_yolov5_val.py +326 -57
- detection/run_tiled_inference.py +153 -43
- detection/video_utils.py +34 -8
- md_utils/ct_utils.py +172 -1
- md_utils/md_tests.py +372 -51
- md_utils/path_utils.py +167 -39
- md_utils/process_utils.py +26 -7
- md_utils/split_locations_into_train_val.py +215 -0
- md_utils/string_utils.py +10 -0
- md_utils/url_utils.py +0 -2
- md_utils/write_html_image_list.py +9 -26
- md_visualization/plot_utils.py +12 -8
- md_visualization/visualization_utils.py +106 -7
- md_visualization/visualize_db.py +16 -8
- md_visualization/visualize_detector_output.py +208 -97
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/METADATA +3 -6
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/RECORD +98 -121
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/WHEEL +1 -1
- taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +1 -1
- taxonomy_mapping/map_new_lila_datasets.py +43 -39
- taxonomy_mapping/prepare_lila_taxonomy_release.py +5 -2
- taxonomy_mapping/preview_lila_taxonomy.py +27 -27
- taxonomy_mapping/species_lookup.py +33 -13
- taxonomy_mapping/taxonomy_csv_checker.py +7 -5
- api/synchronous/api_core/yolov5/detect.py +0 -252
- api/synchronous/api_core/yolov5/export.py +0 -607
- api/synchronous/api_core/yolov5/hubconf.py +0 -146
- api/synchronous/api_core/yolov5/models/__init__.py +0 -0
- api/synchronous/api_core/yolov5/models/common.py +0 -738
- api/synchronous/api_core/yolov5/models/experimental.py +0 -104
- api/synchronous/api_core/yolov5/models/tf.py +0 -574
- api/synchronous/api_core/yolov5/models/yolo.py +0 -338
- api/synchronous/api_core/yolov5/train.py +0 -670
- api/synchronous/api_core/yolov5/utils/__init__.py +0 -36
- api/synchronous/api_core/yolov5/utils/activations.py +0 -103
- api/synchronous/api_core/yolov5/utils/augmentations.py +0 -284
- api/synchronous/api_core/yolov5/utils/autoanchor.py +0 -170
- api/synchronous/api_core/yolov5/utils/autobatch.py +0 -66
- api/synchronous/api_core/yolov5/utils/aws/__init__.py +0 -0
- api/synchronous/api_core/yolov5/utils/aws/resume.py +0 -40
- api/synchronous/api_core/yolov5/utils/benchmarks.py +0 -148
- api/synchronous/api_core/yolov5/utils/callbacks.py +0 -71
- api/synchronous/api_core/yolov5/utils/dataloaders.py +0 -1087
- api/synchronous/api_core/yolov5/utils/downloads.py +0 -178
- api/synchronous/api_core/yolov5/utils/flask_rest_api/example_request.py +0 -19
- api/synchronous/api_core/yolov5/utils/flask_rest_api/restapi.py +0 -46
- api/synchronous/api_core/yolov5/utils/general.py +0 -1018
- api/synchronous/api_core/yolov5/utils/loggers/__init__.py +0 -187
- api/synchronous/api_core/yolov5/utils/loggers/wandb/__init__.py +0 -0
- api/synchronous/api_core/yolov5/utils/loggers/wandb/log_dataset.py +0 -27
- api/synchronous/api_core/yolov5/utils/loggers/wandb/sweep.py +0 -41
- api/synchronous/api_core/yolov5/utils/loggers/wandb/wandb_utils.py +0 -577
- api/synchronous/api_core/yolov5/utils/loss.py +0 -234
- api/synchronous/api_core/yolov5/utils/metrics.py +0 -355
- api/synchronous/api_core/yolov5/utils/plots.py +0 -489
- api/synchronous/api_core/yolov5/utils/torch_utils.py +0 -314
- api/synchronous/api_core/yolov5/val.py +0 -394
- md_utils/matlab_porting_tools.py +0 -97
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/LICENSE +0 -0
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/top_level.txt +0 -0
md_utils/md_tests.py
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
########
|
|
2
2
|
#
|
|
3
|
-
#
|
|
3
|
+
# md_tests.py
|
|
4
4
|
#
|
|
5
5
|
# A series of tests to validate basic repo functionality and verify either "correct"
|
|
6
6
|
# inference behavior, or - when operating in environments other than the training
|
|
7
7
|
# environment - acceptable deviation from the correct results.
|
|
8
8
|
#
|
|
9
|
-
# This module should not depend on anything else in this repo outside of the
|
|
10
|
-
# tests themselves, even if it means some duplicated code (e.g. for downloading files),
|
|
11
|
-
# much of what it tries to test is, e.g., imports.
|
|
9
|
+
# This module should not depend on anything else in this repo outside of the
|
|
10
|
+
# tests themselves, even if it means some duplicated code (e.g. for downloading files),
|
|
11
|
+
# since much of what it tries to test is, e.g., imports.
|
|
12
12
|
#
|
|
13
13
|
########
|
|
14
14
|
|
|
15
|
-
#%% TODO
|
|
16
|
-
|
|
17
|
-
# Video tests
|
|
18
|
-
# Augmented inference tests
|
|
19
|
-
# Checkpoint tests
|
|
20
|
-
|
|
21
|
-
|
|
22
15
|
#%% Imports and constants
|
|
23
16
|
|
|
24
17
|
### Only standard imports belong here, not MD-specific imports ###
|
|
@@ -42,28 +35,61 @@ class MDTestOptions:
|
|
|
42
35
|
|
|
43
36
|
disable_gpu = False
|
|
44
37
|
cpu_execution_is_error = False
|
|
45
|
-
|
|
38
|
+
skip_video_tests = False
|
|
39
|
+
skip_python_tests = False
|
|
40
|
+
skip_cli_tests = False
|
|
46
41
|
scratch_dir = None
|
|
47
42
|
test_data_url = 'https://lila.science/public/md-test-package.zip'
|
|
48
43
|
force_data_download = False
|
|
49
44
|
force_data_unzip = False
|
|
45
|
+
warning_mode = False
|
|
50
46
|
test_image_subdir = 'md-test-images'
|
|
51
47
|
max_coord_error = 0.001
|
|
52
48
|
max_conf_error = 0.005
|
|
53
49
|
cli_working_dir = None
|
|
50
|
+
yolo_working_folder = None
|
|
54
51
|
|
|
55
52
|
|
|
56
53
|
#%% Support functions
|
|
57
54
|
|
|
58
55
|
def get_expected_results_filename(gpu_is_available):
|
|
56
|
+
"""
|
|
57
|
+
Expected results vary just a little across inference environments, particularly
|
|
58
|
+
between PT 1.x and 2.x, so when making sure things are working acceptably, we
|
|
59
|
+
compare to a reference file that matches the current environment.
|
|
60
|
+
"""
|
|
59
61
|
|
|
60
62
|
if gpu_is_available:
|
|
61
|
-
|
|
63
|
+
hw_string = 'gpu'
|
|
64
|
+
else:
|
|
65
|
+
hw_string = 'cpu'
|
|
66
|
+
import torch
|
|
67
|
+
torch_version = str(torch.__version__)
|
|
68
|
+
if torch_version.startswith('1'):
|
|
69
|
+
assert torch_version == '1.10.1', 'Only tested against PT 1.10.1 and PT 2.x'
|
|
70
|
+
pt_string = 'pt1.10.1'
|
|
62
71
|
else:
|
|
63
|
-
|
|
72
|
+
assert torch_version.startswith('2'), 'Unknown torch version: {}'.format(torch_version)
|
|
73
|
+
pt_string = 'pt2.x'
|
|
74
|
+
|
|
75
|
+
# A hack for now to account for the fact that even with acceleration enabled and PT2
|
|
76
|
+
# installed, Apple silicon appears to provide the same results as CPU/PT1 inference
|
|
77
|
+
try:
|
|
78
|
+
import torch
|
|
79
|
+
m1_inference = torch.backends.mps.is_built and torch.backends.mps.is_available()
|
|
80
|
+
if m1_inference:
|
|
81
|
+
hw_string = 'cpu'
|
|
82
|
+
pt_string = 'pt1.10.1'
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
return 'md-test-results-{}-{}.json'.format(hw_string,pt_string)
|
|
64
87
|
|
|
65
88
|
|
|
66
89
|
def download_test_data(options):
|
|
90
|
+
"""
|
|
91
|
+
Download the test zipfile if necessary, unzip if necessary.
|
|
92
|
+
"""
|
|
67
93
|
|
|
68
94
|
if options.scratch_dir is None:
|
|
69
95
|
tempdir_base = tempfile.gettempdir()
|
|
@@ -87,9 +113,9 @@ def download_test_data(options):
|
|
|
87
113
|
if download_zipfile:
|
|
88
114
|
print('Downloading test data zipfile')
|
|
89
115
|
urllib.request.urlretrieve(options.test_data_url, local_zipfile)
|
|
90
|
-
print('Finished download')
|
|
116
|
+
print('Finished download to {}'.format(local_zipfile))
|
|
91
117
|
else:
|
|
92
|
-
print('Bypassing test data zipfile download')
|
|
118
|
+
print('Bypassing test data zipfile download for {}'.format(local_zipfile))
|
|
93
119
|
|
|
94
120
|
|
|
95
121
|
## Unzip data
|
|
@@ -132,23 +158,38 @@ def download_test_data(options):
|
|
|
132
158
|
options.all_test_files = test_files
|
|
133
159
|
options.test_images = [fn for fn in test_files if os.path.splitext(fn.lower())[1] in ('.jpg','.jpeg','.png')]
|
|
134
160
|
options.test_videos = [fn for fn in test_files if os.path.splitext(fn.lower())[1] in ('.mp4','.avi')]
|
|
161
|
+
options.test_videos = [fn for fn in options.test_videos if 'rendered' not in fn]
|
|
135
162
|
|
|
136
163
|
# ...def download_test_data(...)
|
|
137
164
|
|
|
138
165
|
|
|
139
166
|
def is_gpu_available(verbose=True):
|
|
140
|
-
|
|
167
|
+
"""
|
|
168
|
+
Check whether a GPU (including M1/M2 MPS) is available.
|
|
169
|
+
"""
|
|
170
|
+
|
|
141
171
|
# Import torch inside this function, so we have a chance to set CUDA_VISIBLE_DEVICES
|
|
142
172
|
# before checking GPU availability.
|
|
143
173
|
import torch
|
|
144
174
|
gpu_available = torch.cuda.is_available()
|
|
145
175
|
|
|
146
|
-
if
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
176
|
+
if gpu_available:
|
|
177
|
+
if verbose:
|
|
178
|
+
print('CUDA available: {}'.format(gpu_available))
|
|
179
|
+
device_ids = list(range(torch.cuda.device_count()))
|
|
180
|
+
if len(device_ids) > 1:
|
|
181
|
+
print('Found multiple devices: {}'.format(str(device_ids)))
|
|
182
|
+
else:
|
|
183
|
+
try:
|
|
184
|
+
gpu_available = torch.backends.mps.is_built and torch.backends.mps.is_available()
|
|
185
|
+
except AttributeError:
|
|
186
|
+
pass
|
|
187
|
+
if gpu_available:
|
|
188
|
+
print('Metal performance shaders available')
|
|
189
|
+
|
|
190
|
+
if not gpu_available:
|
|
191
|
+
print('No GPU available')
|
|
192
|
+
|
|
152
193
|
return gpu_available
|
|
153
194
|
|
|
154
195
|
|
|
@@ -201,6 +242,8 @@ def execute_and_print(cmd,print_output=True):
|
|
|
201
242
|
|
|
202
243
|
def run_python_tests(options):
|
|
203
244
|
|
|
245
|
+
print('\n*** Starting module tests ***\n')
|
|
246
|
+
|
|
204
247
|
## Prepare data
|
|
205
248
|
|
|
206
249
|
download_test_data(options)
|
|
@@ -237,8 +280,6 @@ def run_python_tests(options):
|
|
|
237
280
|
|
|
238
281
|
## Verify results
|
|
239
282
|
|
|
240
|
-
#%%
|
|
241
|
-
|
|
242
283
|
# Read expected results
|
|
243
284
|
expected_results_filename = get_expected_results_filename(is_gpu_available(verbose=False))
|
|
244
285
|
|
|
@@ -272,9 +313,17 @@ def run_python_tests(options):
|
|
|
272
313
|
actual_detections = actual_image_results['detections']
|
|
273
314
|
expected_detections = expected_image_results['detections']
|
|
274
315
|
|
|
316
|
+
s = 'expected {} detections for file {}, found {}'.format(
|
|
317
|
+
len(expected_detections),fn,len(actual_detections))
|
|
318
|
+
s += '\nExpected results file: {}\nActual results file: {}'.format(
|
|
319
|
+
expected_results_filename,inference_output_file)
|
|
320
|
+
|
|
321
|
+
if options.warning_mode:
|
|
322
|
+
if len(actual_detections) != len(expected_detections):
|
|
323
|
+
print('Warning: {}'.format(s))
|
|
324
|
+
continue
|
|
275
325
|
assert len(actual_detections) == len(expected_detections), \
|
|
276
|
-
'Error:
|
|
277
|
-
len(expected_detections),fn,len(actual_detections))
|
|
326
|
+
'Error: {}'.format(s)
|
|
278
327
|
|
|
279
328
|
# i_det = 0
|
|
280
329
|
for i_det in range(0,len(actual_detections)):
|
|
@@ -296,16 +345,16 @@ def run_python_tests(options):
|
|
|
296
345
|
|
|
297
346
|
# ...for each image
|
|
298
347
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
348
|
+
if not options.warning_mode:
|
|
349
|
+
|
|
350
|
+
assert max_conf_error <= options.max_conf_error, \
|
|
351
|
+
'Confidence error {} is greater than allowable ({})'.format(
|
|
352
|
+
max_conf_error,options.max_conf_error)
|
|
353
|
+
|
|
354
|
+
assert max_coord_error <= options.max_coord_error, \
|
|
355
|
+
'Coord error {} is greater than allowable ({})'.format(
|
|
356
|
+
max_coord_error,options.max_coord_error)
|
|
357
|
+
|
|
309
358
|
print('Max conf error: {}'.format(max_conf_error))
|
|
310
359
|
print('Max coord error: {}'.format(max_coord_error))
|
|
311
360
|
|
|
@@ -341,7 +390,84 @@ def run_python_tests(options):
|
|
|
341
390
|
assert os.path.isfile(rde_results.filterFile),\
|
|
342
391
|
'Could not find RDE output file {}'.format(rde_results.filterFile)
|
|
343
392
|
|
|
344
|
-
|
|
393
|
+
|
|
394
|
+
# TODO: add remove_repeat_detections test here
|
|
395
|
+
#
|
|
396
|
+
# It's already tested in the CLI tests, so this is not urgent.
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
## Video test (single video)
|
|
400
|
+
|
|
401
|
+
from detection.process_video import ProcessVideoOptions, process_video
|
|
402
|
+
|
|
403
|
+
video_options = ProcessVideoOptions()
|
|
404
|
+
video_options.model_file = 'MDV5A'
|
|
405
|
+
video_options.input_video_file = os.path.join(options.scratch_dir,options.test_videos[0])
|
|
406
|
+
video_options.output_json_file = os.path.join(options.scratch_dir,'single_video_output.json')
|
|
407
|
+
video_options.output_video_file = os.path.join(options.scratch_dir,'video_scratch/rendered_video.mp4')
|
|
408
|
+
video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
|
|
409
|
+
video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
|
|
410
|
+
video_options.render_output_video = True
|
|
411
|
+
# video_options.keep_rendered_frames = False
|
|
412
|
+
# video_options.keep_rendered_frames = False
|
|
413
|
+
video_options.force_extracted_frame_folder_deletion = True
|
|
414
|
+
video_options.force_rendered_frame_folder_deletion = True
|
|
415
|
+
# video_options.reuse_results_if_available = False
|
|
416
|
+
# video_options.reuse_frames_if_available = False
|
|
417
|
+
video_options.recursive = True
|
|
418
|
+
video_options.verbose = False
|
|
419
|
+
video_options.fourcc = 'mp4v'
|
|
420
|
+
# video_options.rendering_confidence_threshold = None
|
|
421
|
+
# video_options.json_confidence_threshold = 0.005
|
|
422
|
+
video_options.frame_sample = 5
|
|
423
|
+
video_options.n_cores = 5
|
|
424
|
+
# video_options.debug_max_frames = -1
|
|
425
|
+
# video_options.class_mapping_filename = None
|
|
426
|
+
|
|
427
|
+
_ = process_video(video_options)
|
|
428
|
+
|
|
429
|
+
assert os.path.isfile(video_options.output_video_file), \
|
|
430
|
+
'Python video test failed to render output video file'
|
|
431
|
+
assert os.path.isfile(video_options.output_json_file), \
|
|
432
|
+
'Python video test failed to render output .json file'
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
## Video test (folder)
|
|
436
|
+
|
|
437
|
+
from detection.process_video import ProcessVideoOptions, process_video_folder
|
|
438
|
+
|
|
439
|
+
video_options = ProcessVideoOptions()
|
|
440
|
+
video_options.model_file = 'MDV5A'
|
|
441
|
+
video_options.input_video_file = os.path.join(options.scratch_dir,
|
|
442
|
+
os.path.dirname(options.test_videos[0]))
|
|
443
|
+
video_options.output_json_file = os.path.join(options.scratch_dir,'video_folder_output.json')
|
|
444
|
+
# video_options.output_video_file = None
|
|
445
|
+
video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
|
|
446
|
+
video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
|
|
447
|
+
video_options.render_output_video = False
|
|
448
|
+
# video_options.keep_rendered_frames = False
|
|
449
|
+
# video_options.keep_rendered_frames = False
|
|
450
|
+
video_options.force_extracted_frame_folder_deletion = True
|
|
451
|
+
video_options.force_rendered_frame_folder_deletion = True
|
|
452
|
+
# video_options.reuse_results_if_available = False
|
|
453
|
+
# video_options.reuse_frames_if_available = False
|
|
454
|
+
video_options.recursive = True
|
|
455
|
+
video_options.verbose = False
|
|
456
|
+
# video_options.fourcc = None
|
|
457
|
+
# video_options.rendering_confidence_threshold = None
|
|
458
|
+
# video_options.json_confidence_threshold = 0.005
|
|
459
|
+
video_options.frame_sample = 5
|
|
460
|
+
video_options.n_cores = 5
|
|
461
|
+
# video_options.debug_max_frames = -1
|
|
462
|
+
# video_options.class_mapping_filename = None
|
|
463
|
+
|
|
464
|
+
_ = process_video_folder(video_options)
|
|
465
|
+
|
|
466
|
+
assert os.path.isfile(video_options.output_json_file), \
|
|
467
|
+
'Python video test failed to render output .json file'
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
print('\n*** Finished module tests ***\n')
|
|
345
471
|
|
|
346
472
|
# ...def run_python_tests(...)
|
|
347
473
|
|
|
@@ -350,6 +476,8 @@ def run_python_tests(options):
|
|
|
350
476
|
|
|
351
477
|
def run_cli_tests(options):
|
|
352
478
|
|
|
479
|
+
print('\n*** Starting CLI tests ***\n')
|
|
480
|
+
|
|
353
481
|
## chdir if necessary
|
|
354
482
|
|
|
355
483
|
if options.cli_working_dir is not None:
|
|
@@ -366,7 +494,11 @@ def run_cli_tests(options):
|
|
|
366
494
|
model_file = 'MDV5A'
|
|
367
495
|
image_fn = os.path.join(options.scratch_dir,options.test_images[0])
|
|
368
496
|
output_dir = os.path.join(options.scratch_dir,'single_image_test')
|
|
369
|
-
|
|
497
|
+
if options.cli_working_dir is None:
|
|
498
|
+
cmd = 'python -m detection.run_detector'
|
|
499
|
+
else:
|
|
500
|
+
cmd = 'python detection/run_detector.py'
|
|
501
|
+
cmd += ' {} --image_file {} --output_dir {}'.format(
|
|
370
502
|
model_file,image_fn,output_dir)
|
|
371
503
|
print('Running: {}'.format(cmd))
|
|
372
504
|
cmd_results = execute_and_print(cmd)
|
|
@@ -386,9 +518,14 @@ def run_cli_tests(options):
|
|
|
386
518
|
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
387
519
|
assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
|
|
388
520
|
inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
|
|
389
|
-
|
|
521
|
+
if options.cli_working_dir is None:
|
|
522
|
+
cmd = 'python -m detection.run_detector_batch'
|
|
523
|
+
else:
|
|
524
|
+
cmd = 'python detection/run_detector_batch.py'
|
|
525
|
+
cmd += ' {} {} {} --recursive'.format(
|
|
390
526
|
model_file,image_folder,inference_output_file)
|
|
391
|
-
cmd += ' --output_relative_filenames --quiet --include_image_size
|
|
527
|
+
cmd += ' --output_relative_filenames --quiet --include_image_size'
|
|
528
|
+
cmd += ' --include_image_timestamp --include_exif_data'
|
|
392
529
|
print('Running: {}'.format(cmd))
|
|
393
530
|
cmd_results = execute_and_print(cmd)
|
|
394
531
|
|
|
@@ -402,16 +539,171 @@ def run_cli_tests(options):
|
|
|
402
539
|
|
|
403
540
|
postprocessing_output_dir = os.path.join(options.scratch_dir,'postprocessing_output_cli')
|
|
404
541
|
|
|
405
|
-
|
|
542
|
+
if options.cli_working_dir is None:
|
|
543
|
+
cmd = 'python -m api.batch_processing.postprocessing.postprocess_batch_results'
|
|
544
|
+
else:
|
|
545
|
+
cmd = 'python api/batch_processing/postprocessing/postprocess_batch_results.py'
|
|
546
|
+
cmd += ' {} {}'.format(
|
|
406
547
|
inference_output_file,postprocessing_output_dir)
|
|
407
548
|
cmd += ' --image_base_dir {}'.format(image_folder)
|
|
408
549
|
print('Running: {}'.format(cmd))
|
|
409
550
|
cmd_results = execute_and_print(cmd)
|
|
410
551
|
|
|
552
|
+
|
|
553
|
+
## RDE
|
|
554
|
+
|
|
555
|
+
rde_output_dir = os.path.join(options.scratch_dir,'rde_output_cli')
|
|
556
|
+
|
|
557
|
+
if options.cli_working_dir is None:
|
|
558
|
+
cmd = 'python -m api.batch_processing.postprocessing.repeat_detection_elimination.find_repeat_detections'
|
|
559
|
+
else:
|
|
560
|
+
cmd = 'python api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py'
|
|
561
|
+
cmd += ' {}'.format(inference_output_file)
|
|
562
|
+
cmd += ' --imageBase {}'.format(image_folder)
|
|
563
|
+
cmd += ' --outputBase {}'.format(rde_output_dir)
|
|
564
|
+
cmd += ' --occurrenceThreshold 1' # Use an absurd number here to make sure we get some suspicious detections
|
|
565
|
+
print('Running: {}'.format(cmd))
|
|
566
|
+
cmd_results = execute_and_print(cmd)
|
|
567
|
+
|
|
568
|
+
# Find the latest filtering folder
|
|
569
|
+
filtering_output_dir = os.listdir(rde_output_dir)
|
|
570
|
+
filtering_output_dir = [fn for fn in filtering_output_dir if fn.startswith('filtering_')]
|
|
571
|
+
filtering_output_dir = [os.path.join(rde_output_dir,fn) for fn in filtering_output_dir]
|
|
572
|
+
filtering_output_dir = [fn for fn in filtering_output_dir if os.path.isdir(fn)]
|
|
573
|
+
filtering_output_dir = sorted(filtering_output_dir)[-1]
|
|
574
|
+
|
|
575
|
+
print('Using RDE filtering folder {}'.format(filtering_output_dir))
|
|
576
|
+
|
|
577
|
+
filtered_output_file = inference_output_file.replace('.json','_filtered.json')
|
|
578
|
+
|
|
579
|
+
if options.cli_working_dir is None:
|
|
580
|
+
cmd = 'python -m api.batch_processing.postprocessing.repeat_detection_elimination.remove_repeat_detections'
|
|
581
|
+
else:
|
|
582
|
+
cmd = 'python api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py'
|
|
583
|
+
cmd += ' {} {} {}'.format(inference_output_file,filtered_output_file,filtering_output_dir)
|
|
584
|
+
print('Running: {}'.format(cmd))
|
|
585
|
+
cmd_results = execute_and_print(cmd)
|
|
586
|
+
|
|
587
|
+
assert os.path.isfile(filtered_output_file), \
|
|
588
|
+
'Could not find RDE output file {}'.format(filtered_output_file)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
## Run inference on a folder (tiled)
|
|
592
|
+
|
|
593
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
594
|
+
tiling_folder = os.path.join(options.scratch_dir,'tiling-folder')
|
|
595
|
+
inference_output_file_tiled = os.path.join(options.scratch_dir,'folder_inference_output_tiled.json')
|
|
596
|
+
if options.cli_working_dir is None:
|
|
597
|
+
cmd = 'python -m detection.run_tiled_inference'
|
|
598
|
+
else:
|
|
599
|
+
cmd = 'python detection/run_tiled_inference.py'
|
|
600
|
+
cmd += ' {} {} {} {}'.format(
|
|
601
|
+
model_file,image_folder,tiling_folder,inference_output_file_tiled)
|
|
602
|
+
cmd += ' --overwrite_handling overwrite'
|
|
603
|
+
print('Running: {}'.format(cmd))
|
|
604
|
+
cmd_results = execute_and_print(cmd)
|
|
605
|
+
|
|
606
|
+
with open(inference_output_file_tiled,'r') as f:
|
|
607
|
+
results_from_file = json.load(f) # noqa
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
## Run inference on a folder (augmented)
|
|
611
|
+
|
|
612
|
+
if options.yolo_working_folder is None:
|
|
613
|
+
|
|
614
|
+
print('Bypassing YOLOv5 val tests, no yolo folder supplied')
|
|
615
|
+
|
|
616
|
+
else:
|
|
617
|
+
|
|
618
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
619
|
+
yolo_results_folder = os.path.join(options.scratch_dir,'yolo-output-folder')
|
|
620
|
+
yolo_symlink_folder = os.path.join(options.scratch_dir,'yolo-symlink_folder')
|
|
621
|
+
inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
|
|
622
|
+
if options.cli_working_dir is None:
|
|
623
|
+
cmd = 'python -m detection.run_inference_with_yolov5_val'
|
|
624
|
+
else:
|
|
625
|
+
cmd = 'python detection/run_inference_with_yolov5_val.py'
|
|
626
|
+
cmd += ' {} {} {}'.format(
|
|
627
|
+
model_file,image_folder,inference_output_file_yolo_val)
|
|
628
|
+
cmd += ' --yolo_working_folder {}'.format(options.yolo_working_folder)
|
|
629
|
+
cmd += ' --yolo_results_folder {}'.format(yolo_results_folder)
|
|
630
|
+
cmd += ' --symlink_folder {}'.format(yolo_symlink_folder)
|
|
631
|
+
cmd += ' --augment_enabled 1'
|
|
632
|
+
# cmd += ' --no_use_symlinks'
|
|
633
|
+
cmd += ' --overwrite_handling overwrite'
|
|
634
|
+
print('Running: {}'.format(cmd))
|
|
635
|
+
cmd_results = execute_and_print(cmd)
|
|
636
|
+
|
|
637
|
+
with open(inference_output_file_yolo_val,'r') as f:
|
|
638
|
+
results_from_file = json.load(f) # noqa
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
## Video test
|
|
642
|
+
|
|
643
|
+
model_file = 'MDV5A'
|
|
644
|
+
video_inference_output_file = os.path.join(options.scratch_dir,'video_inference_output.json')
|
|
645
|
+
output_video_file = os.path.join(options.scratch_dir,'video_scratch/cli_rendered_video.mp4')
|
|
646
|
+
frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder_cli')
|
|
647
|
+
frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder_cli')
|
|
648
|
+
|
|
649
|
+
video_fn = os.path.join(options.scratch_dir,options.test_videos[-1])
|
|
650
|
+
output_dir = os.path.join(options.scratch_dir,'single_video_test_cli')
|
|
651
|
+
if options.cli_working_dir is None:
|
|
652
|
+
cmd = 'python -m detection.process_video'
|
|
653
|
+
else:
|
|
654
|
+
cmd = 'python detection/process_video.py'
|
|
655
|
+
cmd += ' {} {}'.format(model_file,video_fn)
|
|
656
|
+
cmd += ' --frame_folder {} --frame_rendering_folder {} --output_json_file {} --output_video_file {}'.format(
|
|
657
|
+
frame_folder,frame_rendering_folder,video_inference_output_file,output_video_file)
|
|
658
|
+
cmd += ' --render_output_video --fourcc mp4v'
|
|
659
|
+
cmd += ' --force_extracted_frame_folder_deletion --force_rendered_frame_folder_deletion --n_cores 5 --frame_sample 3'
|
|
660
|
+
print('Running: {}'.format(cmd))
|
|
661
|
+
cmd_results = execute_and_print(cmd)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
## Run inference on a folder (again, so we can do a comparison)
|
|
665
|
+
|
|
666
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
667
|
+
model_file = 'MDV5B'
|
|
668
|
+
inference_output_file_alt = os.path.join(options.scratch_dir,'folder_inference_output_alt.json')
|
|
669
|
+
if options.cli_working_dir is None:
|
|
670
|
+
cmd = 'python -m detection.run_detector_batch'
|
|
671
|
+
else:
|
|
672
|
+
cmd = 'python detection/run_detector_batch.py'
|
|
673
|
+
cmd += ' {} {} {} --recursive'.format(
|
|
674
|
+
model_file,image_folder,inference_output_file_alt)
|
|
675
|
+
cmd += ' --output_relative_filenames --quiet --include_image_size'
|
|
676
|
+
cmd += ' --include_image_timestamp --include_exif_data'
|
|
677
|
+
print('Running: {}'.format(cmd))
|
|
678
|
+
cmd_results = execute_and_print(cmd)
|
|
679
|
+
|
|
680
|
+
with open(inference_output_file_alt,'r') as f:
|
|
681
|
+
results_from_file = json.load(f) # noqa
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
## Compare the two files
|
|
685
|
+
|
|
686
|
+
comparison_output_folder = os.path.join(options.scratch_dir,'results_comparison')
|
|
687
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
688
|
+
results_files_string = '"{}" "{}"'.format(
|
|
689
|
+
inference_output_file,inference_output_file_alt)
|
|
690
|
+
if options.cli_working_dir is None:
|
|
691
|
+
cmd = 'python -m api.batch_processing.postprocessing.compare_batch_results'
|
|
692
|
+
else:
|
|
693
|
+
cmd = 'python api/batch_processing/postprocessing/compare_batch_results.py'
|
|
694
|
+
cmd += ' {} {} {}'.format(comparison_output_folder,image_folder,results_files_string)
|
|
695
|
+
print('Running: {}'.format(cmd))
|
|
696
|
+
cmd_results = execute_and_print(cmd)
|
|
697
|
+
|
|
698
|
+
assert cmd_results['status'] == 0, 'Error generating comparison HTML'
|
|
699
|
+
assert os.path.isfile(os.path.join(comparison_output_folder,'index.html')), \
|
|
700
|
+
'Failed to generate comparison HTML'
|
|
701
|
+
|
|
702
|
+
print('\n*** Finished CLI tests ***\n')
|
|
703
|
+
|
|
411
704
|
# ...def run_cli_tests(...)
|
|
412
705
|
|
|
413
706
|
|
|
414
|
-
|
|
415
707
|
#%% Main test wrapper
|
|
416
708
|
|
|
417
709
|
def run_tests(options):
|
|
@@ -431,13 +723,15 @@ def run_tests(options):
|
|
|
431
723
|
|
|
432
724
|
# If the GPU should be disabled, verify that it is
|
|
433
725
|
if options.disable_gpu:
|
|
434
|
-
assert (not gpu_available), '
|
|
726
|
+
assert (not gpu_available), 'CPU execution specified, but the GPU appears to be available'
|
|
435
727
|
|
|
436
728
|
# Run python tests
|
|
437
|
-
|
|
729
|
+
if not options.skip_python_tests:
|
|
730
|
+
run_python_tests(options)
|
|
438
731
|
|
|
439
732
|
# Run CLI tests
|
|
440
|
-
|
|
733
|
+
if not options.skip_cli_tests:
|
|
734
|
+
run_cli_tests(options)
|
|
441
735
|
|
|
442
736
|
|
|
443
737
|
#%% Interactive driver
|
|
@@ -452,9 +746,19 @@ if False:
|
|
|
452
746
|
|
|
453
747
|
options.disable_gpu = False
|
|
454
748
|
options.cpu_execution_is_error = False
|
|
455
|
-
options.
|
|
749
|
+
options.skip_video_tests = False
|
|
750
|
+
options.skip_python_tests = False
|
|
751
|
+
options.skip_cli_tests = False
|
|
456
752
|
options.scratch_dir = None
|
|
753
|
+
options.test_data_url = 'https://lila.science/public/md-test-package.zip'
|
|
754
|
+
options.force_data_download = False
|
|
755
|
+
options.force_data_unzip = False
|
|
756
|
+
options.warning_mode = True
|
|
757
|
+
options.test_image_subdir = 'md-test-images'
|
|
758
|
+
options.max_coord_error = 0.001
|
|
759
|
+
options.max_conf_error = 0.005
|
|
457
760
|
options.cli_working_dir = r'c:\git\MegaDetector'
|
|
761
|
+
options.yolo_working_folder = r'c:\git\yolov5'
|
|
458
762
|
|
|
459
763
|
|
|
460
764
|
#%%
|
|
@@ -488,9 +792,19 @@ def main():
|
|
|
488
792
|
help='Directory for temporary storage (defaults to system temp dir)')
|
|
489
793
|
|
|
490
794
|
parser.add_argument(
|
|
491
|
-
'--
|
|
795
|
+
'--skip_video_tests',
|
|
492
796
|
action='store_true',
|
|
493
|
-
help='
|
|
797
|
+
help='Skip tests related to video (which can be slow)')
|
|
798
|
+
|
|
799
|
+
parser.add_argument(
|
|
800
|
+
'--skip_python_tests',
|
|
801
|
+
action='store_true',
|
|
802
|
+
help='Skip python tests')
|
|
803
|
+
|
|
804
|
+
parser.add_argument(
|
|
805
|
+
'--skip_cli_tests',
|
|
806
|
+
action='store_true',
|
|
807
|
+
help='Skip CLI tests')
|
|
494
808
|
|
|
495
809
|
parser.add_argument(
|
|
496
810
|
'--force_data_download',
|
|
@@ -502,6 +816,11 @@ def main():
|
|
|
502
816
|
action='store_true',
|
|
503
817
|
help='Force extraction of all files in the test data file, even if they\'re already available')
|
|
504
818
|
|
|
819
|
+
parser.add_argument(
|
|
820
|
+
'--warning_mode',
|
|
821
|
+
action='store_true',
|
|
822
|
+
help='Turns numeric/content errors into warnings')
|
|
823
|
+
|
|
505
824
|
parser.add_argument(
|
|
506
825
|
'--max_conf_error',
|
|
507
826
|
type=float,
|
|
@@ -526,8 +845,11 @@ def main():
|
|
|
526
845
|
|
|
527
846
|
options.disable_gpu = args.disable_gpu
|
|
528
847
|
options.cpu_execution_is_error = args.cpu_execution_is_error
|
|
529
|
-
options.
|
|
848
|
+
options.skip_video_tests = args.skip_video_tests
|
|
849
|
+
options.skip_python_tests = args.skip_python_tests
|
|
850
|
+
options.skip_cli_tests = args.skip_cli_tests
|
|
530
851
|
options.scratch_dir = args.scratch_dir
|
|
852
|
+
options.warning_mode = args.warning_mode
|
|
531
853
|
options.force_data_download = args.force_data_download
|
|
532
854
|
options.max_conf_error = args.max_conf_error
|
|
533
855
|
options.max_coord_error = args.max_coord_error
|
|
@@ -538,4 +860,3 @@ def main():
|
|
|
538
860
|
|
|
539
861
|
if __name__ == '__main__':
|
|
540
862
|
main()
|
|
541
|
-
|