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.
- megadetector/data_management/cct_json_utils.py +15 -2
- megadetector/data_management/coco_to_yolo.py +53 -31
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +7 -3
- megadetector/data_management/databases/integrity_check_json_db.py +2 -2
- megadetector/data_management/lila/add_locations_to_island_camera_traps.py +73 -69
- megadetector/data_management/lila/add_locations_to_nacti.py +114 -110
- megadetector/data_management/lila/generate_lila_per_image_labels.py +2 -2
- megadetector/data_management/lila/test_lila_metadata_urls.py +21 -10
- megadetector/data_management/remap_coco_categories.py +60 -11
- megadetector/data_management/{wi_to_md.py → speciesnet_to_md.py} +2 -2
- megadetector/data_management/yolo_to_coco.py +45 -15
- megadetector/detection/run_detector.py +1 -0
- megadetector/detection/run_detector_batch.py +5 -4
- megadetector/postprocessing/classification_postprocessing.py +788 -524
- megadetector/postprocessing/compare_batch_results.py +176 -9
- megadetector/postprocessing/create_crop_folder.py +420 -0
- megadetector/postprocessing/load_api_results.py +4 -1
- megadetector/postprocessing/md_to_coco.py +1 -1
- megadetector/postprocessing/postprocess_batch_results.py +158 -44
- megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +3 -8
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +2 -2
- megadetector/postprocessing/separate_detections_into_folders.py +20 -4
- megadetector/postprocessing/subset_json_detector_output.py +180 -15
- megadetector/postprocessing/validate_batch_results.py +13 -5
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +6 -6
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +3 -58
- megadetector/taxonomy_mapping/species_lookup.py +45 -2
- megadetector/utils/ct_utils.py +76 -3
- megadetector/utils/directory_listing.py +4 -4
- megadetector/utils/gpu_test.py +21 -3
- megadetector/utils/md_tests.py +142 -49
- megadetector/utils/path_utils.py +342 -19
- megadetector/utils/wi_utils.py +1286 -212
- megadetector/visualization/visualization_utils.py +16 -4
- megadetector/visualization/visualize_db.py +1 -1
- megadetector/visualization/visualize_detector_output.py +1 -4
- {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info}/METADATA +6 -3
- {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info}/RECORD +41 -40
- {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info}/WHEEL +1 -1
- {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info/licenses}/LICENSE +0 -0
- {megadetector-5.0.24.dist-info → megadetector-5.0.26.dist-info}/top_level.txt +0 -0
megadetector/utils/gpu_test.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
76
|
-
|
|
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
|
megadetector/utils/md_tests.py
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
1536
|
+
if options.skip_download_tests or options.test_mode == 'utils-only':
|
|
1537
|
+
return
|
|
1450
1538
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
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
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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,
|