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
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
#%% Imports
|
|
39
39
|
|
|
40
40
|
import os
|
|
41
|
+
import sys
|
|
41
42
|
import uuid
|
|
42
43
|
import glob
|
|
43
44
|
import tempfile
|
|
@@ -48,7 +49,12 @@ from tqdm import tqdm
|
|
|
48
49
|
|
|
49
50
|
from md_utils import path_utils
|
|
50
51
|
from md_utils import process_utils
|
|
52
|
+
from md_utils import string_utils
|
|
51
53
|
from data_management import yolo_output_to_md_output
|
|
54
|
+
from detection.run_detector import try_download_known_detector
|
|
55
|
+
|
|
56
|
+
default_image_size_with_augmentation = int(1280 * 1.3)
|
|
57
|
+
default_image_size_with_no_augmentation = 1280
|
|
52
58
|
|
|
53
59
|
|
|
54
60
|
#%% Options class
|
|
@@ -59,17 +65,24 @@ class YoloInferenceOptions:
|
|
|
59
65
|
|
|
60
66
|
input_folder = None
|
|
61
67
|
model_filename = None
|
|
62
|
-
yolo_working_folder = None
|
|
63
68
|
output_file = None
|
|
64
|
-
|
|
69
|
+
|
|
65
70
|
## Optional ##
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
# Required for older YOLOv5 inference, not for newer ulytralytics inference
|
|
73
|
+
yolo_working_folder = None
|
|
74
|
+
|
|
75
|
+
# Currently 'yolov5' and 'ultralytics' are supported, and really these are proxies for
|
|
76
|
+
# "the yolov5 repo" and "the ultralytics repo" (typically YOLOv8).
|
|
77
|
+
model_type = 'yolov5'
|
|
78
|
+
|
|
79
|
+
image_size = default_image_size_with_augmentation
|
|
68
80
|
conf_thres = '0.001'
|
|
69
81
|
batch_size = 1
|
|
70
82
|
device_string = '0'
|
|
71
83
|
augment = True
|
|
72
|
-
|
|
84
|
+
half_precision_enabled = None
|
|
85
|
+
|
|
73
86
|
symlink_folder = None
|
|
74
87
|
use_symlinks = True
|
|
75
88
|
|
|
@@ -80,24 +93,54 @@ class YoloInferenceOptions:
|
|
|
80
93
|
|
|
81
94
|
# These are deliberately offset from the standard MD categories; YOLOv5
|
|
82
95
|
# needs categories IDs to start at 0.
|
|
96
|
+
#
|
|
97
|
+
# This can also be a string that points to a YOLOv5 dataset.yaml file.
|
|
83
98
|
yolo_category_id_to_name = {0:'animal',1:'person',2:'vehicle'}
|
|
84
99
|
|
|
85
100
|
# 'error','skip','overwrite'
|
|
86
101
|
overwrite_handling = 'skip'
|
|
102
|
+
|
|
103
|
+
preview_yolo_command_only = False
|
|
104
|
+
|
|
105
|
+
treat_copy_failures_as_warnings = False
|
|
106
|
+
|
|
107
|
+
save_yolo_debug_output = False
|
|
87
108
|
|
|
88
109
|
|
|
89
110
|
#%% Main function
|
|
90
111
|
|
|
91
112
|
def run_inference_with_yolo_val(options):
|
|
92
113
|
|
|
93
|
-
##%%
|
|
114
|
+
##%% Input and path handling
|
|
115
|
+
|
|
116
|
+
if options.model_type == 'yolov8':
|
|
117
|
+
|
|
118
|
+
print('Warning: model type "yolov8" supplied, "ultralytics" is the preferred model type string for YOLOv8 models')
|
|
119
|
+
options.model_type = 'ultralytics'
|
|
120
|
+
|
|
121
|
+
if (options.model_type == 'yolov5') and ('yolov8' in options.model_filename.lower()):
|
|
122
|
+
print('\n\n*** Warning: model type set as "yolov5", but your model filename contains "yolov8"... did you mean to use --model_type yolov8?" ***\n\n')
|
|
94
123
|
|
|
124
|
+
if options.yolo_working_folder is None:
|
|
125
|
+
assert options.model_type == 'ultralytics', \
|
|
126
|
+
'A working folder is required to run YOLOv5 val.py'
|
|
127
|
+
else:
|
|
128
|
+
assert os.path.isdir(options.yolo_working_folder), \
|
|
129
|
+
'Could not find working folder {}'.format(options.yolo_working_folder)
|
|
130
|
+
|
|
95
131
|
assert os.path.isdir(options.input_folder) or os.path.isfile(options.input_folder), \
|
|
96
132
|
'Could not find input {}'.format(options.input_folder)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
133
|
+
|
|
134
|
+
if options.half_precision_enabled is not None:
|
|
135
|
+
assert options.half_precision_enabled in (0,1), \
|
|
136
|
+
'Invalid value {} for --half_precision_enabled (should be 0 or 1)'.format(
|
|
137
|
+
options.half_precision_enabled)
|
|
138
|
+
|
|
139
|
+
# If the model filename is a known model string (e.g. "MDv5A", download the model if necessary)
|
|
140
|
+
model_filename = try_download_known_detector(options.model_filename)
|
|
141
|
+
|
|
142
|
+
assert os.path.isfile(model_filename), \
|
|
143
|
+
'Could not find model file {}'.format(model_filename)
|
|
101
144
|
|
|
102
145
|
if os.path.exists(options.output_file):
|
|
103
146
|
if options.overwrite_handling == 'skip':
|
|
@@ -112,6 +155,17 @@ def run_inference_with_yolo_val(options):
|
|
|
112
155
|
|
|
113
156
|
os.makedirs(os.path.dirname(options.output_file),exist_ok=True)
|
|
114
157
|
|
|
158
|
+
|
|
159
|
+
##%% Other input handling
|
|
160
|
+
|
|
161
|
+
if isinstance(options.yolo_category_id_to_name,str):
|
|
162
|
+
assert os.path.isfile(options.yolo_category_id_to_name)
|
|
163
|
+
yolo_dataset_file = options.yolo_category_id_to_name
|
|
164
|
+
options.yolo_category_id_to_name = \
|
|
165
|
+
yolo_output_to_md_output.read_classes_from_yolo_dataset_file(yolo_dataset_file)
|
|
166
|
+
print('Loaded {} category mappings from {}'.format(
|
|
167
|
+
len(options.yolo_category_id_to_name),yolo_dataset_file))
|
|
168
|
+
|
|
115
169
|
temporary_folder = None
|
|
116
170
|
symlink_folder_is_temp_folder = False
|
|
117
171
|
yolo_folder_is_temp_folder = False
|
|
@@ -185,24 +239,38 @@ def run_inference_with_yolo_val(options):
|
|
|
185
239
|
else:
|
|
186
240
|
shutil.copyfile(image_fn,symlink_full_path)
|
|
187
241
|
except Exception as e:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
242
|
+
error_string = str(e)
|
|
243
|
+
image_id_to_error[image_id] = error_string
|
|
244
|
+
# Always break if the user is trying to create symlinks on Windows without
|
|
245
|
+
# permission, 100% of images will always fail in this case.
|
|
246
|
+
if ('a required privilege is not held by the client' in error_string.lower()) or \
|
|
247
|
+
(not options.treat_copy_failures_as_warnings):
|
|
248
|
+
print('\nError copying/creating link for input file {}: {}'.format(
|
|
249
|
+
image_fn,error_string))
|
|
250
|
+
|
|
251
|
+
raise
|
|
252
|
+
else:
|
|
253
|
+
print('Warning: error copying/creating link for input file {}: {}'.format(
|
|
254
|
+
image_fn,error_string))
|
|
255
|
+
continue
|
|
192
256
|
|
|
193
257
|
# ...for each image
|
|
194
258
|
|
|
195
259
|
|
|
196
|
-
##%% Create the dataset file
|
|
260
|
+
##%% Create the dataset file if necessary
|
|
261
|
+
|
|
262
|
+
# This may have been passed in as a string, but at this point, we should have
|
|
263
|
+
# loaded the dataset file.
|
|
264
|
+
assert isinstance(options.yolo_category_id_to_name,dict)
|
|
197
265
|
|
|
198
266
|
# Category IDs need to be continuous integers starting at 0
|
|
199
267
|
category_ids = sorted(list(options.yolo_category_id_to_name.keys()))
|
|
200
268
|
assert category_ids[0] == 0
|
|
201
269
|
assert len(category_ids) == 1 + category_ids[-1]
|
|
202
270
|
|
|
203
|
-
|
|
271
|
+
yolo_dataset_file = os.path.join(yolo_results_folder,'dataset.yaml')
|
|
204
272
|
|
|
205
|
-
with open(
|
|
273
|
+
with open(yolo_dataset_file,'w') as f:
|
|
206
274
|
f.write('path: {}\n'.format(symlink_folder_inner))
|
|
207
275
|
f.write('train: .\n')
|
|
208
276
|
f.write('val: .\n')
|
|
@@ -217,50 +285,154 @@ def run_inference_with_yolo_val(options):
|
|
|
217
285
|
options.yolo_category_id_to_name[category_id]))
|
|
218
286
|
|
|
219
287
|
|
|
220
|
-
##%% Prepare
|
|
288
|
+
##%% Prepare Python command or YOLO CLI command
|
|
221
289
|
|
|
222
290
|
image_size_string = str(round(options.image_size))
|
|
223
|
-
cmd = 'python val.py --data "{}"'.format(dataset_file)
|
|
224
|
-
cmd += ' --weights "{}"'.format(options.model_filename)
|
|
225
|
-
cmd += ' --batch-size {} --imgsz {} --conf-thres {} --task test'.format(
|
|
226
|
-
options.batch_size,image_size_string,options.conf_thres)
|
|
227
|
-
cmd += ' --device "{}" --save-json'.format(options.device_string)
|
|
228
|
-
cmd += ' --project "{}" --name "{}" --exist-ok'.format(yolo_results_folder,'yolo_results')
|
|
229
291
|
|
|
230
|
-
if options.
|
|
231
|
-
|
|
292
|
+
if options.model_type == 'yolov5':
|
|
293
|
+
|
|
294
|
+
cmd = 'python val.py --task test --data "{}"'.format(yolo_dataset_file)
|
|
295
|
+
cmd += ' --weights "{}"'.format(model_filename)
|
|
296
|
+
cmd += ' --batch-size {} --imgsz {} --conf-thres {}'.format(
|
|
297
|
+
options.batch_size,image_size_string,options.conf_thres)
|
|
298
|
+
cmd += ' --device "{}" --save-json'.format(options.device_string)
|
|
299
|
+
cmd += ' --project "{}" --name "{}" --exist-ok'.format(yolo_results_folder,'yolo_results')
|
|
300
|
+
|
|
301
|
+
if options.augment:
|
|
302
|
+
cmd += ' --augment'
|
|
303
|
+
|
|
304
|
+
# --half is a store_true argument for YOLOv5's val.py
|
|
305
|
+
if (options.half_precision_enabled is not None) and (options.half_precision_enabled == 1):
|
|
306
|
+
cmd += ' --half'
|
|
307
|
+
|
|
308
|
+
# Sometimes useful for debugging
|
|
309
|
+
# cmd += ' --save_conf --save_txt'
|
|
310
|
+
|
|
311
|
+
elif options.model_type == 'ultralytics':
|
|
312
|
+
|
|
313
|
+
if options.augment:
|
|
314
|
+
augment_string = 'augment'
|
|
315
|
+
else:
|
|
316
|
+
augment_string = ''
|
|
317
|
+
|
|
318
|
+
cmd = 'yolo val {} model="{}" imgsz={} batch={} data="{}" project="{}" name="{}" device="{}"'.\
|
|
319
|
+
format(augment_string,model_filename,image_size_string,options.batch_size,
|
|
320
|
+
yolo_dataset_file,yolo_results_folder,'yolo_results',options.device_string)
|
|
321
|
+
cmd += ' save_json exist_ok'
|
|
322
|
+
|
|
323
|
+
if (options.half_precision_enabled is not None):
|
|
324
|
+
if options.half_precision_enabled == 1:
|
|
325
|
+
cmd += ' --half=True'
|
|
326
|
+
else:
|
|
327
|
+
assert options.half_precision_enabled == 0
|
|
328
|
+
cmd += ' --half=False'
|
|
329
|
+
|
|
330
|
+
# Sometimes useful for debugging
|
|
331
|
+
# cmd += ' save_conf save_txt'
|
|
332
|
+
|
|
333
|
+
else:
|
|
334
|
+
|
|
335
|
+
raise ValueError('Unrecognized model type {}'.format(options.model_type))
|
|
336
|
+
|
|
337
|
+
# print(cmd); import clipboard; clipboard.copy(cmd)
|
|
338
|
+
|
|
232
339
|
|
|
340
|
+
##%% Run YOLO command
|
|
341
|
+
|
|
342
|
+
if options.yolo_working_folder is not None:
|
|
343
|
+
current_dir = os.getcwd()
|
|
344
|
+
os.chdir(options.yolo_working_folder)
|
|
233
345
|
|
|
234
|
-
|
|
346
|
+
print('Running YOLO inference command:\n{}\n'.format(cmd))
|
|
235
347
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
348
|
+
if options.preview_yolo_command_only:
|
|
349
|
+
|
|
350
|
+
if options.remove_symlink_folder:
|
|
351
|
+
try:
|
|
352
|
+
print('Removing YOLO symlink folder {}'.format(symlink_folder))
|
|
353
|
+
shutil.rmtree(symlink_folder)
|
|
354
|
+
except Exception:
|
|
355
|
+
print('Warning: error removing symlink folder {}'.format(symlink_folder))
|
|
356
|
+
pass
|
|
357
|
+
if options.remove_yolo_results_folder:
|
|
358
|
+
try:
|
|
359
|
+
print('Removing YOLO results folder {}'.format(yolo_results_folder))
|
|
360
|
+
shutil.rmtree(yolo_results_folder)
|
|
361
|
+
except Exception:
|
|
362
|
+
print('Warning: error removing YOLO results folder {}'.format(yolo_results_folder))
|
|
363
|
+
pass
|
|
364
|
+
|
|
365
|
+
sys.exit()
|
|
366
|
+
|
|
367
|
+
execution_result = process_utils.execute_and_print(cmd,encoding='utf-8',verbose=True)
|
|
368
|
+
assert execution_result['status'] == 0, 'Error running {}'.format(options.model_type)
|
|
240
369
|
yolo_console_output = execution_result['output']
|
|
370
|
+
|
|
371
|
+
if options.save_yolo_debug_output:
|
|
372
|
+
with open(os.path.join(yolo_results_folder,'yolo_console_output.txt'),'w') as f:
|
|
373
|
+
for s in yolo_console_output:
|
|
374
|
+
f.write(s + '\n')
|
|
375
|
+
with open(os.path.join(yolo_results_folder,'image_id_to_file.json'),'w') as f:
|
|
376
|
+
json.dump(image_id_to_file,f,indent=1)
|
|
377
|
+
with open(os.path.join(yolo_results_folder,'image_id_to_error.json'),'w') as f:
|
|
378
|
+
json.dump(image_id_to_error,f,indent=1)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# YOLO console output contains lots of ANSI escape codes, remove them for easier parsing
|
|
382
|
+
yolo_console_output = [string_utils.remove_ansi_codes(s) for s in yolo_console_output]
|
|
241
383
|
|
|
384
|
+
# Find errors that occrred during the initial corruption check; these will not be included in the
|
|
385
|
+
# output. Errors that occur during inference will be handled separately.
|
|
242
386
|
yolo_read_failures = []
|
|
387
|
+
|
|
243
388
|
for line in yolo_console_output:
|
|
389
|
+
# Lines look like:
|
|
390
|
+
#
|
|
391
|
+
# For ultralytics val:
|
|
392
|
+
#
|
|
393
|
+
# val: WARNING ⚠️ /a/b/c/d.jpg: ignoring corrupt image/label: [Errno 13] Permission denied: '/a/b/c/d.jpg'
|
|
394
|
+
# line = "val: WARNING ⚠️ /a/b/c/d.jpg: ignoring corrupt image/label: [Errno 13] Permission denied: '/a/b/c/d.jpg'"
|
|
395
|
+
#
|
|
396
|
+
# For yolov5 val.py:
|
|
397
|
+
#
|
|
398
|
+
# test: WARNING: a/b/c/d.jpg: ignoring corrupt image/label: cannot identify image file '/a/b/c/d.jpg'
|
|
399
|
+
# line = "test: WARNING: a/b/c/d.jpg: ignoring corrupt image/label: cannot identify image file '/a/b/c/d.jpg'"
|
|
244
400
|
if 'cannot identify image file' in line:
|
|
245
401
|
tokens = line.split('cannot identify image file')
|
|
246
402
|
image_name = tokens[-1].strip()
|
|
247
403
|
assert image_name[0] == "'" and image_name [-1] == "'"
|
|
248
404
|
image_name = image_name[1:-1]
|
|
249
405
|
yolo_read_failures.append(image_name)
|
|
250
|
-
|
|
406
|
+
elif 'ignoring corrupt image/label' in line:
|
|
407
|
+
assert 'WARNING' in line
|
|
408
|
+
if '⚠️' in line:
|
|
409
|
+
assert line.startswith('val'), \
|
|
410
|
+
'Unrecognized line in YOLO output: {}'.format(line)
|
|
411
|
+
tokens = line.split('ignoring corrupt image/label')
|
|
412
|
+
image_name = tokens[0].split('⚠️')[-1].strip()
|
|
413
|
+
else:
|
|
414
|
+
assert line.startswith('test'), \
|
|
415
|
+
'Unrecognized line in YOLO output: {}'.format(line)
|
|
416
|
+
tokens = line.split('ignoring corrupt image/label')
|
|
417
|
+
image_name = tokens[0].split('WARNING:')[-1].strip()
|
|
418
|
+
assert image_name.endswith(':')
|
|
419
|
+
image_name = image_name[0:-1]
|
|
420
|
+
yolo_read_failures.append(image_name)
|
|
421
|
+
|
|
251
422
|
# image_file = yolo_read_failures[0]
|
|
252
423
|
for image_file in yolo_read_failures:
|
|
253
424
|
image_id = os.path.splitext(os.path.basename(image_file))[0]
|
|
254
425
|
assert image_id in image_id_to_file
|
|
255
426
|
if image_id not in image_id_to_error:
|
|
256
|
-
image_id_to_error[image_id] = '
|
|
427
|
+
image_id_to_error[image_id] = 'YOLO read failure'
|
|
257
428
|
|
|
258
|
-
|
|
429
|
+
if options.yolo_working_folder is not None:
|
|
430
|
+
os.chdir(current_dir)
|
|
259
431
|
|
|
260
432
|
|
|
261
433
|
##%% Convert results to MD format
|
|
262
434
|
|
|
263
|
-
json_files = glob.glob(yolo_results_folder+ '/yolo_results/*.json')
|
|
435
|
+
json_files = glob.glob(yolo_results_folder + '/yolo_results/*.json')
|
|
264
436
|
assert len(json_files) == 1
|
|
265
437
|
yolo_json_file = json_files[0]
|
|
266
438
|
|
|
@@ -288,7 +460,7 @@ def run_inference_with_yolo_val(options):
|
|
|
288
460
|
image_folder=image_base,
|
|
289
461
|
output_file=options.output_file,
|
|
290
462
|
yolo_category_id_to_name=options.yolo_category_id_to_name,
|
|
291
|
-
detector_name=os.path.basename(
|
|
463
|
+
detector_name=os.path.basename(model_filename),
|
|
292
464
|
image_id_to_relative_path=image_id_to_relative_path,
|
|
293
465
|
image_id_to_error=image_id_to_error)
|
|
294
466
|
|
|
@@ -312,7 +484,7 @@ def run_inference_with_yolo_val(options):
|
|
|
312
484
|
|
|
313
485
|
#%% Command-line driver
|
|
314
486
|
|
|
315
|
-
import argparse
|
|
487
|
+
import argparse
|
|
316
488
|
from md_utils.ct_utils import args_to_object
|
|
317
489
|
|
|
318
490
|
def main():
|
|
@@ -329,14 +501,14 @@ def main():
|
|
|
329
501
|
parser.add_argument(
|
|
330
502
|
'output_file',type=str,
|
|
331
503
|
help='.json file where output will be written')
|
|
332
|
-
parser.add_argument(
|
|
333
|
-
'yolo_working_folder',type=str,
|
|
334
|
-
help='folder in which to execute val.py')
|
|
335
504
|
|
|
336
505
|
parser.add_argument(
|
|
337
|
-
'--
|
|
338
|
-
help='
|
|
339
|
-
|
|
506
|
+
'--yolo_working_folder',type=str,default=None,
|
|
507
|
+
help='folder in which to execute val.py (not necessary for YOLOv8 inference)')
|
|
508
|
+
parser.add_argument(
|
|
509
|
+
'--image_size', default=None, type=int,
|
|
510
|
+
help='image size for model execution (default {} when augmentation is enabled, else {})'.format(
|
|
511
|
+
default_image_size_with_augmentation,default_image_size_with_no_augmentation))
|
|
340
512
|
parser.add_argument(
|
|
341
513
|
'--conf_thres', default=options.conf_thres, type=float,
|
|
342
514
|
help='confidence threshold for including detections in the output file (default {})'.format(
|
|
@@ -344,13 +516,23 @@ def main():
|
|
|
344
516
|
parser.add_argument(
|
|
345
517
|
'--batch_size', default=options.batch_size, type=int,
|
|
346
518
|
help='inference batch size (default {})'.format(options.batch_size))
|
|
519
|
+
parser.add_argument(
|
|
520
|
+
'--half_precision_enabled', default=None, type=int,
|
|
521
|
+
help='use half-precision-inference (1 or 0) (default is the underlying model\'s default, probably half for YOLOv8 and full for YOLOv8')
|
|
347
522
|
parser.add_argument(
|
|
348
523
|
'--device_string', default=options.device_string, type=str,
|
|
349
|
-
help='CUDA device specifier,
|
|
524
|
+
help='CUDA device specifier, typically "0" or "1" for CUDA devices, "mps" for M1/M2 devices, or "cpu" (default {})'.format(options.device_string))
|
|
350
525
|
parser.add_argument(
|
|
351
526
|
'--overwrite_handling', default=options.overwrite_handling, type=str,
|
|
352
527
|
help='action to take if the output file exists (skip, error, overwrite) (default {})'.format(
|
|
353
|
-
options.overwrite_handling)
|
|
528
|
+
options.overwrite_handling))
|
|
529
|
+
parser.add_argument(
|
|
530
|
+
'--yolo_dataset_file', default=None, type=str,
|
|
531
|
+
help='YOLOv5 dataset.yml file from which we should load category information ' + \
|
|
532
|
+
'(otherwise defaults to MD categories)')
|
|
533
|
+
parser.add_argument(
|
|
534
|
+
'--model_type', default=options.model_type, type=str,
|
|
535
|
+
help='Model type ("yolov5" or "ultralytics" ("yolov8" behaves the same as "ultralytics")) (default {})'.format(options.model_type))
|
|
354
536
|
|
|
355
537
|
parser.add_argument(
|
|
356
538
|
'--symlink_folder', type=str,
|
|
@@ -367,11 +549,19 @@ def main():
|
|
|
367
549
|
parser.add_argument(
|
|
368
550
|
'--no_remove_yolo_results_folder', action='store_true',
|
|
369
551
|
help='don\'t remove the temporary folder full of YOLO intermediate files')
|
|
552
|
+
parser.add_argument(
|
|
553
|
+
'--save_yolo_debug_output', action='store_true',
|
|
554
|
+
help='write yolo console output to a text file in the results folder, along with additional debug files')
|
|
555
|
+
|
|
556
|
+
parser.add_argument(
|
|
557
|
+
'--preview_yolo_command_only', action='store_true',
|
|
558
|
+
help='don\'t run inference, just preview the YOLO inference command (still creates symlinks)')
|
|
370
559
|
|
|
371
560
|
if options.augment:
|
|
372
561
|
default_augment_enabled = 1
|
|
373
562
|
else:
|
|
374
563
|
default_augment_enabled = 0
|
|
564
|
+
|
|
375
565
|
parser.add_argument(
|
|
376
566
|
'--augment_enabled', default=default_augment_enabled, type=int,
|
|
377
567
|
help='enable/disable augmentation (default {})'.format(default_augment_enabled))
|
|
@@ -381,8 +571,27 @@ def main():
|
|
|
381
571
|
parser.exit()
|
|
382
572
|
|
|
383
573
|
args = parser.parse_args()
|
|
384
|
-
|
|
574
|
+
|
|
575
|
+
# If the caller hasn't specified an image size, choose one based on whether augmentation
|
|
576
|
+
# is enabled.
|
|
577
|
+
if args.image_size is None:
|
|
578
|
+
assert args.augment_enabled in (0,1), \
|
|
579
|
+
'Illegal augment_enabled value {}'.format(args.augment_enabled)
|
|
580
|
+
if args.augment_enabled == 1:
|
|
581
|
+
args.image_size = default_image_size_with_augmentation
|
|
582
|
+
else:
|
|
583
|
+
args.image_size = default_image_size_with_no_augmentation
|
|
584
|
+
augment_enabled_string = 'enabled'
|
|
585
|
+
if not args.augment_enabled:
|
|
586
|
+
augment_enabled_string = 'disabled'
|
|
587
|
+
print('Augmentation is {}, using default image size {}'.format(
|
|
588
|
+
augment_enabled_string,args.image_size))
|
|
589
|
+
|
|
385
590
|
args_to_object(args, options)
|
|
591
|
+
|
|
592
|
+
if args.yolo_dataset_file is not None:
|
|
593
|
+
options.yolo_category_id_to_name = args.yolo_dataset_file
|
|
594
|
+
|
|
386
595
|
options.remove_symlink_folder = (not options.no_remove_symlink_folder)
|
|
387
596
|
options.remove_yolo_results_folder = (not options.no_remove_yolo_results_folder)
|
|
388
597
|
options.use_symlinks = (not options.no_use_symlinks)
|
|
@@ -400,16 +609,6 @@ if __name__ == '__main__':
|
|
|
400
609
|
#%% Scrap
|
|
401
610
|
|
|
402
611
|
if False:
|
|
403
|
-
|
|
404
|
-
#%% Run from a set of options
|
|
405
|
-
|
|
406
|
-
options = YoloInferenceOptions()
|
|
407
|
-
|
|
408
|
-
args = {'augment': 1, 'batch_size': 1, 'conf_thres': 0.005, 'device_string': '1', 'image_size': 1664.0, 'input_folder': '/home/user/postprocessing/usgs-kissel/usgs-kissel-2023-09-11-aug-v5a.0.0/chunk031.json', 'model_filename': '/home/user/models/camera_traps/megadetector/md_v5.0.0/md_v5a.0.0.pt', 'output_file': '/home/user/postprocessing/usgs-kissel/usgs-kissel-2023-09-11-aug-v5a.0.0/chunk031_results.json', 'overwrite_handling': 'skip', 'symlink_folder': '/home/user/postprocessing/usgs-kissel/usgs-kissel-2023-09-11-aug-v5a.0.0/symlinks/symlinks_031', 'yolo_results_folder': '/home/user/postprocessing/usgs-kissel/usgs-kissel-2023-09-11-aug-v5a.0.0/yolo_results/yolo_results_031', 'yolo_working_folder': '/home/user/git/yolov5', 'remove_symlink_folder': True, 'remove_yolo_results_folder': True, 'use_symlinks': False, 'augment': True}
|
|
409
|
-
|
|
410
|
-
for k in args:
|
|
411
|
-
setattr(options, k, args[k])
|
|
412
|
-
|
|
413
612
|
|
|
414
613
|
#%% Test driver (folder)
|
|
415
614
|
|
|
@@ -452,8 +651,10 @@ if False:
|
|
|
452
651
|
options.remove_temporary_symlink_folder = False
|
|
453
652
|
options.remove_yolo_results_file = False
|
|
454
653
|
|
|
455
|
-
cmd = f'python run_inference_with_yolov5_val.py {model_filename} {input_folder}
|
|
456
|
-
f'
|
|
654
|
+
cmd = f'python run_inference_with_yolov5_val.py {model_filename} {input_folder} ' + \
|
|
655
|
+
f'{output_file} --yolo_working_folder {yolo_working_folder} ' + \
|
|
656
|
+
f' --image_size {options.image_size} --conf_thres {options.conf_thres} ' + \
|
|
657
|
+
f' --batch_size {options.batch_size} ' + \
|
|
457
658
|
f' --symlink_folder {options.symlink_folder} --yolo_results_folder {options.yolo_results_folder} ' + \
|
|
458
659
|
' --no_remove_symlink_folder --no_remove_yolo_results_folder'
|
|
459
660
|
|
|
@@ -470,6 +671,74 @@ if False:
|
|
|
470
671
|
import clipboard; clipboard.copy(cmd)
|
|
471
672
|
|
|
472
673
|
|
|
674
|
+
#%% Test driver (folder) (YOLOv8 model)
|
|
675
|
+
|
|
676
|
+
project_name = 'yolov8-inference-test'
|
|
677
|
+
input_folder = os.path.expanduser('~/data/usgs-kissel-training-resized/val')
|
|
678
|
+
dataset_file = os.path.expanduser('~/data/usgs-kissel-training-yolo/dataset.yaml')
|
|
679
|
+
output_folder = os.path.expanduser(f'~/tmp/{project_name}')
|
|
680
|
+
model_filename = os.path.expanduser(
|
|
681
|
+
'~/models/usgs-tegus/usgs-tegus-yolov8x-2023.10.25-b-1-img640-e200-best.pt')
|
|
682
|
+
model_name = os.path.splitext(os.path.basename(model_filename))[0]
|
|
683
|
+
|
|
684
|
+
assert os.path.isdir(input_folder)
|
|
685
|
+
assert os.path.isfile(dataset_file)
|
|
686
|
+
assert os.path.isfile(model_filename)
|
|
687
|
+
|
|
688
|
+
symlink_folder = os.path.join(output_folder,'symlinks')
|
|
689
|
+
yolo_results_folder = os.path.join(output_folder,'yolo_results')
|
|
690
|
+
|
|
691
|
+
output_file = os.path.join(output_folder,'{}_{}-md_format.json'.format(
|
|
692
|
+
project_name,model_name))
|
|
693
|
+
|
|
694
|
+
options = YoloInferenceOptions()
|
|
695
|
+
|
|
696
|
+
options.model_type = 'yolov8'
|
|
697
|
+
options.yolo_category_id_to_name = dataset_file
|
|
698
|
+
options.yolo_working_folder = None
|
|
699
|
+
options.output_file = output_file
|
|
700
|
+
|
|
701
|
+
options.augment = False
|
|
702
|
+
options.conf_thres = '0.001'
|
|
703
|
+
options.batch_size = 1
|
|
704
|
+
options.device_string = '0'
|
|
705
|
+
|
|
706
|
+
if options.augment:
|
|
707
|
+
options.image_size = round(640 * 1.3)
|
|
708
|
+
else:
|
|
709
|
+
options.image_size = 640
|
|
710
|
+
|
|
711
|
+
options.input_folder = input_folder
|
|
712
|
+
options.model_filename = model_filename
|
|
713
|
+
|
|
714
|
+
options.yolo_results_folder = yolo_results_folder
|
|
715
|
+
options.symlink_folder = symlink_folder
|
|
716
|
+
options.use_symlinks = False
|
|
717
|
+
|
|
718
|
+
options.remove_temporary_symlink_folder = False
|
|
719
|
+
options.remove_yolo_results_file = False
|
|
720
|
+
|
|
721
|
+
cmd = f'python run_inference_with_yolov5_val.py {model_filename} ' + \
|
|
722
|
+
f'{input_folder} {output_file}' + \
|
|
723
|
+
f' --image_size {options.image_size} --conf_thres {options.conf_thres} ' + \
|
|
724
|
+
f' --batch_size {options.batch_size} --symlink_folder {options.symlink_folder} ' + \
|
|
725
|
+
f'--yolo_results_folder {options.yolo_results_folder} --model_type {options.model_type}' + \
|
|
726
|
+
f' --yolo_dataset_file {options.yolo_category_id_to_name}' + \
|
|
727
|
+
' --no_remove_symlink_folder --no_remove_yolo_results_folder'
|
|
728
|
+
|
|
729
|
+
if not options.use_symlinks:
|
|
730
|
+
cmd += ' --no_use_symlinks'
|
|
731
|
+
if not options.augment:
|
|
732
|
+
cmd += ' --augment_enabled 0'
|
|
733
|
+
|
|
734
|
+
print(cmd)
|
|
735
|
+
execute_in_python = False
|
|
736
|
+
if execute_in_python:
|
|
737
|
+
run_inference_with_yolo_val(options)
|
|
738
|
+
else:
|
|
739
|
+
import clipboard; clipboard.copy(cmd)
|
|
740
|
+
|
|
741
|
+
|
|
473
742
|
#%% Preview results
|
|
474
743
|
|
|
475
744
|
postprocessing_output_folder = os.path.join(output_folder,'yolo-val-preview')
|