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.

Files changed (42) hide show
  1. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +2 -3
  2. megadetector/classification/merge_classification_detection_output.py +2 -2
  3. megadetector/data_management/coco_to_labelme.py +2 -1
  4. megadetector/data_management/databases/integrity_check_json_db.py +15 -14
  5. megadetector/data_management/databases/subset_json_db.py +49 -21
  6. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +73 -69
  7. megadetector/data_management/lila/add_locations_to_nacti.py +114 -110
  8. megadetector/data_management/mewc_to_md.py +340 -0
  9. megadetector/data_management/speciesnet_to_md.py +41 -0
  10. megadetector/data_management/yolo_output_to_md_output.py +15 -8
  11. megadetector/detection/process_video.py +24 -7
  12. megadetector/detection/pytorch_detector.py +841 -160
  13. megadetector/detection/run_detector.py +341 -146
  14. megadetector/detection/run_detector_batch.py +307 -70
  15. megadetector/detection/run_inference_with_yolov5_val.py +61 -4
  16. megadetector/detection/tf_detector.py +6 -1
  17. megadetector/postprocessing/{combine_api_outputs.py → combine_batch_outputs.py} +10 -13
  18. megadetector/postprocessing/compare_batch_results.py +236 -7
  19. megadetector/postprocessing/create_crop_folder.py +358 -0
  20. megadetector/postprocessing/md_to_labelme.py +7 -7
  21. megadetector/postprocessing/md_to_wi.py +40 -0
  22. megadetector/postprocessing/merge_detections.py +1 -1
  23. megadetector/postprocessing/postprocess_batch_results.py +12 -5
  24. megadetector/postprocessing/separate_detections_into_folders.py +32 -4
  25. megadetector/postprocessing/validate_batch_results.py +9 -4
  26. megadetector/utils/ct_utils.py +236 -45
  27. megadetector/utils/directory_listing.py +3 -3
  28. megadetector/utils/gpu_test.py +125 -0
  29. megadetector/utils/md_tests.py +455 -116
  30. megadetector/utils/path_utils.py +43 -2
  31. megadetector/utils/wi_utils.py +2691 -0
  32. megadetector/visualization/visualization_utils.py +95 -18
  33. megadetector/visualization/visualize_db.py +25 -7
  34. megadetector/visualization/visualize_detector_output.py +60 -13
  35. {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/METADATA +11 -23
  36. {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/RECORD +39 -36
  37. {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/WHEEL +1 -1
  38. megadetector/detection/detector_training/__init__.py +0 -0
  39. megadetector/detection/detector_training/model_main_tf2.py +0 -114
  40. megadetector/utils/torch_test.py +0 -32
  41. {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/LICENSE +0 -0
  42. {megadetector-5.0.23.dist-info → megadetector-5.0.25.dist-info}/top_level.txt +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
- # Multiprocessing uses processes, not threads... leaving this here (and commented out)
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
- # Number of images to pre-fetch
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,image_files):
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 N images from disk and puts them on the blocking queue for processing.
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'); sys.stdout.flush()
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)); sys.stdout.flush()
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.'.format(im_file))
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)); sys.stdout.flush()
122
- q.put([im_file,image])
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
- print('Finished image loading'); sys.stdout.flush()
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
- detector = load_detector(model_file)
149
- elapsed = time.time() - start_time
150
- print('Loaded model (before queueing) in {}, printing updates every {} images'.format(
151
- humanfriendly.format_timespan(elapsed),n_queue_print))
152
- sys.stdout.flush()
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
- return_queue.put(results)
163
- return
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
- if verbose or ((n_images_processed % n_queue_print) == 1):
168
- elapsed = time.time() - start_time
169
- images_per_second = n_images_processed / elapsed
170
- print('De-queued image {} ({:.2f}/s) ({})'.format(n_images_processed,
171
- images_per_second,
172
- im_file));
173
- sys.stdout.flush()
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
- if use_threads_for_queue:
227
- producer = Thread(target=_producer_func,args=(q,image_files,))
228
- else:
229
- producer = Process(target=_producer_func,args=(q,image_files,))
230
- producer.daemon = False
231
- producer.start()
232
-
233
- # The queue system is a little more elegant if we start one thread for reading and one
234
- # for processing, and this works fine on Windows, but because we import TF at module load,
235
- # CUDA will only work in the main process, so currently the consumer function runs here.
236
- #
237
- # To enable proper multi-GPU support, we may need to move the TF import to a separate module
238
- # that isn't loaded until very close to where inference actually happens.
239
- run_separate_consumer_process = False
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.join()
276
- print('Producer finished')
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
- print('Consumer finished')
442
+ if verbose:
443
+ print('Consumer loop finished')
281
444
 
282
445
  q.join()
283
- print('Queue joined')
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, detector,
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 include_image_size:
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,13 +726,16 @@ 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
549
735
  """
550
736
 
551
737
  # Validate input arguments
552
- if n_cores is None:
738
+ if n_cores is None or n_cores <= 0:
553
739
  n_cores = 1
554
740
 
555
741
  if confidence_threshold is None:
@@ -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))
@@ -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,
@@ -1119,8 +1331,14 @@ def main():
1119
1331
  parser.add_argument(
1120
1332
  '--ncores',
1121
1333
  type=int,
1122
- default=0,
1123
- help='Number of cores to use; only applies to CPU-based inference')
1334
+ default=1,
1335
+ help='Number of cores to use for inference; only applies to CPU-based inference (default 1)')
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 ' + \
1341
+ 'is set (default {})'.format(default_loaders))
1124
1342
  parser.add_argument(
1125
1343
  '--class_mapping_filename',
1126
1344
  type=str,
@@ -1162,13 +1380,29 @@ def main():
1162
1380
  'file will be transferred to the output file without reprocessing those images. Useful ' +\
1163
1381
  'for "updating" a set of results when you may have added new images to a folder you\'ve ' +\
1164
1382
  'already processed. Only supported when using relative paths.'))
1383
+ parser.add_argument(
1384
+ '--detector_options',
1385
+ nargs='*',
1386
+ metavar='KEY=VALUE',
1387
+ default='',
1388
+ help='Detector-specific options, as a space-separated list of key-value pairs')
1165
1389
 
1166
1390
  if len(sys.argv[1:]) == 0:
1167
1391
  parser.print_help()
1168
1392
  parser.exit()
1169
1393
 
1170
1394
  args = parser.parse_args()
1171
-
1395
+
1396
+ global verbose
1397
+ global use_threads_for_queue
1398
+
1399
+ if args.verbose:
1400
+ verbose = True
1401
+ if args.use_threads_for_queue:
1402
+ use_threads_for_queue = True
1403
+
1404
+ detector_options = parse_kvp_list(args.detector_options)
1405
+
1172
1406
  # If the specified detector file is really the name of a known model, find
1173
1407
  # (and possibly download) that model
1174
1408
  args.detector_file = try_download_known_detector(args.detector_file,
@@ -1395,7 +1629,10 @@ def main():
1395
1629
  include_exif_data=args.include_exif_data,
1396
1630
  augment=args.augment,
1397
1631
  # Don't download the model *again*
1398
- force_model_download=False)
1632
+ force_model_download=False,
1633
+ detector_options=detector_options,
1634
+ loader_workers=args.loader_workers,
1635
+ preprocess_on_image_queue=args.preprocess_on_image_queue)
1399
1636
 
1400
1637
  elapsed = time.time() - start_time
1401
1638
  images_per_second = len(results) / elapsed