megadetector 5.0.12__py3-none-any.whl → 5.0.14__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/batch_processing/api_core/server.py +1 -1
- megadetector/api/batch_processing/api_core/server_api_config.py +0 -1
- megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -3
- megadetector/api/batch_processing/api_core/server_utils.py +0 -4
- megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -1
- megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -3
- megadetector/classification/efficientnet/utils.py +0 -3
- megadetector/data_management/camtrap_dp_to_coco.py +0 -2
- megadetector/data_management/cct_json_utils.py +15 -6
- megadetector/data_management/coco_to_labelme.py +12 -1
- megadetector/data_management/databases/integrity_check_json_db.py +43 -27
- megadetector/data_management/importers/cacophony-thermal-importer.py +1 -4
- megadetector/data_management/ocr_tools.py +0 -4
- megadetector/data_management/read_exif.py +178 -44
- megadetector/data_management/rename_images.py +187 -0
- megadetector/data_management/wi_download_csv_to_coco.py +3 -2
- megadetector/data_management/yolo_output_to_md_output.py +7 -2
- megadetector/detection/process_video.py +548 -244
- megadetector/detection/pytorch_detector.py +33 -14
- megadetector/detection/run_detector.py +17 -5
- megadetector/detection/run_detector_batch.py +179 -65
- megadetector/detection/run_inference_with_yolov5_val.py +527 -357
- megadetector/detection/tf_detector.py +14 -3
- megadetector/detection/video_utils.py +284 -61
- megadetector/postprocessing/categorize_detections_by_size.py +16 -14
- megadetector/postprocessing/classification_postprocessing.py +716 -0
- megadetector/postprocessing/compare_batch_results.py +101 -93
- megadetector/postprocessing/convert_output_format.py +12 -5
- megadetector/postprocessing/merge_detections.py +18 -7
- megadetector/postprocessing/postprocess_batch_results.py +133 -127
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +236 -232
- megadetector/postprocessing/subset_json_detector_output.py +66 -62
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +0 -2
- megadetector/utils/ct_utils.py +5 -4
- megadetector/utils/md_tests.py +380 -128
- megadetector/utils/path_utils.py +39 -6
- megadetector/utils/process_utils.py +13 -4
- megadetector/visualization/visualization_utils.py +7 -2
- megadetector/visualization/visualize_db.py +79 -77
- megadetector/visualization/visualize_detector_output.py +0 -1
- {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/LICENSE +0 -0
- {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/METADATA +2 -2
- {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/RECORD +45 -43
- {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/top_level.txt +0 -0
- {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
rename_images.py.py
|
|
4
|
+
|
|
5
|
+
Copies images from a possibly-nested folder structure to a flat folder structure, including EXIF
|
|
6
|
+
timestamps in each filename. Loosely equivalent to camtrapR's imageRename() function.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
#%% Imports and constants
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from megadetector.utils.path_utils import \
|
|
15
|
+
find_images, insert_before_extension, parallel_copy_files
|
|
16
|
+
from megadetector.data_management.read_exif import \
|
|
17
|
+
ReadExifOptions, read_exif_from_folder
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
#%% Functions
|
|
21
|
+
|
|
22
|
+
def rename_images(input_folder,
|
|
23
|
+
output_folder,
|
|
24
|
+
dry_run=False,
|
|
25
|
+
verbose=False,
|
|
26
|
+
read_exif_options=None,
|
|
27
|
+
n_copy_workers=8):
|
|
28
|
+
"""
|
|
29
|
+
For the given image struct in COCO format and associated list of annotations, reformats the
|
|
30
|
+
detections into labelme format.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
input_folder: the folder to search for images, always recursive
|
|
34
|
+
output_folder: the folder to which we will copy images; cannot be the
|
|
35
|
+
same as [input_folder]
|
|
36
|
+
dry_run: only map images, don't actually copy
|
|
37
|
+
verbose (bool, optional): enable additional debug output
|
|
38
|
+
read_exif_options (ReadExifOptions, optional): parameters controlling the reading of
|
|
39
|
+
EXIF information
|
|
40
|
+
n_copy_workers (int, optional): number of parallel threads to use for copying
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
dict: a dict mapping relative filenames in the input folder to relative filenames in the output
|
|
44
|
+
folder
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
assert os.path.isdir(input_folder), 'Input folder {} does not exist'.format(
|
|
48
|
+
input_folder)
|
|
49
|
+
|
|
50
|
+
if not dry_run:
|
|
51
|
+
os.makedirs(output_folder,exist_ok=True)
|
|
52
|
+
|
|
53
|
+
# Read exif information
|
|
54
|
+
if read_exif_options is None:
|
|
55
|
+
read_exif_options = ReadExifOptions()
|
|
56
|
+
|
|
57
|
+
read_exif_options.tags_to_include = ['DateTime','Model','Make','ExifImageWidth','ExifImageHeight','DateTime',
|
|
58
|
+
'DateTimeOriginal']
|
|
59
|
+
read_exif_options.verbose = False
|
|
60
|
+
|
|
61
|
+
exif_info = read_exif_from_folder(input_folder=input_folder,
|
|
62
|
+
output_file=None,
|
|
63
|
+
options=read_exif_options,
|
|
64
|
+
filenames=None,recursive=True)
|
|
65
|
+
|
|
66
|
+
print('Read EXIF information for {} images'.format(len(exif_info)))
|
|
67
|
+
|
|
68
|
+
filename_to_exif_info = {info['file_name']:info for info in exif_info}
|
|
69
|
+
|
|
70
|
+
image_files = find_images(input_folder,return_relative_paths=True,convert_slashes=True,recursive=True)
|
|
71
|
+
|
|
72
|
+
for fn in image_files:
|
|
73
|
+
assert fn in filename_to_exif_info, 'No EXIF info available for {}'.format(fn)
|
|
74
|
+
|
|
75
|
+
input_fn_relative_to_output_fn_relative = {}
|
|
76
|
+
|
|
77
|
+
# fn_relative = image_files[0]
|
|
78
|
+
for fn_relative in image_files:
|
|
79
|
+
|
|
80
|
+
input_fn_abs = os.path.join(input_folder,fn_relative)
|
|
81
|
+
image_exif_info = filename_to_exif_info[fn_relative]
|
|
82
|
+
if 'exif_tags' in image_exif_info:
|
|
83
|
+
image_exif_info = image_exif_info['exif_tags']
|
|
84
|
+
|
|
85
|
+
if image_exif_info is None or \
|
|
86
|
+
'DateTimeOriginal' not in image_exif_info or \
|
|
87
|
+
image_exif_info['DateTimeOriginal'] is None:
|
|
88
|
+
|
|
89
|
+
dt_tag = 'unknown_datetime'
|
|
90
|
+
print('Warning: no datetime for {}'.format(fn_relative))
|
|
91
|
+
|
|
92
|
+
else:
|
|
93
|
+
|
|
94
|
+
dt_tag = str(image_exif_info['DateTimeOriginal']).replace(':','-').replace(' ','_').strip()
|
|
95
|
+
|
|
96
|
+
flat_filename = fn_relative.replace('\\','/').replace('/','_')
|
|
97
|
+
|
|
98
|
+
output_fn_relative = insert_before_extension(flat_filename,dt_tag)
|
|
99
|
+
|
|
100
|
+
input_fn_relative_to_output_fn_relative[fn_relative] = output_fn_relative
|
|
101
|
+
|
|
102
|
+
if not dry_run:
|
|
103
|
+
|
|
104
|
+
input_fn_abs_to_output_fn_abs = {}
|
|
105
|
+
for input_fn_relative in input_fn_relative_to_output_fn_relative:
|
|
106
|
+
output_fn_relative = input_fn_relative_to_output_fn_relative[input_fn_relative]
|
|
107
|
+
input_fn_abs = os.path.join(input_folder,input_fn_relative)
|
|
108
|
+
output_fn_abs = os.path.join(output_folder,output_fn_relative)
|
|
109
|
+
input_fn_abs_to_output_fn_abs[input_fn_abs] = output_fn_abs
|
|
110
|
+
|
|
111
|
+
parallel_copy_files(input_file_to_output_file=input_fn_abs_to_output_fn_abs,
|
|
112
|
+
max_workers=n_copy_workers,
|
|
113
|
+
use_threads=True,
|
|
114
|
+
overwrite=True,
|
|
115
|
+
verbose=verbose)
|
|
116
|
+
|
|
117
|
+
return input_fn_relative_to_output_fn_relative
|
|
118
|
+
|
|
119
|
+
# ...def rename_images()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
#%% Interactive driver
|
|
123
|
+
|
|
124
|
+
if False:
|
|
125
|
+
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
#%% Configure options
|
|
129
|
+
|
|
130
|
+
input_folder = r'G:\camera_traps\camera_trap_videos\2024.05.25\cam3'
|
|
131
|
+
output_folder = r'G:\camera_traps\camera_trap_videos\2024.05.25\cam3_flat'
|
|
132
|
+
dry_run = False
|
|
133
|
+
verbose = True
|
|
134
|
+
read_exif_options = ReadExifOptions()
|
|
135
|
+
read_exif_options.tags_to_include = ['DateTime','Model','Make','ExifImageWidth','ExifImageHeight','DateTime',
|
|
136
|
+
'DateTimeOriginal']
|
|
137
|
+
read_exif_options.n_workers = 8
|
|
138
|
+
read_exif_options.verbose = verbose
|
|
139
|
+
n_copy_workers = 8
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
#%% Programmatic execution
|
|
143
|
+
|
|
144
|
+
input_fn_relative_to_output_fn_relative = rename_images(input_folder,
|
|
145
|
+
output_folder,
|
|
146
|
+
dry_run=dry_run,
|
|
147
|
+
verbose=verbose,
|
|
148
|
+
read_exif_options=read_exif_options,
|
|
149
|
+
n_copy_workers=n_copy_workers)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
#%% Command-line driver
|
|
153
|
+
|
|
154
|
+
import sys,argparse
|
|
155
|
+
|
|
156
|
+
def main():
|
|
157
|
+
|
|
158
|
+
parser = argparse.ArgumentParser(
|
|
159
|
+
description='Copies images from a possibly-nested folder structure to a flat folder structure, ' + \
|
|
160
|
+
'adding datetime information from EXIF to each filename')
|
|
161
|
+
|
|
162
|
+
parser.add_argument(
|
|
163
|
+
'input_folder',
|
|
164
|
+
type=str,
|
|
165
|
+
help='The folder to search for images, always recursive')
|
|
166
|
+
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
'output_folder',
|
|
169
|
+
type=str,
|
|
170
|
+
help='The folder to which we should write the flattened image structure')
|
|
171
|
+
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
'--dry_run',
|
|
174
|
+
action='store_true',
|
|
175
|
+
help="Only map images, don't actually copy")
|
|
176
|
+
|
|
177
|
+
if len(sys.argv[1:]) == 0:
|
|
178
|
+
parser.print_help()
|
|
179
|
+
parser.exit()
|
|
180
|
+
|
|
181
|
+
args = parser.parse_args()
|
|
182
|
+
|
|
183
|
+
rename_images(args.input_folder,args.output_folder,dry_run=args.dry_run,
|
|
184
|
+
verbose=True,read_exif_options=None)
|
|
185
|
+
|
|
186
|
+
if __name__ == '__main__':
|
|
187
|
+
main()
|
|
@@ -180,11 +180,12 @@ def wi_download_csv_to_coco(csv_file_in,
|
|
|
180
180
|
if validate_images:
|
|
181
181
|
|
|
182
182
|
print('Validating images')
|
|
183
|
-
# TODO: trivially parallelizable
|
|
184
183
|
|
|
185
184
|
assert os.path.isdir(image_folder), \
|
|
186
185
|
'Must specify a valid image folder if you specify validate_images=True'
|
|
187
|
-
|
|
186
|
+
|
|
187
|
+
# TODO: trivially parallelizable
|
|
188
|
+
#
|
|
188
189
|
# im = images[0]
|
|
189
190
|
for im in tqdm(images):
|
|
190
191
|
file_name_relative = im['file_name']
|
|
@@ -104,7 +104,8 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
104
104
|
image_id_to_relative_path=None,
|
|
105
105
|
offset_yolo_class_ids=True,
|
|
106
106
|
truncate_to_standard_md_precision=True,
|
|
107
|
-
image_id_to_error=None
|
|
107
|
+
image_id_to_error=None,
|
|
108
|
+
convert_slashes=True):
|
|
108
109
|
"""
|
|
109
110
|
Converts a YOLOv5/YOLOv8 .json file to MD .json format.
|
|
110
111
|
|
|
@@ -128,6 +129,7 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
128
129
|
(not-super-meaningful) precision, set this to truncate to COORD_DIGITS and CONF_DIGITS.
|
|
129
130
|
image_id_to_error (dict, optional): if you want to include image IDs in the output file for which
|
|
130
131
|
you couldn't prepare the input file in the first place due to errors, include them here.
|
|
132
|
+
convert_slashes (bool, optional): force all slashes to be forward slashes in the output file
|
|
131
133
|
"""
|
|
132
134
|
|
|
133
135
|
assert os.path.isfile(yolo_json_file), \
|
|
@@ -225,6 +227,9 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
225
227
|
|
|
226
228
|
im = {}
|
|
227
229
|
im['file'] = image_file_relative
|
|
230
|
+
if convert_slashes:
|
|
231
|
+
im['file'] = im['file'].replace('\\','/')
|
|
232
|
+
|
|
228
233
|
image_id = image_file_relative_to_image_id[image_file_relative]
|
|
229
234
|
if int_formatted_image_ids:
|
|
230
235
|
image_id = int(image_id)
|
|
@@ -289,7 +294,7 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
289
294
|
im['detections'].append(output_det)
|
|
290
295
|
|
|
291
296
|
# ...for each detection
|
|
292
|
-
|
|
297
|
+
|
|
293
298
|
output_images.append(im)
|
|
294
299
|
|
|
295
300
|
# ...for each image file
|