megadetector 5.0.22__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 +306 -70
- 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 +172 -57
- megadetector/utils/gpu_test.py +107 -0
- megadetector/utils/md_tests.py +363 -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.22.dist-info → megadetector-5.0.24.dist-info}/LICENSE +0 -0
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/METADATA +129 -143
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/RECORD +35 -33
- {megadetector-5.0.22.dist-info → megadetector-5.0.24.dist-info}/top_level.txt +0 -0
- 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.22.dist-info → megadetector-5.0.24.dist-info}/WHEEL +0 -0
|
@@ -14,9 +14,8 @@ import redis
|
|
|
14
14
|
import argparse
|
|
15
15
|
import PIL
|
|
16
16
|
|
|
17
|
-
from
|
|
18
|
-
|
|
19
|
-
from detection.run_detector import load_detector, convert_to_tf_coords
|
|
17
|
+
from detection.run_detector import load_detector
|
|
18
|
+
from utils.ct_utils import convert_xywh_to_xyxy as convert_to_tf_coords
|
|
20
19
|
import config
|
|
21
20
|
import visualization.visualization_utils as vis_utils
|
|
22
21
|
|
|
@@ -70,7 +70,7 @@ from typing import Any
|
|
|
70
70
|
import pandas as pd
|
|
71
71
|
from tqdm import tqdm
|
|
72
72
|
|
|
73
|
-
from megadetector.utils.ct_utils import
|
|
73
|
+
from megadetector.utils.ct_utils import round_float
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
#%% Example usage
|
|
@@ -124,7 +124,7 @@ def row_to_classification_list(row: Mapping[str, Any],
|
|
|
124
124
|
|
|
125
125
|
# filter out confidences below the threshold, and set precision to 4
|
|
126
126
|
result = [
|
|
127
|
-
(k,
|
|
127
|
+
(k, round_float(conf, precision=4))
|
|
128
128
|
for k, conf in result if conf >= threshold
|
|
129
129
|
]
|
|
130
130
|
|
|
@@ -18,6 +18,7 @@ from tqdm import tqdm
|
|
|
18
18
|
from collections import defaultdict
|
|
19
19
|
|
|
20
20
|
from megadetector.visualization.visualization_utils import open_image
|
|
21
|
+
from megadetector.detection.run_detector import FAILURE_IMAGE_OPEN
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
#%% Functions
|
|
@@ -145,7 +146,7 @@ def coco_to_labelme(coco_data,image_base,overwrite=False,bypass_image_size_check
|
|
|
145
146
|
except Exception:
|
|
146
147
|
print('Warning: cannot open image {}'.format(im_full_path))
|
|
147
148
|
if 'failure' not in im:
|
|
148
|
-
im['failure'] =
|
|
149
|
+
im['failure'] = FAILURE_IMAGE_OPEN
|
|
149
150
|
|
|
150
151
|
# ...if we need to read w/h information
|
|
151
152
|
|
|
@@ -86,7 +86,7 @@ def _check_image_existence_and_size(image,options=None):
|
|
|
86
86
|
options (IntegrityCheckOptions): parameters impacting validation
|
|
87
87
|
|
|
88
88
|
Returns:
|
|
89
|
-
|
|
89
|
+
str: None if this image passes validation, otherwise an error string
|
|
90
90
|
"""
|
|
91
91
|
|
|
92
92
|
if options is None:
|
|
@@ -96,23 +96,23 @@ def _check_image_existence_and_size(image,options=None):
|
|
|
96
96
|
|
|
97
97
|
filePath = os.path.join(options.baseDir,image['file_name'])
|
|
98
98
|
if not os.path.isfile(filePath):
|
|
99
|
-
|
|
100
|
-
return
|
|
99
|
+
s = 'Image path {} does not exist'.format(filePath)
|
|
100
|
+
return s
|
|
101
101
|
|
|
102
102
|
if options.bCheckImageSizes:
|
|
103
103
|
if not ('height' in image and 'width' in image):
|
|
104
|
-
|
|
105
|
-
return
|
|
104
|
+
s = 'Missing image size in {}'.format(filePath)
|
|
105
|
+
return s
|
|
106
106
|
|
|
107
107
|
# width, height = Image.open(filePath).size
|
|
108
108
|
pil_im = open_image(filePath)
|
|
109
109
|
width,height = pil_im.size
|
|
110
110
|
if (not (width == image['width'] and height == image['height'])):
|
|
111
|
-
|
|
112
|
-
image['id'], filePath, image['width'], image['height'], width, height)
|
|
113
|
-
return
|
|
111
|
+
s = 'Size mismatch for image {}: {} (reported {},{}, actual {},{})'.format(
|
|
112
|
+
image['id'], filePath, image['width'], image['height'], width, height)
|
|
113
|
+
return s
|
|
114
114
|
|
|
115
|
-
return
|
|
115
|
+
return None
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
def integrity_check_json_db(jsonFile, options=None):
|
|
@@ -287,6 +287,7 @@ def integrity_check_json_db(jsonFile, options=None):
|
|
|
287
287
|
if fn_relative not in image_paths_in_json:
|
|
288
288
|
unused_files.append(fn_relative)
|
|
289
289
|
|
|
290
|
+
# List of (filename,error_string) tuples
|
|
290
291
|
validation_errors = []
|
|
291
292
|
|
|
292
293
|
# If we're checking image existence but not image size, we don't need to read the images
|
|
@@ -298,8 +299,8 @@ def integrity_check_json_db(jsonFile, options=None):
|
|
|
298
299
|
image_paths_relative_set = set(image_paths_relative)
|
|
299
300
|
|
|
300
301
|
for im in images:
|
|
301
|
-
if im['file_name'] not in image_paths_relative_set:
|
|
302
|
-
validation_errors.append(im['file_name'])
|
|
302
|
+
if im['file_name'] not in image_paths_relative_set:
|
|
303
|
+
validation_errors.append((im['file_name'],'not found in relative path list'))
|
|
303
304
|
|
|
304
305
|
# If we're checking image size, we need to read the images
|
|
305
306
|
if options.bCheckImageSizes:
|
|
@@ -321,12 +322,12 @@ def integrity_check_json_db(jsonFile, options=None):
|
|
|
321
322
|
results = tqdm(pool.imap(_check_image_existence_and_size, images), total=len(images))
|
|
322
323
|
else:
|
|
323
324
|
results = []
|
|
324
|
-
for im in tqdm(images):
|
|
325
|
+
for im in tqdm(images):
|
|
325
326
|
results.append(_check_image_existence_and_size(im,options))
|
|
326
327
|
|
|
327
328
|
for i_image,result in enumerate(results):
|
|
328
|
-
if result is not None:
|
|
329
|
-
validation_errors.append(images[i_image]['file_name'])
|
|
329
|
+
if result is not None:
|
|
330
|
+
validation_errors.append(images[i_image]['file_name'],result)
|
|
330
331
|
|
|
331
332
|
# ...for each image
|
|
332
333
|
|
|
@@ -12,16 +12,18 @@ subset_json_detector_output.py.
|
|
|
12
12
|
|
|
13
13
|
#%% Constants and imports
|
|
14
14
|
|
|
15
|
+
import os
|
|
15
16
|
import sys
|
|
16
17
|
import json
|
|
17
18
|
import argparse
|
|
18
19
|
|
|
19
20
|
from tqdm import tqdm
|
|
21
|
+
from copy import copy
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
#%% Functions
|
|
23
25
|
|
|
24
|
-
def subset_json_db(input_json, query, output_json=None, ignore_case=False):
|
|
26
|
+
def subset_json_db(input_json, query, output_json=None, ignore_case=False, verbose=False):
|
|
25
27
|
"""
|
|
26
28
|
Given a json file (or dictionary already loaded from a json file), produce a new
|
|
27
29
|
database containing only the images whose filenames contain the string 'query',
|
|
@@ -29,54 +31,80 @@ def subset_json_db(input_json, query, output_json=None, ignore_case=False):
|
|
|
29
31
|
|
|
30
32
|
Args:
|
|
31
33
|
input_json (str): COCO Camera Traps .json file to load, or an already-loaded dict
|
|
32
|
-
query (str): string to query for, only include images in the output whose filenames
|
|
33
|
-
contain this string.
|
|
34
|
+
query (str or list): string to query for, only include images in the output whose filenames
|
|
35
|
+
contain this string. If this is a list, test for exact matches.
|
|
34
36
|
output_json (str, optional): file to write the resulting .json file to
|
|
35
37
|
ignore_case (bool, optional): whether to perform a case-insensitive search for [query]
|
|
38
|
+
verbose (bool, optional): enable additional debug output
|
|
36
39
|
|
|
37
40
|
Returns:
|
|
38
|
-
dict:
|
|
41
|
+
dict: CCT dictionary containing a subset of the images and annotations in the input dict
|
|
39
42
|
"""
|
|
40
|
-
|
|
41
|
-
if ignore_case:
|
|
42
|
-
query = query.lower()
|
|
43
43
|
|
|
44
44
|
# Load the input file if necessary
|
|
45
45
|
if isinstance(input_json,str):
|
|
46
46
|
print('Loading input .json...')
|
|
47
47
|
with open(input_json, 'r') as f:
|
|
48
|
-
|
|
48
|
+
input_data = json.load(f)
|
|
49
49
|
else:
|
|
50
|
-
|
|
50
|
+
input_data = input_json
|
|
51
51
|
|
|
52
52
|
# Find images matching the query
|
|
53
53
|
images = []
|
|
54
|
-
image_ids = set()
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if isinstance(query,str):
|
|
56
|
+
|
|
57
|
+
if ignore_case:
|
|
58
|
+
query = query.lower()
|
|
59
|
+
|
|
60
|
+
for im in tqdm(input_data['images']):
|
|
61
|
+
fn = im['file_name']
|
|
62
|
+
if ignore_case:
|
|
63
|
+
fn = fn.lower()
|
|
64
|
+
if query in fn:
|
|
65
|
+
images.append(im)
|
|
66
|
+
|
|
67
|
+
else:
|
|
68
|
+
|
|
69
|
+
query = set(query)
|
|
70
|
+
|
|
58
71
|
if ignore_case:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
query = set([s.lower() for s in query])
|
|
73
|
+
|
|
74
|
+
for im in input_data['images']:
|
|
75
|
+
fn = im['file_name']
|
|
76
|
+
if ignore_case:
|
|
77
|
+
fn = fn.lower()
|
|
78
|
+
if fn in query:
|
|
79
|
+
images.append(im)
|
|
80
|
+
|
|
81
|
+
image_ids = set([im['id'] for im in images])
|
|
63
82
|
|
|
64
83
|
# Find annotations referring to those images
|
|
65
84
|
annotations = []
|
|
66
85
|
|
|
67
|
-
for ann in
|
|
86
|
+
for ann in input_data['annotations']:
|
|
68
87
|
if ann['image_id'] in image_ids:
|
|
69
88
|
annotations.append(ann)
|
|
70
89
|
|
|
71
|
-
output_data =
|
|
90
|
+
output_data = copy(input_data)
|
|
72
91
|
output_data['images'] = images
|
|
73
92
|
output_data['annotations'] = annotations
|
|
74
93
|
|
|
75
94
|
# Write the output file if requested
|
|
76
95
|
if output_json is not None:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
if verbose:
|
|
97
|
+
print('Writing output .json to {}'.format(output_json))
|
|
98
|
+
output_dir = os.path.dirname(output_json)
|
|
99
|
+
os.makedirs(output_dir,exist_ok=True)
|
|
100
|
+
with open(output_json,'w') as f:
|
|
101
|
+
json.dump(output_data,f,indent=1)
|
|
102
|
+
|
|
103
|
+
if verbose:
|
|
104
|
+
print('Keeping {} of {} images, {} of {} annotations'.format(
|
|
105
|
+
len(output_data['images']),len(input_data['images']),
|
|
106
|
+
len(output_data['annotations']),len(input_data['annotations'])))
|
|
107
|
+
|
|
80
108
|
return output_data
|
|
81
109
|
|
|
82
110
|
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
mewc_to_md.py
|
|
4
|
+
|
|
5
|
+
Converts the output of the MEWC inference scripts to the MD output format.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
#%% Imports and constants
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import json
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
from copy import deepcopy
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
from megadetector.utils.ct_utils import sort_list_of_dicts_by_key, invert_dictionary # noqa
|
|
18
|
+
from megadetector.utils.path_utils import recursive_file_list
|
|
19
|
+
|
|
20
|
+
from megadetector.postprocessing.validate_batch_results import \
|
|
21
|
+
ValidateBatchResultsOptions, validate_batch_results
|
|
22
|
+
|
|
23
|
+
default_mewc_mount_prefix = '/images/'
|
|
24
|
+
default_mewc_category_name_column = 'class_id'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
#%% Functions
|
|
28
|
+
|
|
29
|
+
def mewc_to_md(mewc_input_folder,
|
|
30
|
+
output_file=None,
|
|
31
|
+
mount_prefix=default_mewc_mount_prefix,
|
|
32
|
+
category_name_column=default_mewc_category_name_column,
|
|
33
|
+
mewc_out_filename='mewc_out.csv',
|
|
34
|
+
md_out_filename='md_out.json'):
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
mewc_input_folder (str): the folder we'll search for MEWC output files
|
|
39
|
+
output_file (str, optional): .json file to write with class information
|
|
40
|
+
mount_prefix (str, optional): string to remove from all filenames in the MD
|
|
41
|
+
.json file, typically the prefix used to mount the image folder.
|
|
42
|
+
category_name_column (str, optional): column in the MEWC results .csv to use for
|
|
43
|
+
category naming.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
dict: an MD-formatted dict, the same as what's written to [output_file]
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
##%% Read input files
|
|
50
|
+
|
|
51
|
+
assert os.path.isdir(mewc_input_folder), \
|
|
52
|
+
'Could not find folder {}'.format(mewc_input_folder)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
##%% Find MEWC output files
|
|
56
|
+
|
|
57
|
+
relative_path_to_mewc_info = {}
|
|
58
|
+
|
|
59
|
+
print('Listing files in folder {}'.format(mewc_input_folder))
|
|
60
|
+
all_files_relative = set(recursive_file_list(mewc_input_folder,return_relative_paths=True))
|
|
61
|
+
|
|
62
|
+
for fn_relative in all_files_relative:
|
|
63
|
+
if fn_relative.endswith(mewc_out_filename):
|
|
64
|
+
folder_relative = '/'.join(fn_relative.split('/')[:-1])
|
|
65
|
+
assert folder_relative not in relative_path_to_mewc_info
|
|
66
|
+
md_output_file_relative = os.path.join(folder_relative,md_out_filename).replace('\\','/')
|
|
67
|
+
assert md_output_file_relative in all_files_relative, \
|
|
68
|
+
'Could not find MD output file {} to match to {}'.format(
|
|
69
|
+
md_output_file_relative,fn_relative)
|
|
70
|
+
relative_path_to_mewc_info[folder_relative] = \
|
|
71
|
+
{'mewc_predict_file':fn_relative,'md_file':md_output_file_relative}
|
|
72
|
+
|
|
73
|
+
del folder_relative
|
|
74
|
+
|
|
75
|
+
print('Found {} MEWC results files'.format(len(relative_path_to_mewc_info)))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
##%% Prepare to loop over results files
|
|
79
|
+
|
|
80
|
+
md_results_all = {}
|
|
81
|
+
md_results_all['images'] = []
|
|
82
|
+
md_results_all['detection_categories'] = {}
|
|
83
|
+
md_results_all['classification_categories'] = {}
|
|
84
|
+
md_results_all['info'] = None
|
|
85
|
+
|
|
86
|
+
classification_category_name_to_id = {}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
##%% Loop over results files
|
|
90
|
+
|
|
91
|
+
# relative_folder = next(iter(relative_path_to_mewc_info.keys()))
|
|
92
|
+
for relative_folder in relative_path_to_mewc_info:
|
|
93
|
+
|
|
94
|
+
##%%
|
|
95
|
+
|
|
96
|
+
mewc_info = relative_path_to_mewc_info[relative_folder]
|
|
97
|
+
mewc_csv_fn_abs = os.path.join(mewc_input_folder,mewc_info['mewc_predict_file'])
|
|
98
|
+
mewc_md_fn_abs = os.path.join(mewc_input_folder,mewc_info['md_file'])
|
|
99
|
+
|
|
100
|
+
mewc_classification_info = pd.read_csv(mewc_csv_fn_abs)
|
|
101
|
+
mewc_classification_info = mewc_classification_info.to_dict('records')
|
|
102
|
+
|
|
103
|
+
assert os.path.isfile(mewc_md_fn_abs), \
|
|
104
|
+
'Could not find file {}'.format(mewc_md_fn_abs)
|
|
105
|
+
with open(mewc_md_fn_abs,'r') as f:
|
|
106
|
+
md_results = json.load(f)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
##%% Remove the mount prefix from MD files if necessary
|
|
110
|
+
if mount_prefix is not None and len(mount_prefix) > 0:
|
|
111
|
+
|
|
112
|
+
n_files_without_mount_prefix = 0
|
|
113
|
+
|
|
114
|
+
# im = md_results['images'][0]
|
|
115
|
+
for im in md_results['images']:
|
|
116
|
+
if not im['file'].startswith(mount_prefix):
|
|
117
|
+
n_files_without_mount_prefix += 1
|
|
118
|
+
else:
|
|
119
|
+
im['file'] = im['file'].replace(mount_prefix,'',1)
|
|
120
|
+
|
|
121
|
+
if n_files_without_mount_prefix > 0:
|
|
122
|
+
print('Warning {} of {} files in the MD results did not include the mount prefix {}'.format(
|
|
123
|
+
n_files_without_mount_prefix,len(md_results['images']),mount_prefix))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
##%% Convert MEWC snip IDs to image files
|
|
127
|
+
|
|
128
|
+
# r = mewc_classification_info[0]
|
|
129
|
+
for r in mewc_classification_info:
|
|
130
|
+
|
|
131
|
+
# E.g. "IMG0-0.jpg"
|
|
132
|
+
snip_file = r['filename']
|
|
133
|
+
|
|
134
|
+
# E.g. "IMG0-0"
|
|
135
|
+
snip_file_no_ext = os.path.splitext(snip_file)[0]
|
|
136
|
+
ext = os.path.splitext(snip_file)[1] # noqa
|
|
137
|
+
|
|
138
|
+
tokens = snip_file_no_ext.split('-')
|
|
139
|
+
|
|
140
|
+
if len(tokens) == 1:
|
|
141
|
+
print('Warning: in folder {}, detection ID not found in snip filename {}, skipping'.format(
|
|
142
|
+
relative_folder,snip_file_no_ext))
|
|
143
|
+
r['image_filename_without_extension'] = snip_file_no_ext
|
|
144
|
+
r['snip_id'] = None
|
|
145
|
+
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
filename_without_snip_id = '-'.join(tokens[0:-1])
|
|
149
|
+
snip_id = int(tokens[-1])
|
|
150
|
+
image_filename_without_extension = filename_without_snip_id
|
|
151
|
+
|
|
152
|
+
r['image_filename_without_extension'] = image_filename_without_extension
|
|
153
|
+
r['snip_id'] = snip_id
|
|
154
|
+
|
|
155
|
+
# ...for each MEWC result record
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
##%% Make sure MD results and MEWC results refer to the same files
|
|
159
|
+
|
|
160
|
+
images_in_md_results_no_extension = \
|
|
161
|
+
set([os.path.splitext(im['file'])[0] for im in md_results['images']])
|
|
162
|
+
images_in_mewc_results_no_extension = set(r['image_filename_without_extension'] \
|
|
163
|
+
for r in mewc_classification_info)
|
|
164
|
+
|
|
165
|
+
# All files with classification results should also have detection results
|
|
166
|
+
for fn in images_in_mewc_results_no_extension:
|
|
167
|
+
assert fn in images_in_md_results_no_extension, \
|
|
168
|
+
'Error: file {} is present in mewc-predict results, but not in MD results'.format(fn)
|
|
169
|
+
|
|
170
|
+
# This is just a note to self: no classification results are present for empty images
|
|
171
|
+
if False:
|
|
172
|
+
for fn in images_in_md_results_no_extension:
|
|
173
|
+
if fn not in images_in_mewc_results_no_extension:
|
|
174
|
+
print('Warning: file {}/{} is present in MD results, but not in mewc-predict results'.format(
|
|
175
|
+
relative_folder,fn))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
##%% Validate images
|
|
179
|
+
|
|
180
|
+
for im in md_results['images']:
|
|
181
|
+
fn_relative = im['file']
|
|
182
|
+
fn_abs = os.path.join(mewc_input_folder,relative_folder,fn_relative)
|
|
183
|
+
if not os.path.isfile(fn_abs):
|
|
184
|
+
print('Warning: image file {} does not exist'.format(fn_abs))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
##%% Map filenames to MEWC results
|
|
188
|
+
|
|
189
|
+
image_id_to_mewc_records = defaultdict(list)
|
|
190
|
+
for r in mewc_classification_info:
|
|
191
|
+
image_id_to_mewc_records[r['image_filename_without_extension']].append(r)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
##%% Add classification info to MD results
|
|
195
|
+
|
|
196
|
+
# im = md_results['images'][0]
|
|
197
|
+
for im in md_results['images']:
|
|
198
|
+
|
|
199
|
+
if ('detections' not in im) or (im['detections'] is None) or (len(im['detections']) == 0):
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
detections = im['detections']
|
|
203
|
+
|
|
204
|
+
# *Don't* sort by confidence, it looks like snip IDs use the original sort order
|
|
205
|
+
# detections = sort_list_of_dicts_by_key(detections,'conf',reverse=True)
|
|
206
|
+
|
|
207
|
+
# This is just a debug assist, so I can run this cell more than once
|
|
208
|
+
for det in detections:
|
|
209
|
+
det['classifications'] = []
|
|
210
|
+
|
|
211
|
+
image_id = os.path.splitext(im['file'])[0]
|
|
212
|
+
mewc_records_this_image = image_id_to_mewc_records[image_id]
|
|
213
|
+
|
|
214
|
+
# r = mewc_records_this_image[0]
|
|
215
|
+
for r in mewc_records_this_image:
|
|
216
|
+
|
|
217
|
+
if r['snip_id'] is None:
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
category_name = r[category_name_column]
|
|
221
|
+
|
|
222
|
+
# This is a *global* list of category mappings, across all mewc .csv files
|
|
223
|
+
if category_name not in classification_category_name_to_id:
|
|
224
|
+
category_id = str(len(classification_category_name_to_id))
|
|
225
|
+
classification_category_name_to_id[category_name] = category_id
|
|
226
|
+
else:
|
|
227
|
+
category_id = classification_category_name_to_id[category_name]
|
|
228
|
+
|
|
229
|
+
snip_id = r['snip_id']
|
|
230
|
+
if snip_id >= len(detections):
|
|
231
|
+
print('Warning: image {} has a classified snip ID of {}, but only {} detections are present'.format(
|
|
232
|
+
image_id,snip_id,len(detections)))
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
det = detections[snip_id]
|
|
236
|
+
|
|
237
|
+
if 'classifications' not in det:
|
|
238
|
+
det['classifications'] = []
|
|
239
|
+
det['classifications'].append([category_id,r['prob']])
|
|
240
|
+
|
|
241
|
+
# ...for each classification in this image
|
|
242
|
+
|
|
243
|
+
# ...for each image
|
|
244
|
+
|
|
245
|
+
##%% Map MD reults to the global level
|
|
246
|
+
|
|
247
|
+
if md_results_all['info'] is None:
|
|
248
|
+
md_results_all['info'] = md_results['info']
|
|
249
|
+
|
|
250
|
+
for category_id in md_results['detection_categories']:
|
|
251
|
+
if category_id not in md_results_all['detection_categories']:
|
|
252
|
+
md_results_all['detection_categories'][category_id] = \
|
|
253
|
+
md_results['detection_categories'][category_id]
|
|
254
|
+
else:
|
|
255
|
+
assert md_results_all['detection_categories'][category_id] == \
|
|
256
|
+
md_results['detection_categories'][category_id], \
|
|
257
|
+
'MD results present with incompatible detection categories'
|
|
258
|
+
|
|
259
|
+
# im = md_results['images'][0]
|
|
260
|
+
for im in md_results['images']:
|
|
261
|
+
im_copy = deepcopy(im)
|
|
262
|
+
im_copy['file'] = os.path.join(relative_folder,im['file']).replace('\\','/')
|
|
263
|
+
md_results_all['images'].append(im_copy)
|
|
264
|
+
|
|
265
|
+
# ...for each folder that contains MEWC results
|
|
266
|
+
|
|
267
|
+
del md_results
|
|
268
|
+
|
|
269
|
+
##%% Write output
|
|
270
|
+
|
|
271
|
+
md_results_all['classification_categories'] = invert_dictionary(classification_category_name_to_id)
|
|
272
|
+
|
|
273
|
+
if output_file is not None:
|
|
274
|
+
output_dir = os.path.dirname(output_file)
|
|
275
|
+
os.makedirs(output_dir,exist_ok=True)
|
|
276
|
+
with open(output_file,'w') as f:
|
|
277
|
+
json.dump(md_results_all,f,indent=1)
|
|
278
|
+
|
|
279
|
+
validation_options = ValidateBatchResultsOptions()
|
|
280
|
+
validation_options.check_image_existence = True
|
|
281
|
+
validation_options.relative_path_base = mewc_input_folder
|
|
282
|
+
validation_options.raise_errors = True
|
|
283
|
+
validation_results = validate_batch_results(output_file,validation_options) # noqa
|
|
284
|
+
|
|
285
|
+
# ...def mewc_to_md(...)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
#%% Interactive driver
|
|
289
|
+
|
|
290
|
+
if False:
|
|
291
|
+
|
|
292
|
+
pass
|
|
293
|
+
|
|
294
|
+
#%%
|
|
295
|
+
|
|
296
|
+
mewc_input_folder = r'G:\temp\mewc-test'
|
|
297
|
+
mount_prefix = '/images/'
|
|
298
|
+
output_file = os.path.join(mewc_input_folder,'results_with_classes.json')
|
|
299
|
+
|
|
300
|
+
_ = mewc_to_md(mewc_input_folder=mewc_input_folder,
|
|
301
|
+
output_file=output_file,
|
|
302
|
+
mount_prefix=mount_prefix,
|
|
303
|
+
category_name_column='class_id')
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
#%% Command-line driver
|
|
307
|
+
|
|
308
|
+
import sys
|
|
309
|
+
import argparse
|
|
310
|
+
|
|
311
|
+
def main():
|
|
312
|
+
|
|
313
|
+
parser = argparse.ArgumentParser()
|
|
314
|
+
|
|
315
|
+
parser.add_argument(
|
|
316
|
+
'input_folder',type=str,
|
|
317
|
+
help='Folder containing images and MEWC .json/.csv files')
|
|
318
|
+
parser.add_argument(
|
|
319
|
+
'output_file',type=str,
|
|
320
|
+
help='.json file where output will be written')
|
|
321
|
+
parser.add_argument(
|
|
322
|
+
'--mount_prefix',type=str,default=default_mewc_mount_prefix,
|
|
323
|
+
help='prefix to remove from each filename in MEWC results, typically the Docker mount point')
|
|
324
|
+
parser.add_argument(
|
|
325
|
+
'--category_name_column',type=str,default=default_mewc_category_name_column,
|
|
326
|
+
help='column in the MEWC .csv file to use for category names')
|
|
327
|
+
|
|
328
|
+
if len(sys.argv[1:]) == 0:
|
|
329
|
+
parser.print_help()
|
|
330
|
+
parser.exit()
|
|
331
|
+
|
|
332
|
+
args = parser.parse_args()
|
|
333
|
+
|
|
334
|
+
_ = mewc_to_md(mewc_input_folder=args.input_folder,
|
|
335
|
+
output_file=args.output_file,
|
|
336
|
+
mount_prefix=args.mount_prefix,
|
|
337
|
+
category_name_column=args.category_name_column)
|
|
338
|
+
|
|
339
|
+
if __name__ == '__main__':
|
|
340
|
+
main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
wi_to_md.py
|
|
4
|
+
|
|
5
|
+
Converts the WI predictions.json format to MD .json format. This is just a
|
|
6
|
+
command-line wrapper around utils.wi_utils.generate_md_results_from_predictions_json.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
#%% Imports and constants
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import argparse
|
|
14
|
+
from megadetector.utils.wi_utils import generate_md_results_from_predictions_json
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#%% Command-line driver
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
|
|
21
|
+
parser = argparse.ArgumentParser()
|
|
22
|
+
parser.add_argument('predictions_json_file', action='store', type=str,
|
|
23
|
+
help='.json file to convert from predictions.json format to MD format')
|
|
24
|
+
parser.add_argument('md_results_file', action='store', type=str,
|
|
25
|
+
help='output file to write in MD format')
|
|
26
|
+
parser.add_argument('--base_folder', action='store', type=str, default=None,
|
|
27
|
+
help='leading string to remove from each path in the predictions.json ' + \
|
|
28
|
+
'file (to convert from absolute to relative paths)')
|
|
29
|
+
|
|
30
|
+
if len(sys.argv[1:]) == 0:
|
|
31
|
+
parser.print_help()
|
|
32
|
+
parser.exit()
|
|
33
|
+
|
|
34
|
+
args = parser.parse_args()
|
|
35
|
+
|
|
36
|
+
generate_md_results_from_predictions_json(args.predictions_json_file,
|
|
37
|
+
args.md_results_file,
|
|
38
|
+
args.base_folder)
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
main()
|