megadetector 10.0.3__py3-none-any.whl → 10.0.5__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/animl_to_md.py +158 -0
- megadetector/data_management/cct_json_utils.py +1 -0
- megadetector/data_management/speciesnet_to_md.py +2 -2
- megadetector/data_management/zamba_to_md.py +188 -0
- megadetector/detection/process_video.py +52 -40
- megadetector/detection/pytorch_detector.py +24 -34
- megadetector/detection/run_detector_batch.py +138 -93
- megadetector/detection/run_md_and_speciesnet.py +22 -4
- megadetector/detection/video_utils.py +5 -4
- megadetector/postprocessing/classification_postprocessing.py +26 -10
- megadetector/postprocessing/combine_batch_outputs.py +2 -0
- megadetector/postprocessing/generate_csv_report.py +1 -1
- megadetector/postprocessing/load_api_results.py +1 -1
- megadetector/postprocessing/md_to_wi.py +1 -1
- megadetector/postprocessing/postprocess_batch_results.py +1 -1
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1 -1
- megadetector/postprocessing/separate_detections_into_folders.py +1 -1
- megadetector/postprocessing/subset_json_detector_output.py +1 -3
- megadetector/utils/ct_utils.py +71 -0
- megadetector/utils/md_tests.py +8 -7
- megadetector/utils/path_utils.py +4 -15
- megadetector/utils/wi_platform_utils.py +824 -0
- megadetector/utils/wi_taxonomy_utils.py +1711 -0
- megadetector/visualization/visualization_utils.py +1 -1
- megadetector/visualization/visualize_detector_output.py +7 -5
- megadetector/visualization/visualize_video_output.py +1 -1
- {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/METADATA +2 -2
- {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/RECORD +31 -28
- megadetector/utils/wi_utils.py +0 -2674
- {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/WHEEL +0 -0
- {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/licenses/LICENSE +0 -0
- {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/top_level.txt +0 -0
|
@@ -42,7 +42,7 @@ import pandas as pd
|
|
|
42
42
|
|
|
43
43
|
from copy import deepcopy
|
|
44
44
|
|
|
45
|
-
from megadetector.utils.
|
|
45
|
+
from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
|
|
46
46
|
from megadetector.utils.ct_utils import get_max_conf
|
|
47
47
|
from megadetector.utils.ct_utils import is_list_sorted
|
|
48
48
|
from megadetector.detection.run_detector import \
|
|
@@ -24,7 +24,7 @@ from collections.abc import Mapping
|
|
|
24
24
|
import pandas as pd
|
|
25
25
|
|
|
26
26
|
from megadetector.utils import ct_utils
|
|
27
|
-
from megadetector.utils.
|
|
27
|
+
from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
#%% Functions for loading .json results into a Pandas DataFrame, and writing back to .json
|
|
@@ -10,7 +10,7 @@ Converts the MD .json format to the WI predictions.json format.
|
|
|
10
10
|
|
|
11
11
|
import sys
|
|
12
12
|
import argparse
|
|
13
|
-
from megadetector.utils.
|
|
13
|
+
from megadetector.utils.wi_taxonomy_utils import generate_predictions_json_from_md_results
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
#%% Command-line driver
|
|
@@ -47,7 +47,7 @@ from tqdm import tqdm
|
|
|
47
47
|
from megadetector.visualization import visualization_utils as vis_utils
|
|
48
48
|
from megadetector.visualization import plot_utils
|
|
49
49
|
from megadetector.utils.write_html_image_list import write_html_image_list
|
|
50
|
-
from megadetector.utils.
|
|
50
|
+
from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
|
|
51
51
|
from megadetector.utils import path_utils
|
|
52
52
|
from megadetector.utils.ct_utils import args_to_object
|
|
53
53
|
from megadetector.utils.ct_utils import sets_overlap
|
|
@@ -637,7 +637,7 @@ def _find_matches_in_directory(dir_name_and_rows, options):
|
|
|
637
637
|
|
|
638
638
|
if 'max_detection_conf' not in row or 'detections' not in row or \
|
|
639
639
|
row['detections'] is None:
|
|
640
|
-
print('Skipping row {}'.format(i_directory_row))
|
|
640
|
+
# print('Skipping row {}'.format(i_directory_row))
|
|
641
641
|
continue
|
|
642
642
|
|
|
643
643
|
# Don't bother checking images with no detections above threshold
|
|
@@ -587,7 +587,7 @@ def separate_detections_into_folders(options):
|
|
|
587
587
|
# token = tokens[0]
|
|
588
588
|
for token in tokens:
|
|
589
589
|
subtokens = token.split('=')
|
|
590
|
-
assert len(subtokens) == 2 and is_float(subtokens[1]), \
|
|
590
|
+
assert (len(subtokens) == 2) and (is_float(subtokens[1])), \
|
|
591
591
|
'Illegal classification threshold {}'.format(token)
|
|
592
592
|
classification_thresholds[subtokens[0]] = float(subtokens[1])
|
|
593
593
|
|
|
@@ -221,7 +221,7 @@ def remove_classification_categories_below_count(data, options):
|
|
|
221
221
|
classification_category_ids_to_keep = set()
|
|
222
222
|
|
|
223
223
|
for classification_category_id in classification_category_id_to_count:
|
|
224
|
-
if classification_category_id_to_count[classification_category_id]
|
|
224
|
+
if classification_category_id_to_count[classification_category_id] >= \
|
|
225
225
|
options.remove_classification_categories_below_count:
|
|
226
226
|
classification_category_ids_to_keep.add(classification_category_id)
|
|
227
227
|
|
|
@@ -235,7 +235,6 @@ def remove_classification_categories_below_count(data, options):
|
|
|
235
235
|
if n_categories_removed == 0:
|
|
236
236
|
return data
|
|
237
237
|
|
|
238
|
-
|
|
239
238
|
# Filter the category list
|
|
240
239
|
output_classification_categories = {}
|
|
241
240
|
for category_id in data['classification_categories']:
|
|
@@ -245,7 +244,6 @@ def remove_classification_categories_below_count(data, options):
|
|
|
245
244
|
data['classification_categories'] = output_classification_categories
|
|
246
245
|
assert len(data['classification_categories']) == len(classification_category_ids_to_keep)
|
|
247
246
|
|
|
248
|
-
|
|
249
247
|
# If necessary, filter the category descriptions
|
|
250
248
|
if 'classification_category_descriptions' in data:
|
|
251
249
|
output_classification_category_descriptions = {}
|
megadetector/utils/ct_utils.py
CHANGED
|
@@ -16,6 +16,8 @@ import builtins
|
|
|
16
16
|
import datetime
|
|
17
17
|
import tempfile
|
|
18
18
|
import shutil
|
|
19
|
+
import platform
|
|
20
|
+
import sys
|
|
19
21
|
import uuid
|
|
20
22
|
|
|
21
23
|
import jsonpickle
|
|
@@ -848,6 +850,24 @@ def isnan(v):
|
|
|
848
850
|
return False
|
|
849
851
|
|
|
850
852
|
|
|
853
|
+
def compare_values_nan_equal(v0,v1):
|
|
854
|
+
"""
|
|
855
|
+
Utility function for comparing two values when we want to return True if both
|
|
856
|
+
values are NaN.
|
|
857
|
+
|
|
858
|
+
Args:
|
|
859
|
+
v0 (object): the first value to compare
|
|
860
|
+
v1 (object): the second value to compare
|
|
861
|
+
|
|
862
|
+
Returns:
|
|
863
|
+
bool: True if v0 == v1, or if both v0 and v1 are NaN
|
|
864
|
+
"""
|
|
865
|
+
|
|
866
|
+
if isinstance(v0,float) and isinstance(v1,float) and np.isnan(v0) and np.isnan(v1):
|
|
867
|
+
return True
|
|
868
|
+
return v0 == v1
|
|
869
|
+
|
|
870
|
+
|
|
851
871
|
def sets_overlap(set1, set2):
|
|
852
872
|
"""
|
|
853
873
|
Determines whether two sets overlap.
|
|
@@ -1053,6 +1073,57 @@ def make_test_folder(subfolder=None):
|
|
|
1053
1073
|
append_guid=True)
|
|
1054
1074
|
|
|
1055
1075
|
|
|
1076
|
+
#%% Environment utilities
|
|
1077
|
+
|
|
1078
|
+
def is_sphinx_build():
|
|
1079
|
+
"""
|
|
1080
|
+
Determine whether we are running in the context of our Sphinx build.
|
|
1081
|
+
|
|
1082
|
+
Returns:
|
|
1083
|
+
bool: True if we're running a Sphinx build
|
|
1084
|
+
"""
|
|
1085
|
+
|
|
1086
|
+
is_sphinx = hasattr(builtins, '__sphinx_build__')
|
|
1087
|
+
return is_sphinx
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def is_running_in_gha():
|
|
1091
|
+
"""
|
|
1092
|
+
Determine whether we are running on a GitHub Actions runner.
|
|
1093
|
+
|
|
1094
|
+
Returns:
|
|
1095
|
+
bool: True if we're running in a GHA runner
|
|
1096
|
+
"""
|
|
1097
|
+
|
|
1098
|
+
running_in_gha = False
|
|
1099
|
+
|
|
1100
|
+
if ('GITHUB_ACTIONS' in os.environ):
|
|
1101
|
+
# Documentation is inconsistent on how this variable presents itself
|
|
1102
|
+
if isinstance(os.environ['GITHUB_ACTIONS'],bool) and \
|
|
1103
|
+
os.environ['GITHUB_ACTIONS']:
|
|
1104
|
+
running_in_gha = True
|
|
1105
|
+
elif isinstance(os.environ['GITHUB_ACTIONS'],str) and \
|
|
1106
|
+
os.environ['GITHUB_ACTIONS'].lower() == ('true'):
|
|
1107
|
+
running_in_gha = True
|
|
1108
|
+
|
|
1109
|
+
return running_in_gha
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
def environment_is_wsl():
|
|
1113
|
+
"""
|
|
1114
|
+
Determines whether we're running in WSL.
|
|
1115
|
+
|
|
1116
|
+
Returns:
|
|
1117
|
+
True if we're running in WSL
|
|
1118
|
+
"""
|
|
1119
|
+
|
|
1120
|
+
if sys.platform not in ('linux','posix'):
|
|
1121
|
+
return False
|
|
1122
|
+
platform_string = ' '.join(platform.uname()).lower()
|
|
1123
|
+
return 'microsoft' in platform_string and 'wsl' in platform_string
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
|
|
1056
1127
|
#%% Tests
|
|
1057
1128
|
|
|
1058
1129
|
def test_write_json():
|
megadetector/utils/md_tests.py
CHANGED
|
@@ -21,6 +21,7 @@ since much of what it tries to test is, e.g., imports.
|
|
|
21
21
|
import os
|
|
22
22
|
import json
|
|
23
23
|
import glob
|
|
24
|
+
import sys
|
|
24
25
|
import tempfile
|
|
25
26
|
import urllib
|
|
26
27
|
import urllib.request
|
|
@@ -141,9 +142,6 @@ class MDTestOptions:
|
|
|
141
142
|
#: Number of cores to use for multi-CPU inference tests
|
|
142
143
|
self.n_cores_for_multiprocessing_tests = 2
|
|
143
144
|
|
|
144
|
-
#: Number of cores to use for multi-CPU video tests
|
|
145
|
-
self.n_cores_for_video_tests = 2
|
|
146
|
-
|
|
147
145
|
#: Batch size to use when testing batches of size > 1
|
|
148
146
|
self.alternative_batch_size = 3
|
|
149
147
|
|
|
@@ -1062,7 +1060,6 @@ def run_python_tests(options):
|
|
|
1062
1060
|
video_options.input_video_file = os.path.join(options.scratch_dir,options.test_videos[0])
|
|
1063
1061
|
video_options.output_json_file = os.path.join(options.scratch_dir,'single_video_output.json')
|
|
1064
1062
|
video_options.frame_sample = 10
|
|
1065
|
-
video_options.n_cores = options.n_cores_for_video_tests
|
|
1066
1063
|
video_options.detector_options = copy(options.detector_options)
|
|
1067
1064
|
|
|
1068
1065
|
_ = process_videos(video_options)
|
|
@@ -1086,7 +1083,6 @@ def run_python_tests(options):
|
|
|
1086
1083
|
video_options.output_video_file = None
|
|
1087
1084
|
video_options.recursive = True
|
|
1088
1085
|
video_options.verbose = True
|
|
1089
|
-
video_options.n_cores = options.n_cores_for_video_tests
|
|
1090
1086
|
video_options.json_confidence_threshold = 0.05
|
|
1091
1087
|
video_options.time_sample = 2
|
|
1092
1088
|
video_options.detector_options = copy(options.detector_options)
|
|
@@ -1295,7 +1291,7 @@ def run_cli_tests(options):
|
|
|
1295
1291
|
|
|
1296
1292
|
batch_string = ' --batch_size {}'.format(options.alternative_batch_size)
|
|
1297
1293
|
|
|
1298
|
-
# I reduce the number of loader workers here to force batching to actually
|
|
1294
|
+
# I reduce the number of loader workers here to force batching to actually append; with a small
|
|
1299
1295
|
# number of images and a few that are intentionally corrupt, with the default number of loader
|
|
1300
1296
|
# workers we end up with batches that are mostly just one image.
|
|
1301
1297
|
cmd = base_cmd + ' --use_image_queue --preprocess_on_image_queue --loader_workers 2' + batch_string
|
|
@@ -1591,9 +1587,9 @@ def run_cli_tests(options):
|
|
|
1591
1587
|
|
|
1592
1588
|
cmd += ' "{}" "{}"'.format(options.default_model,options.scratch_dir)
|
|
1593
1589
|
cmd += ' --output_json_file "{}"'.format(video_inference_output_file)
|
|
1594
|
-
cmd += ' --n_cores {}'.format(options.n_cores_for_video_tests)
|
|
1595
1590
|
cmd += ' --frame_sample 4'
|
|
1596
1591
|
cmd += ' --verbose'
|
|
1592
|
+
cmd += ' --recursive'
|
|
1597
1593
|
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
1598
1594
|
|
|
1599
1595
|
cmd_results = execute_and_print(cmd)
|
|
@@ -1789,6 +1785,11 @@ def test_suite_entry_point():
|
|
|
1789
1785
|
options.skip_localhost_downloads = True
|
|
1790
1786
|
options.skip_import_tests = False
|
|
1791
1787
|
|
|
1788
|
+
if sys.platform == 'darwin':
|
|
1789
|
+
print('Detected a Mac environment, widening tolerance')
|
|
1790
|
+
options.max_coord_error = 0.05
|
|
1791
|
+
options.max_conf_error = 0.05
|
|
1792
|
+
|
|
1792
1793
|
options = download_test_data(options)
|
|
1793
1794
|
|
|
1794
1795
|
run_tests(options)
|
megadetector/utils/path_utils.py
CHANGED
|
@@ -36,6 +36,7 @@ from tqdm import tqdm
|
|
|
36
36
|
from megadetector.utils.ct_utils import is_iterable
|
|
37
37
|
from megadetector.utils.ct_utils import make_test_folder
|
|
38
38
|
from megadetector.utils.ct_utils import sort_dictionary_by_value
|
|
39
|
+
from megadetector.utils.ct_utils import environment_is_wsl
|
|
39
40
|
|
|
40
41
|
# Should all be lower-case
|
|
41
42
|
IMG_EXTENSIONS = ('.jpg', '.jpeg', '.gif', '.png', '.tif', '.tiff', '.bmp')
|
|
@@ -622,21 +623,7 @@ def is_executable(filename):
|
|
|
622
623
|
return which(filename) is not None
|
|
623
624
|
|
|
624
625
|
|
|
625
|
-
#%%
|
|
626
|
-
|
|
627
|
-
def environment_is_wsl():
|
|
628
|
-
"""
|
|
629
|
-
Determines whether we're running in WSL.
|
|
630
|
-
|
|
631
|
-
Returns:
|
|
632
|
-
True if we're running in WSL.
|
|
633
|
-
"""
|
|
634
|
-
|
|
635
|
-
if sys.platform not in ('linux','posix'):
|
|
636
|
-
return False
|
|
637
|
-
platform_string = ' '.join(platform.uname()).lower()
|
|
638
|
-
return 'microsoft' in platform_string and 'wsl' in platform_string
|
|
639
|
-
|
|
626
|
+
#%% WSL utilities
|
|
640
627
|
|
|
641
628
|
def wsl_path_to_windows_path(filename, failure_behavior='none'):
|
|
642
629
|
r"""
|
|
@@ -731,6 +718,8 @@ def windows_path_to_wsl_path(filename, failure_behavior='none'):
|
|
|
731
718
|
# ...def window_path_to_wsl_path(...)
|
|
732
719
|
|
|
733
720
|
|
|
721
|
+
#%% Platform-independent file openers
|
|
722
|
+
|
|
734
723
|
def open_file_in_chrome(filename):
|
|
735
724
|
"""
|
|
736
725
|
Open a file in chrome, regardless of file type. I typically use this to open
|