megadetector 5.0.15__py3-none-any.whl → 5.0.16__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 (29) hide show
  1. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +387 -0
  2. megadetector/data_management/lila/generate_lila_per_image_labels.py +3 -3
  3. megadetector/data_management/lila/test_lila_metadata_urls.py +2 -2
  4. megadetector/data_management/remove_exif.py +61 -36
  5. megadetector/data_management/yolo_to_coco.py +25 -6
  6. megadetector/detection/process_video.py +259 -126
  7. megadetector/detection/pytorch_detector.py +13 -11
  8. megadetector/detection/run_detector.py +9 -2
  9. megadetector/detection/run_detector_batch.py +7 -0
  10. megadetector/detection/run_inference_with_yolov5_val.py +58 -10
  11. megadetector/detection/tf_detector.py +8 -2
  12. megadetector/detection/video_utils.py +201 -16
  13. megadetector/postprocessing/md_to_coco.py +31 -9
  14. megadetector/postprocessing/postprocess_batch_results.py +19 -3
  15. megadetector/postprocessing/subset_json_detector_output.py +22 -12
  16. megadetector/taxonomy_mapping/map_new_lila_datasets.py +3 -3
  17. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +2 -1
  18. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +1 -1
  19. megadetector/taxonomy_mapping/simple_image_download.py +5 -0
  20. megadetector/taxonomy_mapping/species_lookup.py +1 -1
  21. megadetector/utils/md_tests.py +196 -49
  22. megadetector/utils/path_utils.py +2 -2
  23. megadetector/utils/url_utils.py +7 -1
  24. megadetector/visualization/visualize_db.py +16 -0
  25. {megadetector-5.0.15.dist-info → megadetector-5.0.16.dist-info}/LICENSE +0 -0
  26. {megadetector-5.0.15.dist-info → megadetector-5.0.16.dist-info}/METADATA +2 -2
  27. {megadetector-5.0.15.dist-info → megadetector-5.0.16.dist-info}/RECORD +29 -28
  28. {megadetector-5.0.15.dist-info → megadetector-5.0.16.dist-info}/WHEEL +1 -1
  29. {megadetector-5.0.15.dist-info → megadetector-5.0.16.dist-info}/top_level.txt +0 -0
@@ -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,117 @@ 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)
347
+ frame_output_folder = None
328
348
 
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
-
358
-
359
- ## Run MegaDetector
360
-
361
- if options.reuse_results_if_available and \
362
- os.path.isfile(options.output_json_file):
349
+ # If we should re-use existing results, and the output file exists, don't bother running MD
350
+ if (options.reuse_results_if_available and os.path.isfile(options.output_json_file)):
351
+
363
352
  print('Loading results from {}'.format(options.output_json_file))
364
353
  with open(options.output_json_file,'r') as f:
365
354
  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)
355
+
356
+ # Run MD in memory if we don't need to generate frames
357
+ #
358
+ # Currently if we're generating an output video, we need to generate frames on disk first.
359
+ elif (not options.keep_extracted_frames and \
360
+ not options.render_output_video and \
361
+ not options.force_on_disk_frame_extraction):
362
+
363
+ # Run MegaDetector in memory
364
+
365
+ if options.verbose:
366
+ print('Running MegaDetector in memory for {}'.format(options.input_video_file))
367
+
368
+ if options.frame_folder is not None:
369
+ print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
370
+ 'not; no raw frames will be written')
371
+
372
+ detector = load_detector(options.model_file)
373
+
374
+ def frame_callback(image_np,image_id):
375
+ return detector.generate_detections_one_image(image_np,
376
+ image_id,
377
+ detection_threshold=options.json_confidence_threshold,
378
+ augment=options.augment)
379
+
380
+ frame_results = run_callback_on_frames(options.input_video_file,
381
+ frame_callback,
382
+ every_n_frames=options.frame_sample,
383
+ verbose=options.verbose,
384
+ frames_to_process=options.frames_to_extract)
385
+
386
+ _add_frame_numbers_to_results(frame_results['results'])
378
387
 
379
388
  run_detector_batch.write_results_to_file(
380
- results, options.output_json_file,
381
- relative_path_base=frame_output_folder,
389
+ frame_results['results'],
390
+ options.output_json_file,
391
+ relative_path_base=None,
382
392
  detector_file=options.model_file,
383
- custom_metadata={'video_frame_rate':Fs})
393
+ custom_metadata={'video_frame_rate':frame_results['frame_rate']})
384
394
 
395
+ # Extract frames and optionally run MegaDetector on those frames
396
+ else:
397
+
398
+ if options.verbose:
399
+ print('Extracting frames for {}'.format(options.input_video_file))
400
+
401
+ # This does not create any folders, just defines temporary folder names in
402
+ # case we need them.
403
+ temporary_folder_info = _select_temporary_output_folders(options)
404
+
405
+ if (caller_provided_frame_output_folder):
406
+ frame_output_folder = options.frame_folder
407
+ else:
408
+ frame_output_folder = temporary_folder_info['frame_output_folder']
409
+
410
+ os.makedirs(frame_output_folder, exist_ok=True)
411
+
412
+
413
+ ## Extract frames
414
+
415
+ frame_filenames, Fs = video_to_frames(
416
+ options.input_video_file,
417
+ frame_output_folder,
418
+ every_n_frames=options.frame_sample,
419
+ overwrite=(not options.reuse_frames_if_available),
420
+ quality=options.quality,
421
+ max_width=options.max_width,
422
+ verbose=options.verbose,
423
+ frames_to_extract=options.frames_to_extract,
424
+ allow_empty_videos=options.allow_empty_videos)
425
+
426
+ image_file_names = frame_filenames
427
+ if options.debug_max_frames > 0:
428
+ image_file_names = image_file_names[0:options.debug_max_frames]
429
+
430
+ ## Run MegaDetector on those frames
431
+
432
+ if options.model_file != 'no_detection':
433
+
434
+ if options.verbose:
435
+ print('Running MD for {}'.format(options.input_video_file))
436
+
437
+ results = run_detector_batch.load_and_run_detector_batch(
438
+ options.model_file,
439
+ image_file_names,
440
+ confidence_threshold=options.json_confidence_threshold,
441
+ n_cores=options.n_cores,
442
+ class_mapping_filename=options.class_mapping_filename,
443
+ quiet=True,
444
+ augment=options.augment,
445
+ image_size=options.image_size)
446
+
447
+ _add_frame_numbers_to_results(results)
448
+
449
+ run_detector_batch.write_results_to_file(
450
+ results,
451
+ options.output_json_file,
452
+ relative_path_base=frame_output_folder,
453
+ detector_file=options.model_file,
454
+ custom_metadata={'video_frame_rate':Fs})
455
+
456
+ # ...if we are/aren't keeping raw frames on disk
457
+
385
458
 
386
459
  ## (Optionally) render output video
387
460
 
@@ -470,76 +543,127 @@ def process_video_folder(options):
470
543
  # case we need them.
471
544
  temporary_folder_info = _select_temporary_output_folders(options)
472
545
 
546
+ frame_output_folder = None
547
+ image_file_names = None
473
548
 
474
- ## Split every video into frames
549
+ # Run MD in memory if we don't need to generate frames
550
+ #
551
+ # Currently if we're generating an output video, we need to generate frames on disk first.
552
+ if (not options.keep_extracted_frames and \
553
+ not options.render_output_video and \
554
+ not options.force_on_disk_frame_extraction):
555
+
556
+ if options.verbose:
557
+ print('Running MegaDetector in memory for folder {}'.format(options.input_video_file))
558
+
559
+ if options.frame_folder is not None:
560
+ print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
561
+ 'not; no raw frames will be written')
562
+
563
+ detector = load_detector(options.model_file)
564
+
565
+ def frame_callback(image_np,image_id):
566
+ return detector.generate_detections_one_image(image_np,
567
+ image_id,
568
+ detection_threshold=options.json_confidence_threshold,
569
+ augment=options.augment)
570
+
571
+ md_results = run_callback_on_frames_for_folder(input_video_folder=options.input_video_file,
572
+ frame_callback=frame_callback,
573
+ every_n_frames=options.frame_sample,
574
+ verbose=options.verbose)
575
+
576
+ video_results = md_results['results']
577
+
578
+ all_frame_results = []
579
+
580
+ # r = video_results[0]
581
+ for frame_results in video_results:
582
+ _add_frame_numbers_to_results(frame_results)
583
+ all_frame_results.extend(frame_results)
584
+
585
+ run_detector_batch.write_results_to_file(
586
+ all_frame_results,
587
+ frames_json,
588
+ relative_path_base=None,
589
+ detector_file=options.model_file,
590
+ custom_metadata={'video_frame_rate':md_results['frame_rates']})
475
591
 
476
- if caller_provided_frame_output_folder:
477
- frame_output_folder = options.frame_folder
478
592
  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))
503
- 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]
510
593
 
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
594
+ ## Split every video into frames
595
+
596
+ if caller_provided_frame_output_folder:
597
+ frame_output_folder = options.frame_folder
598
+ else:
599
+ frame_output_folder = temporary_folder_info['frame_output_folder']
600
+
601
+ os.makedirs(frame_output_folder, exist_ok=True)
518
602
 
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)
603
+ print('Extracting frames')
536
604
 
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})
605
+ frame_filenames, Fs, video_filenames = \
606
+ video_folder_to_frames(input_folder=options.input_video_file,
607
+ output_folder_base=frame_output_folder,
608
+ recursive=options.recursive,
609
+ overwrite=(not options.reuse_frames_if_available),
610
+ n_threads=options.n_cores,
611
+ every_n_frames=options.frame_sample,
612
+ verbose=options.verbose,
613
+ quality=options.quality,
614
+ max_width=options.max_width,
615
+ frames_to_extract=options.frames_to_extract,
616
+ allow_empty_videos=options.allow_empty_videos)
617
+
618
+ print('Extracted frames for {} videos'.format(len(set(video_filenames))))
619
+ image_file_names = list(itertools.chain.from_iterable(frame_filenames))
620
+
621
+ if len(image_file_names) == 0:
622
+ if len(video_filenames) == 0:
623
+ print('No videos found in folder {}'.format(options.input_video_file))
624
+ else:
625
+ print('No frames extracted from folder {}, this may be due to an '\
626
+ 'unsupported video codec'.format(options.input_video_file))
627
+ return
542
628
 
629
+ if options.debug_max_frames is not None and options.debug_max_frames > 0:
630
+ image_file_names = image_file_names[0:options.debug_max_frames]
631
+
632
+ if options.model_file == 'no_detection':
633
+ assert options.keep_extracted_frames, \
634
+ 'Internal error: keep_extracted_frames not set, but no model specified'
635
+ return
636
+
637
+
638
+ ## Run MegaDetector on the extracted frames
639
+
640
+ if options.reuse_results_if_available and \
641
+ os.path.isfile(frames_json):
642
+ print('Bypassing inference, loading results from {}'.format(frames_json))
643
+ with open(frames_json,'r') as f:
644
+ results = json.load(f)
645
+ else:
646
+ print('Running MegaDetector')
647
+ results = run_detector_batch.load_and_run_detector_batch(
648
+ options.model_file,
649
+ image_file_names,
650
+ confidence_threshold=options.json_confidence_threshold,
651
+ n_cores=options.n_cores,
652
+ class_mapping_filename=options.class_mapping_filename,
653
+ quiet=True,
654
+ augment=options.augment,
655
+ image_size=options.image_size)
656
+
657
+ _add_frame_numbers_to_results(results)
658
+
659
+ run_detector_batch.write_results_to_file(
660
+ results,
661
+ frames_json,
662
+ relative_path_base=frame_output_folder,
663
+ detector_file=options.model_file,
664
+ custom_metadata={'video_frame_rate':Fs})
665
+
666
+ # ...if we're running MD on in-memory frames vs. extracting frames to disk
543
667
 
544
668
  ## Convert frame-level results to video-level results
545
669
 
@@ -646,13 +770,13 @@ def process_video_folder(options):
646
770
 
647
771
  def options_to_command(options):
648
772
  """
649
- Convert a ProcessVideoOptions obejct to a corresponding command line.
773
+ Convert a ProcessVideoOptions object to a corresponding command line.
650
774
 
651
775
  Args:
652
776
  options (ProcessVideoOptions): the options set to render as a command line
653
777
 
654
778
  Returns:
655
- str: the command line coresponding to [options]
779
+ str: the command line corresponding to [options]
656
780
 
657
781
  :meta private:
658
782
  """
@@ -725,8 +849,8 @@ if False:
725
849
  #%% Process a folder of videos
726
850
 
727
851
  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'
852
+ input_dir = r'g:\temp\test-videos'
853
+ # input_dir = r'G:\temp\md-test-package\md-test-images\video-samples'
730
854
  output_base = r'g:\temp\video_test'
731
855
  frame_folder = os.path.join(output_base,'frames')
732
856
  rendering_folder = os.path.join(output_base,'rendered-frames')
@@ -744,25 +868,26 @@ if False:
744
868
  options.recursive = True
745
869
  options.reuse_frames_if_available = False
746
870
  options.reuse_results_if_available = False
747
- options.quality = 90
871
+ options.quality = None # 90
748
872
  options.frame_sample = 10
749
- options.max_width = 1280
873
+ options.max_width = None # 1280
750
874
  options.n_cores = 4
751
875
  options.verbose = True
752
- options.render_output_video = True
876
+ options.render_output_video = False
753
877
  options.frame_folder = frame_folder
754
878
  options.frame_rendering_folder = rendering_folder
755
- options.keep_extracted_frames = True
756
- options.keep_rendered_frames = True
879
+ options.keep_extracted_frames = False
880
+ options.keep_rendered_frames = False
757
881
  options.force_extracted_frame_folder_deletion = False
758
882
  options.force_rendered_frame_folder_deletion = False
759
883
  options.fourcc = 'mp4v'
884
+ options.force_on_disk_frame_extraction = True
760
885
  # options.rendering_confidence_threshold = 0.15
761
886
 
762
887
  cmd = options_to_command(options); print(cmd)
763
888
 
764
889
  # import clipboard; clipboard.copy(cmd)
765
- # process_video_folder(options)
890
+ process_video_folder(options)
766
891
 
767
892
 
768
893
  #%% Process a single video
@@ -988,6 +1113,10 @@ def main():
988
1113
  parser.add_argument('--augment',
989
1114
  action='store_true',
990
1115
  help='Enable image augmentation')
1116
+
1117
+ parser.add_argument('--allow_empty_videos',
1118
+ action='store_true',
1119
+ help='By default, videos with no retrievable frames cause an error, this makes it a warning')
991
1120
 
992
1121
  if len(sys.argv[1:]) == 0:
993
1122
  parser.print_help()
@@ -1000,6 +1129,10 @@ def main():
1000
1129
  if os.path.isdir(options.input_video_file):
1001
1130
  process_video_folder(options)
1002
1131
  else:
1132
+ assert os.path.isfile(options.input_video_file), \
1133
+ '{} is not a valid file or folder name'.format(options.input_video_file)
1134
+ assert not options.recursive, \
1135
+ '--recursive is only meaningful when processing a folder'
1003
1136
  process_video(options)
1004
1137
 
1005
1138
  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)
@@ -114,6 +114,8 @@ DETECTION_FILENAME_INSERT = '_detections'
114
114
  # automatic model download to the system temp folder, or they will use the paths specified in the
115
115
  # $MDV4, $MDV5A, or $MDV5B environment variables if they exist.
116
116
  downloadable_models = {
117
+ 'MDV2':'https://lila.science/public/models/megadetector/megadetector_v2.pb',
118
+ 'MDV3':'https://lila.science/public/models/megadetector/megadetector_v3.pb',
117
119
  'MDV4':'https://github.com/agentmorris/MegaDetector/releases/download/v4.1/md_v4.1.0.pb',
118
120
  'MDV5A':'https://github.com/agentmorris/MegaDetector/releases/download/v5.0/md_v5a.0.0.pt',
119
121
  'MDV5B':'https://github.com/agentmorris/MegaDetector/releases/download/v5.0/md_v5b.0.0.pt'
@@ -197,7 +199,7 @@ def get_detector_metadata_from_version_string(detector_version):
197
199
  return DETECTOR_METADATA[detector_version]
198
200
 
199
201
 
200
- def get_detector_version_from_filename(detector_filename):
202
+ def get_detector_version_from_filename(detector_filename,accept_first_match=True):
201
203
  r"""
202
204
  Gets the version number component of the detector from the model filename.
203
205
 
@@ -215,6 +217,8 @@ def get_detector_version_from_filename(detector_filename):
215
217
 
216
218
  Args:
217
219
  detector_filename (str): model filename, e.g. c:/x/z/md_v5a.0.0.pt
220
+ accept_first_match (bool, optional): if multiple candidates match the filename, choose the
221
+ first one, otherwise returns the string "multiple"
218
222
 
219
223
  Returns:
220
224
  str: a detector version string, e.g. "v5a.0.0", or "multiple" if I'm confused
@@ -230,7 +234,10 @@ def get_detector_version_from_filename(detector_filename):
230
234
  return 'unknown'
231
235
  elif len(matches) > 1:
232
236
  print('Warning: multiple MegaDetector versions for model file {}'.format(detector_filename))
233
- return 'multiple'
237
+ if accept_first_match:
238
+ return model_string_to_model_version[matches[0]]
239
+ else:
240
+ return 'multiple'
234
241
  else:
235
242
  return model_string_to_model_version[matches[0]]
236
243
 
@@ -923,6 +923,13 @@ def write_results_to_file(results,
923
923
  'info': info
924
924
  }
925
925
 
926
+ # Create the folder where the output file belongs; this will fail if
927
+ # this is a relative path with no folder component
928
+ try:
929
+ os.makedirs(os.path.dirname(output_file),exist_ok=True)
930
+ except Exception:
931
+ pass
932
+
926
933
  with open(output_file, 'w') as f:
927
934
  json.dump(final_output, f, indent=1, default=str)
928
935
  print('Output file saved at {}'.format(output_file))