GT Studio
  • Introduction
  • Getting started
    • Set up a Project
    • Set up Annotation Specification
    • Set up a Workflow
    • Create Jobs
    • Export Jobs
    • What's next?
  • Tools for Labelling
    • Image Segmentation
      • Tool Introduction
      • Maker Tool
      • Reviewer Tool
      • QC Tool
    • 2D Video and Image Labeling
      • Tool Introduction
      • Maker Tool
      • Reviewer Tool
      • QC Tool
  • Workflow
    • Workflow Steps
      • Steps and Patch
      • Step Analytics
      • Move and Push Jobs
      • Job Assignment
    • Job Build Structure
      • Base type - Image
      • Base type - Segmentation
      • Base type - Video
    • Workflow Routes
  • How to Guides
    • How to set up an image or a video classification task?
    • How to setup a Pose Tracking Project?
    • How to create jobs with pre-labeled data?
    • How to export annotation data in COCO format?
    • How to convert Playment segmentation mask to a grayscale mask
    • How to split a video into frames and create jobs in GT Studio
    • How to add classes after setting up the workflow
    • How to re-open a completed batch for making changes
  • Batches
    • Job Viewer
    • Quality Check
  • Annotator Performance
    • Video and Images
    • Segmentation
  • Annotation Specification
    • Classes
    • Attributes
  • Team management
    • Invite your team
    • Groups
  • API reference
  • Secure Attachment Access
  • What's New?
  • Hybrid Cloud
  • We are phasing out our SaaS offering — GT Studio
Powered by GitBook
On this page

Was this helpful?

  1. How to Guides

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 3 years ago

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:

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
        }]
    }

Original GT Studio Conversion Scirpt

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"
        }]
    }
Export Jobs