megadetector 5.0.23__py3-none-any.whl → 5.0.25__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/lila/add_locations_to_island_camera_traps.py +73 -69
- megadetector/data_management/lila/add_locations_to_nacti.py +114 -110
- megadetector/data_management/mewc_to_md.py +340 -0
- megadetector/data_management/speciesnet_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 +341 -146
- megadetector/detection/run_detector_batch.py +307 -70
- megadetector/detection/run_inference_with_yolov5_val.py +61 -4
- megadetector/detection/tf_detector.py +6 -1
- megadetector/postprocessing/{combine_api_outputs.py → combine_batch_outputs.py} +10 -13
- megadetector/postprocessing/compare_batch_results.py +236 -7
- megadetector/postprocessing/create_crop_folder.py +358 -0
- 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 +12 -5
- megadetector/postprocessing/separate_detections_into_folders.py +32 -4
- megadetector/postprocessing/validate_batch_results.py +9 -4
- megadetector/utils/ct_utils.py +236 -45
- megadetector/utils/directory_listing.py +3 -3
- megadetector/utils/gpu_test.py +125 -0
- megadetector/utils/md_tests.py +455 -116
- megadetector/utils/path_utils.py +43 -2
- megadetector/utils/wi_utils.py +2691 -0
- megadetector/visualization/visualization_utils.py +95 -18
- megadetector/visualization/visualize_db.py +25 -7
- megadetector/visualization/visualize_detector_output.py +60 -13
- {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/METADATA +11 -23
- {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/RECORD +39 -36
- {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/WHEEL +1 -1
- 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.25.dist-info}/LICENSE +0 -0
- {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
mewc_to_md.py
|
|
4
|
+
|
|
5
|
+
Converts the output of the MEWC inference scripts to the MD output format.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
#%% Imports and constants
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import json
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
from copy import deepcopy
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
from megadetector.utils.ct_utils import sort_list_of_dicts_by_key, invert_dictionary # noqa
|
|
18
|
+
from megadetector.utils.path_utils import recursive_file_list
|
|
19
|
+
|
|
20
|
+
from megadetector.postprocessing.validate_batch_results import \
|
|
21
|
+
ValidateBatchResultsOptions, validate_batch_results
|
|
22
|
+
|
|
23
|
+
default_mewc_mount_prefix = '/images/'
|
|
24
|
+
default_mewc_category_name_column = 'class_id'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
#%% Functions
|
|
28
|
+
|
|
29
|
+
def mewc_to_md(mewc_input_folder,
|
|
30
|
+
output_file=None,
|
|
31
|
+
mount_prefix=default_mewc_mount_prefix,
|
|
32
|
+
category_name_column=default_mewc_category_name_column,
|
|
33
|
+
mewc_out_filename='mewc_out.csv',
|
|
34
|
+
md_out_filename='md_out.json'):
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
mewc_input_folder (str): the folder we'll search for MEWC output files
|
|
39
|
+
output_file (str, optional): .json file to write with class information
|
|
40
|
+
mount_prefix (str, optional): string to remove from all filenames in the MD
|
|
41
|
+
.json file, typically the prefix used to mount the image folder.
|
|
42
|
+
category_name_column (str, optional): column in the MEWC results .csv to use for
|
|
43
|
+
category naming.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
dict: an MD-formatted dict, the same as what's written to [output_file]
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
##%% Read input files
|
|
50
|
+
|
|
51
|
+
assert os.path.isdir(mewc_input_folder), \
|
|
52
|
+
'Could not find folder {}'.format(mewc_input_folder)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
##%% Find MEWC output files
|
|
56
|
+
|
|
57
|
+
relative_path_to_mewc_info = {}
|
|
58
|
+
|
|
59
|
+
print('Listing files in folder {}'.format(mewc_input_folder))
|
|
60
|
+
all_files_relative = set(recursive_file_list(mewc_input_folder,return_relative_paths=True))
|
|
61
|
+
|
|
62
|
+
for fn_relative in all_files_relative:
|
|
63
|
+
if fn_relative.endswith(mewc_out_filename):
|
|
64
|
+
folder_relative = '/'.join(fn_relative.split('/')[:-1])
|
|
65
|
+
assert folder_relative not in relative_path_to_mewc_info
|
|
66
|
+
md_output_file_relative = os.path.join(folder_relative,md_out_filename).replace('\\','/')
|
|
67
|
+
assert md_output_file_relative in all_files_relative, \
|
|
68
|
+
'Could not find MD output file {} to match to {}'.format(
|
|
69
|
+
md_output_file_relative,fn_relative)
|
|
70
|
+
relative_path_to_mewc_info[folder_relative] = \
|
|
71
|
+
{'mewc_predict_file':fn_relative,'md_file':md_output_file_relative}
|
|
72
|
+
|
|
73
|
+
del folder_relative
|
|
74
|
+
|
|
75
|
+
print('Found {} MEWC results files'.format(len(relative_path_to_mewc_info)))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
##%% Prepare to loop over results files
|
|
79
|
+
|
|
80
|
+
md_results_all = {}
|
|
81
|
+
md_results_all['images'] = []
|
|
82
|
+
md_results_all['detection_categories'] = {}
|
|
83
|
+
md_results_all['classification_categories'] = {}
|
|
84
|
+
md_results_all['info'] = None
|
|
85
|
+
|
|
86
|
+
classification_category_name_to_id = {}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
##%% Loop over results files
|
|
90
|
+
|
|
91
|
+
# relative_folder = next(iter(relative_path_to_mewc_info.keys()))
|
|
92
|
+
for relative_folder in relative_path_to_mewc_info:
|
|
93
|
+
|
|
94
|
+
##%%
|
|
95
|
+
|
|
96
|
+
mewc_info = relative_path_to_mewc_info[relative_folder]
|
|
97
|
+
mewc_csv_fn_abs = os.path.join(mewc_input_folder,mewc_info['mewc_predict_file'])
|
|
98
|
+
mewc_md_fn_abs = os.path.join(mewc_input_folder,mewc_info['md_file'])
|
|
99
|
+
|
|
100
|
+
mewc_classification_info = pd.read_csv(mewc_csv_fn_abs)
|
|
101
|
+
mewc_classification_info = mewc_classification_info.to_dict('records')
|
|
102
|
+
|
|
103
|
+
assert os.path.isfile(mewc_md_fn_abs), \
|
|
104
|
+
'Could not find file {}'.format(mewc_md_fn_abs)
|
|
105
|
+
with open(mewc_md_fn_abs,'r') as f:
|
|
106
|
+
md_results = json.load(f)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
##%% Remove the mount prefix from MD files if necessary
|
|
110
|
+
if mount_prefix is not None and len(mount_prefix) > 0:
|
|
111
|
+
|
|
112
|
+
n_files_without_mount_prefix = 0
|
|
113
|
+
|
|
114
|
+
# im = md_results['images'][0]
|
|
115
|
+
for im in md_results['images']:
|
|
116
|
+
if not im['file'].startswith(mount_prefix):
|
|
117
|
+
n_files_without_mount_prefix += 1
|
|
118
|
+
else:
|
|
119
|
+
im['file'] = im['file'].replace(mount_prefix,'',1)
|
|
120
|
+
|
|
121
|
+
if n_files_without_mount_prefix > 0:
|
|
122
|
+
print('Warning {} of {} files in the MD results did not include the mount prefix {}'.format(
|
|
123
|
+
n_files_without_mount_prefix,len(md_results['images']),mount_prefix))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
##%% Convert MEWC snip IDs to image files
|
|
127
|
+
|
|
128
|
+
# r = mewc_classification_info[0]
|
|
129
|
+
for r in mewc_classification_info:
|
|
130
|
+
|
|
131
|
+
# E.g. "IMG0-0.jpg"
|
|
132
|
+
snip_file = r['filename']
|
|
133
|
+
|
|
134
|
+
# E.g. "IMG0-0"
|
|
135
|
+
snip_file_no_ext = os.path.splitext(snip_file)[0]
|
|
136
|
+
ext = os.path.splitext(snip_file)[1] # noqa
|
|
137
|
+
|
|
138
|
+
tokens = snip_file_no_ext.split('-')
|
|
139
|
+
|
|
140
|
+
if len(tokens) == 1:
|
|
141
|
+
print('Warning: in folder {}, detection ID not found in snip filename {}, skipping'.format(
|
|
142
|
+
relative_folder,snip_file_no_ext))
|
|
143
|
+
r['image_filename_without_extension'] = snip_file_no_ext
|
|
144
|
+
r['snip_id'] = None
|
|
145
|
+
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
filename_without_snip_id = '-'.join(tokens[0:-1])
|
|
149
|
+
snip_id = int(tokens[-1])
|
|
150
|
+
image_filename_without_extension = filename_without_snip_id
|
|
151
|
+
|
|
152
|
+
r['image_filename_without_extension'] = image_filename_without_extension
|
|
153
|
+
r['snip_id'] = snip_id
|
|
154
|
+
|
|
155
|
+
# ...for each MEWC result record
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
##%% Make sure MD results and MEWC results refer to the same files
|
|
159
|
+
|
|
160
|
+
images_in_md_results_no_extension = \
|
|
161
|
+
set([os.path.splitext(im['file'])[0] for im in md_results['images']])
|
|
162
|
+
images_in_mewc_results_no_extension = set(r['image_filename_without_extension'] \
|
|
163
|
+
for r in mewc_classification_info)
|
|
164
|
+
|
|
165
|
+
# All files with classification results should also have detection results
|
|
166
|
+
for fn in images_in_mewc_results_no_extension:
|
|
167
|
+
assert fn in images_in_md_results_no_extension, \
|
|
168
|
+
'Error: file {} is present in mewc-predict results, but not in MD results'.format(fn)
|
|
169
|
+
|
|
170
|
+
# This is just a note to self: no classification results are present for empty images
|
|
171
|
+
if False:
|
|
172
|
+
for fn in images_in_md_results_no_extension:
|
|
173
|
+
if fn not in images_in_mewc_results_no_extension:
|
|
174
|
+
print('Warning: file {}/{} is present in MD results, but not in mewc-predict results'.format(
|
|
175
|
+
relative_folder,fn))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
##%% Validate images
|
|
179
|
+
|
|
180
|
+
for im in md_results['images']:
|
|
181
|
+
fn_relative = im['file']
|
|
182
|
+
fn_abs = os.path.join(mewc_input_folder,relative_folder,fn_relative)
|
|
183
|
+
if not os.path.isfile(fn_abs):
|
|
184
|
+
print('Warning: image file {} does not exist'.format(fn_abs))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
##%% Map filenames to MEWC results
|
|
188
|
+
|
|
189
|
+
image_id_to_mewc_records = defaultdict(list)
|
|
190
|
+
for r in mewc_classification_info:
|
|
191
|
+
image_id_to_mewc_records[r['image_filename_without_extension']].append(r)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
##%% Add classification info to MD results
|
|
195
|
+
|
|
196
|
+
# im = md_results['images'][0]
|
|
197
|
+
for im in md_results['images']:
|
|
198
|
+
|
|
199
|
+
if ('detections' not in im) or (im['detections'] is None) or (len(im['detections']) == 0):
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
detections = im['detections']
|
|
203
|
+
|
|
204
|
+
# *Don't* sort by confidence, it looks like snip IDs use the original sort order
|
|
205
|
+
# detections = sort_list_of_dicts_by_key(detections,'conf',reverse=True)
|
|
206
|
+
|
|
207
|
+
# This is just a debug assist, so I can run this cell more than once
|
|
208
|
+
for det in detections:
|
|
209
|
+
det['classifications'] = []
|
|
210
|
+
|
|
211
|
+
image_id = os.path.splitext(im['file'])[0]
|
|
212
|
+
mewc_records_this_image = image_id_to_mewc_records[image_id]
|
|
213
|
+
|
|
214
|
+
# r = mewc_records_this_image[0]
|
|
215
|
+
for r in mewc_records_this_image:
|
|
216
|
+
|
|
217
|
+
if r['snip_id'] is None:
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
category_name = r[category_name_column]
|
|
221
|
+
|
|
222
|
+
# This is a *global* list of category mappings, across all mewc .csv files
|
|
223
|
+
if category_name not in classification_category_name_to_id:
|
|
224
|
+
category_id = str(len(classification_category_name_to_id))
|
|
225
|
+
classification_category_name_to_id[category_name] = category_id
|
|
226
|
+
else:
|
|
227
|
+
category_id = classification_category_name_to_id[category_name]
|
|
228
|
+
|
|
229
|
+
snip_id = r['snip_id']
|
|
230
|
+
if snip_id >= len(detections):
|
|
231
|
+
print('Warning: image {} has a classified snip ID of {}, but only {} detections are present'.format(
|
|
232
|
+
image_id,snip_id,len(detections)))
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
det = detections[snip_id]
|
|
236
|
+
|
|
237
|
+
if 'classifications' not in det:
|
|
238
|
+
det['classifications'] = []
|
|
239
|
+
det['classifications'].append([category_id,r['prob']])
|
|
240
|
+
|
|
241
|
+
# ...for each classification in this image
|
|
242
|
+
|
|
243
|
+
# ...for each image
|
|
244
|
+
|
|
245
|
+
##%% Map MD reults to the global level
|
|
246
|
+
|
|
247
|
+
if md_results_all['info'] is None:
|
|
248
|
+
md_results_all['info'] = md_results['info']
|
|
249
|
+
|
|
250
|
+
for category_id in md_results['detection_categories']:
|
|
251
|
+
if category_id not in md_results_all['detection_categories']:
|
|
252
|
+
md_results_all['detection_categories'][category_id] = \
|
|
253
|
+
md_results['detection_categories'][category_id]
|
|
254
|
+
else:
|
|
255
|
+
assert md_results_all['detection_categories'][category_id] == \
|
|
256
|
+
md_results['detection_categories'][category_id], \
|
|
257
|
+
'MD results present with incompatible detection categories'
|
|
258
|
+
|
|
259
|
+
# im = md_results['images'][0]
|
|
260
|
+
for im in md_results['images']:
|
|
261
|
+
im_copy = deepcopy(im)
|
|
262
|
+
im_copy['file'] = os.path.join(relative_folder,im['file']).replace('\\','/')
|
|
263
|
+
md_results_all['images'].append(im_copy)
|
|
264
|
+
|
|
265
|
+
# ...for each folder that contains MEWC results
|
|
266
|
+
|
|
267
|
+
del md_results
|
|
268
|
+
|
|
269
|
+
##%% Write output
|
|
270
|
+
|
|
271
|
+
md_results_all['classification_categories'] = invert_dictionary(classification_category_name_to_id)
|
|
272
|
+
|
|
273
|
+
if output_file is not None:
|
|
274
|
+
output_dir = os.path.dirname(output_file)
|
|
275
|
+
os.makedirs(output_dir,exist_ok=True)
|
|
276
|
+
with open(output_file,'w') as f:
|
|
277
|
+
json.dump(md_results_all,f,indent=1)
|
|
278
|
+
|
|
279
|
+
validation_options = ValidateBatchResultsOptions()
|
|
280
|
+
validation_options.check_image_existence = True
|
|
281
|
+
validation_options.relative_path_base = mewc_input_folder
|
|
282
|
+
validation_options.raise_errors = True
|
|
283
|
+
validation_results = validate_batch_results(output_file,validation_options) # noqa
|
|
284
|
+
|
|
285
|
+
# ...def mewc_to_md(...)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
#%% Interactive driver
|
|
289
|
+
|
|
290
|
+
if False:
|
|
291
|
+
|
|
292
|
+
pass
|
|
293
|
+
|
|
294
|
+
#%%
|
|
295
|
+
|
|
296
|
+
mewc_input_folder = r'G:\temp\mewc-test'
|
|
297
|
+
mount_prefix = '/images/'
|
|
298
|
+
output_file = os.path.join(mewc_input_folder,'results_with_classes.json')
|
|
299
|
+
|
|
300
|
+
_ = mewc_to_md(mewc_input_folder=mewc_input_folder,
|
|
301
|
+
output_file=output_file,
|
|
302
|
+
mount_prefix=mount_prefix,
|
|
303
|
+
category_name_column='class_id')
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
#%% Command-line driver
|
|
307
|
+
|
|
308
|
+
import sys
|
|
309
|
+
import argparse
|
|
310
|
+
|
|
311
|
+
def main():
|
|
312
|
+
|
|
313
|
+
parser = argparse.ArgumentParser()
|
|
314
|
+
|
|
315
|
+
parser.add_argument(
|
|
316
|
+
'input_folder',type=str,
|
|
317
|
+
help='Folder containing images and MEWC .json/.csv files')
|
|
318
|
+
parser.add_argument(
|
|
319
|
+
'output_file',type=str,
|
|
320
|
+
help='.json file where output will be written')
|
|
321
|
+
parser.add_argument(
|
|
322
|
+
'--mount_prefix',type=str,default=default_mewc_mount_prefix,
|
|
323
|
+
help='prefix to remove from each filename in MEWC results, typically the Docker mount point')
|
|
324
|
+
parser.add_argument(
|
|
325
|
+
'--category_name_column',type=str,default=default_mewc_category_name_column,
|
|
326
|
+
help='column in the MEWC .csv file to use for category names')
|
|
327
|
+
|
|
328
|
+
if len(sys.argv[1:]) == 0:
|
|
329
|
+
parser.print_help()
|
|
330
|
+
parser.exit()
|
|
331
|
+
|
|
332
|
+
args = parser.parse_args()
|
|
333
|
+
|
|
334
|
+
_ = mewc_to_md(mewc_input_folder=args.input_folder,
|
|
335
|
+
output_file=args.output_file,
|
|
336
|
+
mount_prefix=args.mount_prefix,
|
|
337
|
+
category_name_column=args.category_name_column)
|
|
338
|
+
|
|
339
|
+
if __name__ == '__main__':
|
|
340
|
+
main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
wi_to_md.py
|
|
4
|
+
|
|
5
|
+
Converts the WI (SpeciesNet) predictions.json format to MD .json format. This is just a
|
|
6
|
+
command-line wrapper around utils.wi_utils.generate_md_results_from_predictions_json.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
#%% Imports and constants
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import argparse
|
|
14
|
+
from megadetector.utils.wi_utils import generate_md_results_from_predictions_json
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#%% Command-line driver
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
|
|
21
|
+
parser = argparse.ArgumentParser()
|
|
22
|
+
parser.add_argument('predictions_json_file', action='store', type=str,
|
|
23
|
+
help='.json file to convert from SpeciesNet predictions.json format to MD format')
|
|
24
|
+
parser.add_argument('md_results_file', action='store', type=str,
|
|
25
|
+
help='output file to write in MD format')
|
|
26
|
+
parser.add_argument('--base_folder', action='store', type=str, default=None,
|
|
27
|
+
help='leading string to remove from each path in the predictions.json ' + \
|
|
28
|
+
'file (to convert from absolute to relative paths)')
|
|
29
|
+
|
|
30
|
+
if len(sys.argv[1:]) == 0:
|
|
31
|
+
parser.print_help()
|
|
32
|
+
parser.exit()
|
|
33
|
+
|
|
34
|
+
args = parser.parse_args()
|
|
35
|
+
|
|
36
|
+
generate_md_results_from_predictions_json(args.predictions_json_file,
|
|
37
|
+
args.md_results_file,
|
|
38
|
+
args.base_folder)
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
main()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
yolo_output_to_md_output.py
|
|
4
4
|
|
|
5
|
-
Converts the output of YOLOv5's detect.py or val.py to the MD
|
|
5
|
+
Converts the output of YOLOv5's detect.py or val.py to the MD output format.
|
|
6
6
|
|
|
7
7
|
**Converting .txt files**
|
|
8
8
|
|
|
@@ -74,7 +74,7 @@ def read_classes_from_yolo_dataset_file(fn):
|
|
|
74
74
|
with open(fn,'r') as f:
|
|
75
75
|
lines = f.readlines()
|
|
76
76
|
|
|
77
|
-
pat = '\d+:.+'
|
|
77
|
+
pat = r'\d+:.+'
|
|
78
78
|
for s in lines:
|
|
79
79
|
if re.search(pat,s) is not None:
|
|
80
80
|
tokens = s.split(':')
|
|
@@ -281,7 +281,7 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
281
281
|
output_det['category'] = str(int(yolo_cat_id))
|
|
282
282
|
conf = det['score']
|
|
283
283
|
if truncate_to_standard_md_precision:
|
|
284
|
-
conf = ct_utils.
|
|
284
|
+
conf = ct_utils.round_float(conf,CONF_DIGITS)
|
|
285
285
|
output_det['conf'] = conf
|
|
286
286
|
input_bbox = det['bbox']
|
|
287
287
|
|
|
@@ -301,7 +301,7 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
301
301
|
box_width_relative,box_height_relative]
|
|
302
302
|
|
|
303
303
|
if truncate_to_standard_md_precision:
|
|
304
|
-
output_bbox = ct_utils.
|
|
304
|
+
output_bbox = ct_utils.round_float_array(output_bbox,COORD_DIGITS)
|
|
305
305
|
|
|
306
306
|
output_det['bbox'] = output_bbox
|
|
307
307
|
im['detections'].append(output_det)
|
|
@@ -332,7 +332,8 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
332
332
|
def yolo_txt_output_to_md_output(input_results_folder,
|
|
333
333
|
image_folder,
|
|
334
334
|
output_file,
|
|
335
|
-
detector_tag=None
|
|
335
|
+
detector_tag=None,
|
|
336
|
+
truncate_to_standard_md_precision=True):
|
|
336
337
|
"""
|
|
337
338
|
Converts a folder of YOLO-output .txt files to MD .json format.
|
|
338
339
|
|
|
@@ -347,7 +348,9 @@ def yolo_txt_output_to_md_output(input_results_folder,
|
|
|
347
348
|
output_file (str): the MD-formatted .json file to which we should write
|
|
348
349
|
results
|
|
349
350
|
detector_tag (str, optional): string to put in the 'detector' field in the
|
|
350
|
-
output file
|
|
351
|
+
output file
|
|
352
|
+
truncate_to_standard_md_precision (bool, optional): set this to truncate to
|
|
353
|
+
COORD_DIGITS and CONF_DIGITS, like the standard MD pipeline does.
|
|
351
354
|
"""
|
|
352
355
|
|
|
353
356
|
assert os.path.isdir(input_results_folder)
|
|
@@ -398,12 +401,16 @@ def yolo_txt_output_to_md_output(input_results_folder,
|
|
|
398
401
|
api_box = ct_utils.convert_yolo_to_xywh([float(row[1]), float(row[2]),
|
|
399
402
|
float(row[3]), float(row[4])])
|
|
400
403
|
|
|
401
|
-
conf =
|
|
404
|
+
conf = float(row[5])
|
|
405
|
+
|
|
406
|
+
if truncate_to_standard_md_precision:
|
|
407
|
+
conf = ct_utils.round_float(conf, precision=CONF_DIGITS)
|
|
408
|
+
api_box = ct_utils.round_float_array(api_box, precision=COORD_DIGITS)
|
|
402
409
|
|
|
403
410
|
detections.append({
|
|
404
411
|
'category': str(category),
|
|
405
412
|
'conf': conf,
|
|
406
|
-
'bbox':
|
|
413
|
+
'bbox': api_box
|
|
407
414
|
})
|
|
408
415
|
|
|
409
416
|
images_entries.append({
|
|
@@ -28,6 +28,7 @@ from uuid import uuid1
|
|
|
28
28
|
from megadetector.detection import run_detector_batch
|
|
29
29
|
from megadetector.visualization import visualize_detector_output
|
|
30
30
|
from megadetector.utils.ct_utils import args_to_object
|
|
31
|
+
from megadetector.utils.ct_utils import dict_to_kvp_list, parse_kvp_list
|
|
31
32
|
from megadetector.utils.path_utils import insert_before_extension, clean_path
|
|
32
33
|
from megadetector.detection.video_utils import video_to_frames
|
|
33
34
|
from megadetector.detection.video_utils import run_callback_on_frames
|
|
@@ -163,7 +164,7 @@ class ProcessVideoOptions:
|
|
|
163
164
|
self.max_width = None
|
|
164
165
|
|
|
165
166
|
#: Run the model at this image size (don't mess with this unless you know what you're
|
|
166
|
-
#: getting into)
|
|
167
|
+
#: getting into)... if you just want to pass smaller frames to MD, use max_width
|
|
167
168
|
self.image_size = None
|
|
168
169
|
|
|
169
170
|
#: Enable image augmentation
|
|
@@ -178,6 +179,9 @@ class ProcessVideoOptions:
|
|
|
178
179
|
#: frame result for each video (default), or every frame that was processed?
|
|
179
180
|
self.include_all_processed_frames = False
|
|
180
181
|
|
|
182
|
+
#: Detector-specific options
|
|
183
|
+
self.detector_options = None
|
|
184
|
+
|
|
181
185
|
# ...class ProcessVideoOptions
|
|
182
186
|
|
|
183
187
|
|
|
@@ -402,7 +406,7 @@ def process_video(options):
|
|
|
402
406
|
print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
|
|
403
407
|
'not; no raw frames will be written')
|
|
404
408
|
|
|
405
|
-
detector = load_detector(options.model_file)
|
|
409
|
+
detector = load_detector(options.model_file,detector_options=options.detector_options)
|
|
406
410
|
|
|
407
411
|
def frame_callback(image_np,image_id):
|
|
408
412
|
return detector.generate_detections_one_image(image_np,
|
|
@@ -475,7 +479,8 @@ def process_video(options):
|
|
|
475
479
|
class_mapping_filename=options.class_mapping_filename,
|
|
476
480
|
quiet=True,
|
|
477
481
|
augment=options.augment,
|
|
478
|
-
image_size=options.image_size
|
|
482
|
+
image_size=options.image_size,
|
|
483
|
+
detector_options=options.detector_options)
|
|
479
484
|
|
|
480
485
|
results = _add_frame_numbers_to_results(results)
|
|
481
486
|
|
|
@@ -612,7 +617,7 @@ def process_video_folder(options):
|
|
|
612
617
|
print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
|
|
613
618
|
'not; no raw frames will be written')
|
|
614
619
|
|
|
615
|
-
detector = load_detector(options.model_file)
|
|
620
|
+
detector = load_detector(options.model_file,detector_options=options.detector_options)
|
|
616
621
|
|
|
617
622
|
def frame_callback(image_np,image_id):
|
|
618
623
|
return detector.generate_detections_one_image(image_np,
|
|
@@ -719,7 +724,8 @@ def process_video_folder(options):
|
|
|
719
724
|
class_mapping_filename=options.class_mapping_filename,
|
|
720
725
|
quiet=True,
|
|
721
726
|
augment=options.augment,
|
|
722
|
-
image_size=options.image_size
|
|
727
|
+
image_size=options.image_size,
|
|
728
|
+
detector_options=options.detector_options)
|
|
723
729
|
|
|
724
730
|
_add_frame_numbers_to_results(results)
|
|
725
731
|
|
|
@@ -910,6 +916,8 @@ def options_to_command(options):
|
|
|
910
916
|
cmd += ' --force_extracted_frame_folder_deletion'
|
|
911
917
|
if options.force_rendered_frame_folder_deletion:
|
|
912
918
|
cmd += ' --force_rendered_frame_folder_deletion'
|
|
919
|
+
if options.detector_options is not None and len(options.detector_options) > 0:
|
|
920
|
+
cmd += '--detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
913
921
|
|
|
914
922
|
return cmd
|
|
915
923
|
|
|
@@ -1209,14 +1217,23 @@ def main():
|
|
|
1209
1217
|
parser.add_argument('--allow_empty_videos',
|
|
1210
1218
|
action='store_true',
|
|
1211
1219
|
help='By default, videos with no retrievable frames cause an error, this makes it a warning')
|
|
1212
|
-
|
|
1220
|
+
|
|
1221
|
+
parser.add_argument(
|
|
1222
|
+
'--detector_options',
|
|
1223
|
+
nargs='*',
|
|
1224
|
+
metavar='KEY=VALUE',
|
|
1225
|
+
default='',
|
|
1226
|
+
help='Detector-specific options, as a space-separated list of key-value pairs')
|
|
1227
|
+
|
|
1213
1228
|
if len(sys.argv[1:]) == 0:
|
|
1214
1229
|
parser.print_help()
|
|
1215
1230
|
parser.exit()
|
|
1216
1231
|
|
|
1217
1232
|
args = parser.parse_args()
|
|
1218
|
-
options = ProcessVideoOptions()
|
|
1233
|
+
options = ProcessVideoOptions()
|
|
1219
1234
|
args_to_object(args,options)
|
|
1235
|
+
|
|
1236
|
+
options.detector_options = parse_kvp_list(args.detector_options)
|
|
1220
1237
|
|
|
1221
1238
|
if os.path.isdir(options.input_video_file):
|
|
1222
1239
|
process_video_folder(options)
|