Skip to content

Commit 48bfa06

Browse files
committed
json converter
1 parent feab539 commit 48bfa06

File tree

9 files changed

+754
-13
lines changed

9 files changed

+754
-13
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ _____________________________________________________________
274274
.. autofunction:: superannotate.export_annotation
275275
.. autofunction:: superannotate.convert_project_type
276276
.. autofunction:: superannotate.coco_split_dataset
277+
.. autofunction:: superannotate.convert_json_version
277278

278279

279280

superannotate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def consensus(*args, **kwargs):
9090
)
9191
from .input_converters.conversion import (
9292
coco_split_dataset, convert_project_type, export_annotation,
93-
import_annotation
93+
import_annotation, convert_json_version
9494
)
9595
from .old_to_new_format_convertor import update_json_format
9696
from .version import __version__

superannotate/input_converters/conversion.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from ..exceptions import SABaseException
88
from .export_from_sa_conversions import export_from_sa
99
from .import_to_sa_conversions import import_to_sa
10-
from .sa_conversion import (sa_convert_project_type, split_coco)
10+
from .sa_conversion import (
11+
degrade_json, sa_convert_project_type, split_coco, upgrade_json
12+
)
1113

1214
ALLOWED_TASK_TYPES = [
1315
'panoptic_segmentation', 'instance_segmentation', 'keypoint_detection',
@@ -154,9 +156,9 @@ def export_annotation(
154156
============== ======================
155157
156158
:param input_dir: Path to the dataset folder that you want to convert.
157-
:type input_dir: str
159+
:type input_dir: Pathlike(str or Path)
158160
:param output_dir: Path to the folder, where you want to have converted dataset.
159-
:type output_dir: str
161+
:type output_dir: Pathlike(str or Path)
160162
:param dataset_format: One of the formats that are possible to convert. Available candidates are: ["COCO"]
161163
:type dataset_format: str
162164
:param dataset_name: Will be used to create json file in the output_dir.
@@ -309,9 +311,9 @@ def import_annotation(
309311
============== ======================
310312
311313
:param input_dir: Path to the dataset folder that you want to convert.
312-
:type input_dir: str
314+
:type input_dir: Pathlike(str or Path)
313315
:param output_dir: Path to the folder, where you want to have converted dataset.
314-
:type output_dir: str
316+
:type output_dir: Pathlike(str or Path)
315317
:param dataset_format: Annotation format to convert SuperAnnotate annotation format. Available candidates are: ["COCO", "VOC", "LabelBox", "DataLoop",
316318
"Supervisely", 'VGG', 'YOLO', 'SageMake', 'VoTT', 'GoogleCloud']
317319
:type dataset_format: str
@@ -361,9 +363,9 @@ def convert_project_type(input_dir, output_dir):
361363
""" Converts SuperAnnotate 'Vector' project type to 'Pixel' or reverse.
362364
363365
:param input_dir: Path to the dataset folder that you want to convert.
364-
:type input_dir: str or PathLike
366+
:type input_dir: Pathlike(str or Path)
365367
:param output_dir: Path to the folder where you want to have converted files.
366-
:type output_dir: str or PathLike
368+
:type output_dir: Pathlike(str or Path)
367369
368370
"""
369371
param_info = [
@@ -387,11 +389,11 @@ def coco_split_dataset(
387389
""" Splits COCO dataset to few datsets.
388390
389391
:param coco_json_path: Path to main COCO JSON dataset, which should be splitted.
390-
:type coco_json_path: str or PathLike
392+
:type coco_json_path: Pathlike(str or Path)
391393
:param image_dir: Path to all images in the original dataset.
392-
:type coco_json_path: str or PathLike
394+
:type coco_json_path: str or Pathlike
393395
:param coco_json_path: Path to the folder where you want to output splitted COCO JSON files.
394-
:type coco_json_path: str or PathLike
396+
:type coco_json_path: str or Pathlike
395397
:param dataset_list_name: List of dataset names.
396398
:type dataset_list_name: list
397399
:param ratio_list: List of ratios for each splitted dataset.
@@ -448,3 +450,40 @@ def _type_sanity(var, var_name, var_type):
448450
var_name, var_type, type(var)
449451
)
450452
)
453+
454+
455+
def convert_json_version(input_dir, output_dir, version=2):
456+
"""
457+
Converts SuperAnnotate JSON versions. Newest JSON version is 2.
458+
459+
:param input_dir: Path to the dataset folder that you want to convert.
460+
:type input_dir: Pathlike(str or Path)
461+
:param output_dir: Path to the folder, where you want to have converted dataset.
462+
:type output_dir: Pathlike(str or Path)
463+
:param version: Output version number. Currently is either 1 or 2. Default value is 2. It will upgrade version 1 to version 2. Set 1 to degrade from version 2 to version 1.
464+
:type version: int
465+
466+
:return: List of converted files
467+
:rtype: list
468+
"""
469+
param_info = [
470+
(input_dir, 'input_dir', (str, Path)),
471+
(output_dir, 'output_dir', (str, Path)), (version, 'version', int)
472+
]
473+
for param in param_info:
474+
_type_sanity(param[0], param[1], param[2])
475+
476+
if isinstance(input_dir, str):
477+
input_dir = Path(input_dir)
478+
if isinstance(output_dir, str):
479+
output_dir = Path(output_dir)
480+
output_dir.mkdir(parents=True, exist_ok=True)
481+
482+
if version == 2:
483+
converted_files = upgrade_json(input_dir, output_dir)
484+
elif version == 1:
485+
converted_files = degrade_json(input_dir, output_dir)
486+
else:
487+
raise SABaseException(0, "'version' is either 1 or 2.")
488+
489+
return converted_files

superannotate/input_converters/sa_conversion.py

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import cv2
99
import numpy as np
1010

11-
from ..common import (blue_color_generator, hex_to_rgb, write_to_json)
11+
from ..common import blue_color_generator, hex_to_rgb, write_to_json
1212
from ..exceptions import SABaseException
1313

1414
logger = logging.getLogger("superannotate-python-sdk")
@@ -181,7 +181,7 @@ def sa_convert_project_type(input_dir, output_dir):
181181
else:
182182
raise SABaseException(
183183
0,
184-
"'input_dir' should contain JSON files with '[IMAGE_NAME]___objects.json' or '[IMAGE_NAME]___pixel.json' names structure."
184+
"'input_dir' should contain JSON files with '[IMAGE_NAME]___objects.json' or '[IMAGE_NAME]___pixel.json' names structure."
185185
)
186186

187187
for img_name in img_names:
@@ -239,3 +239,148 @@ def split_coco(
239239
for file_name, value in groups.items():
240240
with open(output_dir / (file_name + '.json'), 'w') as fw:
241241
json.dump(value, fw, indent=2)
242+
243+
244+
def upgrade_json(input_dir, output_dir):
245+
files_list = list(input_dir.glob('*.json'))
246+
ptype = 'Vector'
247+
if '___pixel' in str(files_list[0].name):
248+
ptype = "Pixel"
249+
250+
converted_files = []
251+
failed_files = []
252+
for file in files_list:
253+
file_name = file.name
254+
try:
255+
output_json = _update_json_format(file, ptype)
256+
converted_files.append(file_name)
257+
write_to_json(output_dir / file_name, output_json)
258+
except Exception as e:
259+
failed_files.append(file_name)
260+
261+
return converted_files
262+
263+
264+
def degrade_json(input_dir, output_dir):
265+
files_list = list(input_dir.glob('*.json'))
266+
267+
converted_files = []
268+
failed_files = []
269+
for file in files_list:
270+
file_name = file.name
271+
try:
272+
output_json = _degrade_json_format(file)
273+
converted_files.append(output_dir / file_name)
274+
write_to_json(output_dir / file_name, output_json)
275+
except Exception as e:
276+
failed_files.append(file_name)
277+
278+
return converted_files
279+
280+
281+
def _update_json_format(old_json_path, project_type):
282+
old_json_data = json.load(open(old_json_path))
283+
new_json_data = {
284+
"metadata": {},
285+
"instances": [],
286+
"tags": [],
287+
"comments": []
288+
}
289+
290+
meta_keys = [
291+
"name", "width", "height", "status", "pinned", "isPredicted",
292+
"projectId", "annotatorEmail", "qaEmail"
293+
]
294+
if project_type == "Pixel":
295+
meta_keys.append("isSegmented")
296+
297+
new_json_data["metadata"] = dict.fromkeys(meta_keys)
298+
299+
#set image name
300+
suffix = "___objects.json" if project_type == "Vector" else "___pixel.json"
301+
# image_name = os.path.basename(old_json_path).split(suffix)[0]
302+
image_name = str(old_json_path.name).split(suffix)[0]
303+
metadata = new_json_data["metadata"]
304+
metadata["name"] = image_name
305+
306+
for item in old_json_data:
307+
object_type = item.get("type")
308+
#add metadata
309+
if object_type == "meta":
310+
meta_name = item["name"]
311+
if meta_name == "imageAttributes":
312+
metadata["height"] = item.get("height")
313+
metadata["width"] = item.get("width")
314+
metadata["status"] = item.get("status")
315+
metadata["pinned"] = item.get("pinned")
316+
if meta_name == "lastAction":
317+
metadata["lastAction"] = dict.fromkeys(["email", "timestamp"])
318+
metadata["lastAction"]["email"] = item.get("userId")
319+
metadata["lastAction"]["timestamp"] = item.get("timestamp")
320+
#add tags
321+
elif object_type == "tag":
322+
new_json_data["tags"].append(item.get("name"))
323+
#add comments
324+
elif object_type == "comment":
325+
item.pop("type")
326+
item["correspondence"] = item["comments"]
327+
for comment in item["correspondence"]:
328+
comment["email"] = comment["id"]
329+
comment.pop("id")
330+
item.pop("comments")
331+
new_json_data["comments"].append(item)
332+
#add instances
333+
else:
334+
new_json_data["instances"].append(item)
335+
336+
return new_json_data
337+
338+
339+
def _degrade_json_format(new_json_path):
340+
sa_loader = []
341+
new_json_data = json.load(open(new_json_path))
342+
343+
meta = {'type': 'meta', 'name': 'imageAttributes'}
344+
meta_keys = ['height', 'width', 'status', 'pinned']
345+
for meta_key in meta_keys:
346+
if meta_key in new_json_data['metadata']:
347+
meta[meta_key] = new_json_data['metadata'][meta_key]
348+
sa_loader.append(meta)
349+
350+
if 'lastAction' in new_json_data['metadata']:
351+
meta = {
352+
'type': 'meta',
353+
'name': 'lastAction',
354+
'userId': new_json_data['metadata']['lastAction']['email'],
355+
'timestamp': new_json_data['metadata']['lastAction']['timestamp']
356+
}
357+
sa_loader.append(meta)
358+
359+
for item in new_json_data['instances']:
360+
sa_loader.append(item)
361+
362+
for item in new_json_data['comments']:
363+
comments = []
364+
for item2 in item['correspondence']:
365+
comments.append({'text': item2['text'], 'id': item2['email']})
366+
item['comments'] = comments
367+
item['createdAt'] = item['correspondence'][0]['timestamp']
368+
item['createdBy'] = {
369+
'email': item['correspondence'][0]['email'],
370+
'role': item['correspondence'][0]['role']
371+
}
372+
item['updatedAt'] = item['correspondence'][-1]['timestamp']
373+
item['updatedBy'] = {
374+
'email': item['correspondence'][-1]['email'],
375+
'role': item['correspondence'][-1]['role']
376+
}
377+
item.pop('correspondence')
378+
item['type'] = 'comment'
379+
item['comments'] = comments
380+
sa_loader.append(item)
381+
382+
for item in new_json_data['tags']:
383+
tag = {'type': 'tag', 'name': item}
384+
sa_loader.append(tag)
385+
386+
return sa_loader

0 commit comments

Comments
 (0)