megadetector 5.0.22__py3-none-any.whl → 5.0.24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +2 -3
- megadetector/classification/merge_classification_detection_output.py +2 -2
- megadetector/data_management/coco_to_labelme.py +2 -1
- megadetector/data_management/databases/integrity_check_json_db.py +15 -14
- megadetector/data_management/databases/subset_json_db.py +49 -21
- megadetector/data_management/mewc_to_md.py +340 -0
- megadetector/data_management/wi_to_md.py +41 -0
- megadetector/data_management/yolo_output_to_md_output.py +15 -8
- megadetector/detection/process_video.py +24 -7
- megadetector/detection/pytorch_detector.py +841 -160
- megadetector/detection/run_detector.py +340 -146
- megadetector/detection/run_detector_batch.py +306 -70
- megadetector/detection/run_inference_with_yolov5_val.py +61 -4
- megadetector/detection/tf_detector.py +6 -1
- megadetector/postprocessing/{combine_api_outputs.py → combine_batch_outputs.py} +10 -13
- megadetector/postprocessing/compare_batch_results.py +68 -6
- megadetector/postprocessing/md_to_labelme.py +7 -7
- megadetector/postprocessing/md_to_wi.py +40 -0
- megadetector/postprocessing/merge_detections.py +1 -1
- megadetector/postprocessing/postprocess_batch_results.py +10 -3
- megadetector/postprocessing/separate_detections_into_folders.py +32 -4
- megadetector/postprocessing/validate_batch_results.py +9 -4
- megadetector/utils/ct_utils.py +172 -57
- megadetector/utils/gpu_test.py +107 -0
- megadetector/utils/md_tests.py +363 -108
- megadetector/utils/path_utils.py +9 -2
- megadetector/utils/wi_utils.py +1794 -0
- megadetector/visualization/visualization_utils.py +82 -16
- megadetector/visualization/visualize_db.py +25 -7
- megadetector/visualization/visualize_detector_output.py +60 -13
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/LICENSE +0 -0
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/METADATA +129 -143
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/RECORD +35 -33
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/top_level.txt +0 -0
- megadetector/detection/detector_training/__init__.py +0 -0
- megadetector/detection/detector_training/model_main_tf2.py +0 -114
- megadetector/utils/torch_test.py +0 -32
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/WHEEL +0 -0
|
@@ -17,9 +17,6 @@ are processed and final results file written to output_file, the temporary
|
|
|
17
17
|
checkpoint file will be deleted. If you want to resume from a checkpoint, set
|
|
18
18
|
the checkpoint file's path using --resume_from_checkpoint.
|
|
19
19
|
|
|
20
|
-
The `threshold` you can provide as an argument is the confidence threshold above
|
|
21
|
-
which detections will be included in the output file.
|
|
22
|
-
|
|
23
20
|
Has multiprocessing support for CPUs only; if a GPU is available, it will
|
|
24
21
|
use the GPU instead of CPUs, and the --ncores option will be ignored. Checkpointing
|
|
25
22
|
is not supported when using a GPU.
|
|
@@ -53,15 +50,14 @@ import humanfriendly
|
|
|
53
50
|
|
|
54
51
|
from datetime import datetime
|
|
55
52
|
from functools import partial
|
|
53
|
+
from copy import deepcopy
|
|
56
54
|
from tqdm import tqdm
|
|
57
55
|
|
|
58
56
|
import multiprocessing
|
|
59
57
|
from threading import Thread
|
|
60
58
|
from multiprocessing import Process, Manager
|
|
61
59
|
|
|
62
|
-
#
|
|
63
|
-
# to make sure I don't change this casually at some point, it changes a number of
|
|
64
|
-
# assumptions about interaction with PyTorch and TF.
|
|
60
|
+
# This pool is used for multi-CPU parallelization, not for data loading workers
|
|
65
61
|
# from multiprocessing.pool import ThreadPool as workerpool
|
|
66
62
|
from multiprocessing.pool import Pool as workerpool
|
|
67
63
|
|
|
@@ -74,6 +70,9 @@ from megadetector.detection.run_detector import \
|
|
|
74
70
|
get_detector_metadata_from_version_string
|
|
75
71
|
|
|
76
72
|
from megadetector.utils import path_utils
|
|
73
|
+
from megadetector.utils.ct_utils import parse_kvp_list
|
|
74
|
+
from megadetector.utils.ct_utils import split_list_into_n_chunks
|
|
75
|
+
from megadetector.utils.ct_utils import sort_list_of_dicts_by_key
|
|
77
76
|
from megadetector.visualization import visualization_utils as vis_utils
|
|
78
77
|
from megadetector.data_management import read_exif
|
|
79
78
|
from megadetector.data_management.yolo_output_to_md_output import read_classes_from_yolo_dataset_file
|
|
@@ -81,12 +80,24 @@ from megadetector.data_management.yolo_output_to_md_output import read_classes_f
|
|
|
81
80
|
# Numpy FutureWarnings from tensorflow import
|
|
82
81
|
warnings.filterwarnings('ignore', category=FutureWarning)
|
|
83
82
|
|
|
84
|
-
#
|
|
83
|
+
# Default number of loaders to use when --image_queue is set
|
|
84
|
+
default_loaders = 4
|
|
85
|
+
|
|
86
|
+
# Should we do preprocessing on the image queue?
|
|
87
|
+
default_preprocess_on_image_queue = False
|
|
88
|
+
|
|
89
|
+
# Number of images to pre-fetch per worker
|
|
85
90
|
max_queue_size = 10
|
|
86
91
|
|
|
87
92
|
# How often should we print progress when using the image queue?
|
|
88
93
|
n_queue_print = 1000
|
|
89
94
|
|
|
95
|
+
# TODO: it's a little sloppy that these are module-level globals, but in practice it
|
|
96
|
+
# doesn't really matter, so I'm not in a big rush to move these to options until I do
|
|
97
|
+
# a larger cleanup of all the long argument lists in this module.
|
|
98
|
+
#
|
|
99
|
+
# Should the consumer loop run on its own process, or here in the main process?
|
|
100
|
+
run_separate_consumer_process = False
|
|
90
101
|
use_threads_for_queue = False
|
|
91
102
|
verbose = False
|
|
92
103
|
|
|
@@ -97,84 +108,175 @@ exif_options.byte_handling = 'convert_to_string'
|
|
|
97
108
|
|
|
98
109
|
#%% Support functions for multiprocessing
|
|
99
110
|
|
|
100
|
-
def _producer_func(q,
|
|
111
|
+
def _producer_func(q,
|
|
112
|
+
image_files,
|
|
113
|
+
producer_id=-1,
|
|
114
|
+
preprocessor=None,
|
|
115
|
+
detector_options=None,
|
|
116
|
+
verbose=False,
|
|
117
|
+
image_size=None,
|
|
118
|
+
augment=None):
|
|
101
119
|
"""
|
|
102
120
|
Producer function; only used when using the (optional) image queue.
|
|
103
121
|
|
|
104
|
-
Reads up to
|
|
122
|
+
Reads up to images from disk and puts them on the blocking queue for
|
|
123
|
+
processing. Each image is queued as a tuple of [filename,Image]. Sends
|
|
124
|
+
"None" to the queue when finished.
|
|
125
|
+
|
|
126
|
+
The "detector" argument is only used for preprocessing.
|
|
105
127
|
"""
|
|
106
128
|
|
|
107
129
|
if verbose:
|
|
108
|
-
print('Producer starting'
|
|
130
|
+
print('Producer starting: ID {}, preprocessor {}'.format(producer_id,preprocessor))
|
|
131
|
+
sys.stdout.flush()
|
|
132
|
+
|
|
133
|
+
if preprocessor is not None:
|
|
134
|
+
assert isinstance(preprocessor,str)
|
|
135
|
+
detector_options = deepcopy(detector_options)
|
|
136
|
+
detector_options['preprocess_only'] = True
|
|
137
|
+
preprocessor = load_detector(preprocessor,detector_options=detector_options,verbose=verbose)
|
|
109
138
|
|
|
110
139
|
for im_file in image_files:
|
|
111
140
|
|
|
112
141
|
try:
|
|
113
142
|
if verbose:
|
|
114
|
-
print('Loading image {}'.format(im_file))
|
|
143
|
+
print('Loading image {} on producer {}'.format(im_file,producer_id))
|
|
144
|
+
sys.stdout.flush()
|
|
115
145
|
image = vis_utils.load_image(im_file)
|
|
146
|
+
|
|
147
|
+
if preprocessor is not None:
|
|
148
|
+
|
|
149
|
+
image_info = preprocessor.generate_detections_one_image(
|
|
150
|
+
image,
|
|
151
|
+
im_file,
|
|
152
|
+
detection_threshold=None,
|
|
153
|
+
image_size=image_size,
|
|
154
|
+
skip_image_resizing=False,
|
|
155
|
+
augment=augment,
|
|
156
|
+
preprocess_only=True,
|
|
157
|
+
verbose=verbose)
|
|
158
|
+
if 'failure' in image_info:
|
|
159
|
+
assert image_info['failure'] == run_detector.FAILURE_INFER
|
|
160
|
+
raise
|
|
161
|
+
|
|
162
|
+
image = image_info
|
|
163
|
+
|
|
116
164
|
except Exception:
|
|
117
|
-
print('Producer process: image {} cannot be loaded
|
|
165
|
+
print('Producer process: image {} cannot be loaded'.format(im_file))
|
|
118
166
|
image = run_detector.FAILURE_IMAGE_OPEN
|
|
119
167
|
|
|
120
168
|
if verbose:
|
|
121
|
-
print('Queueing image {}'.format(im_file))
|
|
122
|
-
|
|
169
|
+
print('Queueing image {} from producer {}'.format(im_file,producer_id))
|
|
170
|
+
sys.stdout.flush()
|
|
171
|
+
|
|
172
|
+
q.put([im_file,image,producer_id])
|
|
123
173
|
|
|
174
|
+
# This is a signal to the consumer function that a worker has finished
|
|
124
175
|
q.put(None)
|
|
125
176
|
|
|
126
|
-
|
|
177
|
+
if verbose:
|
|
178
|
+
print('Loader worker {} finished'.format(producer_id))
|
|
179
|
+
sys.stdout.flush()
|
|
180
|
+
|
|
181
|
+
# ...def _producer_func(...)
|
|
127
182
|
|
|
128
183
|
|
|
129
184
|
def _consumer_func(q,
|
|
130
185
|
return_queue,
|
|
131
186
|
model_file,
|
|
132
187
|
confidence_threshold,
|
|
188
|
+
loader_workers,
|
|
133
189
|
image_size=None,
|
|
134
190
|
include_image_size=False,
|
|
135
191
|
include_image_timestamp=False,
|
|
136
192
|
include_exif_data=False,
|
|
137
|
-
augment=False
|
|
193
|
+
augment=False,
|
|
194
|
+
detector_options=None,
|
|
195
|
+
preprocess_on_image_queue=default_preprocess_on_image_queue,
|
|
196
|
+
n_total_images=None
|
|
197
|
+
):
|
|
138
198
|
"""
|
|
139
199
|
Consumer function; only used when using the (optional) image queue.
|
|
140
200
|
|
|
141
|
-
Pulls images from a blocking queue and processes them.
|
|
201
|
+
Pulls images from a blocking queue and processes them. Returns when "None" has
|
|
202
|
+
been read from each loader's queue.
|
|
142
203
|
"""
|
|
143
204
|
|
|
144
205
|
if verbose:
|
|
145
206
|
print('Consumer starting'); sys.stdout.flush()
|
|
146
207
|
|
|
147
208
|
start_time = time.time()
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
209
|
+
|
|
210
|
+
if isinstance(model_file,str):
|
|
211
|
+
detector = load_detector(model_file,detector_options=detector_options,verbose=verbose)
|
|
212
|
+
elapsed = time.time() - start_time
|
|
213
|
+
print('Loaded model (before queueing) in {}, printing updates every {} images'.format(
|
|
214
|
+
humanfriendly.format_timespan(elapsed),n_queue_print))
|
|
215
|
+
sys.stdout.flush()
|
|
216
|
+
else:
|
|
217
|
+
detector = model_file
|
|
218
|
+
print('Detector of type {} passed to consumer function'.format(type(detector)))
|
|
153
219
|
|
|
154
220
|
results = []
|
|
155
221
|
|
|
156
222
|
n_images_processed = 0
|
|
223
|
+
n_queues_finished = 0
|
|
157
224
|
|
|
225
|
+
pbar = None
|
|
226
|
+
if n_total_images is not None:
|
|
227
|
+
# TODO: in principle I should close this pbar
|
|
228
|
+
pbar = tqdm(total=n_total_images)
|
|
229
|
+
|
|
158
230
|
while True:
|
|
231
|
+
|
|
159
232
|
r = q.get()
|
|
233
|
+
|
|
234
|
+
# Is this the last image in one of the producer queues?
|
|
160
235
|
if r is None:
|
|
236
|
+
n_queues_finished += 1
|
|
161
237
|
q.task_done()
|
|
162
|
-
|
|
163
|
-
|
|
238
|
+
if verbose:
|
|
239
|
+
print('Consumer thread: {} of {} queues finished'.format(
|
|
240
|
+
n_queues_finished,loader_workers))
|
|
241
|
+
if n_queues_finished == loader_workers:
|
|
242
|
+
return_queue.put(results)
|
|
243
|
+
return
|
|
244
|
+
else:
|
|
245
|
+
continue
|
|
164
246
|
n_images_processed += 1
|
|
165
247
|
im_file = r[0]
|
|
166
248
|
image = r[1]
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
249
|
+
|
|
250
|
+
"""
|
|
251
|
+
result['img_processed'] = img
|
|
252
|
+
result['img_original'] = img_original
|
|
253
|
+
result['target_shape'] = target_shape
|
|
254
|
+
result['scaling_shape'] = scaling_shape
|
|
255
|
+
result['letterbox_ratio'] = letterbox_ratio
|
|
256
|
+
result['letterbox_pad'] = letterbox_pad
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
if pbar is not None:
|
|
260
|
+
pbar.update(1)
|
|
261
|
+
|
|
262
|
+
if False:
|
|
263
|
+
if verbose or ((n_images_processed % n_queue_print) == 1):
|
|
264
|
+
elapsed = time.time() - start_time
|
|
265
|
+
images_per_second = n_images_processed / elapsed
|
|
266
|
+
print('De-queued image {} ({:.2f}/s) ({})'.format(n_images_processed,
|
|
267
|
+
images_per_second,
|
|
268
|
+
im_file));
|
|
269
|
+
sys.stdout.flush()
|
|
270
|
+
|
|
174
271
|
if isinstance(image,str):
|
|
175
272
|
# This is how the producer function communicates read errors
|
|
176
273
|
results.append({'file': im_file,
|
|
177
274
|
'failure': image})
|
|
275
|
+
elif preprocess_on_image_queue and (not isinstance(image,dict)):
|
|
276
|
+
print('Expected a dict, received an image of type {}'.format(type(image)))
|
|
277
|
+
results.append({'file': im_file,
|
|
278
|
+
'failure': 'illegal image type'})
|
|
279
|
+
|
|
178
280
|
else:
|
|
179
281
|
results.append(process_image(im_file=im_file,
|
|
180
282
|
detector=detector,
|
|
@@ -185,11 +287,16 @@ def _consumer_func(q,
|
|
|
185
287
|
include_image_size=include_image_size,
|
|
186
288
|
include_image_timestamp=include_image_timestamp,
|
|
187
289
|
include_exif_data=include_exif_data,
|
|
188
|
-
augment=augment
|
|
290
|
+
augment=augment,
|
|
291
|
+
skip_image_resizing=preprocess_on_image_queue))
|
|
189
292
|
if verbose:
|
|
190
293
|
print('Processed image {}'.format(im_file)); sys.stdout.flush()
|
|
191
294
|
q.task_done()
|
|
192
295
|
|
|
296
|
+
# ...while True (consumer loop)
|
|
297
|
+
|
|
298
|
+
# ...def _consumer_func(...)
|
|
299
|
+
|
|
193
300
|
|
|
194
301
|
def run_detector_with_image_queue(image_files,
|
|
195
302
|
model_file,
|
|
@@ -199,7 +306,10 @@ def run_detector_with_image_queue(image_files,
|
|
|
199
306
|
include_image_size=False,
|
|
200
307
|
include_image_timestamp=False,
|
|
201
308
|
include_exif_data=False,
|
|
202
|
-
augment=False
|
|
309
|
+
augment=False,
|
|
310
|
+
detector_options=None,
|
|
311
|
+
loader_workers=default_loaders,
|
|
312
|
+
preprocess_on_image_queue=default_preprocess_on_image_queue):
|
|
203
313
|
"""
|
|
204
314
|
Driver function for the (optional) multiprocessing-based image queue; only used
|
|
205
315
|
when --use_image_queue is specified. Starts a reader process to read images from disk, but
|
|
@@ -215,50 +325,93 @@ def run_detector_with_image_queue(image_files,
|
|
|
215
325
|
image_size (tuple, optional): image size to use for inference, only mess with this
|
|
216
326
|
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
217
327
|
doing
|
|
328
|
+
include_image_size (bool, optional): should we include image size in the output for each image?
|
|
329
|
+
include_image_timestamp (bool, optional): should we include image timestamps in the output for each image?
|
|
330
|
+
include_exif_data (bool, optional): should we include EXIF data in the output for each image?
|
|
331
|
+
augment (bool, optional): enable image augmentation
|
|
332
|
+
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
333
|
+
by different detectors
|
|
334
|
+
loader_workers (int, optional): number of loaders to use
|
|
218
335
|
|
|
219
336
|
Returns:
|
|
220
337
|
list: list of dicts in the format returned by process_image()
|
|
221
338
|
"""
|
|
222
339
|
|
|
340
|
+
# Validate inputs
|
|
341
|
+
assert isinstance(model_file,str)
|
|
342
|
+
|
|
343
|
+
if loader_workers <= 0:
|
|
344
|
+
loader_workers = 1
|
|
345
|
+
|
|
223
346
|
q = multiprocessing.JoinableQueue(max_queue_size)
|
|
224
347
|
return_queue = multiprocessing.Queue(1)
|
|
225
348
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
else
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
349
|
+
producers = []
|
|
350
|
+
|
|
351
|
+
worker_string = 'thread' if use_threads_for_queue else 'process'
|
|
352
|
+
print('Starting a {} pool with {} workers'.format(worker_string,loader_workers))
|
|
353
|
+
|
|
354
|
+
preprocessor = None
|
|
355
|
+
|
|
356
|
+
if preprocess_on_image_queue:
|
|
357
|
+
preprocessor = model_file
|
|
358
|
+
|
|
359
|
+
n_total_images = len(image_files)
|
|
360
|
+
|
|
361
|
+
chunks = split_list_into_n_chunks(image_files, loader_workers, chunk_strategy='greedy')
|
|
362
|
+
for i_chunk,chunk in enumerate(chunks):
|
|
363
|
+
if use_threads_for_queue:
|
|
364
|
+
producer = Thread(target=_producer_func,args=(q,
|
|
365
|
+
chunk,
|
|
366
|
+
i_chunk,preprocessor,
|
|
367
|
+
detector_options,
|
|
368
|
+
verbose,
|
|
369
|
+
image_size,
|
|
370
|
+
augment))
|
|
371
|
+
else:
|
|
372
|
+
producer = Process(target=_producer_func,args=(q,
|
|
373
|
+
chunk,
|
|
374
|
+
i_chunk,
|
|
375
|
+
preprocessor,
|
|
376
|
+
detector_options,
|
|
377
|
+
verbose,
|
|
378
|
+
image_size,
|
|
379
|
+
augment))
|
|
380
|
+
producers.append(producer)
|
|
381
|
+
|
|
382
|
+
for producer in producers:
|
|
383
|
+
producer.daemon = False
|
|
384
|
+
producer.start()
|
|
385
|
+
|
|
241
386
|
if run_separate_consumer_process:
|
|
242
387
|
if use_threads_for_queue:
|
|
243
388
|
consumer = Thread(target=_consumer_func,args=(q,
|
|
244
389
|
return_queue,
|
|
245
390
|
model_file,
|
|
246
391
|
confidence_threshold,
|
|
392
|
+
loader_workers,
|
|
247
393
|
image_size,
|
|
248
394
|
include_image_size,
|
|
249
395
|
include_image_timestamp,
|
|
250
396
|
include_exif_data,
|
|
251
|
-
augment
|
|
397
|
+
augment,
|
|
398
|
+
detector_options,
|
|
399
|
+
preprocess_on_image_queue,
|
|
400
|
+
n_total_images))
|
|
252
401
|
else:
|
|
253
402
|
consumer = Process(target=_consumer_func,args=(q,
|
|
254
403
|
return_queue,
|
|
255
404
|
model_file,
|
|
256
405
|
confidence_threshold,
|
|
406
|
+
loader_workers,
|
|
257
407
|
image_size,
|
|
258
408
|
include_image_size,
|
|
259
409
|
include_image_timestamp,
|
|
260
410
|
include_exif_data,
|
|
261
|
-
augment
|
|
411
|
+
augment,
|
|
412
|
+
detector_options,
|
|
413
|
+
preprocess_on_image_queue,
|
|
414
|
+
n_total_images))
|
|
262
415
|
consumer.daemon = True
|
|
263
416
|
consumer.start()
|
|
264
417
|
else:
|
|
@@ -266,26 +419,39 @@ def run_detector_with_image_queue(image_files,
|
|
|
266
419
|
return_queue,
|
|
267
420
|
model_file,
|
|
268
421
|
confidence_threshold,
|
|
422
|
+
loader_workers,
|
|
269
423
|
image_size,
|
|
270
424
|
include_image_size,
|
|
271
425
|
include_image_timestamp,
|
|
272
426
|
include_exif_data,
|
|
273
|
-
augment
|
|
427
|
+
augment,
|
|
428
|
+
detector_options,
|
|
429
|
+
preprocess_on_image_queue,
|
|
430
|
+
n_total_images)
|
|
274
431
|
|
|
275
|
-
producer
|
|
276
|
-
|
|
432
|
+
for i_producer,producer in enumerate(producers):
|
|
433
|
+
producer.join()
|
|
434
|
+
if verbose:
|
|
435
|
+
print('Producer {} finished'.format(i_producer))
|
|
436
|
+
|
|
437
|
+
if verbose:
|
|
438
|
+
print('All producers finished')
|
|
277
439
|
|
|
278
440
|
if run_separate_consumer_process:
|
|
279
441
|
consumer.join()
|
|
280
|
-
|
|
442
|
+
if verbose:
|
|
443
|
+
print('Consumer loop finished')
|
|
281
444
|
|
|
282
445
|
q.join()
|
|
283
|
-
|
|
446
|
+
if verbose:
|
|
447
|
+
print('Queue joined')
|
|
284
448
|
|
|
285
449
|
results = return_queue.get()
|
|
286
450
|
|
|
287
451
|
return results
|
|
288
452
|
|
|
453
|
+
# ...def run_detector_with_image_queue(...)
|
|
454
|
+
|
|
289
455
|
|
|
290
456
|
#%% Other support functions
|
|
291
457
|
|
|
@@ -316,7 +482,10 @@ def process_images(im_files,
|
|
|
316
482
|
include_image_size=False,
|
|
317
483
|
include_image_timestamp=False,
|
|
318
484
|
include_exif_data=False,
|
|
319
|
-
augment=False
|
|
485
|
+
augment=False,
|
|
486
|
+
detector_options=None,
|
|
487
|
+
loader_workers=default_loaders,
|
|
488
|
+
preprocess_on_image_queue=default_preprocess_on_image_queue):
|
|
320
489
|
"""
|
|
321
490
|
Runs a detector (typically MegaDetector) over a list of image files on a single thread.
|
|
322
491
|
|
|
@@ -335,6 +504,9 @@ def process_images(im_files,
|
|
|
335
504
|
include_image_timestamp (bool, optional): should we include image timestamps in the output for each image?
|
|
336
505
|
include_exif_data (bool, optional): should we include EXIF data in the output for each image?
|
|
337
506
|
augment (bool, optional): enable image augmentation
|
|
507
|
+
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
508
|
+
by different detectors
|
|
509
|
+
loader_workers (int, optional): number of loaders to use (only relevant when using image queue)
|
|
338
510
|
|
|
339
511
|
Returns:
|
|
340
512
|
list: list of dicts, in which each dict represents detections on one image,
|
|
@@ -344,7 +516,7 @@ def process_images(im_files,
|
|
|
344
516
|
if isinstance(detector, str):
|
|
345
517
|
|
|
346
518
|
start_time = time.time()
|
|
347
|
-
detector = load_detector(detector)
|
|
519
|
+
detector = load_detector(detector,detector_options=detector_options,verbose=verbose)
|
|
348
520
|
elapsed = time.time() - start_time
|
|
349
521
|
print('Loaded model (batch level) in {}'.format(humanfriendly.format_timespan(elapsed)))
|
|
350
522
|
|
|
@@ -358,7 +530,10 @@ def process_images(im_files,
|
|
|
358
530
|
include_image_size=include_image_size,
|
|
359
531
|
include_image_timestamp=include_image_timestamp,
|
|
360
532
|
include_exif_data=include_exif_data,
|
|
361
|
-
augment=augment
|
|
533
|
+
augment=augment,
|
|
534
|
+
detector_options=detector_options,
|
|
535
|
+
loader_workers=loader_workers,
|
|
536
|
+
preprocess_on_image_queue=preprocess_on_image_queue)
|
|
362
537
|
|
|
363
538
|
else:
|
|
364
539
|
|
|
@@ -383,7 +558,8 @@ def process_images(im_files,
|
|
|
383
558
|
# ...def process_images(...)
|
|
384
559
|
|
|
385
560
|
|
|
386
|
-
def process_image(im_file,
|
|
561
|
+
def process_image(im_file,
|
|
562
|
+
detector,
|
|
387
563
|
confidence_threshold,
|
|
388
564
|
image=None,
|
|
389
565
|
quiet=False,
|
|
@@ -435,6 +611,7 @@ def process_image(im_file, detector,
|
|
|
435
611
|
return result
|
|
436
612
|
|
|
437
613
|
try:
|
|
614
|
+
|
|
438
615
|
result = detector.generate_detections_one_image(
|
|
439
616
|
image,
|
|
440
617
|
im_file,
|
|
@@ -451,7 +628,10 @@ def process_image(im_file, detector,
|
|
|
451
628
|
}
|
|
452
629
|
return result
|
|
453
630
|
|
|
454
|
-
if
|
|
631
|
+
if isinstance(image,dict):
|
|
632
|
+
image = image['img_original_pil']
|
|
633
|
+
|
|
634
|
+
if include_image_size:
|
|
455
635
|
result['width'] = image.width
|
|
456
636
|
result['height'] = image.height
|
|
457
637
|
|
|
@@ -511,7 +691,10 @@ def load_and_run_detector_batch(model_file,
|
|
|
511
691
|
include_image_timestamp=False,
|
|
512
692
|
include_exif_data=False,
|
|
513
693
|
augment=False,
|
|
514
|
-
force_model_download=False
|
|
694
|
+
force_model_download=False,
|
|
695
|
+
detector_options=None,
|
|
696
|
+
loader_workers=default_loaders,
|
|
697
|
+
preprocess_on_image_queue=default_preprocess_on_image_queue):
|
|
515
698
|
"""
|
|
516
699
|
Load a model file and run it on a list of images.
|
|
517
700
|
|
|
@@ -543,6 +726,9 @@ def load_and_run_detector_batch(model_file,
|
|
|
543
726
|
force_model_download (bool, optional): force downloading the model file if
|
|
544
727
|
a named model (e.g. "MDV5A") is supplied, even if the local file already
|
|
545
728
|
exists
|
|
729
|
+
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
730
|
+
by different detectors
|
|
731
|
+
loader_workers (int, optional): number of loaders to use, only relevant when use_image_queue is True
|
|
546
732
|
|
|
547
733
|
Returns:
|
|
548
734
|
results: list of dicts; each dict represents detections on one image
|
|
@@ -634,13 +820,16 @@ def load_and_run_detector_batch(model_file,
|
|
|
634
820
|
include_image_size=include_image_size,
|
|
635
821
|
include_image_timestamp=include_image_timestamp,
|
|
636
822
|
include_exif_data=include_exif_data,
|
|
637
|
-
augment=augment
|
|
823
|
+
augment=augment,
|
|
824
|
+
detector_options=detector_options,
|
|
825
|
+
loader_workers=loader_workers,
|
|
826
|
+
preprocess_on_image_queue=preprocess_on_image_queue)
|
|
638
827
|
|
|
639
828
|
elif n_cores <= 1:
|
|
640
829
|
|
|
641
830
|
# Load the detector
|
|
642
831
|
start_time = time.time()
|
|
643
|
-
detector = load_detector(model_file)
|
|
832
|
+
detector = load_detector(model_file,detector_options=detector_options,verbose=verbose)
|
|
644
833
|
elapsed = time.time() - start_time
|
|
645
834
|
print('Loaded model in {}'.format(humanfriendly.format_timespan(elapsed)))
|
|
646
835
|
|
|
@@ -723,7 +912,8 @@ def load_and_run_detector_batch(model_file,
|
|
|
723
912
|
include_image_size=include_image_size,
|
|
724
913
|
include_image_timestamp=include_image_timestamp,
|
|
725
914
|
include_exif_data=include_exif_data,
|
|
726
|
-
augment=augment
|
|
915
|
+
augment=augment,
|
|
916
|
+
detector_options=detector_options),
|
|
727
917
|
image_batches)
|
|
728
918
|
|
|
729
919
|
checkpoint_queue.put(None)
|
|
@@ -742,7 +932,8 @@ def load_and_run_detector_batch(model_file,
|
|
|
742
932
|
include_image_size=include_image_size,
|
|
743
933
|
include_image_timestamp=include_image_timestamp,
|
|
744
934
|
include_exif_data=include_exif_data,
|
|
745
|
-
augment=augment
|
|
935
|
+
augment=augment,
|
|
936
|
+
detector_options=detector_options),
|
|
746
937
|
image_batches)
|
|
747
938
|
|
|
748
939
|
new_results = list(itertools.chain.from_iterable(new_results))
|
|
@@ -889,7 +1080,7 @@ def write_results_to_file(results,
|
|
|
889
1080
|
if info is None:
|
|
890
1081
|
|
|
891
1082
|
info = {
|
|
892
|
-
'detection_completion_time': datetime.
|
|
1083
|
+
'detection_completion_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
893
1084
|
'format_version': '1.4'
|
|
894
1085
|
}
|
|
895
1086
|
|
|
@@ -919,6 +1110,15 @@ def write_results_to_file(results,
|
|
|
919
1110
|
for im in results:
|
|
920
1111
|
if 'max_detection_conf' in im:
|
|
921
1112
|
del im['max_detection_conf']
|
|
1113
|
+
|
|
1114
|
+
# Sort results by filename; not required by the format, but convenient for consistency
|
|
1115
|
+
results = sort_list_of_dicts_by_key(results,'file')
|
|
1116
|
+
|
|
1117
|
+
# Sort detections in descending order by confidence; not required by the format, but
|
|
1118
|
+
# convenient for consistency
|
|
1119
|
+
for r in results:
|
|
1120
|
+
if ('detections' in r) and (r['detections'] is not None):
|
|
1121
|
+
r['detections'] = sort_list_of_dicts_by_key(r['detections'], 'conf', reverse=True)
|
|
922
1122
|
|
|
923
1123
|
final_output = {
|
|
924
1124
|
'images': results,
|
|
@@ -1070,6 +1270,10 @@ def main():
|
|
|
1070
1270
|
'--quiet',
|
|
1071
1271
|
action='store_true',
|
|
1072
1272
|
help='Suppress per-image console output')
|
|
1273
|
+
parser.add_argument(
|
|
1274
|
+
'--verbose',
|
|
1275
|
+
action='store_true',
|
|
1276
|
+
help='Enable additional debug output')
|
|
1073
1277
|
parser.add_argument(
|
|
1074
1278
|
'--image_size',
|
|
1075
1279
|
type=int,
|
|
@@ -1085,6 +1289,14 @@ def main():
|
|
|
1085
1289
|
action='store_true',
|
|
1086
1290
|
help='Pre-load images, may help keep your GPU busy; does not currently support ' + \
|
|
1087
1291
|
'checkpointing. Useful if you have a very fast GPU and a very slow disk.')
|
|
1292
|
+
parser.add_argument(
|
|
1293
|
+
'--preprocess_on_image_queue',
|
|
1294
|
+
action='store_true',
|
|
1295
|
+
help='Whether to do image resizing on the image queue (PyTorch detectors only)')
|
|
1296
|
+
parser.add_argument(
|
|
1297
|
+
'--use_threads_for_queue',
|
|
1298
|
+
action='store_true',
|
|
1299
|
+
help='Use threads (rather than processes) for the image queue; only relevant if --use_image_queue is set')
|
|
1088
1300
|
parser.add_argument(
|
|
1089
1301
|
'--threshold',
|
|
1090
1302
|
type=float,
|
|
@@ -1120,7 +1332,12 @@ def main():
|
|
|
1120
1332
|
'--ncores',
|
|
1121
1333
|
type=int,
|
|
1122
1334
|
default=0,
|
|
1123
|
-
help='Number of cores to use; only applies to CPU-based inference')
|
|
1335
|
+
help='Number of cores to use for inference; only applies to CPU-based inference')
|
|
1336
|
+
parser.add_argument(
|
|
1337
|
+
'--loader_workers',
|
|
1338
|
+
type=int,
|
|
1339
|
+
default=default_loaders,
|
|
1340
|
+
help='Number of image loader workers to use; only relevant when --use_image_queue is set')
|
|
1124
1341
|
parser.add_argument(
|
|
1125
1342
|
'--class_mapping_filename',
|
|
1126
1343
|
type=str,
|
|
@@ -1162,13 +1379,29 @@ def main():
|
|
|
1162
1379
|
'file will be transferred to the output file without reprocessing those images. Useful ' +\
|
|
1163
1380
|
'for "updating" a set of results when you may have added new images to a folder you\'ve ' +\
|
|
1164
1381
|
'already processed. Only supported when using relative paths.'))
|
|
1382
|
+
parser.add_argument(
|
|
1383
|
+
'--detector_options',
|
|
1384
|
+
nargs='*',
|
|
1385
|
+
metavar='KEY=VALUE',
|
|
1386
|
+
default='',
|
|
1387
|
+
help='Detector-specific options, as a space-separated list of key-value pairs')
|
|
1165
1388
|
|
|
1166
1389
|
if len(sys.argv[1:]) == 0:
|
|
1167
1390
|
parser.print_help()
|
|
1168
1391
|
parser.exit()
|
|
1169
1392
|
|
|
1170
1393
|
args = parser.parse_args()
|
|
1171
|
-
|
|
1394
|
+
|
|
1395
|
+
global verbose
|
|
1396
|
+
global use_threads_for_queue
|
|
1397
|
+
|
|
1398
|
+
if args.verbose:
|
|
1399
|
+
verbose = True
|
|
1400
|
+
if args.use_threads_for_queue:
|
|
1401
|
+
use_threads_for_queue = True
|
|
1402
|
+
|
|
1403
|
+
detector_options = parse_kvp_list(args.detector_options)
|
|
1404
|
+
|
|
1172
1405
|
# If the specified detector file is really the name of a known model, find
|
|
1173
1406
|
# (and possibly download) that model
|
|
1174
1407
|
args.detector_file = try_download_known_detector(args.detector_file,
|
|
@@ -1346,7 +1579,7 @@ def main():
|
|
|
1346
1579
|
else:
|
|
1347
1580
|
checkpoint_path = os.path.join(output_dir,
|
|
1348
1581
|
'md_checkpoint_{}.json'.format(
|
|
1349
|
-
datetime.
|
|
1582
|
+
datetime.now().strftime("%Y%m%d%H%M%S")))
|
|
1350
1583
|
|
|
1351
1584
|
# Don't overwrite existing checkpoint files, this is a sure-fire way to eventually
|
|
1352
1585
|
# erase someone's checkpoint.
|
|
@@ -1395,7 +1628,10 @@ def main():
|
|
|
1395
1628
|
include_exif_data=args.include_exif_data,
|
|
1396
1629
|
augment=args.augment,
|
|
1397
1630
|
# Don't download the model *again*
|
|
1398
|
-
force_model_download=False
|
|
1631
|
+
force_model_download=False,
|
|
1632
|
+
detector_options=detector_options,
|
|
1633
|
+
loader_workers=args.loader_workers,
|
|
1634
|
+
preprocess_on_image_queue=args.preprocess_on_image_queue)
|
|
1399
1635
|
|
|
1400
1636
|
elapsed = time.time() - start_time
|
|
1401
1637
|
images_per_second = len(results) / elapsed
|