megadetector 5.0.7__py3-none-any.whl → 5.0.8__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 (48) hide show
  1. api/batch_processing/data_preparation/manage_local_batch.py +28 -14
  2. api/batch_processing/postprocessing/combine_api_outputs.py +2 -2
  3. api/batch_processing/postprocessing/compare_batch_results.py +1 -1
  4. api/batch_processing/postprocessing/convert_output_format.py +24 -6
  5. api/batch_processing/postprocessing/load_api_results.py +1 -3
  6. api/batch_processing/postprocessing/md_to_labelme.py +118 -51
  7. api/batch_processing/postprocessing/merge_detections.py +30 -5
  8. api/batch_processing/postprocessing/postprocess_batch_results.py +24 -12
  9. api/batch_processing/postprocessing/remap_detection_categories.py +163 -0
  10. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +15 -12
  11. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +2 -2
  12. data_management/cct_json_utils.py +7 -2
  13. data_management/coco_to_labelme.py +263 -0
  14. data_management/coco_to_yolo.py +7 -4
  15. data_management/databases/integrity_check_json_db.py +68 -59
  16. data_management/databases/subset_json_db.py +1 -1
  17. data_management/get_image_sizes.py +44 -26
  18. data_management/importers/animl_results_to_md_results.py +1 -3
  19. data_management/importers/noaa_seals_2019.py +1 -1
  20. data_management/labelme_to_coco.py +252 -143
  21. data_management/labelme_to_yolo.py +95 -52
  22. data_management/lila/create_lila_blank_set.py +106 -23
  23. data_management/lila/download_lila_subset.py +133 -65
  24. data_management/lila/generate_lila_per_image_labels.py +1 -1
  25. data_management/lila/lila_common.py +8 -38
  26. data_management/read_exif.py +65 -16
  27. data_management/remap_coco_categories.py +84 -0
  28. data_management/resize_coco_dataset.py +3 -22
  29. data_management/wi_download_csv_to_coco.py +239 -0
  30. data_management/yolo_to_coco.py +283 -83
  31. detection/run_detector_batch.py +12 -3
  32. detection/run_inference_with_yolov5_val.py +10 -3
  33. detection/run_tiled_inference.py +2 -2
  34. detection/tf_detector.py +2 -1
  35. detection/video_utils.py +1 -1
  36. md_utils/ct_utils.py +22 -3
  37. md_utils/md_tests.py +11 -2
  38. md_utils/path_utils.py +206 -32
  39. md_utils/url_utils.py +66 -1
  40. md_utils/write_html_image_list.py +12 -3
  41. md_visualization/visualization_utils.py +363 -72
  42. md_visualization/visualize_db.py +33 -10
  43. {megadetector-5.0.7.dist-info → megadetector-5.0.8.dist-info}/METADATA +10 -12
  44. {megadetector-5.0.7.dist-info → megadetector-5.0.8.dist-info}/RECORD +47 -44
  45. {megadetector-5.0.7.dist-info → megadetector-5.0.8.dist-info}/WHEEL +1 -1
  46. md_visualization/visualize_megadb.py +0 -183
  47. {megadetector-5.0.7.dist-info → megadetector-5.0.8.dist-info}/LICENSE +0 -0
  48. {megadetector-5.0.7.dist-info → megadetector-5.0.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,239 @@
1
+ ########
2
+ #
3
+ # wi_download_csv_to_coco.py
4
+ #
5
+ # Convert a .csv file from a Wildlife Insights project export to a COCO camera traps .json file.
6
+ #
7
+ # Currently assumes that common names are unique identifiers, which is convenient but unreliable.
8
+ #
9
+ ########
10
+
11
+ #%% Imports and constants
12
+
13
+ import os
14
+ import json
15
+ import pandas as pd
16
+ import numpy as np
17
+
18
+ from tqdm import tqdm
19
+ from collections import defaultdict
20
+
21
+ from md_visualization import visualization_utils as vis_utils
22
+
23
+ wi_extra_annotation_columns = \
24
+ ('is_blank','identified_by','wi_taxon_id','class','order','family','genus','species','uncertainty',
25
+ 'number_of_objects','age','sex','animal_recognizable','individual_id','individual_animal_notes',
26
+ 'behavior','highlighted','markings')
27
+
28
+ wi_extra_image_columns = ('project_id','deployment_id')
29
+
30
+ def make_location_id(project_id,deployment_id):
31
+ return 'project_' + str(project_id) + '_deployment_' + deployment_id
32
+
33
+ def isnan(v):
34
+ try:
35
+ return np.isnan(v)
36
+ except Exception:
37
+ return False
38
+
39
+ default_category_remappings = {
40
+ 'Homo Species':'Human',
41
+ 'Human-Camera Trapper':'Human',
42
+ 'No CV Result':'Unknown'
43
+ }
44
+
45
+
46
+ #%%
47
+
48
+ def wi_download_csv_to_coco(csv_file_in,
49
+ coco_file_out=None,
50
+ image_folder=None,
51
+ validate_images=False,
52
+ gs_prefix=None,
53
+ verbose=True,
54
+ category_remappings=default_category_remappings):
55
+ """
56
+ Convert a .csv file from a Wildlife Insights project export to a COCO
57
+ camera traps .json file.
58
+
59
+ If [coco_file_out] is None, uses [csv_file_in].json
60
+
61
+ gs_prefix is a string to remove from GS URLs to convert to path names... for example, if
62
+ your gs:// URLs look like:
63
+
64
+ gs://11234134_xyz/deployment/55554/dfadfasdfs.jpg
65
+
66
+ ...and you specify gs_prefix='11234134_xyz/deployment/', the filenames in
67
+ the .json file will look like:
68
+
69
+ 55554/dfadfasdfs.jpg
70
+
71
+ exclude_re discards matching images; typically use to omit thumbnail images.
72
+ """
73
+
74
+ #%% Create COCO dictionaries
75
+
76
+ category_name_to_id = {}
77
+ category_name_to_id['empty'] = 0
78
+
79
+ df = pd.read_csv(csv_file_in)
80
+
81
+ print('Read {} rows from {}'.format(len(df),csv_file_in))
82
+
83
+ image_id_to_image = {}
84
+ image_id_to_annotations = defaultdict(list)
85
+
86
+ # i_row = 0; row = df.iloc[i_row]
87
+ for i_row,row in df.iterrows():
88
+
89
+ image_id = row['image_id']
90
+
91
+ if image_id not in image_id_to_image:
92
+
93
+ im = {}
94
+ image_id_to_image[image_id] = im
95
+
96
+ im['id'] = image_id
97
+
98
+ gs_url = row['location']
99
+ assert gs_url.startswith('gs://')
100
+
101
+ file_name = gs_url.replace('gs://','')
102
+ if gs_prefix is not None:
103
+ file_name = file_name.replace(gs_prefix,'')
104
+
105
+ location_id = make_location_id(row['project_id'],row['deployment_id'])
106
+ im['file_name'] = file_name
107
+ im['location'] = location_id
108
+ im['datetime'] = row['timestamp']
109
+
110
+ im['wi_image_info'] = {}
111
+ for s in wi_extra_image_columns:
112
+ im['wi_image_info'][s] = str(row[s])
113
+
114
+ else:
115
+
116
+ im = image_id_to_image[image_id]
117
+ assert im['datetime'] == row['timestamp']
118
+ location_id = make_location_id(row['project_id'],row['deployment_id'])
119
+ assert im['location'] == location_id
120
+
121
+ category_name = row['common_name']
122
+ if category_remappings is not None and category_name in category_remappings:
123
+ category_name = category_remappings[category_name]
124
+
125
+ if category_name == 'Blank':
126
+ category_name = 'empty'
127
+ assert row['is_blank'] == 1
128
+ else:
129
+ assert row['is_blank'] == 0
130
+ assert isinstance(category_name,str)
131
+ if category_name in category_name_to_id:
132
+ category_id = category_name_to_id[category_name]
133
+ else:
134
+ category_id = len(category_name_to_id)
135
+ category_name_to_id[category_name] = category_id
136
+
137
+ ann = {}
138
+ ann['image_id'] = image_id
139
+ annotations_this_image = image_id_to_annotations[image_id]
140
+ annotation_number = len(annotations_this_image)
141
+ ann['id'] = image_id + '_' + str(annotation_number).zfill(2)
142
+ ann['category_id'] = category_id
143
+ annotations_this_image.append(ann)
144
+
145
+ extra_info = {}
146
+ for s in wi_extra_annotation_columns:
147
+ v = row[s]
148
+ if not isnan(v):
149
+ extra_info[s] = v
150
+ ann['wi_extra_info'] = extra_info
151
+
152
+ # ...for each row
153
+
154
+ images = list(image_id_to_image.values())
155
+ categories = []
156
+ for category_name in category_name_to_id:
157
+ category_id = category_name_to_id[category_name]
158
+ categories.append({'id':category_id,'name':category_name})
159
+ annotations = []
160
+ for image_id in image_id_to_annotations:
161
+ annotations_this_image = image_id_to_annotations[image_id]
162
+ for ann in annotations_this_image:
163
+ annotations.append(ann)
164
+ info = {'version':'1.00','description':'converted from WI export'}
165
+ info['source_file'] = csv_file_in
166
+ coco_data = {}
167
+ coco_data['info'] = info
168
+ coco_data['images'] = images
169
+ coco_data['annotations'] = annotations
170
+ coco_data['categories'] = categories
171
+
172
+
173
+ ##%% Validate images, add sizes
174
+
175
+ if validate_images:
176
+
177
+ print('Validating images')
178
+ # TODO: trivially parallelizable
179
+
180
+ assert os.path.isdir(image_folder), \
181
+ 'Must specify a valid image folder if you specify validate_images=True'
182
+
183
+ # im = images[0]
184
+ for im in tqdm(images):
185
+ file_name_relative = im['file_name']
186
+ file_name_abs = os.path.join(image_folder,file_name_relative)
187
+ assert os.path.isfile(file_name_abs)
188
+
189
+ im['corrupt'] = False
190
+ try:
191
+ pil_im = vis_utils.load_image(file_name_abs)
192
+ except Exception:
193
+ im['corrupt'] = True
194
+ if not im['corrupt']:
195
+ im['width'] = pil_im.width
196
+ im['height'] = pil_im.height
197
+
198
+
199
+ ##%% Write output json
200
+
201
+ if coco_file_out is None:
202
+
203
+ coco_file_out = csv_file_in + '.json'
204
+
205
+ with open(coco_file_out,'w') as f:
206
+ json.dump(coco_data,f,indent=1)
207
+
208
+
209
+ ##%% Validate output
210
+
211
+ from data_management.databases.integrity_check_json_db import \
212
+ IntegrityCheckOptions,integrity_check_json_db
213
+ options = IntegrityCheckOptions()
214
+ options.baseDir = image_folder
215
+ options.bCheckImageExistence = True
216
+ options.verbose = verbose
217
+ _ = integrity_check_json_db(coco_file_out,options)
218
+
219
+
220
+
221
+ #%% Interactive driver
222
+
223
+ if False:
224
+
225
+ #%%
226
+
227
+ base_folder = r'a/b/c'
228
+ csv_file_in = os.path.join(base_folder,'images.csv')
229
+ coco_file_out = None
230
+ gs_prefix = 'a_b_c_main/'
231
+ image_folder = os.path.join(base_folder,'images')
232
+ validate_images = False
233
+ verbose = True
234
+ category_remappings = default_category_remappings
235
+
236
+
237
+ #%% Command-line driver
238
+
239
+ # TODO
@@ -2,10 +2,7 @@
2
2
  #
3
3
  # yolo_to_coco.py
4
4
  #
5
- # Converts a YOLO-formatted dataset to a COCO-formatted dataset.
6
- #
7
- # Currently supports only a single folder (i.e., no recursion). Treats images without
8
- # corresponding .txt files as empty.
5
+ # Converts a folder of YOLO-formatted annotation files to a COCO-formatted dataset.
9
6
  #
10
7
  ########
11
8
 
@@ -14,37 +11,181 @@
14
11
  import json
15
12
  import os
16
13
 
17
- from PIL import Image
14
+ from multiprocessing.pool import ThreadPool
15
+ from multiprocessing.pool import Pool
16
+ from functools import partial
17
+
18
18
  from tqdm import tqdm
19
19
 
20
20
  from md_utils.path_utils import find_images
21
+ from md_utils.ct_utils import invert_dictionary
22
+ from md_visualization.visualization_utils import open_image
21
23
  from data_management.yolo_output_to_md_output import read_classes_from_yolo_dataset_file
22
24
 
23
25
 
26
+ #%% Support functions
27
+
28
+ def filename_to_image_id(fn):
29
+ return fn.replace(' ','_')
30
+
31
+ def _process_image(fn_abs,input_folder,category_id_to_name):
32
+ """
33
+ Internal support function for processing one image's labels.
34
+ """
35
+
36
+ # Create the image object for this image
37
+ fn_relative = os.path.relpath(fn_abs,input_folder)
38
+ image_id = filename_to_image_id(fn_relative)
39
+
40
+ # This is done in a separate loop now
41
+ #
42
+ # assert image_id not in image_ids, \
43
+ # 'Oops, you have hit a very esoteric case where you have the same filename ' + \
44
+ # 'with both spaces and underscores, this is not currently handled.'
45
+ # image_ids.add(image_id)
46
+
47
+ im = {}
48
+ im['file_name'] = fn_relative
49
+ im['id'] = image_id
50
+
51
+ annotations_this_image = []
52
+
53
+ try:
54
+ pil_im = open_image(fn_abs)
55
+ im_width, im_height = pil_im.size
56
+ im['width'] = im_width
57
+ im['height'] = im_height
58
+ im['error'] = None
59
+ except Exception as e:
60
+ print('Warning: error reading {}:\n{}'.format(fn_relative,str(e)))
61
+ im['width'] = -1
62
+ im['height'] = -1
63
+ im['error'] = str(e)
64
+ return (im,annotations_this_image)
65
+
66
+ # Is there an annotation file for this image?
67
+ annotation_file = os.path.splitext(fn_abs)[0] + '.txt'
68
+ if not os.path.isfile(annotation_file):
69
+ annotation_file = os.path.splitext(fn_abs)[0] + '.TXT'
70
+
71
+ if os.path.isfile(annotation_file):
72
+
73
+ with open(annotation_file,'r') as f:
74
+ lines = f.readlines()
75
+ lines = [s.strip() for s in lines]
76
+
77
+ # s = lines[0]
78
+ annotation_number = 0
79
+
80
+ for s in lines:
81
+
82
+ if len(s.strip()) == 0:
83
+ continue
84
+
85
+ tokens = s.split()
86
+ assert len(tokens) == 5
87
+ category_id = int(tokens[0])
88
+ assert category_id in category_id_to_name, \
89
+ 'Unrecognized category ID {} in annotation file {}'.format(
90
+ category_id,annotation_file)
91
+ ann = {}
92
+ ann['id'] = im['id'] + '_' + str(annotation_number)
93
+ ann['image_id'] = im['id']
94
+ ann['category_id'] = category_id
95
+ ann['sequence_level_annotation'] = False
96
+
97
+ # COCO: [x_min, y_min, width, height] in absolute coordinates
98
+ # YOLO: [class, x_center, y_center, width, height] in normalized coordinates
99
+
100
+ yolo_bbox = [float(x) for x in tokens[1:]]
101
+
102
+ normalized_x_center = yolo_bbox[0]
103
+ normalized_y_center = yolo_bbox[1]
104
+ normalized_width = yolo_bbox[2]
105
+ normalized_height = yolo_bbox[3]
106
+
107
+ absolute_x_center = normalized_x_center * im_width
108
+ absolute_y_center = normalized_y_center * im_height
109
+ absolute_width = normalized_width * im_width
110
+ absolute_height = normalized_height * im_height
111
+ absolute_x_min = absolute_x_center - absolute_width / 2
112
+ absolute_y_min = absolute_y_center - absolute_height / 2
113
+
114
+ coco_bbox = [absolute_x_min, absolute_y_min, absolute_width, absolute_height]
115
+
116
+ ann['bbox'] = coco_bbox
117
+ annotation_number += 1
118
+
119
+ annotations_this_image.append(ann)
120
+
121
+ # ...for each annotation
122
+
123
+ # ...if this image has annotations
124
+
125
+ return (im,annotations_this_image)
126
+
127
+ # ...def _process_image(...)
128
+
129
+
130
+
24
131
  #%% Main conversion function
25
132
 
26
- def yolo_to_coco(input_folder,class_name_file,output_file=None):
133
+ def yolo_to_coco(input_folder,
134
+ class_name_file,
135
+ output_file=None,
136
+ empty_image_handling='no_annotations',
137
+ empty_image_category_name='empty',
138
+ error_image_handling='no_annotations',
139
+ allow_images_without_label_files=True,
140
+ n_workers=1,
141
+ pool_type='thread',
142
+ recursive=True,
143
+ exclude_string=None,
144
+ include_string=None):
27
145
  """
28
146
  Convert the YOLO-formatted data in [input_folder] to a COCO-formatted dictionary,
29
147
  reading class names from [class_name_file], which can be a flat list with a .txt
30
148
  extension or a YOLO dataset.yml file. Optionally writes the output dataset to [output_file].
31
149
 
150
+ empty_image_handling can be:
151
+
152
+ * 'no_annotations': include the image in the image list, with no annotations
153
+
154
+ * 'empty_annotations': include the image in the image list, and add an annotation without
155
+ any bounding boxes, using a category called [empty_image_category_name].
156
+
157
+ * 'skip': don't include the image in the image list
158
+
159
+ * 'error': there shouldn't be any empty images
160
+
161
+ error_image_handling can be:
162
+
163
+ * 'skip': don't include the image at all
164
+
165
+ * 'no_annotations': include with no annotations
166
+
167
+ All images will be assigned an "error" value, usually None.
168
+
32
169
  Returns a COCO-formatted dictionary.
33
170
  """
34
171
 
35
- # Validate input
172
+ ## Validate input
36
173
 
37
174
  assert os.path.isdir(input_folder)
38
175
  assert os.path.isfile(class_name_file)
39
176
 
40
-
41
- # Read class names
177
+ assert empty_image_handling in \
178
+ ('no_annotations','empty_annotations','skip','error'), \
179
+ 'Unrecognized empty image handling spec: {}'.format(empty_image_handling)
180
+
181
+
182
+ ## Read class names
42
183
 
43
184
  ext = os.path.splitext(class_name_file)[1][1:]
44
- assert ext in ('yml','txt','yaml'), 'Unrecognized class name file type {}'.format(
185
+ assert ext in ('yml','txt','yaml','data'), 'Unrecognized class name file type {}'.format(
45
186
  class_name_file)
46
187
 
47
- if ext == 'txt':
188
+ if ext in ('txt','data'):
48
189
 
49
190
  with open(class_name_file,'r') as f:
50
191
  lines = f.readlines()
@@ -70,14 +211,41 @@ def yolo_to_coco(input_folder,class_name_file,output_file=None):
70
211
 
71
212
  assert ext in ('yml','yaml')
72
213
  category_id_to_name = read_classes_from_yolo_dataset_file(class_name_file)
214
+
215
+ # Find or create the empty image category, if necessary
216
+ empty_category_id = None
217
+
218
+ if (empty_image_handling == 'empty_annotations'):
219
+ category_name_to_id = invert_dictionary(category_id_to_name)
220
+ if empty_image_category_name in category_name_to_id:
221
+ empty_category_id = category_name_to_id[empty_image_category_name]
222
+ print('Using existing empty image category with name {}, ID {}'.format(
223
+ empty_image_category_name,empty_category_id))
224
+ else:
225
+ empty_category_id = len(category_id_to_name)
226
+ print('Adding an empty category with name {}, ID {}'.format(
227
+ empty_image_category_name,empty_category_id))
228
+ category_id_to_name[empty_category_id] = empty_image_category_name
73
229
 
74
-
75
- # Enumerate images
230
+
231
+ ## Enumerate images
232
+
233
+ print('Enumerating images...')
76
234
 
77
- image_files = find_images(input_folder,recursive=False)
235
+ image_files_abs = find_images(input_folder,recursive=recursive,convert_slashes=True)
78
236
 
79
- images = []
80
- annotations = []
237
+ n_files_original = len(image_files_abs)
238
+
239
+ # Optionally include/exclude images matching specific strings
240
+ if exclude_string is not None:
241
+ image_files_abs = [fn for fn in image_files_abs if exclude_string not in fn]
242
+ if include_string is not None:
243
+ image_files_abs = [fn for fn in image_files_abs if include_string in fn]
244
+
245
+ if len(image_files_abs) != n_files_original or exclude_string is not None or include_string is not None:
246
+ n_excluded = n_files_original - len(image_files_abs)
247
+ print('Excluded {} of {} images based on filenames'.format(n_excluded,n_files_original))
248
+
81
249
  categories = []
82
250
 
83
251
  for category_id in category_id_to_name:
@@ -87,79 +255,111 @@ def yolo_to_coco(input_folder,class_name_file,output_file=None):
87
255
  info['version'] = '1.0'
88
256
  info['description'] = 'Converted from YOLO format'
89
257
 
90
- # fn = image_files[0]
91
- for fn in tqdm(image_files):
92
-
93
- im = Image.open(fn)
94
- im_width, im_height = im.size
95
-
96
- # Create the image object for this image
97
- im = {}
98
- fn_relative = os.path.relpath(fn,input_folder)
99
- im['file_name'] = fn_relative
100
- im['id'] = fn_relative.replace(' ','_')
101
- im['location'] = 'unknown'
102
- images.append(im)
103
-
104
- # Is there an annotation file for this image?
105
- annotation_file = os.path.splitext(fn)[0] + '.txt'
106
- if not os.path.isfile(annotation_file):
107
- annotation_file = os.path.splitext(fn)[0] + '.TXT'
108
- if not os.path.isfile(annotation_file):
109
- # This is an image with no annotations, currently don't do anything special
110
- # here
111
- pass
258
+ image_ids = set()
259
+
260
+
261
+ ## If we're expected to have labels for every image, check before we process all the images
262
+
263
+ if not allow_images_without_label_files:
264
+ print('Verifying that label files exist')
265
+ for image_file_abs in tqdm(image_files_abs):
266
+ label_file_abs = os.path.splitext(image_file_abs)[0] + '.txt'
267
+ assert os.path.isfile(label_file_abs), \
268
+ 'No annotation file for {}'.format(image_file_abs)
269
+
270
+
271
+ ## Initial loop to make sure image IDs will be unique
272
+
273
+ print('Validating image IDs...')
274
+
275
+ for fn_abs in tqdm(image_files_abs):
276
+
277
+ fn_relative = os.path.relpath(fn_abs,input_folder)
278
+ image_id = filename_to_image_id(fn_relative)
279
+ assert image_id not in image_ids, \
280
+ 'Oops, you have hit a very esoteric case where you have the same filename ' + \
281
+ 'with both spaces and underscores, this is not currently handled.'
282
+ image_ids.add(image_id)
283
+
284
+
285
+ ## Main loop to process labels
286
+
287
+ print('Processing labels...')
288
+
289
+ if n_workers <= 1:
290
+
291
+ image_results = []
292
+ for fn_abs in tqdm(image_files_abs):
293
+ image_results.append(_process_image(fn_abs,input_folder,category_id_to_name))
294
+
295
+ else:
296
+
297
+ assert pool_type in ('process','thread'), 'Illegal pool type {}'.format(pool_type)
298
+
299
+ if pool_type == 'thread':
300
+ pool = ThreadPool(n_workers)
112
301
  else:
113
- with open(annotation_file,'r') as f:
114
- lines = f.readlines()
115
- lines = [s.strip() for s in lines]
116
-
117
- # s = lines[0]
118
- annotation_number = 0
119
- for s in lines:
120
- if len(s.strip()) == 0:
121
- continue
122
- tokens = s.split()
123
- assert len(tokens) == 5
124
- category_id = int(tokens[0])
125
- assert category_id in category_id_to_name, \
126
- 'Unrecognized category ID {} in annotation file {}'.format(
127
- category_id,annotation_file)
128
- ann = {}
129
- ann['id'] = im['id'] + '_' + str(annotation_number)
130
- ann['image_id'] = im['id']
131
- ann['category_id'] = category_id
132
- ann['sequence_level_annotation'] = False
133
-
134
- # COCO: [x_min, y_min, width, height] in absolute coordinates
135
- # YOLO: [class, x_center, y_center, width, height] in normalized coordinates
136
-
137
- yolo_bbox = [float(x) for x in tokens[1:]]
138
-
139
- normalized_x_center = yolo_bbox[0]
140
- normalized_y_center = yolo_bbox[1]
141
- normalized_width = yolo_bbox[2]
142
- normalized_height = yolo_bbox[3]
143
-
144
- absolute_x_center = normalized_x_center * im_width
145
- absolute_y_center = normalized_y_center * im_height
146
- absolute_width = normalized_width * im_width
147
- absolute_height = normalized_height * im_height
148
- absolute_x_min = absolute_x_center - absolute_width / 2
149
- absolute_y_min = absolute_y_center - absolute_height / 2
150
-
151
- coco_bbox = [absolute_x_min, absolute_y_min, absolute_width, absolute_height]
302
+ pool = Pool(n_workers)
303
+
304
+ print('Starting a {} pool of {} workers'.format(pool_type,n_workers))
305
+
306
+ p = partial(_process_image,input_folder=input_folder,
307
+ category_id_to_name=category_id_to_name)
308
+ image_results = list(tqdm(pool.imap(p, image_files_abs),
309
+ total=len(image_files_abs)))
152
310
 
153
- ann['bbox'] = coco_bbox
154
- annotation_number += 1
311
+
312
+ assert len(image_results) == len(image_files_abs)
313
+
314
+
315
+ ## Re-assembly of results into a COCO dict
316
+
317
+ print('Assembling labels...')
318
+
319
+ images = []
320
+ annotations = []
321
+
322
+ for image_result in tqdm(image_results):
323
+
324
+ im = image_result[0]
325
+ annotations_this_image = image_result[1]
326
+
327
+ # If we have annotations for this image
328
+ if len(annotations_this_image) > 0:
329
+ assert im['error'] is None
330
+ images.append(im)
331
+ for ann in annotations_this_image:
332
+ annotations.append(ann)
155
333
 
156
- annotations.append(ann)
334
+ # If this image failed to read
335
+ elif im['error'] is not None:
336
+
337
+ if error_image_handling == 'skip':
338
+ pass
339
+ elif error_image_handling == 'no_annotations':
340
+ images.append(im)
157
341
 
158
- # ...for each annotation
342
+ # If this image read successfully, but there are no annotations
343
+ else:
159
344
 
160
- # ...if this image has annotations
345
+ if empty_image_handling == 'skip':
346
+ pass
347
+ elif empty_image_handling == 'no_annotations':
348
+ images.append(im)
349
+ elif empty_image_handling == 'empty_annotations':
350
+ assert empty_category_id is not None
351
+ ann = {}
352
+ ann['id'] = im['id'] + '_0'
353
+ ann['image_id'] = im['id']
354
+ ann['category_id'] = empty_category_id
355
+ ann['sequence_level_annotation'] = False
356
+ # This would also be a reasonable thing to do, but it's not the convention
357
+ # we're adopting.
358
+ # ann['bbox'] = [0,0,0,0]
359
+ annotations.append(ann)
360
+ images.append(im)
161
361
 
162
- # ...for each image
362
+ # ...for each image result
163
363
 
164
364
  print('Read {} annotations for {} images'.format(len(annotations),
165
365
  len(images)))