megadetector 5.0.29__py3-none-any.whl → 10.0.0__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 (95) hide show
  1. megadetector/classification/efficientnet/model.py +8 -8
  2. megadetector/classification/efficientnet/utils.py +6 -5
  3. megadetector/classification/prepare_classification_script_mc.py +3 -3
  4. megadetector/data_management/annotations/annotation_constants.py +0 -1
  5. megadetector/data_management/camtrap_dp_to_coco.py +34 -1
  6. megadetector/data_management/cct_json_utils.py +2 -2
  7. megadetector/data_management/coco_to_yolo.py +22 -5
  8. megadetector/data_management/databases/add_width_and_height_to_db.py +85 -12
  9. megadetector/data_management/databases/combine_coco_camera_traps_files.py +2 -2
  10. megadetector/data_management/databases/integrity_check_json_db.py +29 -15
  11. megadetector/data_management/generate_crops_from_cct.py +50 -1
  12. megadetector/data_management/labelme_to_coco.py +4 -2
  13. megadetector/data_management/labelme_to_yolo.py +82 -2
  14. megadetector/data_management/lila/generate_lila_per_image_labels.py +276 -18
  15. megadetector/data_management/lila/get_lila_annotation_counts.py +5 -3
  16. megadetector/data_management/lila/lila_common.py +3 -0
  17. megadetector/data_management/lila/test_lila_metadata_urls.py +15 -5
  18. megadetector/data_management/mewc_to_md.py +5 -0
  19. megadetector/data_management/ocr_tools.py +4 -3
  20. megadetector/data_management/read_exif.py +20 -5
  21. megadetector/data_management/remap_coco_categories.py +66 -4
  22. megadetector/data_management/remove_exif.py +50 -1
  23. megadetector/data_management/rename_images.py +3 -3
  24. megadetector/data_management/resize_coco_dataset.py +563 -95
  25. megadetector/data_management/yolo_output_to_md_output.py +131 -2
  26. megadetector/data_management/yolo_to_coco.py +140 -5
  27. megadetector/detection/change_detection.py +4 -3
  28. megadetector/detection/pytorch_detector.py +60 -22
  29. megadetector/detection/run_detector.py +225 -25
  30. megadetector/detection/run_detector_batch.py +42 -16
  31. megadetector/detection/run_inference_with_yolov5_val.py +12 -2
  32. megadetector/detection/run_tiled_inference.py +1 -0
  33. megadetector/detection/video_utils.py +53 -24
  34. megadetector/postprocessing/add_max_conf.py +4 -0
  35. megadetector/postprocessing/categorize_detections_by_size.py +1 -1
  36. megadetector/postprocessing/classification_postprocessing.py +55 -20
  37. megadetector/postprocessing/combine_batch_outputs.py +3 -2
  38. megadetector/postprocessing/compare_batch_results.py +64 -10
  39. megadetector/postprocessing/convert_output_format.py +12 -8
  40. megadetector/postprocessing/create_crop_folder.py +137 -10
  41. megadetector/postprocessing/load_api_results.py +26 -8
  42. megadetector/postprocessing/md_to_coco.py +4 -4
  43. megadetector/postprocessing/md_to_labelme.py +18 -7
  44. megadetector/postprocessing/merge_detections.py +5 -0
  45. megadetector/postprocessing/postprocess_batch_results.py +6 -3
  46. megadetector/postprocessing/remap_detection_categories.py +55 -2
  47. megadetector/postprocessing/render_detection_confusion_matrix.py +9 -6
  48. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +2 -2
  49. megadetector/taxonomy_mapping/map_new_lila_datasets.py +3 -4
  50. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +40 -19
  51. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +1 -1
  52. megadetector/taxonomy_mapping/species_lookup.py +123 -41
  53. megadetector/utils/ct_utils.py +133 -113
  54. megadetector/utils/md_tests.py +93 -13
  55. megadetector/utils/path_utils.py +137 -107
  56. megadetector/utils/split_locations_into_train_val.py +2 -2
  57. megadetector/utils/string_utils.py +7 -7
  58. megadetector/utils/url_utils.py +81 -58
  59. megadetector/utils/wi_utils.py +46 -17
  60. megadetector/visualization/plot_utils.py +13 -9
  61. megadetector/visualization/render_images_with_thumbnails.py +2 -1
  62. megadetector/visualization/visualization_utils.py +94 -46
  63. megadetector/visualization/visualize_db.py +36 -9
  64. megadetector/visualization/visualize_detector_output.py +4 -4
  65. {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/METADATA +135 -135
  66. megadetector-10.0.0.dist-info/RECORD +139 -0
  67. {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
  68. {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/top_level.txt +0 -0
  69. megadetector/api/batch_processing/api_core/__init__.py +0 -0
  70. megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  71. megadetector/api/batch_processing/api_core/batch_service/score.py +0 -438
  72. megadetector/api/batch_processing/api_core/server.py +0 -294
  73. megadetector/api/batch_processing/api_core/server_api_config.py +0 -97
  74. megadetector/api/batch_processing/api_core/server_app_config.py +0 -55
  75. megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -220
  76. megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -149
  77. megadetector/api/batch_processing/api_core/server_orchestration.py +0 -360
  78. megadetector/api/batch_processing/api_core/server_utils.py +0 -88
  79. megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  80. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
  81. megadetector/api/batch_processing/api_support/__init__.py +0 -0
  82. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -152
  83. megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  84. megadetector/api/synchronous/__init__.py +0 -0
  85. megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  86. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -151
  87. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -263
  88. megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -35
  89. megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  90. megadetector/api/synchronous/api_core/tests/load_test.py +0 -109
  91. megadetector/utils/azure_utils.py +0 -178
  92. megadetector/utils/sas_blob_utils.py +0 -513
  93. megadetector-5.0.29.dist-info/RECORD +0 -163
  94. /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
  95. {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/WHEEL +0 -0
@@ -40,6 +40,8 @@ import json
40
40
  import csv
41
41
  import os
42
42
  import re
43
+ import sys
44
+ import argparse
43
45
 
44
46
  from collections import defaultdict
45
47
  from tqdm import tqdm
@@ -123,8 +125,9 @@ def yolo_json_output_to_md_output(yolo_json_file,
123
125
  Converts a YOLOv5/YOLOv8 .json file to MD .json format.
124
126
 
125
127
  Args:
126
- yolo_json_file (str): the .json file to convert from YOLOv5 format to MD output format
128
+ yolo_json_file (str): the YOLO-formatted .json file to convert to MD output format
127
129
  image_folder (str): the .json file contains relative path names, this is the path base
130
+ output_file (str): the MD-formatted .json file to write
128
131
  yolo_category_id_to_name (str or dict): the .json results file contains only numeric
129
132
  identifiers for categories, but we want names and numbers for the output format;
130
133
  yolo_category_id_to_name provides that mapping either as a dict or as a YOLOv5
@@ -460,4 +463,130 @@ if False:
460
463
 
461
464
  #%% Command-line driver
462
465
 
463
- # TODO
466
+ def main():
467
+ """
468
+ Command-line interface to convert YOLOv5/YOLOv8 output (.json or .txt)
469
+ to MegaDetector output format.
470
+ """
471
+
472
+ parser = argparse.ArgumentParser(
473
+ description='Converts YOLOv5 output (.json or .txt) to MD output format.'
474
+ )
475
+
476
+ # The first argument determines which series of additional arguments are supported, for
477
+ # json/txt input
478
+ subparsers = parser.add_subparsers(dest='mode', required=True,
479
+ help="Mode of operation: 'json' for YOLO JSON output, 'txt' for YOLO TXT output.")
480
+
481
+
482
+ ## 'json' mode subparser
483
+
484
+ parser_json = subparsers.add_parser('json', help='Convert YOLO-formatted .json results.')
485
+
486
+ parser_json.add_argument(
487
+ 'yolo_json_file', type=str,
488
+ help='Path to the input YOLO-formatted .json results file'
489
+ )
490
+ parser_json.add_argument(
491
+ 'image_folder', type=str,
492
+ help='Path to the image folder'
493
+ )
494
+ parser_json.add_argument(
495
+ 'output_file', type=str,
496
+ help='Path to the MD-formatted .json output file'
497
+ )
498
+ parser_json.add_argument(
499
+ 'yolo_category_id_to_name_file', type=str,
500
+ help='Path to the .yml, .yaml, .json, or .txt file mapping YOLO category IDs to names'
501
+ )
502
+ parser_json.add_argument(
503
+ '--detector_name', type=str, default='unknown',
504
+ help="Detector name to store in the output file (default: 'unknown')"
505
+ )
506
+ parser_json.add_argument(
507
+ '--image_id_to_relative_path_file', type=str, default=None,
508
+ help='Path to a .json file mapping image IDs to relative paths'
509
+ )
510
+ parser_json.add_argument(
511
+ '--offset_yolo_class_ids', type=str, default='true', choices=['true', 'false'],
512
+ help="Offset YOLO class IDs in the output (default: 'true')"
513
+ )
514
+ parser_json.add_argument(
515
+ '--truncate_to_standard_md_precision', type=str, default='true', choices=['true', 'false'],
516
+ help="Truncate coordinates and confidences to standard MD precision (default: 'true')"
517
+ )
518
+ parser_json.add_argument(
519
+ '--convert_slashes', type=str, default='true', choices=['true', 'false'],
520
+ help="Convert backslashes to forward slashes in output file paths (default: 'true')"
521
+ )
522
+
523
+
524
+ ## 'txt' mode subparser
525
+
526
+ parser_txt = subparsers.add_parser('txt', help='Convert YOLO-formatted .txt results from a folder')
527
+ parser_txt.add_argument(
528
+ 'input_results_folder', type=str,
529
+ help='Path to the folder containing YOLO .txt output files'
530
+ )
531
+ parser_txt.add_argument(
532
+ 'image_folder', type=str,
533
+ help='Path to the image folder'
534
+ )
535
+ parser_txt.add_argument(
536
+ 'output_file', type=str,
537
+ help='Path to the MD-formatted .json file output'
538
+ )
539
+ parser_txt.add_argument(
540
+ '--detector_tag', type=str, default=None,
541
+ help='Detector tag to store in the output file'
542
+ )
543
+ parser_txt.add_argument(
544
+ '--truncate_to_standard_md_precision', type=str, default='true', choices=['true', 'false'],
545
+ help="Truncate coordinates and confidences to standard MD precision (default: 'true')."
546
+ )
547
+
548
+ args = parser.parse_args()
549
+
550
+ if args.mode == 'json':
551
+
552
+ image_id_to_relative_path = None
553
+ if args.image_id_to_relative_path_file:
554
+ try:
555
+ with open(args.image_id_to_relative_path_file, 'r') as f:
556
+ image_id_to_relative_path = json.load(f)
557
+ except Exception as e:
558
+ print(f"Error loading image_id_to_relative_path_file: {e}")
559
+ sys.exit(1)
560
+
561
+ offset_yolo_class_ids = args.offset_yolo_class_ids.lower() == 'true'
562
+ truncate_json = args.truncate_to_standard_md_precision.lower() == 'true'
563
+ convert_slashes = args.convert_slashes.lower() == 'true'
564
+
565
+ yolo_json_output_to_md_output(
566
+ yolo_json_file=args.yolo_json_file,
567
+ image_folder=args.image_folder,
568
+ output_file=args.output_file,
569
+ yolo_category_id_to_name=args.yolo_category_id_to_name_file, # Function handles reading this file
570
+ detector_name=args.detector_name,
571
+ image_id_to_relative_path=image_id_to_relative_path,
572
+ offset_yolo_class_ids=offset_yolo_class_ids,
573
+ truncate_to_standard_md_precision=truncate_json,
574
+ convert_slashes=convert_slashes
575
+ )
576
+ print('Converted {} to {}'.format(args.yolo_json_file,args.output_file))
577
+
578
+ elif args.mode == 'txt':
579
+
580
+ truncate_txt = args.truncate_to_standard_md_precision.lower() == 'true'
581
+
582
+ yolo_txt_output_to_md_output(
583
+ input_results_folder=args.input_results_folder,
584
+ image_folder=args.image_folder,
585
+ output_file=args.output_file,
586
+ detector_tag=args.detector_tag,
587
+ truncate_to_standard_md_precision=truncate_txt
588
+ )
589
+ print('Converted results from {} to {}'.format(args.input_results_folder,args.output_file))
590
+
591
+ if __name__ == '__main__':
592
+ main()
@@ -10,6 +10,8 @@ Converts a folder of YOLO-formatted annotation files to a COCO-formatted dataset
10
10
 
11
11
  import json
12
12
  import os
13
+ import argparse
14
+ import sys
13
15
 
14
16
  from multiprocessing.pool import ThreadPool
15
17
  from multiprocessing.pool import Pool
@@ -22,7 +24,8 @@ from megadetector.utils.path_utils import recursive_file_list
22
24
  from megadetector.utils.path_utils import find_image_strings
23
25
  from megadetector.utils.ct_utils import invert_dictionary
24
26
  from megadetector.visualization.visualization_utils import open_image
25
- from megadetector.data_management.yolo_output_to_md_output import read_classes_from_yolo_dataset_file
27
+ from megadetector.data_management.yolo_output_to_md_output import \
28
+ read_classes_from_yolo_dataset_file
26
29
 
27
30
 
28
31
  #%% Support functions
@@ -430,7 +433,7 @@ def yolo_to_coco(input_folder,
430
433
  input_folder as the base folder, though this is not explicitly checked.
431
434
  output_file (str, optional): .json file to which we should write COCO .json data
432
435
  empty_image_handling (str, optional): how to handle images with no boxes; whether
433
- this includes images with no .txt files depending on the value of
436
+ this includes images with no .txt files depends on the value of
434
437
  [allow_images_without_label_files]. Can be:
435
438
 
436
439
  - 'no_annotations': include the image in the image list, with no annotations
@@ -438,12 +441,15 @@ def yolo_to_coco(input_folder,
438
441
  any bounding boxes, using a category called [empty_image_category_name].
439
442
  - 'skip': don't include the image in the image list
440
443
  - 'error': there shouldn't be any empty images
444
+ empty_image_category_name (str, optional): if we're going to be inserting annotations for
445
+ images with no boxes, what category name should we use?
441
446
  error_image_handling (str, optional): how to handle images that don't load properly; can
442
447
  be:
443
448
 
444
449
  - 'skip': don't include the image at all
445
450
  - 'no_annotations': include with no annotations
446
-
451
+ allow_images_without_label_files (bool, optional): whether to silently allow images with
452
+ no label files (True) or raise errors for images with no label files (False)
447
453
  n_workers (int, optional): number of concurrent workers, set to <= 1 to disable
448
454
  parallelization
449
455
  pool_type (str, optional): 'thread' or 'process', worker type to use for parallelization;
@@ -493,7 +499,7 @@ def yolo_to_coco(input_folder,
493
499
  # Find or create the empty image category, if necessary
494
500
  empty_category_id = None
495
501
 
496
- if (empty_image_handling == 'empty_annotations'):
502
+ if empty_image_handling == 'empty_annotations':
497
503
  category_name_to_id = invert_dictionary(category_id_to_name)
498
504
  if empty_image_category_name in category_name_to_id:
499
505
  empty_category_id = category_name_to_id[empty_image_category_name]
@@ -652,6 +658,11 @@ def yolo_to_coco(input_folder,
652
658
 
653
659
  # ...for each image result
654
660
 
661
+ # Clean up unnecessary error fields
662
+ for im in images:
663
+ if 'error' in im and im['error'] is None:
664
+ del im['error']
665
+
655
666
  print('Read {} annotations for {} images'.format(len(annotations),
656
667
  len(images)))
657
668
 
@@ -729,4 +740,128 @@ if False:
729
740
 
730
741
  #%% Command-line driver
731
742
 
732
- # TODO
743
+ def main():
744
+ """
745
+ Command-line driver for YOLO to COCO conversion.
746
+ """
747
+
748
+ parser = argparse.ArgumentParser(
749
+ description='Convert a YOLO-formatted dataset to COCO format'
750
+ )
751
+ parser.add_argument(
752
+ 'input_folder',
753
+ type=str,
754
+ help='Path to the YOLO dataset folder (image folder)'
755
+ )
756
+ parser.add_argument(
757
+ 'class_name_file',
758
+ type=str,
759
+ help='Path to the file containing class names (e.g., classes.txt or dataset.yaml)'
760
+ )
761
+ parser.add_argument(
762
+ 'output_file',
763
+ type=str,
764
+ help='Path to the output COCO .json file.'
765
+ )
766
+ parser.add_argument(
767
+ '--label_folder',
768
+ type=str,
769
+ default=None,
770
+ help='Label folder, if different from the image folder. Default: None (labels are in the image folder)'
771
+ )
772
+ parser.add_argument(
773
+ '--empty_image_handling',
774
+ type=str,
775
+ default='no_annotations',
776
+ choices=['no_annotations', 'empty_annotations', 'skip', 'error'],
777
+ help='How to handle images with no bounding boxes.'
778
+ )
779
+ parser.add_argument(
780
+ '--empty_image_category_name',
781
+ type=str,
782
+ default='empty',
783
+ help='Category name for empty images if empty_image_handling is "empty_annotations"'
784
+ )
785
+ parser.add_argument(
786
+ '--error_image_handling',
787
+ type=str,
788
+ default='no_annotations',
789
+ choices=['skip', 'no_annotations'],
790
+ help='How to handle images that fail to load'
791
+ )
792
+ parser.add_argument(
793
+ '--allow_images_without_label_files',
794
+ type=str,
795
+ default='true',
796
+ choices=['true', 'false'],
797
+ help='Whether to allow images that do not have corresponding label files (true/false)'
798
+ )
799
+ parser.add_argument(
800
+ '--n_workers',
801
+ type=int,
802
+ default=1,
803
+ help='Number of workers for parallel processing. <=1 for sequential'
804
+ )
805
+ parser.add_argument(
806
+ '--pool_type',
807
+ type=str,
808
+ default='thread',
809
+ choices=['thread', 'process'],
810
+ help='Type of multiprocessing pool if n_workers > 1'
811
+ )
812
+ parser.add_argument(
813
+ '--recursive',
814
+ type=str,
815
+ default='true',
816
+ choices=['true', 'false'],
817
+ help='Whether to search for images recursively in the input folder (true/false)'
818
+ )
819
+ parser.add_argument(
820
+ '--exclude_string',
821
+ type=str,
822
+ default=None,
823
+ help='Exclude images whose filename contains this string'
824
+ )
825
+ parser.add_argument(
826
+ '--include_string',
827
+ type=str,
828
+ default=None,
829
+ help='Include images only if filename contains this string'
830
+ )
831
+ parser.add_argument(
832
+ '--overwrite_handling',
833
+ type=str,
834
+ default='overwrite',
835
+ choices=['load', 'overwrite', 'error'],
836
+ help='Behavior if output_file exists.'
837
+ )
838
+
839
+ if len(sys.argv[1:]) == 0:
840
+ parser.print_help()
841
+ parser.exit()
842
+
843
+ args = parser.parse_args()
844
+
845
+ parsed_allow_images = args.allow_images_without_label_files.lower() == 'true'
846
+ parsed_recursive = args.recursive.lower() == 'true'
847
+
848
+ yolo_to_coco(
849
+ args.input_folder,
850
+ args.class_name_file,
851
+ output_file=args.output_file,
852
+ label_folder=args.label_folder,
853
+ empty_image_handling=args.empty_image_handling,
854
+ empty_image_category_name=args.empty_image_category_name,
855
+ error_image_handling=args.error_image_handling,
856
+ allow_images_without_label_files=parsed_allow_images,
857
+ n_workers=args.n_workers,
858
+ pool_type=args.pool_type,
859
+ recursive=parsed_recursive,
860
+ exclude_string=args.exclude_string,
861
+ include_string=args.include_string,
862
+ overwrite_handling=args.overwrite_handling
863
+ )
864
+ print(f"Dataset conversion complete, output written to {args.output_file}")
865
+
866
+ if __name__ == '__main__':
867
+ main()
@@ -228,7 +228,8 @@ def detect_motion(prev_image_path,
228
228
  curr_image_path (str): path to the current image
229
229
  options (ChangeDetectionOptions, optional): detection settings
230
230
  motion_state (MotionHistoryState, optional): state for motion history
231
- bg_subtractor: background subtractor model for MOG2/KNN methods
231
+ bg_subtractor (cv2 background subtractor object): background subtractor model for
232
+ MOG2/KNN methods
232
233
 
233
234
  Returns:
234
235
  tuple: (motion_result, updated_motion_state)
@@ -611,8 +612,8 @@ def create_change_previews(motion_results, output_folder, num_samples=10, random
611
612
  Create side-by-side previews of images with detected motion
612
613
 
613
614
  Args:
614
- motion_results: DataFrame with motion detection results
615
- output_folder: folder where preview images will be saved
615
+ motion_results (DataFrame): DataFrame with motion detection results
616
+ output_folder (str): folder where preview images will be saved
616
617
  num_samples (int, optional): number of random samples to create
617
618
  random_seed (int, optional): seed for random sampling (for reproducibility)
618
619
 
@@ -28,7 +28,6 @@ from megadetector.detection.run_detector import \
28
28
  get_detector_version_from_model_file, \
29
29
  known_models
30
30
  from megadetector.utils.ct_utils import parse_bool_string
31
- from megadetector.utils.ct_utils import to_bool
32
31
  from megadetector.utils import ct_utils
33
32
 
34
33
  # We support a few ways of accessing the YOLOv5 dependencies:
@@ -176,7 +175,7 @@ def _initialize_yolo_imports_for_model(model_file,
176
175
  return model_type
177
176
 
178
177
 
179
- def _clean_yolo_imports(verbose=False):
178
+ def _clean_yolo_imports(verbose=False,aggressive_cleanup=False):
180
179
  """
181
180
  Remove all YOLO-related imports from sys.modules and sys.path, to allow a clean re-import
182
181
  of another YOLO library version. The reason we jump through all these hoops, rather than
@@ -187,30 +186,68 @@ def _clean_yolo_imports(verbose=False):
187
186
 
188
187
  Args:
189
188
  verbose (bool, optional): enable additional debug output
189
+ aggressive_cleanup (bool, optional): err on the side of removing modules,
190
+ at least by ignoring whether they are/aren't in a site-packages folder.
191
+ By default, only modules in a folder that includes "site-packages" will
192
+ be considered for unloading.
190
193
  """
191
194
 
192
195
  modules_to_delete = []
196
+
193
197
  for module_name in sys.modules.keys():
198
+
194
199
  module = sys.modules[module_name]
200
+ if not hasattr(module,'__file__') or (module.__file__ is None):
201
+ continue
195
202
  try:
196
203
  module_file = module.__file__.replace('\\','/')
197
- if 'site-packages' not in module_file:
198
- continue
199
- tokens = module_file.split('/')[-4:]
200
- for token in tokens:
201
- if 'yolov5' in token or 'yolov9' in token or 'ultralytics' in token:
204
+ if not aggressive_cleanup:
205
+ if 'site-packages' not in module_file:
206
+ continue
207
+ tokens = module_file.split('/')
208
+
209
+ # For local path imports, a module filename that should be unloaded might
210
+ # look like:
211
+ #
212
+ # c:/git/yolov9/models/common.py
213
+ #
214
+ # For pip imports, a module filename that should be unloaded might look like:
215
+ #
216
+ # c:/users/user/miniforge3/envs/megadetector/lib/site-packages/yolov9/utils/__init__.py
217
+ first_token_to_check = len(tokens) - 4
218
+ for i_token,token in enumerate(tokens):
219
+ if i_token < first_token_to_check:
220
+ continue
221
+ # Don't remove anything based on the environment name, which
222
+ # always follows "envs" in the path
223
+ if (i_token > 1) and (tokens[i_token-1] == 'envs'):
224
+ continue
225
+ if ('yolov5' in token) or ('yolov9' in token) or ('ultralytics' in token):
226
+ if verbose:
227
+ print('Module {} ({}) looks deletable'.format(module_name,module_file))
202
228
  modules_to_delete.append(module_name)
203
229
  break
204
- except Exception:
230
+ except Exception as e:
231
+ if verbose:
232
+ print('Exception during module review: {}'.format(str(e)))
205
233
  pass
206
234
 
235
+ # ...for each module in the global namespace
236
+
207
237
  for module_name in modules_to_delete:
238
+
208
239
  if module_name in sys.modules.keys():
209
- module_file = module.__file__.replace('\\','/')
210
240
  if verbose:
211
- print('clean_yolo_imports: deleting module {}: {}'.format(module_name,module_file))
241
+ try:
242
+ module = sys.modules[module_name]
243
+ module_file = module.__file__.replace('\\','/')
244
+ print('clean_yolo_imports: deleting module {}: {}'.format(module_name,module_file))
245
+ except Exception:
246
+ pass
212
247
  del sys.modules[module_name]
213
248
 
249
+ # ...for each module we want to remove from the global namespace
250
+
214
251
  paths_to_delete = []
215
252
 
216
253
  for p in sys.path:
@@ -227,7 +264,7 @@ def _clean_yolo_imports(verbose=False):
227
264
  def _initialize_yolo_imports(model_type='yolov5',
228
265
  allow_fallback_import=True,
229
266
  force_reimport=False,
230
- verbose=True):
267
+ verbose=False):
231
268
  """
232
269
  Imports required functions from one or more yolo libraries (yolov5, yolov9,
233
270
  ultralytics, targeting support for [model_type]).
@@ -246,7 +283,7 @@ def _initialize_yolo_imports(model_type='yolov5',
246
283
  """
247
284
 
248
285
  # When running in pytest, the megadetector 'utils' module is put in the global
249
- # namespace, which creates conflicts with yolov5; remove it from the global
286
+ # namespace, which creates conflicts with yolov5; remove it from the global
250
287
  # namespsace.
251
288
  if ('PYTEST_CURRENT_TEST' in os.environ):
252
289
  print('*** pytest detected ***')
@@ -328,7 +365,7 @@ def _initialize_yolo_imports(model_type='yolov5',
328
365
 
329
366
  try:
330
367
 
331
- import ultralytics # noqa
368
+ import ultralytics # type: ignore # noqa
332
369
 
333
370
  except Exception:
334
371
 
@@ -340,15 +377,15 @@ def _initialize_yolo_imports(model_type='yolov5',
340
377
 
341
378
  try:
342
379
 
343
- from ultralytics.utils.ops import non_max_suppression # noqa
344
- from ultralytics.utils.ops import xyxy2xywh # noqa
380
+ from ultralytics.utils.ops import non_max_suppression # type: ignore # noqa
381
+ from ultralytics.utils.ops import xyxy2xywh # type: ignore # noqa
345
382
 
346
383
  # In the ultralytics package, scale_boxes and scale_coords both exist;
347
384
  # we want scale_boxes.
348
385
  #
349
386
  # from ultralytics.utils.ops import scale_coords # noqa
350
- from ultralytics.utils.ops import scale_boxes as scale_coords # noqa
351
- from ultralytics.data.augment import LetterBox
387
+ from ultralytics.utils.ops import scale_boxes as scale_coords # type: ignore # noqa
388
+ from ultralytics.data.augment import LetterBox # type: ignore # noqa
352
389
 
353
390
  # letterbox() became a LetterBox class in the ultralytics package. Create a
354
391
  # backwards-compatible letterbox function wrapper that wraps the class up.
@@ -373,7 +410,7 @@ def _initialize_yolo_imports(model_type='yolov5',
373
410
  else:
374
411
  letterbox_transformer = LetterBox(new_shape,auto=auto,scale_fill=scaleFill,
375
412
  scaleup=scaleup,center=center,stride=stride)
376
-
413
+
377
414
  letterbox_result = letterbox_transformer(image=img)
378
415
 
379
416
  if isinstance(new_shape,int):
@@ -516,6 +553,7 @@ def read_metadata_from_megadetector_model_file(model_file,
516
553
  archive from which we should read the metadata. This is not relative to the root
517
554
  of the archive, it's relative to the one and only folder at the root of the archive
518
555
  (this is a PyTorch convention).
556
+ verbose (str, optional): enable additional debug output
519
557
 
520
558
  Returns:
521
559
  object: whatever we read from the metadata file, always a dict in practice. Returns
@@ -575,7 +613,7 @@ class PTDetector:
575
613
 
576
614
  if verbose:
577
615
  print('Initializing PTDetector (verbose)')
578
-
616
+
579
617
  # Set up the import environment for this model, unloading previous
580
618
  # YOLO library versions if necessary.
581
619
  _initialize_yolo_imports_for_model(model_path,
@@ -620,9 +658,9 @@ class PTDetector:
620
658
  #: aspect ratio".
621
659
  if model_metadata is not None and 'image_size' in model_metadata:
622
660
  self.default_image_size = model_metadata['image_size']
623
- if verbose:
624
- print('Loaded image size {} from model metadata'.format(self.default_image_size))
661
+ print('Loaded image size {} from model metadata'.format(self.default_image_size))
625
662
  else:
663
+ print('No image size available in model metadata, defaulting to 1280')
626
664
  self.default_image_size = 1280
627
665
 
628
666
  #: Either a string ('cpu','cuda:0') or a torch.device()
@@ -764,7 +802,7 @@ class PTDetector:
764
802
  of the output object
765
803
  detection_threshold (float, optional): only detections above this confidence threshold
766
804
  will be included in the return value
767
- image_size (tuple, optional): image size to use for inference, only mess with this if
805
+ image_size (int, optional): image size to use for inference, only mess with this if
768
806
  (a) you're using a model other than MegaDetector or (b) you know what you're getting into
769
807
  skip_image_resizing (bool, optional): whether to skip internal image resizing (and rely on
770
808
  external resizing), only mess with this if (a) you're using a model other than MegaDetector