How to export annotation data in COCO format?
PreviousHow to create jobs with pre-labeled data?NextHow to convert Playment segmentation mask to a grayscale mask
Last updated
Was this helpful?
Last updated
Was this helpful?
In GT Studio, you can also export the annotated in COCO format by modifying the export submission code from project settings.
On the submission code section, you can paste the below code and save it. Now, all the new submissions will be generated in the COCO format. To learn how to create submissions, you can have a look here: Export Jobs
If you make any changes to the submission code, you'll have to send jobs back for rework to a manual step and push it again to the end step.
import json
import pandas as pd
import requests
from PIL import Image
from io import BytesIO
import re
def natural_sort(l):
"""
natural sort a list of strings
:param l:
:return:
"""
convert = lambda text: int(text) if text.isdigit() else text.lower()
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(l, key=alphanum_key)
def get_dimensions(image_url):
res = requests.get(image_url)
if res.status_code == 200:
image = Image.open(BytesIO(res.content))
width, height = image.size
return width, height
else:
raise ValueError("Image url not accessible")
def mini_maxi(coor, key):
mini, maxi = float('inf'), -float('inf')
for each in coor:
mini = min(mini, each[key])
maxi = max(maxi, each[key])
return mini, maxi
def get_bbox(coor, w, h):
x, y = coor[0]['x'], coor[0]['y']
x = round(x * w, 4)
y = round(y * h, 4)
x_mini, x_maxi = mini_maxi(coor, 'x')
y_mini, y_maxi = mini_maxi(coor, 'y')
x_diff = round((x_maxi - x_mini) * w, 4)
y_diff = round((y_maxi - y_mini) * h, 4)
temp = [x, y, x_diff, y_diff]
return temp
def get_polygon(edge, points, w, h):
coco = []
point_list = natural_sort(list(points.keys()))
for p in point_list:
coco+= [points[p]['x'] * w, points[p]['y'] * h]
coco += [points[point_list[0]]['x'] *w,points[point_list[0]]['y']*h]
return [coco]
def get_polygon_bbox(polygon):
x_min = min(polygon[::2])
y_min = min(polygon[1::2])
x_max = max(polygon[::2])
y_max = max(polygon[1::2])
x_diff = round((x_max - x_min) , 4)
y_diff = round((y_max - y_min) , 4)
return [x_min, y_min , x_diff, y_diff]
# Ask for the support of external library.
# Code starts with main function
def main(fn, build, meta):
"""
:param fn: Will be used for custom logging. Ignore it for now
:param build: Flu build
:param meta: Flu meta present in feed_line except the build.
:return: Will return updated flu build
"""
# Tip: spaces are better than tabs
ref = meta['reference_id']
data = {"images": [], "category": [], "annotations": []}
image_id = ref
image_name = ref.split("/")[-1]
width, height = get_dimensions(build['image_url_original'])
image_data = {"id": image_id, "name": image_name, "width": width, "height": height,
"license": None, "date_captured": None}
data['images'].append(image_data)
for ann in build['maker_response']['rectangles']['data']:
ann_data = {"id": ann['_id'],"attributes": ann["attributes"], "category_id": ann['label'], 'image_id': image_id,'bbox': get_bbox(ann['coordinates'],
width, height)}
data['annotations'].append(ann_data)
for ann in build['maker_response']['polygons']['data']:
ann_data = {"id": ann['_id'], "category_id": ann['label'], 'image_id': image_id,
'segmentation': get_polygon(ann['edges'],ann['points'],width, height) ,
"attributes": ann["attributes"]}
ann_data['bbox'] = get_polygon_bbox(ann_data['segmentation'][0])
data['annotations'].append(ann_data)
# code here
image_id = image_id + '.json'
image_name = image_name
return {
"export_data": [{
"type": "JSON",
"data": data,
"key": image_name,
"path": image_id
}]
}
import numpy as np
from io import BytesIO
import requests
import os
import cv2
from PIL import Image
from concurrent.futures import ThreadPoolExecutor, as_completed
from skimage.io import imread, imsave
import uuid
import json
from PIL import Image, ImageFile
import pandas as pd
from io import BytesIO
import re
ImageFile.LOAD_TRUNCATED_IMAGES = True
def natural_sort(l):
"""
natural sort a list of strings
:param l:
:return:
"""
convert = lambda text: int(text) if text.isdigit() else text.lower()
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(l, key=alphanum_key)
def get_polygon(edge, points, w, h):
coco = []
point_list = natural_sort(list(points.keys()))
for p in point_list:
coco+= [points[p]['x'] * w, points[p]['y'] * h]
coco += [points[point_list[0]]['x'] *w,points[point_list[0]]['y']*h]
return [coco]
def split_mask(mask):
unique_colors = np.unique(np.reshape(mask, (-1, 3)), axis=0)
mask_data = {}
def split(color):
binary_mask = np.uint8(np.all(mask == color, axis=-1))
contours, _ = cv2.findContours(binary_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
return [x[:, 0, :] for x in contours]
with ThreadPoolExecutor(max_workers=8) as ex:
future_color = {ex.submit(split, color): color for color in unique_colors}
for future in as_completed(future_color):
color = future_color[future]
# if (color == [0, 0, 0]).all(): continue
hex_color = '#{:02x}{:02x}{:02x}'.format(*color)
mask_data[hex_color] = future.result()
return mask_data
def raster2vector(data):
mask_url = data['url']
legend = data['legend']
mask = np.ascontiguousarray(imread(mask_url)[..., :-1])
vector_data = split_mask(mask)
return vector_data
def calculate_image_dimensions(image_url):
res = requests.get(image_url)
if res.status_code == 200:
image = Image.open(BytesIO(res.content))
width, height = image.size
return width, height
else:
return False
def find_instance(annotations):
mapi = {}
for annotation in annotations:
mapi[annotation['color']] = [annotation['value'].split(" #")[0].strip(), annotation["_id"]]
return mapi
def get_polygon_build(build, meta):
"""
:param fn: Will be used for custom logging. Ignore it for now
:param build: Flu build
:param meta: Flu meta present in feed_line except the build.
:return: Will return updated flu build
"""
# Tip: spaces are better than tabs
# code here
data = build['maker_response']['raster']['data']
color_id_map = find_instance(data['instances'])
# print(json.dumps(color_id_map))
# return
legend = data['legend']
vector_seg_data = raster2vector(data)
w, h = calculate_image_dimensions(build['image_url_original'])
polygons = []
for color in vector_seg_data:
if color in color_id_map:
for polygon in vector_seg_data[color]:
polygon = polygon.tolist()
if len(polygon) > 3:
if type(legend[color]) == str:
polygon_data = {
"_id": str(uuid.uuid4()),
"type": "polygon",
"state": "editable",
"label": color_id_map[color][0],
"color": None,
"edges": {},
"points": {},
"attributes": {}
}
for i in range(len(polygon)):
coo = polygon[i]
x, y = float(coo[0] / w), float(coo[1] / h)
polygon_data['points']['p' + str(i + 1)] = {"x": x, "y": y}
polygon_data['edges']['e' + str(i + 1)] = ['p' + str(i + 1), 'p' + str(i + 2)]
polygon_data['edges']['e' + str(len(polygon))] = ['p' + str(len(polygon)), 'p1']
polygons.append(polygon_data)
build['maker_response']['polygons']['data'] = polygons
return build
def main(fn, build, meta):
"""
:param fn: Will be used for custom logging. Ignore it for now
:param build: Flu build
:param meta: Flu meta present in feed_line except the build.
:return: Will return updated flu build
"""
# Tip: spaces are better than tabs
ref = meta['reference_id']
build = get_polygon_build(build, meta)
data = {"images": [], "category": [], "annotations": []}
image_id = ref
image_name = ref.split("/")[-1]
width, height = calculate_image_dimensions(build['image_url_original'])
image_data = {"id": image_id, "name": image_name, "width": width, "height": height,
"license": None, "date_captured": None}
data['images'].append(image_data)
for ann in build['maker_response']['polygons']['data']:
ann_data = {"id": ann['_id'], "category_id": ann['label'], 'image_id': image_id,
'segmentation': get_polygon(ann['edges'],ann['points'],width, height)}
data['annotations'].append(ann_data)
# code here
image_id = image_id + '.json'
image_name = image_name
return {
"export_data": [{
"type": "JSON",
"data": data,
"key": image_name,
"path": image_id
}]
}
In case you want to go back to the GT Studio JSON format, you can paste the below code in the submission code section and save it.
# Ask for the support of external library.
# Code starts with main function
def main(fn, build, meta):
"""
:param fn: Will be used for custom logging. Ignore it for now
:param build: Flu build
:param meta: Flu meta present in feed_line except the build.
:return: Will return updated flu build
"""
# Tip: spaces are better than tabs
# code here
return {
"export_data": [{
"type": "JSON",
"data": {
"annotations": {
"rectangles": build['maker_response']['rectangles']['data'],
"lines": build['maker_response']['lines']['data'],
"landmarks": build['maker_response']['landmarks']['data'],
"cuboids": build['maker_response']['cuboids']['data'],
"polygons": build['maker_response']['polygons']['data']
},
"image_url": build['image_url']
},
"key": "data"
}]
}
# Ask for the support of external library.
# Code starts with main function
def main(fn, build, meta):
"""
:param fn: Will be used for custom logging. Ignore it for now
:param build: Flu build
:param meta: Flu meta present in feed_line except the build.
:return: Will return updated flu build
"""
# Tip: spaces are better than tabs
# code here
return {
"export_data": [{
"type": "PNG",
"data": build['maker_response']['raster']['data']['url'],
"key": "url"
}, {
"type": "JSON",
"data": build['maker_response']['raster']['data']['legend'],
"key": "legend"
}]
}