megadetector 5.0.15__py3-none-any.whl → 5.0.17__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 (34) hide show
  1. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +387 -0
  2. megadetector/data_management/importers/snapshot_safari_importer_reprise.py +28 -16
  3. megadetector/data_management/lila/generate_lila_per_image_labels.py +3 -3
  4. megadetector/data_management/lila/test_lila_metadata_urls.py +2 -2
  5. megadetector/data_management/remove_exif.py +61 -36
  6. megadetector/data_management/yolo_to_coco.py +25 -6
  7. megadetector/detection/process_video.py +270 -127
  8. megadetector/detection/pytorch_detector.py +13 -11
  9. megadetector/detection/run_detector.py +9 -2
  10. megadetector/detection/run_detector_batch.py +8 -1
  11. megadetector/detection/run_inference_with_yolov5_val.py +58 -10
  12. megadetector/detection/tf_detector.py +8 -2
  13. megadetector/detection/video_utils.py +214 -18
  14. megadetector/postprocessing/md_to_coco.py +31 -9
  15. megadetector/postprocessing/postprocess_batch_results.py +23 -7
  16. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +5 -2
  17. megadetector/postprocessing/subset_json_detector_output.py +22 -12
  18. megadetector/taxonomy_mapping/map_new_lila_datasets.py +3 -3
  19. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +2 -1
  20. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +1 -1
  21. megadetector/taxonomy_mapping/simple_image_download.py +5 -0
  22. megadetector/taxonomy_mapping/species_lookup.py +1 -1
  23. megadetector/utils/ct_utils.py +48 -0
  24. megadetector/utils/md_tests.py +231 -56
  25. megadetector/utils/path_utils.py +2 -2
  26. megadetector/utils/torch_test.py +32 -0
  27. megadetector/utils/url_utils.py +101 -4
  28. megadetector/visualization/visualization_utils.py +21 -6
  29. megadetector/visualization/visualize_db.py +16 -0
  30. {megadetector-5.0.15.dist-info → megadetector-5.0.17.dist-info}/LICENSE +0 -0
  31. {megadetector-5.0.15.dist-info → megadetector-5.0.17.dist-info}/METADATA +5 -7
  32. {megadetector-5.0.15.dist-info → megadetector-5.0.17.dist-info}/RECORD +34 -32
  33. {megadetector-5.0.15.dist-info → megadetector-5.0.17.dist-info}/WHEEL +1 -1
  34. {megadetector-5.0.15.dist-info → megadetector-5.0.17.dist-info}/top_level.txt +0 -0
@@ -29,9 +29,9 @@ from megadetector.data_management.yolo_output_to_md_output import read_classes_f
29
29
 
30
30
  def _filename_to_image_id(fn):
31
31
  """
32
- Image IDs can't have spaces in them, replae spaces with underscores
32
+ Image IDs can't have spaces in them, replace spaces with underscores
33
33
  """
34
- return fn.replace(' ','_')
34
+ return fn.replace(' ','_').replace('\\','/')
35
35
 
36
36
 
37
37
  def _process_image(fn_abs,input_folder,category_id_to_name):
@@ -40,7 +40,9 @@ def _process_image(fn_abs,input_folder,category_id_to_name):
40
40
  """
41
41
 
42
42
  # Create the image object for this image
43
- fn_relative = os.path.relpath(fn_abs,input_folder)
43
+ #
44
+ # Always use forward slashes in image filenames and IDs
45
+ fn_relative = os.path.relpath(fn_abs,input_folder).replace('\\','/')
44
46
  image_id = _filename_to_image_id(fn_relative)
45
47
 
46
48
  # This is done in a separate loop now
@@ -51,7 +53,7 @@ def _process_image(fn_abs,input_folder,category_id_to_name):
51
53
  # image_ids.add(image_id)
52
54
 
53
55
  im = {}
54
- im['file_name'] = fn_relative
56
+ im['file_name'] = fn_relative
55
57
  im['id'] = image_id
56
58
 
57
59
  annotations_this_image = []
@@ -393,7 +395,8 @@ def yolo_to_coco(input_folder,
393
395
  pool_type='thread',
394
396
  recursive=True,
395
397
  exclude_string=None,
396
- include_string=None):
398
+ include_string=None,
399
+ overwrite_handling='overwrite'):
397
400
  """
398
401
  Converts a YOLO-formatted dataset to a COCO-formatted dataset.
399
402
 
@@ -427,6 +430,8 @@ def yolo_to_coco(input_folder,
427
430
  recursive (bool, optional): whether to recurse into [input_folder]
428
431
  exclude_string (str, optional): exclude any images whose filename contains a string
429
432
  include_string (str, optional): include only images whose filename contains a string
433
+ overwrite_handling (bool, optional): behavior if output_file exists ('load', 'overwrite', or
434
+ 'error')
430
435
 
431
436
  Returns:
432
437
  dict: COCO-formatted data, the same as what's written to [output_file]
@@ -441,7 +446,21 @@ def yolo_to_coco(input_folder,
441
446
  ('no_annotations','empty_annotations','skip','error'), \
442
447
  'Unrecognized empty image handling spec: {}'.format(empty_image_handling)
443
448
 
444
-
449
+ if (output_file is not None) and os.path.isfile(output_file):
450
+
451
+ if overwrite_handling == 'overwrite':
452
+ print('Warning: output file {} exists, over-writing'.format(output_file))
453
+ elif overwrite_handling == 'load':
454
+ print('Output file {} exists, loading and returning'.format(output_file))
455
+ with open(output_file,'r') as f:
456
+ d = json.load(f)
457
+ return d
458
+ elif overwrite_handling == 'error':
459
+ raise ValueError('Output file {} exists'.format(output_file))
460
+ else:
461
+ raise ValueError('Unrecognized overwrite_handling value: {}'.format(overwrite_handling))
462
+
463
+
445
464
  ## Read class names
446
465
 
447
466
  category_id_to_name = load_yolo_class_list(class_name_file)
@@ -32,11 +32,14 @@ from megadetector.visualization import visualize_detector_output
32
32
  from megadetector.utils.ct_utils import args_to_object
33
33
  from megadetector.utils.path_utils import insert_before_extension, clean_path
34
34
  from megadetector.detection.video_utils import video_to_frames
35
+ from megadetector.detection.video_utils import run_callback_on_frames
36
+ from megadetector.detection.video_utils import run_callback_on_frames_for_folder
35
37
  from megadetector.detection.video_utils import frames_to_video
36
38
  from megadetector.detection.video_utils import frame_results_to_video_results
37
39
  from megadetector.detection.video_utils import _add_frame_numbers_to_results
38
40
  from megadetector.detection.video_utils import video_folder_to_frames
39
41
  from megadetector.detection.video_utils import default_fourcc
42
+ from megadetector.detection.run_detector import load_detector
40
43
 
41
44
 
42
45
  #%% Classes
@@ -76,7 +79,8 @@ class ProcessVideoOptions:
76
79
 
77
80
  #: Should we render a video with detection boxes?
78
81
  #:
79
- #: Only supported when processing a single video, not a folder.
82
+ #: If processing a folder, this renders each input video to a separate
83
+ #: video with detection boxes.
80
84
  self.render_output_video = False
81
85
 
82
86
  #: If we are rendering boxes to a new video, should we keep the temporary
@@ -142,6 +146,10 @@ class ProcessVideoOptions:
142
146
  #: For debugging only, stop processing after a certain number of frames.
143
147
  self.debug_max_frames = -1
144
148
 
149
+ #: For debugging only, force on-disk frame extraction, even if it wouldn't otherwise be
150
+ #: necessary
151
+ self.force_on_disk_frame_extraction = False
152
+
145
153
  #: File containing non-standard categories, typically only used if you're running a non-MD
146
154
  #: detector.
147
155
  self.class_mapping_filename = None
@@ -158,6 +166,11 @@ class ProcessVideoOptions:
158
166
 
159
167
  #: Enable image augmentation
160
168
  self.augment = False
169
+
170
+ #: By default, a video with no frames (or no frames retrievable with the current parameters)
171
+ #: is an error, this makes it a warning. This would apply if you request, e.g., the 100th
172
+ #: frame from each video, but a video only has 50 frames.
173
+ self.allow_empty_videos = False
161
174
 
162
175
  # ...class ProcessVideoOptions
163
176
 
@@ -206,6 +219,9 @@ def _clean_up_rendered_frames(options,rendering_output_folder,detected_frame_fil
206
219
  If necessary, delete rendered frames and/or the entire rendering output folder.
207
220
  """
208
221
 
222
+ if rendering_output_folder is None:
223
+ return
224
+
209
225
  caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
210
226
 
211
227
  # (Optionally) delete the temporary directory we used for rendered detection images
@@ -254,6 +270,9 @@ def _clean_up_extracted_frames(options,frame_output_folder,frame_filenames):
254
270
  If necessary, delete extracted frames and/or the entire temporary frame folder.
255
271
  """
256
272
 
273
+ if frame_output_folder is None:
274
+ return
275
+
257
276
  caller_provided_frame_output_folder = (options.frame_folder is not None)
258
277
 
259
278
  if not options.keep_extracted_frames:
@@ -274,6 +293,9 @@ def _clean_up_extracted_frames(options,frame_output_folder,frame_filenames):
274
293
  # ...otherwise just delete the frames, but leave the folder in place
275
294
  else:
276
295
 
296
+ if frame_filenames is None:
297
+ return
298
+
277
299
  if options.force_extracted_frame_folder_deletion:
278
300
  assert caller_provided_frame_output_folder
279
301
  print('Warning: force_extracted_frame_folder_deletion supplied with a ' + \
@@ -322,66 +344,118 @@ def process_video(options):
322
344
  caller_provided_frame_output_folder = (options.frame_folder is not None)
323
345
  caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
324
346
 
325
- # This does not create any folders, just defines temporary folder names in
326
- # case we need them.
327
- temporary_folder_info = _select_temporary_output_folders(options)
328
-
329
- if (caller_provided_frame_output_folder):
330
- frame_output_folder = options.frame_folder
331
- else:
332
- frame_output_folder = temporary_folder_info['frame_output_folder']
333
-
334
- os.makedirs(frame_output_folder, exist_ok=True)
335
-
336
-
337
- ## Extract frames
338
-
339
- frame_filenames, Fs = video_to_frames(
340
- options.input_video_file,
341
- frame_output_folder,
342
- every_n_frames=options.frame_sample,
343
- overwrite=(not options.reuse_frames_if_available),
344
- quality=options.quality,
345
- max_width=options.max_width,
346
- verbose=options.verbose,
347
- frames_to_extract=options.frames_to_extract)
348
-
349
- image_file_names = frame_filenames
350
- if options.debug_max_frames > 0:
351
- image_file_names = image_file_names[0:options.debug_max_frames]
352
-
353
- if options.model_file == 'no_detection':
354
- assert options.keep_extracted_frames, \
355
- 'Internal error: keep_extracted_frames not set, but no model specified'
356
- return
357
-
347
+ frame_output_folder = None
348
+ frame_filenames = None
358
349
 
359
- ## Run MegaDetector
360
-
361
- if options.reuse_results_if_available and \
362
- os.path.isfile(options.output_json_file):
350
+ # If we should re-use existing results, and the output file exists, don't bother running MD
351
+ if (options.reuse_results_if_available and os.path.isfile(options.output_json_file)):
352
+
363
353
  print('Loading results from {}'.format(options.output_json_file))
364
354
  with open(options.output_json_file,'r') as f:
365
355
  results = json.load(f)
366
- else:
367
- results = run_detector_batch.load_and_run_detector_batch(
368
- options.model_file,
369
- image_file_names,
370
- confidence_threshold=options.json_confidence_threshold,
371
- n_cores=options.n_cores,
372
- class_mapping_filename=options.class_mapping_filename,
373
- quiet=True,
374
- augment=options.augment,
375
- image_size=options.image_size)
376
-
377
- _add_frame_numbers_to_results(results)
356
+
357
+ # Run MD in memory if we don't need to generate frames
358
+ #
359
+ # Currently if we're generating an output video, we need to generate frames on disk first.
360
+ elif (not options.keep_extracted_frames and \
361
+ not options.render_output_video and \
362
+ not options.force_on_disk_frame_extraction):
363
+
364
+ # Run MegaDetector in memory
365
+
366
+ if options.verbose:
367
+ print('Running MegaDetector in memory for {}'.format(options.input_video_file))
368
+
369
+ if options.frame_folder is not None:
370
+ print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
371
+ 'not; no raw frames will be written')
372
+
373
+ detector = load_detector(options.model_file)
374
+
375
+ def frame_callback(image_np,image_id):
376
+ return detector.generate_detections_one_image(image_np,
377
+ image_id,
378
+ detection_threshold=options.json_confidence_threshold,
379
+ augment=options.augment)
380
+
381
+ frame_results = run_callback_on_frames(options.input_video_file,
382
+ frame_callback,
383
+ every_n_frames=options.frame_sample,
384
+ verbose=options.verbose,
385
+ frames_to_process=options.frames_to_extract)
386
+
387
+ frame_results['results'] = _add_frame_numbers_to_results(frame_results['results'])
378
388
 
379
389
  run_detector_batch.write_results_to_file(
380
- results, options.output_json_file,
381
- relative_path_base=frame_output_folder,
390
+ frame_results['results'],
391
+ options.output_json_file,
392
+ relative_path_base=None,
382
393
  detector_file=options.model_file,
383
- custom_metadata={'video_frame_rate':Fs})
394
+ custom_metadata={'video_frame_rate':frame_results['frame_rate']})
384
395
 
396
+ # Extract frames and optionally run MegaDetector on those frames
397
+ else:
398
+
399
+ if options.verbose:
400
+ print('Extracting frames for {}'.format(options.input_video_file))
401
+
402
+ # This does not create any folders, just defines temporary folder names in
403
+ # case we need them.
404
+ temporary_folder_info = _select_temporary_output_folders(options)
405
+
406
+ if (caller_provided_frame_output_folder):
407
+ frame_output_folder = options.frame_folder
408
+ else:
409
+ frame_output_folder = temporary_folder_info['frame_output_folder']
410
+
411
+ os.makedirs(frame_output_folder, exist_ok=True)
412
+
413
+
414
+ ## Extract frames
415
+
416
+ frame_filenames, Fs = video_to_frames(
417
+ options.input_video_file,
418
+ frame_output_folder,
419
+ every_n_frames=options.frame_sample,
420
+ overwrite=(not options.reuse_frames_if_available),
421
+ quality=options.quality,
422
+ max_width=options.max_width,
423
+ verbose=options.verbose,
424
+ frames_to_extract=options.frames_to_extract,
425
+ allow_empty_videos=options.allow_empty_videos)
426
+
427
+ image_file_names = frame_filenames
428
+ if options.debug_max_frames > 0:
429
+ image_file_names = image_file_names[0:options.debug_max_frames]
430
+
431
+ ## Run MegaDetector on those frames
432
+
433
+ if options.model_file != 'no_detection':
434
+
435
+ if options.verbose:
436
+ print('Running MD for {}'.format(options.input_video_file))
437
+
438
+ results = run_detector_batch.load_and_run_detector_batch(
439
+ options.model_file,
440
+ image_file_names,
441
+ confidence_threshold=options.json_confidence_threshold,
442
+ n_cores=options.n_cores,
443
+ class_mapping_filename=options.class_mapping_filename,
444
+ quiet=True,
445
+ augment=options.augment,
446
+ image_size=options.image_size)
447
+
448
+ results = _add_frame_numbers_to_results(results)
449
+
450
+ run_detector_batch.write_results_to_file(
451
+ results,
452
+ options.output_json_file,
453
+ relative_path_base=frame_output_folder,
454
+ detector_file=options.model_file,
455
+ custom_metadata={'video_frame_rate':Fs})
456
+
457
+ # ...if we are/aren't keeping raw frames on disk
458
+
385
459
 
386
460
  ## (Optionally) render output video
387
461
 
@@ -470,81 +544,141 @@ def process_video_folder(options):
470
544
  # case we need them.
471
545
  temporary_folder_info = _select_temporary_output_folders(options)
472
546
 
547
+ frame_output_folder = None
548
+ image_file_names = None
549
+ video_filename_to_fs = {}
473
550
 
474
- ## Split every video into frames
551
+ # Run MD in memory if we don't need to generate frames
552
+ #
553
+ # Currently if we're generating an output video, we need to generate frames on disk first.
554
+ if (not options.keep_extracted_frames and \
555
+ not options.render_output_video and \
556
+ not options.force_on_disk_frame_extraction):
557
+
558
+ if options.verbose:
559
+ print('Running MegaDetector in memory for folder {}'.format(options.input_video_file))
560
+
561
+ if options.frame_folder is not None:
562
+ print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
563
+ 'not; no raw frames will be written')
564
+
565
+ detector = load_detector(options.model_file)
566
+
567
+ def frame_callback(image_np,image_id):
568
+ return detector.generate_detections_one_image(image_np,
569
+ image_id,
570
+ detection_threshold=options.json_confidence_threshold,
571
+ augment=options.augment)
572
+
573
+ md_results = run_callback_on_frames_for_folder(input_video_folder=options.input_video_file,
574
+ frame_callback=frame_callback,
575
+ every_n_frames=options.frame_sample,
576
+ verbose=options.verbose)
577
+
578
+ video_results = md_results['results']
579
+
580
+ for i_video,video_filename in enumerate(md_results['video_filenames']):
581
+ assert video_filename not in video_filename_to_fs
582
+ video_filename_to_fs[video_filename] = md_results['frame_rates'][i_video]
583
+
584
+ all_frame_results = []
585
+
586
+ # r = video_results[0]
587
+ for frame_results in video_results:
588
+ _add_frame_numbers_to_results(frame_results)
589
+ all_frame_results.extend(frame_results)
590
+
591
+ run_detector_batch.write_results_to_file(
592
+ all_frame_results,
593
+ frames_json,
594
+ relative_path_base=None,
595
+ detector_file=options.model_file)
475
596
 
476
- if caller_provided_frame_output_folder:
477
- frame_output_folder = options.frame_folder
478
597
  else:
479
- frame_output_folder = temporary_folder_info['frame_output_folder']
480
-
481
- os.makedirs(frame_output_folder, exist_ok=True)
482
-
483
- print('Extracting frames')
484
-
485
- frame_filenames, Fs, video_filenames = \
486
- video_folder_to_frames(input_folder=options.input_video_file,
487
- output_folder_base=frame_output_folder,
488
- recursive=options.recursive,
489
- overwrite=(not options.reuse_frames_if_available),
490
- n_threads=options.n_cores,
491
- every_n_frames=options.frame_sample,
492
- verbose=options.verbose,
493
- quality=options.quality,
494
- max_width=options.max_width,
495
- frames_to_extract=options.frames_to_extract)
496
-
497
- print('Extracted frames for {} videos'.format(len(set(video_filenames))))
498
- image_file_names = list(itertools.chain.from_iterable(frame_filenames))
499
-
500
- if len(image_file_names) == 0:
501
- if len(video_filenames) == 0:
502
- print('No videos found in folder {}'.format(options.input_video_file))
598
+
599
+ ## Split every video into frames
600
+
601
+ if options.verbose:
602
+ print('Extracting frames for folder {}'.format(options.input_video_file))
603
+
604
+ if caller_provided_frame_output_folder:
605
+ frame_output_folder = options.frame_folder
503
606
  else:
504
- print('No frames extracted from folder {}, this may be due to an '\
505
- 'unsupported video codec'.format(options.input_video_file))
506
- return
507
-
508
- if options.debug_max_frames is not None and options.debug_max_frames > 0:
509
- image_file_names = image_file_names[0:options.debug_max_frames]
607
+ frame_output_folder = temporary_folder_info['frame_output_folder']
608
+
609
+ os.makedirs(frame_output_folder, exist_ok=True)
610
+
611
+ frame_filenames, Fs, video_filenames = \
612
+ video_folder_to_frames(input_folder=options.input_video_file,
613
+ output_folder_base=frame_output_folder,
614
+ recursive=options.recursive,
615
+ overwrite=(not options.reuse_frames_if_available),
616
+ n_threads=options.n_cores,
617
+ every_n_frames=options.frame_sample,
618
+ verbose=options.verbose,
619
+ quality=options.quality,
620
+ max_width=options.max_width,
621
+ frames_to_extract=options.frames_to_extract,
622
+ allow_empty_videos=options.allow_empty_videos)
510
623
 
511
- if options.model_file == 'no_detection':
512
- assert options.keep_extracted_frames, \
513
- 'Internal error: keep_extracted_frames not set, but no model specified'
514
- return
515
-
516
-
517
- ## Run MegaDetector on the extracted frames
518
-
519
- if options.reuse_results_if_available and \
520
- os.path.isfile(frames_json):
521
- print('Bypassing inference, loading results from {}'.format(frames_json))
522
- results = None
523
- else:
524
- print('Running MegaDetector')
525
- results = run_detector_batch.load_and_run_detector_batch(
526
- options.model_file,
527
- image_file_names,
528
- confidence_threshold=options.json_confidence_threshold,
529
- n_cores=options.n_cores,
530
- class_mapping_filename=options.class_mapping_filename,
531
- quiet=True,
532
- augment=options.augment,
533
- image_size=options.image_size)
534
-
535
- _add_frame_numbers_to_results(results)
624
+ for i_video,video_filename in enumerate(video_filenames):
625
+ assert video_filename not in video_filename_to_fs
626
+ video_filename_to_fs[video_filename] = Fs[i_video]
627
+
628
+ print('Extracted frames for {} videos'.format(len(set(video_filenames))))
629
+ image_file_names = list(itertools.chain.from_iterable(frame_filenames))
536
630
 
537
- run_detector_batch.write_results_to_file(
538
- results, frames_json,
539
- relative_path_base=frame_output_folder,
540
- detector_file=options.model_file,
541
- custom_metadata={'video_frame_rate':Fs})
631
+ if len(image_file_names) == 0:
632
+ if len(video_filenames) == 0:
633
+ print('No videos found in folder {}'.format(options.input_video_file))
634
+ else:
635
+ print('No frames extracted from folder {}, this may be due to an '\
636
+ 'unsupported video codec'.format(options.input_video_file))
637
+ return
542
638
 
639
+ if options.debug_max_frames is not None and options.debug_max_frames > 0:
640
+ image_file_names = image_file_names[0:options.debug_max_frames]
641
+
642
+ if options.model_file == 'no_detection':
643
+ assert options.keep_extracted_frames, \
644
+ 'Internal error: keep_extracted_frames not set, but no model specified'
645
+ return
646
+
647
+
648
+ ## Run MegaDetector on the extracted frames
649
+
650
+ if options.reuse_results_if_available and \
651
+ os.path.isfile(frames_json):
652
+ print('Bypassing inference, loading results from {}'.format(frames_json))
653
+ with open(frames_json,'r') as f:
654
+ results = json.load(f)
655
+ else:
656
+ print('Running MegaDetector')
657
+ results = run_detector_batch.load_and_run_detector_batch(
658
+ options.model_file,
659
+ image_file_names,
660
+ confidence_threshold=options.json_confidence_threshold,
661
+ n_cores=options.n_cores,
662
+ class_mapping_filename=options.class_mapping_filename,
663
+ quiet=True,
664
+ augment=options.augment,
665
+ image_size=options.image_size)
666
+
667
+ _add_frame_numbers_to_results(results)
668
+
669
+ run_detector_batch.write_results_to_file(
670
+ results,
671
+ frames_json,
672
+ relative_path_base=frame_output_folder,
673
+ detector_file=options.model_file)
674
+
675
+ # ...if we're running MD on in-memory frames vs. extracting frames to disk
543
676
 
544
677
  ## Convert frame-level results to video-level results
545
678
 
546
679
  print('Converting frame-level results to video-level results')
547
- frame_results_to_video_results(frames_json,video_json)
680
+ frame_results_to_video_results(frames_json,video_json,
681
+ video_filename_to_frame_rate=video_filename_to_fs)
548
682
 
549
683
 
550
684
  ## (Optionally) render output videos
@@ -646,13 +780,13 @@ def process_video_folder(options):
646
780
 
647
781
  def options_to_command(options):
648
782
  """
649
- Convert a ProcessVideoOptions obejct to a corresponding command line.
783
+ Convert a ProcessVideoOptions object to a corresponding command line.
650
784
 
651
785
  Args:
652
786
  options (ProcessVideoOptions): the options set to render as a command line
653
787
 
654
788
  Returns:
655
- str: the command line coresponding to [options]
789
+ str: the command line corresponding to [options]
656
790
 
657
791
  :meta private:
658
792
  """
@@ -725,8 +859,8 @@ if False:
725
859
  #%% Process a folder of videos
726
860
 
727
861
  model_file = 'MDV5A'
728
- # input_dir = r'g:\temp\test-videos'
729
- input_dir = r'G:\temp\md-test-package\md-test-images\video-samples'
862
+ input_dir = r'g:\temp\test-videos'
863
+ # input_dir = r'G:\temp\md-test-package\md-test-images\video-samples'
730
864
  output_base = r'g:\temp\video_test'
731
865
  frame_folder = os.path.join(output_base,'frames')
732
866
  rendering_folder = os.path.join(output_base,'rendered-frames')
@@ -744,25 +878,26 @@ if False:
744
878
  options.recursive = True
745
879
  options.reuse_frames_if_available = False
746
880
  options.reuse_results_if_available = False
747
- options.quality = 90
881
+ options.quality = None # 90
748
882
  options.frame_sample = 10
749
- options.max_width = 1280
883
+ options.max_width = None # 1280
750
884
  options.n_cores = 4
751
885
  options.verbose = True
752
- options.render_output_video = True
886
+ options.render_output_video = False
753
887
  options.frame_folder = frame_folder
754
888
  options.frame_rendering_folder = rendering_folder
755
- options.keep_extracted_frames = True
756
- options.keep_rendered_frames = True
889
+ options.keep_extracted_frames = False
890
+ options.keep_rendered_frames = False
757
891
  options.force_extracted_frame_folder_deletion = False
758
892
  options.force_rendered_frame_folder_deletion = False
759
893
  options.fourcc = 'mp4v'
894
+ options.force_on_disk_frame_extraction = True
760
895
  # options.rendering_confidence_threshold = 0.15
761
896
 
762
897
  cmd = options_to_command(options); print(cmd)
763
898
 
764
899
  # import clipboard; clipboard.copy(cmd)
765
- # process_video_folder(options)
900
+ process_video_folder(options)
766
901
 
767
902
 
768
903
  #%% Process a single video
@@ -988,6 +1123,10 @@ def main():
988
1123
  parser.add_argument('--augment',
989
1124
  action='store_true',
990
1125
  help='Enable image augmentation')
1126
+
1127
+ parser.add_argument('--allow_empty_videos',
1128
+ action='store_true',
1129
+ help='By default, videos with no retrievable frames cause an error, this makes it a warning')
991
1130
 
992
1131
  if len(sys.argv[1:]) == 0:
993
1132
  parser.print_help()
@@ -1000,6 +1139,10 @@ def main():
1000
1139
  if os.path.isdir(options.input_video_file):
1001
1140
  process_video_folder(options)
1002
1141
  else:
1142
+ assert os.path.isfile(options.input_video_file), \
1143
+ '{} is not a valid file or folder name'.format(options.input_video_file)
1144
+ assert not options.recursive, \
1145
+ '--recursive is only meaningful when processing a folder'
1003
1146
  process_video(options)
1004
1147
 
1005
1148
  if __name__ == '__main__':
@@ -2,7 +2,7 @@
2
2
 
3
3
  pytorch_detector.py
4
4
 
5
- Module to run MegaDetector v5, a PyTorch YOLOv5 animal detection model.
5
+ Module to run MegaDetector v5.
6
6
 
7
7
  """
8
8
 
@@ -131,7 +131,7 @@ class PTDetector:
131
131
  try:
132
132
  self.model = PTDetector._load_model(model_path, self.device)
133
133
  except Exception as e:
134
- # In a very estoeric scenario where an old version of YOLOv5 is used to run
134
+ # In a very esoteric scenario where an old version of YOLOv5 is used to run
135
135
  # newer models, we run into an issue because the "Model" class became
136
136
  # "DetectionModel". New YOLOv5 code handles this case by just setting them
137
137
  # to be the same, so doing that via monkey-patch doesn't seem *that* rude.
@@ -180,7 +180,8 @@ class PTDetector:
180
180
 
181
181
  return model
182
182
 
183
- def generate_detections_one_image(self, img_original,
183
+ def generate_detections_one_image(self,
184
+ img_original,
184
185
  image_id='unknown',
185
186
  detection_threshold=0.00001,
186
187
  image_size=None,
@@ -190,7 +191,8 @@ class PTDetector:
190
191
  Applies the detector to an image.
191
192
 
192
193
  Args:
193
- img_original (Image): the PIL Image object with EXIF rotation taken into account
194
+ img_original (Image): the PIL Image object (or numpy array) on which we should run the
195
+ detector, with EXIF rotation already handled.
194
196
  image_id (str, optional): a path to identify the image; will be in the "file" field
195
197
  of the output object
196
198
  detection_threshold (float, optional): only detections above this confidence threshold
@@ -209,20 +211,20 @@ class PTDetector:
209
211
  - 'failure' (a failure string, or None if everything went fine)
210
212
  """
211
213
 
212
- result = {
213
- 'file': image_id
214
- }
214
+ result = {'file': image_id }
215
215
  detections = []
216
216
  max_conf = 0.0
217
217
 
218
218
  if detection_threshold is None:
219
+
219
220
  detection_threshold = 0
220
221
 
221
222
  try:
222
223
 
223
- img_original = np.asarray(img_original)
224
+ if not isinstance(img_original,np.ndarray):
225
+ img_original = np.asarray(img_original)
224
226
 
225
- # padded resize
227
+ # Padded resize
226
228
  target_size = PTDetector.IMAGE_SIZE
227
229
 
228
230
  # Image size can be an int (which translates to a square target size) or (h,w)
@@ -267,9 +269,9 @@ class PTDetector:
267
269
 
268
270
  # NMS
269
271
  if self.device == 'mps':
270
- # As of v1.13.0.dev20220824, nms is not implemented for MPS.
272
+ # As of PyTorch 1.13.0.dev20220824, nms is not implemented for MPS.
271
273
  #
272
- # Send prediction back to the CPU to fix.
274
+ # Send predictions back to the CPU for NMS.
273
275
  pred = non_max_suppression(prediction=pred.cpu(), conf_thres=detection_threshold)
274
276
  else:
275
277
  pred = non_max_suppression(prediction=pred, conf_thres=detection_threshold)