pycocowriter 0.0.1__py2.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.
File without changes
pycocowriter/coco.py ADDED
@@ -0,0 +1,348 @@
1
+ import datetime
2
+ import json
3
+ from collections.abc import Iterable
4
+ import numpy as np
5
+ from . import utils
6
+
7
+ class COCOBase(object):
8
+ '''
9
+ base class to facilitate conversion of COCO stuff to a dictionary.
10
+
11
+ TODO: refactor COCO classes to extend pycocotools' COCO
12
+
13
+ TODO: refactor COCO classes to use AttrDict
14
+ '''
15
+ def _to_dict_fields(self, fields:list[str]) -> dict:
16
+ return {field: self.__dict__[field] for field in fields if self.__dict__[field] is not None}
17
+ def to_dict(self):
18
+ '''convert COCO object to dictionary'''
19
+ raise NotImplementedError('must implement a to_dict method!')
20
+
21
+
22
+ class COCOAnnotation(COCOBase):
23
+ '''
24
+ see https://cocodataset.org/#format-data
25
+
26
+ see https://github.com/cocodataset/cocoapi/issues/184
27
+
28
+ annotation {
29
+ "id": int,
30
+ "image_id": int,
31
+ "category_id": int,
32
+ "segmentation": RLE or [polygon],
33
+ "area": float,
34
+ "bbox": [x,y,width,height],
35
+ "iscrowd": 0 or 1,
36
+ "keypoints": [x1,y1,v1...],
37
+ "num_keypoints": int
38
+ }
39
+
40
+ keypoints should be 3*len(keypoints_category), where keypoints_category is the keypoints in the corresponding category
41
+ '''
42
+ def __init__(self, image_id:int, eye_d:int, category_id:int, bbox:tuple[int]=None, area:float=None, segmentation=None, iscrowd:int=None, keypoints:list[int]=None):
43
+ self.image_id = image_id
44
+ self.id = eye_d
45
+ self.category_id = category_id
46
+ self.bbox = bbox
47
+ area = area or self._compute_area()
48
+ self.area = area
49
+ self.segmentation = segmentation
50
+ self.iscrowd = iscrowd or 0
51
+ self.keypoints = keypoints
52
+ self.num_keypoints = self._compute_num_keypoints()
53
+
54
+ def _compute_num_keypoints(self):
55
+ if self.keypoints is not None:
56
+ # the number of keypoints is the number of "visible" keypoints.
57
+ return sum([v > 0 for v in self.keypoints[::3]])
58
+ return None
59
+
60
+ def _compute_area(self):
61
+ if self.bbox is not None:
62
+ return self.bbox[-1] * self.bbox[-2]
63
+ return None
64
+
65
+ def to_dict(self):
66
+ return self._to_dict_fields(
67
+ ['image_id', 'id', 'category_id', 'bbox',
68
+ 'area', 'segmentation', 'iscrowd',
69
+ 'keypoints', 'num_keypoints'])
70
+
71
+
72
+ class COCOCategory(COCOBase):
73
+ '''
74
+ see https://cocodataset.org/#format-data
75
+
76
+ see https://github.com/facebookresearch/Detectron/issues/640
77
+
78
+ category {
79
+ "id": int,
80
+ "name": str,
81
+ "supercategory": str,
82
+ "keypoints": [str],
83
+ "skeleton": [edge]
84
+ }
85
+
86
+ An edge is a tuple [a,b] where a,b are 1-indexed indices in the keypoints list. So if keypoints is ["a", "b"], then [1,2] is an edge between "a" and "b"
87
+ '''
88
+ def __init__(self, name:str, eye_d:int, supercategory:str=None, keypoints:list[str]=None, skeleton:list[list[int]]=None):
89
+ self.name = name
90
+ self.id = eye_d
91
+ self.supercategory = supercategory
92
+ self.keypoints = keypoints
93
+ self.skeleton = skeleton
94
+
95
+ def to_dict(self):
96
+ return self._to_dict_fields(
97
+ ['name', 'id', 'supercategory', 'keypoints', 'skeleton']
98
+ )
99
+
100
+
101
+ class COCOLicense(COCOBase):
102
+ '''
103
+ see https://cocodataset.org/#format-data
104
+
105
+ license {
106
+ "id": int,
107
+ "name": str,
108
+ "url": str,
109
+ }
110
+ '''
111
+ def __init__(self, name:str, eye_d:int, url:str=None):
112
+ self.name = name
113
+ self.id = eye_d
114
+ self.url = url
115
+
116
+ def to_dict(self):
117
+ return self._to_dict_fields(
118
+ ['name', 'id', 'url']
119
+ )
120
+
121
+
122
+ class COCOImage(COCOBase):
123
+ '''
124
+ see https://cocodataset.org/#format-data
125
+
126
+ image {
127
+ "id": int,
128
+ "width": int,
129
+ "height": int,
130
+ "file_name": str,
131
+ "license": int,
132
+ "flickr_url": str,
133
+ "coco_url": str,
134
+ "date_captured": datetime,
135
+ }
136
+ '''
137
+ def __init__(self,
138
+ eye_d:int, file_name:str,
139
+ width:int=None, height:int=None,
140
+ license:int=None, coco_url:str=None,
141
+ date_captured:datetime.datetime=None):
142
+ self.id = eye_d
143
+ self.file_name = file_name
144
+ self.width = width
145
+ self.height = height
146
+ self.license = license
147
+ self.coco_url = coco_url
148
+ self.date_captured = date_captured
149
+
150
+ def to_dict(self) -> dict:
151
+ the_dict = self._to_dict_fields(
152
+ ['id', 'file_name', 'width', 'height', 'license', 'coco_url']
153
+ )
154
+ if self.date_captured:
155
+ the_dict['date_captured'] = self.date_captured.isoformat()
156
+ return the_dict
157
+
158
+ class COCOInfo(COCOBase):
159
+ '''
160
+ see https://cocodataset.org/#format-data
161
+
162
+ info {
163
+ "year": int,
164
+ "version": str,
165
+ "description": str,
166
+ "contributor": str,
167
+ "url": str,
168
+ "date_created": datetime,
169
+ }
170
+ '''
171
+ def __init__(self, year:int=None, version:str=None, description:str=None, contributor:str=None, url:str=None, date_created:datetime.datetime=None):
172
+ self.year = year
173
+ self.version = version
174
+ self.description = description
175
+ self.contributor = contributor
176
+ self.url = url
177
+ self.date_created = date_created
178
+
179
+ def to_dict(self) -> dict:
180
+ the_dict = self._to_dict_fields(
181
+ ['year', 'version', 'description', 'contributor', 'url']
182
+ )
183
+ if self.date_created:
184
+ the_dict['date_created'] = self.date_created.isoformat()
185
+ return the_dict
186
+
187
+
188
+
189
+ class COCOCategories(object):
190
+ '''
191
+ helper class to hold the index on categories so that we can find categories by name or by index
192
+
193
+ Parameters
194
+ ----------
195
+ categories: list[COCOCategory]
196
+ existing list of COCOCategories if available
197
+
198
+ Attributes
199
+ ----------
200
+ categories: list[COCOCategory]
201
+ a list of unique categories
202
+ category_map: dict[str, COCOCategory]
203
+ maps category names to categories
204
+ '''
205
+ def __init__(self, categories: list[COCOCategory] | None = None):
206
+ categories = categories or []
207
+ # category ids MUST match their index in the category list!
208
+ self.categories = categories
209
+ for i, category in enumerate(self.categories):
210
+ assert category.id == i
211
+ self.category_map = {category.name: category.id for category in self.categories}
212
+
213
+ def add(self, label:str, keypoints:list[str]=None, skeleton:list[list[int]]=None) -> COCOCategory:
214
+ '''
215
+ Add a new category to the list. Updates the map as well
216
+
217
+ Parameters
218
+ ----------
219
+ label: str
220
+ the string name of this category
221
+ keypoints: list[str]
222
+ the list of keypoint names for this category, if applicable
223
+ skeleton: list[list[int]]
224
+ the skeleton for this category, if applicable
225
+
226
+ Returns
227
+ -------
228
+ category: COCOCategory
229
+ returns the built COCOCategory
230
+ '''
231
+ if label not in self.category_map:
232
+ category = COCOCategory(label, len(self.categories), keypoints=keypoints, skeleton=skeleton)
233
+ self.categories.append(category)
234
+ self.category_map[self.categories[-1].name] = self.categories[-1].id
235
+ return self.category_map[label]
236
+
237
+ def __len__(self):
238
+ return len(self.categories)
239
+
240
+ class COCOImages(object):
241
+ '''
242
+ helper class to hold the index on images so that we can find images by name or by index
243
+
244
+ Parameters
245
+ ----------
246
+ images: list[COCOImage]
247
+ existing list of COCOImages if available
248
+
249
+ Attributes
250
+ ----------
251
+ images: list[COCOImage]
252
+ a list of unique images
253
+ image_map: dict[str, COCOImage]
254
+ maps image names to images
255
+ '''
256
+ def __init__(self, images: list[COCOImage] | None = None):
257
+ images = images or []
258
+ # image ids MUST match their index in the image list!
259
+ self.images = images
260
+ for i, image in enumerate(self.images):
261
+ assert image.id == i
262
+ self.image_map = {image.filename: image.id for image in self.images}
263
+
264
+ def add(self, filename:str, width:int=None, height:int=None,
265
+ url:str=None, license:int=None, date_captured:datetime.datetime=None):
266
+ '''
267
+ Add a new image to the list. Updates the map as well
268
+
269
+ Parameters
270
+ ----------
271
+ filename: str
272
+ the filename of this image
273
+ width: int
274
+ the width of this image, if known
275
+ height: int
276
+ the height of this image, if known
277
+ url: str
278
+ the url at which this image can be downloaded
279
+ license: int
280
+ the index in the list of COCOLicense with the applicable license
281
+ date_captured: datetime.datetime
282
+ the date and time when the image was captured
283
+
284
+ Returns
285
+ -------
286
+ image: COCOImage
287
+ returns the built COCOImage
288
+ '''
289
+ if filename not in self.image_map:
290
+ image = COCOImage(len(self.images), filename,
291
+ width=width, height=height, coco_url=url,
292
+ license=license, date_captured=date_captured)
293
+ self.images.append(image)
294
+ self.image_map[self.images[-1].file_name] = self.images[-1].id
295
+ return self.image_map[filename]
296
+
297
+ def __len__(self):
298
+ return len(self.images)
299
+
300
+
301
+ class COCOData(COCOBase):
302
+ '''
303
+ see https://cocodataset.org/#format-data
304
+
305
+ coco {
306
+ "info": info,
307
+ "images": [image],
308
+ "annotations": [annotation],
309
+ "licenses": [license],
310
+ "categories": [category]
311
+ }
312
+ '''
313
+
314
+ def __init__(self, info:COCOInfo, images:list[COCOImage], annotations:list[COCOAnnotation], licenses:list[COCOLicense], categories:list[COCOCategory]):
315
+ self.info = info
316
+ self.images = images
317
+ self.annotations = annotations
318
+ self.licenses = licenses
319
+ self.categories = categories
320
+
321
+ def to_dict(self) -> dict:
322
+ return {
323
+ 'info': self.info.to_dict(),
324
+ 'images': [image.to_dict() for image in self.images],
325
+ 'annotations': [annotation.to_dict() for annotation in self.annotations],
326
+ 'licenses': [license.to_dict() for license in self.licenses],
327
+ 'categories': [category.to_dict() for category in self.categories]
328
+ }
329
+
330
+ def to_json(self, filename:str=None):
331
+ '''
332
+ dumps this COCO to json. If a filename is provided, writes to disk, else, returns
333
+ the string JSON
334
+
335
+ Parameters
336
+ ----------
337
+ filename: str
338
+ the optional location to which we should write the json
339
+
340
+ Returns
341
+ -------
342
+ json: str | None
343
+ if no filename is provided, returns the JSON COCO data as a string
344
+ '''
345
+ if filename is None:
346
+ return json.dumps(self.to_dict(), cls=utils.NPEncoder)
347
+ with open(filename, 'w') as f:
348
+ json.dump(self.to_dict(), f, cls=utils.NPEncoder)
@@ -0,0 +1,359 @@
1
+ import json
2
+ import jsonschema
3
+ from . import coco, utils
4
+ import csv
5
+ from collections.abc import Sequence, Iterable
6
+
7
+
8
+ def parse_csv(config: dict, filename: str) -> tuple[
9
+ list[coco.COCOImage], list[coco.COCOAnnotation], list[coco.COCOCategory]]:
10
+ """Helper method to open a csv file and pass it row-by-row into the COCO builder
11
+
12
+ Parameters
13
+ ----------
14
+ config : dict
15
+ a dictionary conforming to the Iterable2COCOConfig.SCHEMA
16
+ filename : str
17
+ a csv file to be read and converted to COCO
18
+
19
+ Returns
20
+ -------
21
+ images : list[COCOImage]
22
+ a list of COCOImage reflecting the images from the csv file
23
+ annotations : list[COCOAnnotation]
24
+ a list of COCOAnnotation reflecting the annotations from the csv file
25
+ categories : list[COCOCategory]
26
+ a list of COCOCategory reflecting the categories of the annotations
27
+ """
28
+ csv2coco = Iterable2COCO(Iterable2COCOConfig(config))
29
+ with open(filename) as f:
30
+ reader = csv.reader(f)
31
+ images, annotations, categories = csv2coco.parse(reader)
32
+ return images, annotations, categories
33
+
34
+
35
+ def bbox_tlbr2xywh(bbox: tuple[int, int, int, int]) -> tuple[int, int, int, int]:
36
+ '''
37
+ Convert a bounding box in "top left, bottom right" format
38
+ to a bounding box in "top left, width height" format
39
+
40
+ Parameters
41
+ ----------
42
+ bbox: tuple[int,int,int,int]
43
+ a four-tuple of [top_left_x, top_left_y, bottom_right_x, bottom_right_y]
44
+
45
+ Returns
46
+ -------
47
+ bbox: tuple[int,int,int,int]
48
+ a four-tuple of [top_left_x, top_left_y, width, height]
49
+ '''
50
+ return (bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1])
51
+
52
+
53
+ class Iterable2COCOConfig(utils.AttrDict):
54
+ '''
55
+ This class validates a configuration to convert a "flat" iterable type into COCO.
56
+ Because COCO has complex nested and optional types, it is not possible to have a
57
+ "one-size-fits-all" flat iterable to COCO conversion. This configuration tells
58
+ the converter which fields are present, and in which columns they are located.
59
+ This class exists only to validate a dict as a valid configuration.
60
+
61
+ Parameters
62
+ ----------
63
+ config: dict
64
+ a configuration dictionary adhering to SCHEMA. Set up this way to be read from .json
65
+
66
+ Attributes
67
+ ----------
68
+ SCHEMA: dict
69
+ a jsonschema representing a valid configuration
70
+ '''
71
+
72
+ # TODO: This is the 'anything' schema. Update to reflect the actual rules
73
+ SCHEMA = {}
74
+
75
+ def __init__(self, config: dict):
76
+ self._validate_config(config)
77
+ super().__init__(config)
78
+
79
+ def _validate_config(self, config: dict):
80
+ jsonschema.validate(config, Iterable2COCOConfig.SCHEMA)
81
+
82
+
83
+ class IterableBBoxParser(object):
84
+ '''
85
+ This class is to help parse bounding boxes from "row" data. Sometimes these data
86
+ are in different formats, so this class is intended to assist in dealing with these nuances
87
+
88
+ Parameters
89
+ ----------
90
+ config: Iterable2COCOConfig
91
+ this configuration should define how (and if) bounding boxes are present in each row
92
+ '''
93
+
94
+ def __init__(self, config: Iterable2COCOConfig):
95
+ self.config = config
96
+ self._init_bbox_method()
97
+
98
+ def _init_bbox_tlbr(self):
99
+ '''
100
+ configures the `get_bbox` method to get bounding boxes in "top left, width/height"
101
+ format, given bounding boxes in "top left, bottom right" format
102
+ '''
103
+ self.get_bbox = self._get_bbox_tlbr
104
+ self.bbox_cols = [
105
+ self.config.bbox_tlbr.tlx,
106
+ self.config.bbox_tlbr.tly,
107
+ self.config.bbox_tlbr.brx,
108
+ self.config.bbox_tlbr.bry,
109
+ ]
110
+
111
+ def _init_bbox_xywh(self):
112
+ '''
113
+ configures the `get_bbox` method to get bounding boxes in "top left, width/height"
114
+ format, given bounding boxes in "top left, width/height" format
115
+ '''
116
+ self.get_bbox = self._get_bbox_xywh
117
+ self.bbox_cols = [
118
+ self.config.bbox_xywh.x,
119
+ self.config.bbox_xywh.y,
120
+ self.config.bbox_xywh.w,
121
+ self.config.bbox_xywh.h,
122
+ ]
123
+
124
+ def _init_bbox_method(self):
125
+ '''
126
+ dispatches configuration of the `get_bbox` method depending on the
127
+ contents of the configuration file.
128
+ '''
129
+ if 'bbox_tlbr' in self.config:
130
+ self._init_bbox_tlbr()
131
+ elif 'bbox_xywh' in self.config:
132
+ self._init_bbox_xywh()
133
+
134
+ def get_bbox(self, row: Sequence) -> list[int, int, int, int] | None:
135
+ '''
136
+ this method gets overwritten in __init__ if the config has a bbox option
137
+
138
+ Parameters
139
+ ----------
140
+ row: Sequence
141
+ a row, e.g. from a csv. The bounding box should be in some columns of this row
142
+ as defined in the configuration
143
+
144
+ Returns
145
+ -------
146
+ bbox: list[int,int,int,int]
147
+ the bounding box as [top_left_x, top_left_y, width, height]
148
+ '''
149
+ return None
150
+
151
+ def _get_bbox_tlbr(self, row: Sequence) -> list[int, int, int, int]:
152
+ '''
153
+ gets a bounding box in "top left, width/height" format given bounding box subsetted from a
154
+ row from, e.g. a csv. The subset of columns in the input row should be defined in the config,
155
+ and should contain a bounding box in "top left, bottom right" format.
156
+
157
+ Parameters
158
+ ----------
159
+ row: Sequence
160
+ a row, e.g. from a csv. The bounding box should be in some columns of this row
161
+ as defined in the configuration
162
+
163
+ Returns
164
+ -------
165
+ bbox: list[int,int,int,int]
166
+ the bounding box as [top_left_x, top_left_y, width, height]
167
+ '''
168
+ return bbox_tlbr2xywh([int(float(row[i])) for i in self.bbox_cols])
169
+
170
+ def _get_bbox_xywh(self, row: Sequence) -> list[int, int, int, int]:
171
+ '''
172
+ gets a bounding box in "top left, width/height" format given bounding box subsetted from a
173
+ row from, e.g. a csv. The subset of columns in the input row should be defined in the config,
174
+ and should contain a bounding box in "top left, width/height" format.
175
+
176
+ Parameters
177
+ ----------
178
+ row: Sequence
179
+ a row, e.g. from a csv. The bounding box should be in some columns of this row
180
+ as defined in the configuration
181
+
182
+ Returns
183
+ -------
184
+ bbox: list[int,int,int,int]
185
+ the bounding box as [top_left_x, top_left_y, width, height]
186
+ '''
187
+ return [int(float(row[i])) for i in self.bbox_cols]
188
+
189
+
190
+ class IterableKeypointParser(object):
191
+ '''
192
+ This class is to help parse keypoints from "row" data. Sometimes these data
193
+ are in different formats, so this class is intended to assist in dealing with these nuances
194
+
195
+ Parameters
196
+ ----------
197
+ config: Iterable2COCOConfig
198
+ this configuration should define how (and if) bounding boxes are present in each row
199
+ '''
200
+ FULLY_VISIBLE_COCO_KEYPOINT = 2
201
+
202
+ def __init__(self, config: Iterable2COCOConfig):
203
+ self.config = config
204
+
205
+ def keypoint_config(self) -> tuple[list[str], list[list[int]]]:
206
+ '''
207
+ get the keypoint layout from the configuration file. the coco keypoint layout is
208
+ ['kpname1', 'kpname2', ...]
209
+ and also a skeleton
210
+ [edge1, edge2, ...]
211
+ where edges are two-tuples of 1-indexed indexes of keypoints. For example, if keypoints are:
212
+ ['hip', 'knee', 'ankle'],
213
+ the skeleton would be:
214
+ [[1,2],[2,3]] because the hip has an edge with the knee, and the knee has an edge to the ankle.
215
+
216
+ both of these items should be defined in the configuration
217
+
218
+ TODO: we only support ONE keypoint structure per configuration right now....
219
+ if you have multiple possible keypoint structures
220
+ e.g. hands and also human poses, then we need to rework this to be more general
221
+
222
+ Returns
223
+ -------
224
+ keypoints: list[str]
225
+ the list of keypoint names
226
+ skeleton: list[list[int]]
227
+ the skeleton corresponding to the keypoint names
228
+ '''
229
+ return (
230
+ [
231
+ keypoint.name for keypoint in self.config.keypoints
232
+ ],
233
+ self.config.keypoint_skeleton
234
+ )
235
+
236
+ def get_keypoints(self, row: Sequence) -> list[int]:
237
+ '''
238
+ get keypoints from a "flat" row using expected indices in the row of keypoints
239
+ defined in self.config
240
+
241
+ Parameters
242
+ ----------
243
+ row: Sequence
244
+ a row, e.g. from a csv. The keypoints should be in some columns of this row
245
+ as defined in the configuration
246
+
247
+ Returns
248
+ -------
249
+ keypoints: list[int]
250
+ keypoint locations in form [x1,y1,v1,x2,y2,v2,....] where x,y are the location and v is the
251
+ "visibility" according to the COCO docs
252
+ '''
253
+ if 'keypoints' not in self.config:
254
+ return None
255
+ return sum(
256
+ [
257
+ [
258
+ int(float(row[keypoint.x])),
259
+ int(float(row[int(keypoint.y)])),
260
+ IterableKeypointParser.FULLY_VISIBLE_COCO_KEYPOINT if 'visibility' not in keypoint else int(float(row[keypoint.visibility]))
261
+ ]
262
+ for keypoint in self.config.keypoints
263
+ ],
264
+ []
265
+ )
266
+
267
+
268
+ class Iterable2COCO(object):
269
+ '''
270
+ class providing methods for parsing a "flat" iterable (e.g. a csv) of annotations into COCO
271
+ format. Each "row" should contain things such as the image filename,
272
+ and the annotation information (e.g a bounding box or keypoints and a category label)
273
+
274
+ Parameters
275
+ ----------
276
+ config: Iterable2COCOConfig
277
+ a configuration dictionary detailing which columns in each row correspond to various COCO
278
+ features such as filename and bounding box coordinates.
279
+
280
+ Attributes
281
+ ----------
282
+ bbox_parser: IterableBBoxParser
283
+ a helper for parsing bounding boxes and the concomitant configuration
284
+ keypoint_parser: IterableKeypointParser
285
+ a helper for parsing keypoints and the concomitant configuration
286
+ '''
287
+
288
+ def __init__(self, config: Iterable2COCOConfig):
289
+ self.config = config
290
+ self.bbox_parser = IterableBBoxParser(config)
291
+ self.keypoint_parser = IterableKeypointParser(config)
292
+
293
+ def _get_scalar(self, field: str, row: Sequence):
294
+ '''
295
+ get a single value from a row given the field name in the configuration
296
+
297
+ Parameters
298
+ ----------
299
+ field: str
300
+ the field name as expected in the configuration, and should point to a column index.
301
+ viz. self.config[field] should provide an index into row
302
+ row: Sequence
303
+ an indexable "row" e.g. from a csv file
304
+
305
+ Returns
306
+ -------
307
+ scalar: any
308
+ a single value expected for that field.
309
+ '''
310
+ if field not in self.config:
311
+ return None
312
+ return row[self.config[field]]
313
+
314
+ def parse(self, row_iterable: Iterable[Sequence]) -> tuple[
315
+ list[coco.COCOImage], list[coco.COCOAnnotation], list[coco.COCOCategory]
316
+ ]:
317
+ '''
318
+ parse an iterable of rows (e.g. from a csv file) containing image annotation information into
319
+ COCO format.
320
+
321
+ Parameters
322
+ ----------
323
+ row_iterable: Iterable[Sequence]
324
+ an iterable of rows containing annotation information
325
+
326
+ Returns
327
+ -------
328
+ images: list[COCOImage]
329
+ a list of all unique images listed in the iterable, in COCO format
330
+ annotations: list[COCOAnnotation]
331
+ a list of all annotations listed in the iterable, correctly indexed
332
+ against the images and categories lists
333
+ categories: list[COCOCategory]
334
+ a list of all unique categories listed in the iterable, in COCO format
335
+ '''
336
+ categories = coco.COCOCategories()
337
+ images = coco.COCOImages()
338
+ annotations = []
339
+ utils.skiprows(row_iterable, self.config.meta.skiprows)
340
+ keypoint_names, keypoint_skeleton = self.keypoint_parser.keypoint_config()
341
+ for row in row_iterable:
342
+ bbox = self.bbox_parser.get_bbox(row)
343
+ keypoints = self.keypoint_parser.get_keypoints(row)
344
+ filename = self._get_scalar('filename', row)
345
+ width = self._get_scalar('width', row)
346
+ height = self._get_scalar('height', row)
347
+ label = self._get_scalar('label', row)
348
+ images.add(filename, width, height)
349
+ categories.add(label, keypoint_names, keypoint_skeleton)
350
+ annotations.append(
351
+ coco.COCOAnnotation(
352
+ images.image_map[filename],
353
+ len(annotations),
354
+ categories.category_map[label],
355
+ bbox=bbox,
356
+ keypoints=keypoints
357
+ )
358
+ )
359
+ return images.images, annotations, categories.categories
pycocowriter/utils.py ADDED
@@ -0,0 +1,89 @@
1
+ from collections.abc import Iterable
2
+ import json
3
+ import numpy as np
4
+
5
+ class AttrDict(dict):
6
+ '''
7
+ This class allows javascript-like attribute access for dictionaries.
8
+ Pass in a nested dictionary and recursively access members as attributes.
9
+
10
+ Examples
11
+ --------
12
+ >>> attr_dict = AttrDict({'foo': [{'bar': [1,2]}]})
13
+ >>> attr_dict.foo[0].bar[1] == 2
14
+ True
15
+ '''
16
+ def __init__(self, some_dict:dict):
17
+ super().__init__(some_dict)
18
+ self.__dict__ = {
19
+ k: _to_attrdict(v) for k,v in some_dict.items()
20
+ }
21
+
22
+ def _to_attrdict(el) -> AttrDict:
23
+ '''
24
+ This method facilitates recursive application of AttrDict.
25
+ Probably you don't want to call this method on its own -
26
+ instead, instantiate an `AttrDict`
27
+
28
+ Parameters
29
+ ----------
30
+ el: any
31
+ element to recursively convert to AttrDict.
32
+
33
+ Returns
34
+ -------
35
+ any
36
+ the element, possibly converted to an AttrDict depending on type
37
+ '''
38
+ # cast dictionaries at AttrDict
39
+ if isinstance(el, dict):
40
+ return AttrDict(el)
41
+ # return strings
42
+ if isinstance(el, str):
43
+ return el
44
+ # try to recurse into other iterable types
45
+ if isinstance(el, Iterable):
46
+ return type(el)((
47
+ _to_attrdict(v) for v in el
48
+ ))
49
+ # return simple types unchanged
50
+ return el
51
+
52
+ class NPEncoder(json.JSONEncoder):
53
+ '''
54
+ json encoder class used to convert numpy types into plain python types during json.dumps.
55
+
56
+ Examples
57
+ --------
58
+ >>> json.dumps({'array': np.array([1,2,3])}, cls=NPEncoder)
59
+ '{"array": [1, 2, 3]}'
60
+
61
+ '''
62
+ def default(self, obj):
63
+ if isinstance(obj, np.integer):
64
+ return int(obj)
65
+ if isinstance(obj, np.floating):
66
+ return float(obj)
67
+ if isinstance(obj, np.ndarray):
68
+ return obj.tolist()
69
+ return super(NPEncoder, self).default(obj)
70
+
71
+ def skiprows(iterable: Iterable, n: int) -> Iterable:
72
+ '''
73
+ skips the first n rows of iterable. returns iterable as a convenience
74
+
75
+ Parameters
76
+ ----------
77
+ iterable: Iterable
78
+ the iterable to skip rows of
79
+ n: int
80
+ the number of rows to skip
81
+
82
+ Returns
83
+ -------
84
+ iterable: Iterable
85
+ the original iterable, but now with n rows having been skipped
86
+ '''
87
+ for i in range(n):
88
+ next(iterable)
89
+ return iterable
@@ -0,0 +1,132 @@
1
+ FOR ALL WORK SUBMITTED BY FEDERAL EMPLOYEES:
2
+
3
+ Software code created by U.S. Government employees is not subject to copyright in the United States
4
+ (17 U.S.C. §105). The United States/Department of Commerce reserve all rights to seek and obtain
5
+ copyright protection in countries other than the United States for Software authored in its entirety by the
6
+ Department of Commerce. To this end, the Department of Commerce hereby grants to Recipient a
7
+ royalty-free, nonexclusive license to use, copy, and create derivative works of the Software outside of the
8
+ United States.
9
+
10
+ FOR ALL WORK SUBMITTED BY NON-FEDERAL EMPLOYEES:
11
+
12
+ Creative Commons Legal Code
13
+
14
+ CC0 1.0 Universal
15
+
16
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
17
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
18
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
19
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
20
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
21
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
22
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
23
+ HEREUNDER.
24
+
25
+ Statement of Purpose
26
+
27
+ The laws of most jurisdictions throughout the world automatically confer
28
+ exclusive Copyright and Related Rights (defined below) upon the creator
29
+ and subsequent owner(s) (each and all, an "owner") of an original work of
30
+ authorship and/or a database (each, a "Work").
31
+
32
+ Certain owners wish to permanently relinquish those rights to a Work for
33
+ the purpose of contributing to a commons of creative, cultural and
34
+ scientific works ("Commons") that the public can reliably and without fear
35
+ of later claims of infringement build upon, modify, incorporate in other
36
+ works, reuse and redistribute as freely as possible in any form whatsoever
37
+ and for any purposes, including without limitation commercial purposes.
38
+ These owners may contribute to the Commons to promote the ideal of a free
39
+ culture and the further production of creative, cultural and scientific
40
+ works, or to gain reputation or greater distribution for their Work in
41
+ part through the use and efforts of others.
42
+
43
+ For these and/or other purposes and motivations, and without any
44
+ expectation of additional consideration or compensation, the person
45
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
46
+ is an owner of Copyright and Related Rights in the Work, voluntarily
47
+ elects to apply CC0 to the Work and publicly distribute the Work under its
48
+ terms, with knowledge of his or her Copyright and Related Rights in the
49
+ Work and the meaning and intended legal effect of CC0 on those rights.
50
+
51
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
52
+ protected by copyright and related or neighboring rights ("Copyright and
53
+ Related Rights"). Copyright and Related Rights include, but are not
54
+ limited to, the following:
55
+
56
+ i. the right to reproduce, adapt, distribute, perform, display,
57
+ communicate, and translate a Work;
58
+ ii. moral rights retained by the original author(s) and/or performer(s);
59
+ iii. publicity and privacy rights pertaining to a person's image or
60
+ likeness depicted in a Work;
61
+ iv. rights protecting against unfair competition in regards to a Work,
62
+ subject to the limitations in paragraph 4(a), below;
63
+ v. rights protecting the extraction, dissemination, use and reuse of data
64
+ in a Work;
65
+ vi. database rights (such as those arising under Directive 96/9/EC of the
66
+ European Parliament and of the Council of 11 March 1996 on the legal
67
+ protection of databases, and under any national implementation
68
+ thereof, including any amended or successor version of such
69
+ directive); and
70
+ vii. other similar, equivalent or corresponding rights throughout the
71
+ world based on applicable law or treaty, and any national
72
+ implementations thereof.
73
+
74
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
75
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
76
+ irrevocably and unconditionally waives, abandons, and surrenders all of
77
+ Affirmer's Copyright and Related Rights and associated claims and causes
78
+ of action, whether now known or unknown (including existing as well as
79
+ future claims and causes of action), in the Work (i) in all territories
80
+ worldwide, (ii) for the maximum duration provided by applicable law or
81
+ treaty (including future time extensions), (iii) in any current or future
82
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
83
+ including without limitation commercial, advertising or promotional
84
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
85
+ member of the public at large and to the detriment of Affirmer's heirs and
86
+ successors, fully intending that such Waiver shall not be subject to
87
+ revocation, rescission, cancellation, termination, or any other legal or
88
+ equitable action to disrupt the quiet enjoyment of the Work by the public
89
+ as contemplated by Affirmer's express Statement of Purpose.
90
+
91
+ 3. Public License Fallback. Should any part of the Waiver for any reason
92
+ be judged legally invalid or ineffective under applicable law, then the
93
+ Waiver shall be preserved to the maximum extent permitted taking into
94
+ account Affirmer's express Statement of Purpose. In addition, to the
95
+ extent the Waiver is so judged Affirmer hereby grants to each affected
96
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
97
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
98
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
99
+ maximum duration provided by applicable law or treaty (including future
100
+ time extensions), (iii) in any current or future medium and for any number
101
+ of copies, and (iv) for any purpose whatsoever, including without
102
+ limitation commercial, advertising or promotional purposes (the
103
+ "License"). The License shall be deemed effective as of the date CC0 was
104
+ applied by Affirmer to the Work. Should any part of the License for any
105
+ reason be judged legally invalid or ineffective under applicable law, such
106
+ partial invalidity or ineffectiveness shall not invalidate the remainder
107
+ of the License, and in such case Affirmer hereby affirms that he or she
108
+ will not (i) exercise any of his or her remaining Copyright and Related
109
+ Rights in the Work or (ii) assert any associated claims and causes of
110
+ action with respect to the Work, in either case contrary to Affirmer's
111
+ express Statement of Purpose.
112
+
113
+ 4. Limitations and Disclaimers.
114
+
115
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
116
+ surrendered, licensed or otherwise affected by this document.
117
+ b. Affirmer offers the Work as-is and makes no representations or
118
+ warranties of any kind concerning the Work, express, implied,
119
+ statutory or otherwise, including without limitation warranties of
120
+ title, merchantability, fitness for a particular purpose, non
121
+ infringement, or the absence of latent or other defects, accuracy, or
122
+ the present or absence of errors, whether or not discoverable, all to
123
+ the greatest extent permissible under applicable law.
124
+ c. Affirmer disclaims responsibility for clearing rights of other persons
125
+ that may apply to the Work or any use thereof, including without
126
+ limitation any person's Copyright and Related Rights in the Work.
127
+ Further, Affirmer disclaims responsibility for obtaining any necessary
128
+ consents, permissions or other rights required for any use of the
129
+ Work.
130
+ d. Affirmer understands and acknowledges that Creative Commons is not a
131
+ party to this document and has no duty or obligation with respect to
132
+ this CC0 or use of the Work.
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.3
2
+ Name: pycocowriter
3
+ Version: 0.0.1
4
+ Summary: tools for writing coco files
5
+ Author: scott brown
6
+ Description-Content-Type: text/markdown
7
+ Classifier: Programming Language :: Python :: 3
8
+ Requires-Dist: numpy
9
+ Requires-Dist: jsonschema
10
+
11
+ # pycocowriter
12
+
13
+ This library contains tools to assist in writing COCO-format annotation files. Contains [typing and constructors](nodd-tools.github.io/pycocotools/api/coco) to assist in building a COCO-format file from scratch and then [dump it to json]()
14
+
15
+
16
+ # Disclaimer
17
+
18
+ This repository is a scientific product and is not official communication of the National Oceanic and
19
+ Atmospheric Administration, or the United States Department of Commerce. All NOAA GitHub project
20
+ code is provided on an ‘as is’ basis and the user assumes responsibility for its use. Any claims against the
21
+ Department of Commerce or Department of Commerce bureaus stemming from the use of this GitHub
22
+ project will be governed by all applicable Federal law. Any reference to specific commercial products,
23
+ processes, or services by service mark, trademark, manufacturer, or otherwise, does not constitute or
24
+ imply their endorsement, recommendation or favoring by the Department of Commerce. The Department
25
+ of Commerce seal and logo, or the seal and logo of a DOC bureau, shall not be used in any manner to
26
+ imply endorsement of any commercial product or activity by DOC or the United States Government.
27
+
@@ -0,0 +1,8 @@
1
+ pycocowriter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pycocowriter/coco.py,sha256=kjNVZmFRqO07rjhF4Vzk0JCUkippoAnf1eBb4ClMRgY,11529
3
+ pycocowriter/csv2coco.py,sha256=UntYVmIfNNfIqE1ml17qNlhCtbjuvb7YvEZG1iaqgyw,13010
4
+ pycocowriter/utils.py,sha256=e94E2gAE2IIjBePkIL7hlrNsz6HhwCY8PnyeweVhAio,2376
5
+ pycocowriter-0.0.1.dist-info/LICENSE,sha256=WqdJz_L8LA3dvoxyqR-a2tVUBsuAIa5C8Kf1HBQB9QA,7667
6
+ pycocowriter-0.0.1.dist-info/WHEEL,sha256=ssQ84EZ5gH1pCOujd3iW7HClo_O_aDaClUbX4B8bjKY,100
7
+ pycocowriter-0.0.1.dist-info/METADATA,sha256=pOey7DhAJ1-yJ9scxgs1JZA-gt5EEzO8072-G-ERiwE,1429
8
+ pycocowriter-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.10.1
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any