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.

Files changed (45) hide show
  1. megadetector/api/batch_processing/api_core/server.py +1 -1
  2. megadetector/api/batch_processing/api_core/server_api_config.py +0 -1
  3. megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -3
  4. megadetector/api/batch_processing/api_core/server_utils.py +0 -4
  5. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -1
  6. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -3
  7. megadetector/classification/efficientnet/utils.py +0 -3
  8. megadetector/data_management/camtrap_dp_to_coco.py +0 -2
  9. megadetector/data_management/cct_json_utils.py +15 -6
  10. megadetector/data_management/coco_to_labelme.py +12 -1
  11. megadetector/data_management/databases/integrity_check_json_db.py +43 -27
  12. megadetector/data_management/importers/cacophony-thermal-importer.py +1 -4
  13. megadetector/data_management/ocr_tools.py +0 -4
  14. megadetector/data_management/read_exif.py +178 -44
  15. megadetector/data_management/rename_images.py +187 -0
  16. megadetector/data_management/wi_download_csv_to_coco.py +3 -2
  17. megadetector/data_management/yolo_output_to_md_output.py +7 -2
  18. megadetector/detection/process_video.py +548 -244
  19. megadetector/detection/pytorch_detector.py +33 -14
  20. megadetector/detection/run_detector.py +17 -5
  21. megadetector/detection/run_detector_batch.py +179 -65
  22. megadetector/detection/run_inference_with_yolov5_val.py +527 -357
  23. megadetector/detection/tf_detector.py +14 -3
  24. megadetector/detection/video_utils.py +284 -61
  25. megadetector/postprocessing/categorize_detections_by_size.py +16 -14
  26. megadetector/postprocessing/classification_postprocessing.py +716 -0
  27. megadetector/postprocessing/compare_batch_results.py +101 -93
  28. megadetector/postprocessing/convert_output_format.py +12 -5
  29. megadetector/postprocessing/merge_detections.py +18 -7
  30. megadetector/postprocessing/postprocess_batch_results.py +133 -127
  31. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +236 -232
  32. megadetector/postprocessing/subset_json_detector_output.py +66 -62
  33. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +0 -2
  34. megadetector/utils/ct_utils.py +5 -4
  35. megadetector/utils/md_tests.py +380 -128
  36. megadetector/utils/path_utils.py +39 -6
  37. megadetector/utils/process_utils.py +13 -4
  38. megadetector/visualization/visualization_utils.py +7 -2
  39. megadetector/visualization/visualize_db.py +79 -77
  40. megadetector/visualization/visualize_detector_output.py +0 -1
  41. {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/LICENSE +0 -0
  42. {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/METADATA +2 -2
  43. {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/RECORD +45 -43
  44. {megadetector-5.0.12.dist-info → megadetector-5.0.14.dist-info}/top_level.txt +0 -0
  45. {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