megadetector 10.0.2__py3-none-any.whl → 10.0.4__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/zamba_to_md.py +188 -0
- megadetector/detection/process_video.py +165 -946
- megadetector/detection/pytorch_detector.py +575 -276
- megadetector/detection/run_detector_batch.py +629 -202
- megadetector/detection/run_md_and_speciesnet.py +1319 -0
- megadetector/detection/video_utils.py +243 -107
- megadetector/postprocessing/classification_postprocessing.py +12 -1
- megadetector/postprocessing/combine_batch_outputs.py +2 -0
- megadetector/postprocessing/compare_batch_results.py +21 -2
- megadetector/postprocessing/merge_detections.py +16 -12
- megadetector/postprocessing/separate_detections_into_folders.py +1 -1
- megadetector/postprocessing/subset_json_detector_output.py +1 -3
- megadetector/postprocessing/validate_batch_results.py +25 -2
- megadetector/tests/__init__.py +0 -0
- megadetector/tests/test_nms_synthetic.py +335 -0
- megadetector/utils/ct_utils.py +69 -5
- megadetector/utils/extract_frames_from_video.py +303 -0
- megadetector/utils/md_tests.py +583 -524
- megadetector/utils/path_utils.py +4 -15
- megadetector/utils/wi_utils.py +20 -4
- megadetector/visualization/visualization_utils.py +1 -1
- megadetector/visualization/visualize_db.py +8 -22
- megadetector/visualization/visualize_detector_output.py +7 -5
- megadetector/visualization/visualize_video_output.py +607 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/METADATA +134 -135
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/RECORD +30 -23
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/licenses/LICENSE +0 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/top_level.txt +0 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
animl_to_md.py
|
|
4
|
+
|
|
5
|
+
Convert a .csv file produced by the Animl package:
|
|
6
|
+
|
|
7
|
+
https://github.com/conservationtechlab/animl-py
|
|
8
|
+
|
|
9
|
+
...to a MD results file suitable for import into Timelapse.
|
|
10
|
+
|
|
11
|
+
Columns are expected to be:
|
|
12
|
+
|
|
13
|
+
file
|
|
14
|
+
category (MD category identifies: 1==animal, 2==person, 3==vehicle)
|
|
15
|
+
detection_conf
|
|
16
|
+
bbox1,bbox2,bbox3,bbox4
|
|
17
|
+
class
|
|
18
|
+
classification_conf
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
#%% Imports and constants
|
|
23
|
+
|
|
24
|
+
import sys
|
|
25
|
+
import argparse
|
|
26
|
+
|
|
27
|
+
import pandas as pd
|
|
28
|
+
|
|
29
|
+
from megadetector.utils.ct_utils import write_json
|
|
30
|
+
from megadetector.detection.run_detector import DEFAULT_DETECTOR_LABEL_MAP
|
|
31
|
+
detection_category_id_to_name = DEFAULT_DETECTOR_LABEL_MAP
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#%% Main function
|
|
35
|
+
|
|
36
|
+
def animl_results_to_md_results(input_file,output_file=None):
|
|
37
|
+
"""
|
|
38
|
+
Converts the .csv file [input_file] to the MD-formatted .json file [output_file].
|
|
39
|
+
|
|
40
|
+
If [output_file] is None, '.json' will be appended to the input file.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
if output_file is None:
|
|
44
|
+
output_file = input_file + '.json'
|
|
45
|
+
|
|
46
|
+
df = pd.read_csv(input_file)
|
|
47
|
+
|
|
48
|
+
expected_columns = ('file','category','detection_conf',
|
|
49
|
+
'bbox1','bbox2','bbox3','bbox4','class','classification_conf')
|
|
50
|
+
|
|
51
|
+
for s in expected_columns:
|
|
52
|
+
assert s in df.columns,\
|
|
53
|
+
'Expected column {} not found'.format(s)
|
|
54
|
+
|
|
55
|
+
classification_category_name_to_id = {}
|
|
56
|
+
filename_to_results = {}
|
|
57
|
+
|
|
58
|
+
# i_row = 0; row = df.iloc[i_row]
|
|
59
|
+
for i_row,row in df.iterrows():
|
|
60
|
+
|
|
61
|
+
# Is this the first detection we've seen for this file?
|
|
62
|
+
if row['file'] in filename_to_results:
|
|
63
|
+
im = filename_to_results[row['file']]
|
|
64
|
+
else:
|
|
65
|
+
im = {}
|
|
66
|
+
im['detections'] = []
|
|
67
|
+
im['file'] = row['file']
|
|
68
|
+
filename_to_results[im['file']] = im
|
|
69
|
+
|
|
70
|
+
assert isinstance(row['category'],int),'Invalid category identifier in row {}'.format(im['file'])
|
|
71
|
+
detection_category_id = str(row['category'])
|
|
72
|
+
assert detection_category_id in detection_category_id_to_name,\
|
|
73
|
+
'Unrecognized detection category ID {}'.format(detection_category_id)
|
|
74
|
+
|
|
75
|
+
detection = {}
|
|
76
|
+
detection['category'] = detection_category_id
|
|
77
|
+
detection['conf'] = row['detection_conf']
|
|
78
|
+
bbox = [row['bbox1'],row['bbox2'],row['bbox3'],row['bbox4']]
|
|
79
|
+
detection['bbox'] = bbox
|
|
80
|
+
classification_category_name = row['class']
|
|
81
|
+
|
|
82
|
+
# Have we seen this classification category before?
|
|
83
|
+
if classification_category_name in classification_category_name_to_id:
|
|
84
|
+
classification_category_id = \
|
|
85
|
+
classification_category_name_to_id[classification_category_name]
|
|
86
|
+
else:
|
|
87
|
+
classification_category_id = str(len(classification_category_name_to_id))
|
|
88
|
+
classification_category_name_to_id[classification_category_name] = \
|
|
89
|
+
classification_category_id
|
|
90
|
+
|
|
91
|
+
classifications = [[classification_category_id,row['classification_conf']]]
|
|
92
|
+
detection['classifications'] = classifications
|
|
93
|
+
|
|
94
|
+
im['detections'].append(detection)
|
|
95
|
+
|
|
96
|
+
# ...for each row
|
|
97
|
+
|
|
98
|
+
info = {}
|
|
99
|
+
info['format_version'] = '1.3'
|
|
100
|
+
info['detector'] = 'Animl'
|
|
101
|
+
info['classifier'] = 'Animl'
|
|
102
|
+
|
|
103
|
+
results = {}
|
|
104
|
+
results['info'] = info
|
|
105
|
+
results['detection_categories'] = detection_category_id_to_name
|
|
106
|
+
results['classification_categories'] = \
|
|
107
|
+
{v: k for k, v in classification_category_name_to_id.items()}
|
|
108
|
+
results['images'] = list(filename_to_results.values())
|
|
109
|
+
|
|
110
|
+
write_json(output_file,results)
|
|
111
|
+
|
|
112
|
+
# ...animl_results_to_md_results(...)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
#%% Interactive driver
|
|
116
|
+
|
|
117
|
+
if False:
|
|
118
|
+
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
#%%
|
|
122
|
+
|
|
123
|
+
input_file = r"G:\temp\animl-runs\animl-runs\Coati_v2\manifest.csv"
|
|
124
|
+
output_file = None
|
|
125
|
+
animl_results_to_md_results(input_file,output_file)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
#%% Command-line driver
|
|
129
|
+
|
|
130
|
+
def main():
|
|
131
|
+
"""
|
|
132
|
+
Command-line driver for animl_to_md
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
parser = argparse.ArgumentParser(
|
|
136
|
+
description='Convert an Animl-formatted .csv results file to MD-formatted .json results file')
|
|
137
|
+
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
'input_file',
|
|
140
|
+
type=str,
|
|
141
|
+
help='input .csv file')
|
|
142
|
+
|
|
143
|
+
parser.add_argument(
|
|
144
|
+
'--output_file',
|
|
145
|
+
type=str,
|
|
146
|
+
default=None,
|
|
147
|
+
help='output .json file (defaults to input file appended with ".json")')
|
|
148
|
+
|
|
149
|
+
if len(sys.argv[1:]) == 0:
|
|
150
|
+
parser.print_help()
|
|
151
|
+
parser.exit()
|
|
152
|
+
|
|
153
|
+
args = parser.parse_args()
|
|
154
|
+
|
|
155
|
+
animl_results_to_md_results(args.input_file,args.output_file)
|
|
156
|
+
|
|
157
|
+
if __name__ == '__main__':
|
|
158
|
+
main()
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
zamba_to_md.py
|
|
4
|
+
|
|
5
|
+
Convert a labels.csv file produced by Zamba Cloud to a MD results file suitable
|
|
6
|
+
for import into Timelapse.
|
|
7
|
+
|
|
8
|
+
Columns are expected to be:
|
|
9
|
+
|
|
10
|
+
video_uuid (not used)
|
|
11
|
+
original_filename (assumed to be a relative path name)
|
|
12
|
+
top_k_label,top_k_probability, for k = 1..N
|
|
13
|
+
[category name 1],[category name 2],...
|
|
14
|
+
corrected_label
|
|
15
|
+
|
|
16
|
+
Because the MD results file fundamentally stores detections, what we'll
|
|
17
|
+
actually do is create bogus detections that fill the entire image.
|
|
18
|
+
|
|
19
|
+
There is no special handling of empty/blank categories; because these results are
|
|
20
|
+
based on a classifier, rather than a detector (where "blank" would be the absence of
|
|
21
|
+
all other categories), "blank" can be queried in Timelapse just like any other class.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
#%% Imports and constants
|
|
26
|
+
|
|
27
|
+
import sys
|
|
28
|
+
import argparse
|
|
29
|
+
|
|
30
|
+
import pandas as pd
|
|
31
|
+
|
|
32
|
+
from megadetector.utils.ct_utils import write_json
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
#%% Main function
|
|
36
|
+
|
|
37
|
+
def zamba_results_to_md_results(input_file,output_file=None):
|
|
38
|
+
"""
|
|
39
|
+
Converts the .csv file [input_file] to the MD-formatted .json file [output_file].
|
|
40
|
+
|
|
41
|
+
If [output_file] is None, '.json' will be appended to the input file.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
input_file (str): the .csv file to convert
|
|
45
|
+
output_file (str, optional): the output .json file (defaults to
|
|
46
|
+
[input_file].json)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
if output_file is None:
|
|
50
|
+
output_file = input_file + '.json'
|
|
51
|
+
|
|
52
|
+
df = pd.read_csv(input_file)
|
|
53
|
+
|
|
54
|
+
expected_columns = ('video_uuid','corrected_label','original_filename')
|
|
55
|
+
for s in expected_columns:
|
|
56
|
+
assert s in df.columns,\
|
|
57
|
+
'Expected column {} not found, are you sure this is a Zamba results .csv file?'.format(
|
|
58
|
+
s)
|
|
59
|
+
|
|
60
|
+
# How many results are included per file?
|
|
61
|
+
assert 'top_1_probability' in df.columns and 'top_1_label' in df.columns
|
|
62
|
+
top_k = 2
|
|
63
|
+
while(True):
|
|
64
|
+
p_string = 'top_' + str(top_k) + '_probability'
|
|
65
|
+
label_string = 'top_' + str(top_k) + '_label'
|
|
66
|
+
|
|
67
|
+
if p_string in df.columns:
|
|
68
|
+
assert label_string in df.columns,\
|
|
69
|
+
'Oops, {} is a column but {} is not'.format(
|
|
70
|
+
p_string,label_string)
|
|
71
|
+
top_k += 1
|
|
72
|
+
continue
|
|
73
|
+
else:
|
|
74
|
+
assert label_string not in df.columns,\
|
|
75
|
+
'Oops, {} is a column but {} is not'.format(
|
|
76
|
+
label_string,p_string)
|
|
77
|
+
top_k -= 1
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
print('Found {} probability column pairs'.format(top_k))
|
|
81
|
+
|
|
82
|
+
# Category names start after the fixed columns and the probability columns
|
|
83
|
+
category_names = []
|
|
84
|
+
column_names = list(df.columns)
|
|
85
|
+
first_category_name_index = 0
|
|
86
|
+
while('top_' in column_names[first_category_name_index] or \
|
|
87
|
+
column_names[first_category_name_index] in expected_columns):
|
|
88
|
+
first_category_name_index += 1
|
|
89
|
+
|
|
90
|
+
i_column = first_category_name_index
|
|
91
|
+
while( (i_column < len(column_names)) and (column_names[i_column] != 'corrected_label') ):
|
|
92
|
+
category_names.append(column_names[i_column])
|
|
93
|
+
i_column += 1
|
|
94
|
+
|
|
95
|
+
print('Found {} categories:\n'.format(len(category_names)))
|
|
96
|
+
|
|
97
|
+
for s in category_names:
|
|
98
|
+
print(s)
|
|
99
|
+
|
|
100
|
+
info = {}
|
|
101
|
+
info['format_version'] = '1.3'
|
|
102
|
+
info['detector'] = 'Zamba Cloud'
|
|
103
|
+
info['classifier'] = 'Zamba Cloud'
|
|
104
|
+
|
|
105
|
+
detection_category_id_to_name = {}
|
|
106
|
+
for category_id,category_name in enumerate(category_names):
|
|
107
|
+
detection_category_id_to_name[str(category_id)] = category_name
|
|
108
|
+
detection_category_name_to_id = {v: k for k, v in detection_category_id_to_name.items()}
|
|
109
|
+
|
|
110
|
+
images = []
|
|
111
|
+
|
|
112
|
+
# i_row = 0; row = df.iloc[i_row]
|
|
113
|
+
for i_row,row in df.iterrows():
|
|
114
|
+
|
|
115
|
+
im = {}
|
|
116
|
+
images.append(im)
|
|
117
|
+
im['file'] = row['original_filename']
|
|
118
|
+
|
|
119
|
+
detections = []
|
|
120
|
+
|
|
121
|
+
# k = 1
|
|
122
|
+
for k in range(1,top_k+1):
|
|
123
|
+
label = row['top_{}_label'.format(k)]
|
|
124
|
+
confidence = row['top_{}_probability'.format(k)]
|
|
125
|
+
det = {}
|
|
126
|
+
det['category'] = detection_category_name_to_id[label]
|
|
127
|
+
det['conf'] = confidence
|
|
128
|
+
det['bbox'] = [0,0,1.0,1.0]
|
|
129
|
+
detections.append(det)
|
|
130
|
+
|
|
131
|
+
im['detections'] = detections
|
|
132
|
+
|
|
133
|
+
# ...for each row
|
|
134
|
+
|
|
135
|
+
results = {}
|
|
136
|
+
results['info'] = info
|
|
137
|
+
results['detection_categories'] = detection_category_id_to_name
|
|
138
|
+
results['images'] = images
|
|
139
|
+
|
|
140
|
+
write_json(output_file,results)
|
|
141
|
+
|
|
142
|
+
# ...zamba_results_to_md_results(...)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
#%% Interactive driver
|
|
146
|
+
|
|
147
|
+
if False:
|
|
148
|
+
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
#%%
|
|
152
|
+
|
|
153
|
+
input_file = r"G:\temp\labels-job-b95a4b76-e332-4e17-ab40-03469392d36a-2023-11-04_16-28-50.060130.csv"
|
|
154
|
+
output_file = None
|
|
155
|
+
zamba_results_to_md_results(input_file,output_file)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
#%% Command-line driver
|
|
159
|
+
|
|
160
|
+
def main():
|
|
161
|
+
"""
|
|
162
|
+
Command-line driver for zamba_to_md
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
parser = argparse.ArgumentParser(
|
|
166
|
+
description='Convert a Zamba-formatted .csv results file to a MD-formatted .json results file')
|
|
167
|
+
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
'input_file',
|
|
170
|
+
type=str,
|
|
171
|
+
help='input .csv file')
|
|
172
|
+
|
|
173
|
+
parser.add_argument(
|
|
174
|
+
'--output_file',
|
|
175
|
+
type=str,
|
|
176
|
+
default=None,
|
|
177
|
+
help='output .json file (defaults to input file appended with ".json")')
|
|
178
|
+
|
|
179
|
+
if len(sys.argv[1:]) == 0:
|
|
180
|
+
parser.print_help()
|
|
181
|
+
parser.exit()
|
|
182
|
+
|
|
183
|
+
args = parser.parse_args()
|
|
184
|
+
|
|
185
|
+
zamba_results_to_md_results(args.input_file,args.output_file)
|
|
186
|
+
|
|
187
|
+
if __name__ == '__main__':
|
|
188
|
+
main()
|