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.
- pycocowriter/__init__.py +0 -0
- pycocowriter/coco.py +348 -0
- pycocowriter/csv2coco.py +359 -0
- pycocowriter/utils.py +89 -0
- pycocowriter-0.0.1.dist-info/LICENSE +132 -0
- pycocowriter-0.0.1.dist-info/METADATA +27 -0
- pycocowriter-0.0.1.dist-info/RECORD +8 -0
- pycocowriter-0.0.1.dist-info/WHEEL +5 -0
pycocowriter/__init__.py
ADDED
|
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)
|
pycocowriter/csv2coco.py
ADDED
|
@@ -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,,
|