megadetector 5.0.23__py3-none-any.whl → 5.0.24__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/api/synchronous/api_core/animal_detection_api/api_backend.py +2 -3
- megadetector/classification/merge_classification_detection_output.py +2 -2
- megadetector/data_management/coco_to_labelme.py +2 -1
- megadetector/data_management/databases/integrity_check_json_db.py +15 -14
- megadetector/data_management/databases/subset_json_db.py +49 -21
- megadetector/data_management/mewc_to_md.py +340 -0
- megadetector/data_management/wi_to_md.py +41 -0
- megadetector/data_management/yolo_output_to_md_output.py +15 -8
- megadetector/detection/process_video.py +24 -7
- megadetector/detection/pytorch_detector.py +841 -160
- megadetector/detection/run_detector.py +340 -146
- megadetector/detection/run_detector_batch.py +304 -68
- megadetector/detection/run_inference_with_yolov5_val.py +61 -4
- megadetector/detection/tf_detector.py +6 -1
- megadetector/postprocessing/{combine_api_outputs.py → combine_batch_outputs.py} +10 -13
- megadetector/postprocessing/compare_batch_results.py +68 -6
- megadetector/postprocessing/md_to_labelme.py +7 -7
- megadetector/postprocessing/md_to_wi.py +40 -0
- megadetector/postprocessing/merge_detections.py +1 -1
- megadetector/postprocessing/postprocess_batch_results.py +10 -3
- megadetector/postprocessing/separate_detections_into_folders.py +32 -4
- megadetector/postprocessing/validate_batch_results.py +9 -4
- megadetector/utils/ct_utils.py +165 -45
- megadetector/utils/gpu_test.py +107 -0
- megadetector/utils/md_tests.py +355 -108
- megadetector/utils/path_utils.py +9 -2
- megadetector/utils/wi_utils.py +1794 -0
- megadetector/visualization/visualization_utils.py +82 -16
- megadetector/visualization/visualize_db.py +25 -7
- megadetector/visualization/visualize_detector_output.py +60 -13
- {megadetector-5.0.23.dist-info → megadetector-5.0.24.dist-info}/METADATA +10 -24
- {megadetector-5.0.23.dist-info → megadetector-5.0.24.dist-info}/RECORD +35 -33
- megadetector/detection/detector_training/__init__.py +0 -0
- megadetector/detection/detector_training/model_main_tf2.py +0 -114
- megadetector/utils/torch_test.py +0 -32
- {megadetector-5.0.23.dist-info → megadetector-5.0.24.dist-info}/LICENSE +0 -0
- {megadetector-5.0.23.dist-info → megadetector-5.0.24.dist-info}/WHEEL +0 -0
- {megadetector-5.0.23.dist-info → megadetector-5.0.24.dist-info}/top_level.txt +0 -0
megadetector/utils/ct_utils.py
CHANGED
|
@@ -28,8 +28,8 @@ image_extensions = ['.jpg', '.jpeg', '.gif', '.png']
|
|
|
28
28
|
|
|
29
29
|
def truncate_float_array(xs, precision=3):
|
|
30
30
|
"""
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Truncates the fractional portion of each floating-point value in the array [xs]
|
|
32
|
+
to a specific number of floating-point digits.
|
|
33
33
|
|
|
34
34
|
Args:
|
|
35
35
|
xs (list): list of floats to truncate
|
|
@@ -42,6 +42,37 @@ def truncate_float_array(xs, precision=3):
|
|
|
42
42
|
return [truncate_float(x, precision=precision) for x in xs]
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def round_float_array(xs, precision=3):
|
|
46
|
+
"""
|
|
47
|
+
Truncates the fractional portion of each floating-point value in the array [xs]
|
|
48
|
+
to a specific number of floating-point digits.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
xs (list): list of floats to round
|
|
52
|
+
precision (int, optional): the number of significant digits to preserve, should be >= 1
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
list: list of rounded floats
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
return [round_float(x,precision) for x in xs]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def round_float(x, precision=3):
|
|
62
|
+
"""
|
|
63
|
+
Convenience wrapper for the native Python round()
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
x (float): number to truncate
|
|
67
|
+
precision (int, optional): the number of significant digits to preserve, should be >= 1
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
float: rounded value
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
return round(x,precision)
|
|
74
|
+
|
|
75
|
+
|
|
45
76
|
def truncate_float(x, precision=3):
|
|
46
77
|
"""
|
|
47
78
|
Truncates the fractional portion of a floating-point value to a specific number of
|
|
@@ -63,26 +94,7 @@ def truncate_float(x, precision=3):
|
|
|
63
94
|
float: truncated version of [x]
|
|
64
95
|
"""
|
|
65
96
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if np.isclose(x, 0):
|
|
69
|
-
|
|
70
|
-
return 0
|
|
71
|
-
|
|
72
|
-
elif (x > 1):
|
|
73
|
-
|
|
74
|
-
fractional_component = x - 1.0
|
|
75
|
-
return 1 + truncate_float(fractional_component)
|
|
76
|
-
|
|
77
|
-
else:
|
|
78
|
-
|
|
79
|
-
# Determine the factor, which shifts the decimal point of x
|
|
80
|
-
# just behind the last significant digit.
|
|
81
|
-
factor = math.pow(10, precision - 1 - math.floor(math.log10(abs(x))))
|
|
82
|
-
|
|
83
|
-
# Shift decimal point by multiplication with factor, flooring, and
|
|
84
|
-
# division by factor.
|
|
85
|
-
return math.floor(x * factor)/factor
|
|
97
|
+
return math.floor(x * (10 ** precision)) / (10 ** precision)
|
|
86
98
|
|
|
87
99
|
|
|
88
100
|
def args_to_object(args, obj):
|
|
@@ -187,7 +199,8 @@ def write_json(path, content, indent=1):
|
|
|
187
199
|
|
|
188
200
|
def convert_yolo_to_xywh(yolo_box):
|
|
189
201
|
"""
|
|
190
|
-
Converts a YOLO format bounding box
|
|
202
|
+
Converts a YOLO format bounding box [x_center, y_center, w, h] to
|
|
203
|
+
[x_min, y_min, width_of_box, height_of_box].
|
|
191
204
|
|
|
192
205
|
Args:
|
|
193
206
|
yolo_box (list): bounding box of format [x_center, y_center, width_of_box, height_of_box]
|
|
@@ -202,37 +215,21 @@ def convert_yolo_to_xywh(yolo_box):
|
|
|
202
215
|
return [x_min, y_min, width_of_box, height_of_box]
|
|
203
216
|
|
|
204
217
|
|
|
205
|
-
def
|
|
218
|
+
def convert_xywh_to_xyxy(api_box):
|
|
206
219
|
"""
|
|
207
|
-
Converts an xywh bounding box (the
|
|
208
|
-
|
|
220
|
+
Converts an xywh bounding box (the MD output format) to an xyxy bounding box (the format
|
|
221
|
+
produced by TF-based MD models).
|
|
209
222
|
|
|
210
223
|
Args:
|
|
211
|
-
api_box: bbox
|
|
224
|
+
api_box (list): bbox formatted as [x_min, y_min, width_of_box, height_of_box]
|
|
212
225
|
|
|
213
226
|
Returns:
|
|
214
|
-
list: bbox
|
|
227
|
+
list: bbox formatted as [x_min, y_min, x_max, y_max]
|
|
215
228
|
"""
|
|
216
|
-
|
|
229
|
+
|
|
217
230
|
x_min, y_min, width_of_box, height_of_box = api_box
|
|
218
231
|
x_max = x_min + width_of_box
|
|
219
232
|
y_max = y_min + height_of_box
|
|
220
|
-
return [y_min, x_min, y_max, x_max]
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def convert_xywh_to_xyxy(api_bbox):
|
|
224
|
-
"""
|
|
225
|
-
Converts an xywh bounding box (the MD output format) to an xyxy bounding box.
|
|
226
|
-
|
|
227
|
-
Args:
|
|
228
|
-
api_bbox (list): bbox formatted as [x_min, y_min, width_of_box, height_of_box]
|
|
229
|
-
|
|
230
|
-
Returns:
|
|
231
|
-
list: bbox formatted as [x_min, y_min, x_max, y_max]
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
x_min, y_min, width_of_box, height_of_box = api_bbox
|
|
235
|
-
x_max, y_max = x_min + width_of_box, y_min + height_of_box
|
|
236
233
|
return [x_min, y_min, x_max, y_max]
|
|
237
234
|
|
|
238
235
|
|
|
@@ -706,7 +703,130 @@ def is_function_name(s,calling_namespace):
|
|
|
706
703
|
callable(locals().get(s)) or \
|
|
707
704
|
callable(calling_namespace.get(s)) or \
|
|
708
705
|
callable(getattr(builtins, s, None))
|
|
706
|
+
|
|
709
707
|
|
|
708
|
+
# From https://gist.github.com/fralau/061a4f6c13251367ef1d9a9a99fb3e8d
|
|
709
|
+
def parse_kvp(s,kv_separator='='):
|
|
710
|
+
"""
|
|
711
|
+
Parse a key/value pair, separated by [kv_separator]. Errors if s is not
|
|
712
|
+
a valid key/value pair string.
|
|
713
|
+
|
|
714
|
+
Args:
|
|
715
|
+
s (str): the string to parse
|
|
716
|
+
kv_separator (str, optional): the string separating keys from values.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
tuple: a 2-tuple formatted as (key,value)
|
|
720
|
+
"""
|
|
721
|
+
|
|
722
|
+
items = s.split(kv_separator)
|
|
723
|
+
assert len(items) > 1, 'Illegal key-value pair'
|
|
724
|
+
key = items[0].strip()
|
|
725
|
+
if len(items) > 1:
|
|
726
|
+
value = kv_separator.join(items[1:])
|
|
727
|
+
return (key, value)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def parse_kvp_list(items,kv_separator='=',d=None):
|
|
731
|
+
"""
|
|
732
|
+
Parse a list key-value pairs into a dictionary. If items is None or [],
|
|
733
|
+
returns {}.
|
|
734
|
+
|
|
735
|
+
Args:
|
|
736
|
+
items (list): the list of KVPs to parse
|
|
737
|
+
kv_separator (str, optional): the string separating keys from values.
|
|
738
|
+
d (dict, optional): the initial dictionary, defaults to {}
|
|
739
|
+
|
|
740
|
+
Returns:
|
|
741
|
+
dict: a dict mapping keys to values
|
|
742
|
+
"""
|
|
743
|
+
|
|
744
|
+
if d is None:
|
|
745
|
+
d = {}
|
|
746
|
+
|
|
747
|
+
if items is None or len(items) == 0:
|
|
748
|
+
return d
|
|
749
|
+
|
|
750
|
+
for item in items:
|
|
751
|
+
key, value = parse_kvp(item)
|
|
752
|
+
d[key] = value
|
|
753
|
+
|
|
754
|
+
return d
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def dict_to_kvp_list(d,
|
|
758
|
+
item_separator=' ',
|
|
759
|
+
kv_separator='=',
|
|
760
|
+
non_string_value_handling='error'):
|
|
761
|
+
"""
|
|
762
|
+
Convert a string <--> string dict into a string containing list of list of
|
|
763
|
+
key-value pairs. I.e., converts {'a':'dog','b':'cat'} to 'a=dog b=cat'. If
|
|
764
|
+
d is None, returns None. If d is empty, returns ''.
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
d (dict): the dictionary to convert, must contain only strings
|
|
768
|
+
item_separator (str, optional): the delimiter between KV pairs
|
|
769
|
+
kv_separator (str, optional): the separator betweena a key and its value
|
|
770
|
+
non_string_value_handling (str, optional): what do do with non-string values,
|
|
771
|
+
can be "omit", "error", or "convert"
|
|
772
|
+
|
|
773
|
+
Returns:
|
|
774
|
+
str: the string representation of [d]
|
|
775
|
+
"""
|
|
776
|
+
|
|
777
|
+
if d is None:
|
|
778
|
+
return None
|
|
779
|
+
|
|
780
|
+
if len(d) == 0:
|
|
781
|
+
return ''
|
|
782
|
+
|
|
783
|
+
s = ''
|
|
784
|
+
for k in d.keys():
|
|
785
|
+
assert isinstance(k,str), 'Input {} is not a str <--> str dict'.format(str(d))
|
|
786
|
+
v = d[k]
|
|
787
|
+
if not isinstance(v,str):
|
|
788
|
+
if non_string_value_handling == 'error':
|
|
789
|
+
raise ValueError('Input {} is not a str <--> str dict'.format(str(d)))
|
|
790
|
+
elif non_string_value_handling == 'omit':
|
|
791
|
+
continue
|
|
792
|
+
elif non_string_value_handling == 'convert':
|
|
793
|
+
v = str(v)
|
|
794
|
+
else:
|
|
795
|
+
raise ValueError('Unrecognized non_string_value_handling value: {}'.format(
|
|
796
|
+
non_string_value_handling))
|
|
797
|
+
if s is None:
|
|
798
|
+
s = ''
|
|
799
|
+
else:
|
|
800
|
+
s += item_separator
|
|
801
|
+
s += k + kv_separator + v
|
|
802
|
+
|
|
803
|
+
return s
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def parse_bool_string(s):
|
|
807
|
+
"""
|
|
808
|
+
Convert the strings "true" or "false" to boolean values. Case-insensitive, discards
|
|
809
|
+
leading and trailing whitespace. If s is already a bool, returns s.
|
|
810
|
+
|
|
811
|
+
Args:
|
|
812
|
+
s (str or bool): the string to parse, or the bool to return
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
bool: the parsed value
|
|
816
|
+
"""
|
|
817
|
+
|
|
818
|
+
if isinstance(s,bool):
|
|
819
|
+
return s
|
|
820
|
+
s = s.lower().strip()
|
|
821
|
+
if s == 'true':
|
|
822
|
+
return True
|
|
823
|
+
elif s == 'false':
|
|
824
|
+
return False
|
|
825
|
+
else:
|
|
826
|
+
raise ValueError('Cannot parse bool from string {}'.format(str(s)))
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
#%% Test driver
|
|
710
830
|
|
|
711
831
|
def __module_test__():
|
|
712
832
|
"""
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
gpu_test.py
|
|
4
|
+
|
|
5
|
+
Simple script to verify CUDA availability, used to verify a CUDA environment
|
|
6
|
+
for TF or PyTorch
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
#%% Torch/TF test functions
|
|
11
|
+
|
|
12
|
+
def torch_test():
|
|
13
|
+
"""
|
|
14
|
+
Print diagnostic information about Torch/CUDA status, including Torch/CUDA versions
|
|
15
|
+
and all available CUDA device names.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
int: The number of CUDA devices reported by PyTorch.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import torch
|
|
23
|
+
except Exception as e: #noqa
|
|
24
|
+
print('PyTorch unavailable, not running PyTorch tests. PyTorch import error was:\n{}'.format(
|
|
25
|
+
str(e)))
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
print('Torch version: {}'.format(str(torch.__version__)))
|
|
29
|
+
print('CUDA available (according to PyTorch): {}'.format(torch.cuda.is_available()))
|
|
30
|
+
if torch.cuda.is_available():
|
|
31
|
+
print('CUDA version (according to PyTorch): {}'.format(torch.version.cuda))
|
|
32
|
+
print('CuDNN version (according to PyTorch): {}'.format(torch.backends.cudnn.version()))
|
|
33
|
+
|
|
34
|
+
device_ids = list(range(torch.cuda.device_count()))
|
|
35
|
+
|
|
36
|
+
if len(device_ids) > 0:
|
|
37
|
+
cuda_str = 'Found {} CUDA devices:'.format(len(device_ids))
|
|
38
|
+
print(cuda_str)
|
|
39
|
+
|
|
40
|
+
for device_id in device_ids:
|
|
41
|
+
device_name = 'unknown'
|
|
42
|
+
try:
|
|
43
|
+
device_name = torch.cuda.get_device_name(device=device_id)
|
|
44
|
+
except Exception as e: #noqa
|
|
45
|
+
pass
|
|
46
|
+
print('{}: {}'.format(device_id,device_name))
|
|
47
|
+
else:
|
|
48
|
+
print('No CUDA GPUs reported by PyTorch')
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
if torch.backends.mps.is_built and torch.backends.mps.is_available():
|
|
52
|
+
print('PyTorch reports that Metal Performance Shaders are available')
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
return len(device_ids)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def tf_test():
|
|
59
|
+
"""
|
|
60
|
+
Print diagnostic information about TF/CUDA status.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
int: The number of CUDA devices reported by PyTorch.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
import tensorflow as tf
|
|
68
|
+
except Exception as e: #noqa
|
|
69
|
+
print('TensorFlow unavailable, not running TF tests. TF import error was:\n{}'.format(
|
|
70
|
+
str(e)))
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
from tensorflow.python.platform import build_info as build
|
|
74
|
+
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']}")
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
from tensorflow.python.compiler.tensorrt import trt_convert as trt
|
|
80
|
+
print("Linked TensorRT version: {}".format(trt.trt_utils._pywrap_py_utils.get_linked_tensorrt_version()))
|
|
81
|
+
except Exception:
|
|
82
|
+
print('Could not probe TensorRT version')
|
|
83
|
+
|
|
84
|
+
gpus = tf.config.list_physical_devices('GPU')
|
|
85
|
+
if gpus is None:
|
|
86
|
+
gpus = []
|
|
87
|
+
|
|
88
|
+
if len(gpus) > 0:
|
|
89
|
+
print('TensorFlow found the following GPUs:')
|
|
90
|
+
for gpu in gpus:
|
|
91
|
+
print(gpu.name)
|
|
92
|
+
|
|
93
|
+
else:
|
|
94
|
+
print('No GPUs reported by TensorFlow')
|
|
95
|
+
|
|
96
|
+
return len(gpus)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
#%% Command-line driver
|
|
100
|
+
|
|
101
|
+
if __name__ == '__main__':
|
|
102
|
+
|
|
103
|
+
print('*** Running Torch tests ***\n')
|
|
104
|
+
torch_test()
|
|
105
|
+
|
|
106
|
+
print('\n*** Running TF tests ***\n')
|
|
107
|
+
tf_test()
|