megadetector 5.0.24__py3-none-any.whl → 5.0.26__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 (41) hide show
  1. megadetector/data_management/cct_json_utils.py +15 -2
  2. megadetector/data_management/coco_to_yolo.py +53 -31
  3. megadetector/data_management/databases/combine_coco_camera_traps_files.py +7 -3
  4. megadetector/data_management/databases/integrity_check_json_db.py +2 -2
  5. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +73 -69
  6. megadetector/data_management/lila/add_locations_to_nacti.py +114 -110
  7. megadetector/data_management/lila/generate_lila_per_image_labels.py +2 -2
  8. megadetector/data_management/lila/test_lila_metadata_urls.py +21 -10
  9. megadetector/data_management/remap_coco_categories.py +60 -11
  10. megadetector/data_management/{wi_to_md.py → speciesnet_to_md.py} +2 -2
  11. megadetector/data_management/yolo_to_coco.py +45 -15
  12. megadetector/detection/run_detector.py +1 -0
  13. megadetector/detection/run_detector_batch.py +5 -4
  14. megadetector/postprocessing/classification_postprocessing.py +788 -524
  15. megadetector/postprocessing/compare_batch_results.py +176 -9
  16. megadetector/postprocessing/create_crop_folder.py +420 -0
  17. megadetector/postprocessing/load_api_results.py +4 -1
  18. megadetector/postprocessing/md_to_coco.py +1 -1
  19. megadetector/postprocessing/postprocess_batch_results.py +158 -44
  20. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +3 -8
  21. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +2 -2
  22. megadetector/postprocessing/separate_detections_into_folders.py +20 -4
  23. megadetector/postprocessing/subset_json_detector_output.py +180 -15
  24. megadetector/postprocessing/validate_batch_results.py +13 -5
  25. megadetector/taxonomy_mapping/map_new_lila_datasets.py +6 -6
  26. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +3 -58
  27. megadetector/taxonomy_mapping/species_lookup.py +45 -2
  28. megadetector/utils/ct_utils.py +76 -3
  29. megadetector/utils/directory_listing.py +4 -4
  30. megadetector/utils/gpu_test.py +21 -3
  31. megadetector/utils/md_tests.py +142 -49
  32. megadetector/utils/path_utils.py +342 -19
  33. megadetector/utils/wi_utils.py +1286 -212
  34. megadetector/visualization/visualization_utils.py +16 -4
  35. megadetector/visualization/visualize_db.py +1 -1
  36. megadetector/visualization/visualize_detector_output.py +1 -4
  37. {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info}/METADATA +6 -3
  38. {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info}/RECORD +41 -40
  39. {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info}/WHEEL +1 -1
  40. {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info/licenses}/LICENSE +0 -0
  41. {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,17 @@ for TF or PyTorch
7
7
 
8
8
  """
9
9
 
10
+ # Minimize TF printouts
11
+ import os
12
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
13
+
14
+ try:
15
+ import logging
16
+ logging.getLogger('tensorflow').setLevel(logging.ERROR)
17
+ except Exception:
18
+ pass
19
+
20
+
10
21
  #%% Torch/TF test functions
11
22
 
12
23
  def torch_test():
@@ -45,7 +56,7 @@ def torch_test():
45
56
  pass
46
57
  print('{}: {}'.format(device_id,device_name))
47
58
  else:
48
- print('No CUDA GPUs reported by PyTorch')
59
+ print('No GPUs reported by PyTorch')
49
60
 
50
61
  try:
51
62
  if torch.backends.mps.is_built and torch.backends.mps.is_available():
@@ -72,8 +83,15 @@ def tf_test():
72
83
 
73
84
  from tensorflow.python.platform import build_info as build
74
85
  print(f"TF version: {tf.__version__}")
75
- print(f"CUDA build version reported by TensorFlow: {build.build_info['cuda_version']}")
76
- print(f"CuDNN build version reported by TensorFlow: {build.build_info['cudnn_version']}")
86
+
87
+ if 'cuda_version' not in build.build_info:
88
+ print('TF does not appear to be built with CUDA')
89
+ else:
90
+ print(f"CUDA build version reported by TensorFlow: {build.build_info['cuda_version']}")
91
+ if 'cudnn_version' not in build.build_info:
92
+ print('TF does not appear to be built with CuDNN')
93
+ else:
94
+ print(f"CuDNN build version reported by TensorFlow: {build.build_info['cudnn_version']}")
77
95
 
78
96
  try:
79
97
  from tensorflow.python.compiler.tensorrt import trt_convert as trt
@@ -131,6 +131,15 @@ class MDTestOptions:
131
131
  #: this is a range from 0-100.
132
132
  self.python_test_depth = 100
133
133
 
134
+ #: Currently should be 'all' or 'utils-only'
135
+ self.test_mode = 'all'
136
+
137
+ #: Number of cores to use for multi-CPU inference tests
138
+ self.n_cores_for_multiprocessing_tests = 2
139
+
140
+ #: Number of cores to use for multi-CPU video tests
141
+ self.n_cores_for_video_tests = 2
142
+
134
143
  # ...def __init__()
135
144
 
136
145
  # ...class MDTestOptions()
@@ -302,18 +311,24 @@ def download_test_data(options=None):
302
311
 
303
312
  def is_gpu_available(verbose=True):
304
313
  """
305
- Checks whether a GPU (including M1/M2 MPS) is available, according to PyTorch.
314
+ Checks whether a GPU (including M1/M2 MPS) is available, according to PyTorch. Returns
315
+ false if PT fails to import.
306
316
 
307
317
  Args:
308
- verbose (bool, optional): enable additional debug console output
318
+ verbose (bool, optional): enable additional debug console output
309
319
 
310
320
  Returns:
311
- bool: whether a GPU is available
321
+ bool: whether a GPU is available
312
322
  """
313
323
 
314
324
  # Import torch inside this function, so we have a chance to set CUDA_VISIBLE_DEVICES
315
325
  # before checking GPU availability.
316
- import torch
326
+ try:
327
+ import torch
328
+ except Exception:
329
+ print('Warning: could not import torch')
330
+ return False
331
+
317
332
  gpu_available = torch.cuda.is_available()
318
333
 
319
334
  if gpu_available:
@@ -705,6 +720,47 @@ def execute_and_print(cmd,print_output=True,catch_exceptions=False,echo_command=
705
720
 
706
721
  #%% Python tests
707
722
 
723
+ def test_package_imports(package_name,exceptions=None,verbose=True):
724
+ """
725
+ Imports all modules in [package_name]
726
+
727
+ Args:
728
+ package_name (str): the package name to test
729
+ exceptions (list, optional): exclude any modules that contain any of these strings
730
+ verbose (bool, optional): enable additional debug output
731
+ """
732
+ import importlib
733
+ import pkgutil
734
+
735
+ package = importlib.import_module(package_name)
736
+ package_path = package.__path__
737
+ imported_modules = []
738
+
739
+ if exceptions is None:
740
+ exceptions = []
741
+
742
+ for _, modname, _ in pkgutil.walk_packages(package_path, package_name + '.'):
743
+
744
+ skip_module = False
745
+ for s in exceptions:
746
+ if s in modname:
747
+ skip_module = True
748
+ break
749
+ if skip_module:
750
+ continue
751
+
752
+ if verbose:
753
+ print('Testing import: {}'.format(modname))
754
+
755
+ try:
756
+ # Attempt to import each module
757
+ _ = importlib.import_module(modname)
758
+ imported_modules.append(modname)
759
+ except ImportError as e:
760
+ print(f"Failed to import module {modname}: {e}")
761
+ raise
762
+
763
+ #%%
708
764
  def run_python_tests(options):
709
765
  """
710
766
  Runs Python-based (as opposed to CLI-based) package tests.
@@ -716,11 +772,6 @@ def run_python_tests(options):
716
772
  print('\n*** Starting module tests ***\n')
717
773
 
718
774
 
719
- ## Make sure our tests are doing what we think they're doing
720
-
721
- from megadetector.detection import pytorch_detector
722
- pytorch_detector.require_non_default_compatibility_mode = True
723
-
724
775
  ## Prepare data
725
776
 
726
777
  download_test_data(options)
@@ -734,12 +785,34 @@ def run_python_tests(options):
734
785
  ct_utils_test()
735
786
 
736
787
 
788
+ ## Import tests
789
+
790
+ print('\n** Running package import tests **\n')
791
+ test_package_imports('megadetector.visualization')
792
+ test_package_imports('megadetector.postprocessing')
793
+ test_package_imports('megadetector.postprocessing.repeat_detection_elimination')
794
+ test_package_imports('megadetector.utils',exceptions=['azure_utils','sas_blob_utils','md_tests'])
795
+ test_package_imports('megadetector.data_management',exceptions=['lila','ocr_tools'])
796
+
797
+
798
+ ## Return early if we're not running torch-related tests
799
+
800
+ if options.test_mode == 'utils-only':
801
+ return
802
+
803
+
804
+ ## Make sure our tests are doing what we think they're doing
805
+
806
+ from megadetector.detection import pytorch_detector
807
+ pytorch_detector.require_non_default_compatibility_mode = True
808
+
809
+
737
810
  ## Run inference on an image
738
811
 
739
812
  print('\n** Running MD on a single image (module) **\n')
740
813
 
741
814
  from megadetector.detection import run_detector
742
- from megadetector.visualization import visualization_utils as vis_utils
815
+ from megadetector.visualization import visualization_utils as vis_utils # noqa
743
816
  image_fn = os.path.join(options.scratch_dir,options.test_images[0])
744
817
  model = run_detector.load_detector(options.default_model,
745
818
  detector_options=copy(options.detector_options))
@@ -755,7 +828,7 @@ def run_python_tests(options):
755
828
  print('\n** Running MD on a folder of images (module) **\n')
756
829
 
757
830
  from megadetector.detection.run_detector_batch import load_and_run_detector_batch,write_results_to_file
758
- from megadetector.utils import path_utils
831
+ from megadetector.utils import path_utils # noqa
759
832
 
760
833
  image_folder = os.path.join(options.scratch_dir,'md-test-images')
761
834
  assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
@@ -773,7 +846,7 @@ def run_python_tests(options):
773
846
  ## Verify results
774
847
 
775
848
  # Verify format correctness
776
- from megadetector.postprocessing.validate_batch_results import validate_batch_results
849
+ from megadetector.postprocessing.validate_batch_results import validate_batch_results #noqa
777
850
  validate_batch_results(inference_output_file)
778
851
 
779
852
  # Verify value correctness
@@ -955,7 +1028,7 @@ def run_python_tests(options):
955
1028
  # video_options.rendering_confidence_threshold = None
956
1029
  # video_options.json_confidence_threshold = 0.005
957
1030
  video_options.frame_sample = 10
958
- video_options.n_cores = 5
1031
+ video_options.n_cores = options.n_cores_for_video_tests
959
1032
  # video_options.debug_max_frames = -1
960
1033
  # video_options.class_mapping_filename = None
961
1034
  video_options.detector_options = copy(options.detector_options)
@@ -996,7 +1069,7 @@ def run_python_tests(options):
996
1069
  # video_options.rendering_confidence_threshold = None
997
1070
  # video_options.json_confidence_threshold = 0.005
998
1071
  video_options.frame_sample = 10
999
- video_options.n_cores = 5
1072
+ video_options.n_cores = options.n_cores_for_video_tests
1000
1073
 
1001
1074
  # Force frame extraction to disk, since that's how we generated our expected results file
1002
1075
  video_options.force_on_disk_frame_extraction = True
@@ -1092,6 +1165,18 @@ def run_cli_tests(options):
1092
1165
  from megadetector.utils.path_utils import insert_before_extension
1093
1166
 
1094
1167
 
1168
+ ## Utility tests
1169
+
1170
+ # TODO: move postprocessing tests up to this point, using pre-generated .json results files
1171
+
1172
+
1173
+ ## Return early if we're not running torch-related tests
1174
+
1175
+ if options.test_mode == 'utils-only':
1176
+ print('utils-only tests finished, returning')
1177
+ return
1178
+
1179
+
1095
1180
  ## Run inference on an image
1096
1181
 
1097
1182
  print('\n** Running MD on a single image (CLI) **\n')
@@ -1218,7 +1303,7 @@ def run_cli_tests(options):
1218
1303
 
1219
1304
  print('\n** Running MD on a folder (multiple CPUs) (CLI) **\n')
1220
1305
 
1221
- cpu_string = ' --ncores 4'
1306
+ cpu_string = ' --ncores {}'.format(options.n_cores_for_multiprocessing_tests)
1222
1307
  cmd = base_cmd + cpu_string
1223
1308
  inference_output_file_cpu_multicore = insert_before_extension(inference_output_file,'multicore')
1224
1309
  cmd = cmd.replace(inference_output_file,inference_output_file_cpu_multicore)
@@ -1385,7 +1470,9 @@ def run_cli_tests(options):
1385
1470
  cmd += ' --frame_folder "{}" --frame_rendering_folder "{}" --output_json_file "{}" --output_video_file "{}"'.format(
1386
1471
  frame_folder,frame_rendering_folder,video_inference_output_file,output_video_file)
1387
1472
  cmd += ' --fourcc {}'.format(options.video_fourcc)
1388
- cmd += ' --force_extracted_frame_folder_deletion --force_rendered_frame_folder_deletion --n_cores 5 --frame_sample 3'
1473
+ cmd += ' --force_extracted_frame_folder_deletion --force_rendered_frame_folder_deletion'
1474
+ cmd += ' --n_cores {}'.format(options.n_cores_for_video_tests)
1475
+ cmd += ' --frame_sample 4'
1389
1476
  cmd += ' --verbose'
1390
1477
  cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1391
1478
 
@@ -1446,40 +1533,39 @@ def run_download_tests(options):
1446
1533
  options (MDTestOptions): see MDTestOptions for details
1447
1534
  """
1448
1535
 
1449
- if not options.skip_download_tests:
1536
+ if options.skip_download_tests or options.test_mode == 'utils-only':
1537
+ return
1450
1538
 
1451
- from megadetector.detection.run_detector import known_models, \
1452
- try_download_known_detector, \
1453
- get_detector_version_from_model_file, \
1454
- model_string_to_model_version
1539
+ from megadetector.detection.run_detector import known_models, \
1540
+ try_download_known_detector, \
1541
+ get_detector_version_from_model_file, \
1542
+ model_string_to_model_version
1455
1543
 
1456
- # Make sure we can download models based on canonical version numbers,
1457
- # e.g. "v5a.0.0"
1458
- for model_name in known_models:
1459
- url = known_models[model_name]['url']
1460
- if 'localhost' in url:
1461
- continue
1462
- print('Testing download for known model {}'.format(model_name))
1463
- fn = try_download_known_detector(model_name,
1464
- force_download=False,
1465
- verbose=False)
1466
- version_string = get_detector_version_from_model_file(fn, verbose=False)
1467
- assert version_string == model_name
1468
-
1469
- # Make sure we can download models based on short names, e.g. "MDV5A"
1470
- for model_name in model_string_to_model_version:
1471
- model_version = model_string_to_model_version[model_name]
1472
- assert model_version in known_models
1473
- url = known_models[model_version]['url']
1474
- if 'localhost' in url:
1475
- continue
1476
- print('Testing download for model short name {}'.format(model_name))
1477
- fn = try_download_known_detector(model_name,
1478
- force_download=False,
1479
- verbose=False)
1480
- assert fn != model_name
1481
-
1482
- # ...if we need to test model downloads
1544
+ # Make sure we can download models based on canonical version numbers,
1545
+ # e.g. "v5a.0.0"
1546
+ for model_name in known_models:
1547
+ url = known_models[model_name]['url']
1548
+ if 'localhost' in url:
1549
+ continue
1550
+ print('Testing download for known model {}'.format(model_name))
1551
+ fn = try_download_known_detector(model_name,
1552
+ force_download=False,
1553
+ verbose=False)
1554
+ version_string = get_detector_version_from_model_file(fn, verbose=False)
1555
+ assert version_string == model_name
1556
+
1557
+ # Make sure we can download models based on short names, e.g. "MDV5A"
1558
+ for model_name in model_string_to_model_version:
1559
+ model_version = model_string_to_model_version[model_name]
1560
+ assert model_version in known_models
1561
+ url = known_models[model_version]['url']
1562
+ if 'localhost' in url:
1563
+ continue
1564
+ print('Testing download for model short name {}'.format(model_name))
1565
+ fn = try_download_known_detector(model_name,
1566
+ force_download=False,
1567
+ verbose=False)
1568
+ assert fn != model_name
1483
1569
 
1484
1570
  # ...def run_download_tests()
1485
1571
 
@@ -1497,7 +1583,7 @@ def run_tests(options):
1497
1583
  # Prepare data folder
1498
1584
  download_test_data(options)
1499
1585
 
1500
- # Run download tests if necessary
1586
+ # Run model download tests if necessary
1501
1587
  run_download_tests(options)
1502
1588
 
1503
1589
  if options.disable_gpu:
@@ -1740,6 +1826,13 @@ def main():
1740
1826
  help='PYTHONPATH to set for CLI tests; if None, inherits from the parent process'
1741
1827
  )
1742
1828
 
1829
+ parser.add_argument(
1830
+ '--test_mode',
1831
+ type=str,
1832
+ default='all',
1833
+ help='Test mode: "all" or "utils-only"'
1834
+ )
1835
+
1743
1836
  parser.add_argument(
1744
1837
  '--python_test_depth',
1745
1838
  type=int,