Notebooks

Convert class shape

iPython Project Created 4 days ago Free
Script converts all objects of selected classes to bitmaps or to bounding boxes
Free Signup

Convert class shape

Script converts all objects of selected classes to bitmaps or to bounding boxes

Input:

  • Existing Project (e.g. "london_roads")
  • Conversion rules (see below "convertions" dict)

Output:

  • New Project with converted objects

Configuration

Edit the following settings for your own case

In [1]:
import supervisely_lib as sly
import collections
import os
from tqdm import tqdm

team_name = "jupyter_tutorials"
workspace_name = "cookbook"
project_name = "tutorial_project"

dst_project_name = "converted_project"

ClassConvertion = collections.namedtuple('ConvertInfo', 'dst_name dst_shape save_src_class')

# You can convert shape of any class only to BoundignBox (Rectangle) or to Bitmap 
convertions = {"car": ClassConvertion(dst_name="car_bbox", dst_shape=sly.Rectangle, save_src_class=True),
               "dog": ClassConvertion(dst_name="dog_bitmap", dst_shape=sly.Bitmap, save_src_class=False)}

# Obtain server address and your api_token from environment variables
# Edit those values if you run this notebook on your own PC
address = os.environ['SERVER_ADDRESS']
token = os.environ['API_TOKEN']

Script setup

Import nessesary packages and initialize Supervisely API to remotely manage your projects

In [2]:
# Initialize API object
api = sly.Api(address, token)

Verify input values

Test that context (team / workspace / project) exists

In [3]:
# Get IDs of team, workspace and project by names

team = api.team.get_info_by_name(team_name)
if team is None:
    raise RuntimeError("Team {!r} not found".format(team_name))

workspace = api.workspace.get_info_by_name(team.id, workspace_name)
if workspace is None:
    raise RuntimeError("Workspace {!r} not found".format(workspace_name))
    
project = api.project.get_info_by_name(workspace.id, project_name)
if project is None:
    raise RuntimeError("Project {!r} not found".format(project_name))
    
print("Team: id={}, name={}".format(team.id, team.name))
print("Workspace: id={}, name={}".format(workspace.id, workspace.name))
print("Project: id={}, name={}".format(project.id, project.name))
Out [3]:
Team: id=30, name=jupyter_tutorials
Workspace: id=76, name=cookbook
Project: id=898, name=tutorial_project

Get Project Meta of Source Project

Project Meta contains information about classes and tags# Get source project meta

In [4]:
meta_json = api.project.get_meta(project.id)
meta = sly.ProjectMeta.from_json(meta_json)

# Check if all classes exist
for src_name, cc in convertions.items():
    if meta.get_obj_class(src_name) is None:
        raise RuntimeError("Class {!r} not found in source project {!r}".format(src_name, project.name))

Create Destination project

In [5]:
# check if destination project name already exists. If yes - generate new free name
if api.project.exists(workspace.id, dst_project_name):
    dst_project_name = api.project.get_free_name(workspace.id, dst_project_name)
    
# create remote project
dst_project = api.project.create(workspace.id, dst_project_name)

print("Destination project: id={}, name={!r}".format(dst_project.id, dst_project.name))
Out [5]:
Destination project: id=1139, name='converted_project'

Construct Destination ProjectMeta

In [6]:
# Remove old classes and create new ones

class_mapping = {}

dst_meta = meta.clone()
for src_name, cc in convertions.items():
    obj_class = dst_meta.get_obj_class(src_name)
    if not cc.save_src_class:
        dst_meta = dst_meta.delete_obj_class(src_name)
    
    new_obj_class = obj_class.clone(name=cc.dst_name, geometry_type=cc.dst_shape)
    dst_meta = dst_meta.add_obj_class(new_obj_class)
    
    class_mapping[obj_class.name] = new_obj_class

api.project.update_meta(dst_project.id, dst_meta.to_json())
print(dst_meta)
Out [6]:
ProjectMeta:
Object Classes
+------------+-----------+----------------+
|    Name    |   Shape   |     Color      |
+------------+-----------+----------------+
|    bike    | Rectangle | [246, 255, 0]  |
|    car     |  Polygon  | [190, 85, 206] |
|   person   |   Bitmap  |  [0, 255, 18]  |
|  car_bbox  | Rectangle | [190, 85, 206] |
| dog_bitmap |   Bitmap  |  [253, 0, 0]   |
+------------+-----------+----------------+
Image Tags
+-------------+--------------+-----------------------+
|     Name    |  Value type  |    Possible values    |
+-------------+--------------+-----------------------+
|   situated  | oneof_string | ['inside', 'outside'] |
|     like    |     none     |          None         |
| cars_number |  any_number  |          None         |
+-------------+--------------+-----------------------+
Object Tags
+---------------+--------------+-----------------------+
|      Name     |  Value type  |    Possible values    |
+---------------+--------------+-----------------------+
|   car_color   |  any_string  |          None         |
| person_gender | oneof_string |   ['male', 'female']  |
|  vehicle_age  | oneof_string | ['modern', 'vintage'] |
+---------------+--------------+-----------------------+

Helper function, takes annotation, converts labels and returns new annotation

In [7]:
def convert_classes(ann):
    labels = ann.labels
    new_labels = []
    for label in labels:
        dst_class = class_mapping.get(label.obj_class.name, None)
        
        if dst_class is not None:
            dest_shape = convertions[label.obj_class.name].dst_shape
            
            if dest_shape is sly.Bitmap:
                converted_geometry = sly.geometry_to_bitmap(label.geometry)[0]
            elif dest_shape is sly.Rectangle:
                converted_geometry = label.geometry.to_bbox()
            else:
                raise ValueError('Unsupported destination shape ({}) type!'.format(dest_shape.geometry_name()))
                
            new_label = label.clone(obj_class=dst_class, geometry=converted_geometry)
            new_labels.append(new_label) 
        else:
            new_labels.append(label)  
    return ann.clone(labels=new_labels)

Iterate over all images, convert and upload to destination project

In [8]:
 for dataset in api.dataset.get_list(project.id):
    
    # generate dataset name in destination project if it exists
    dst_dataset_name = dataset.name
    if api.dataset.exists(dst_project.id, dst_dataset_name):
        dst_dataset_name = api.project.get_free_name(workspace.id, dst_project_name)
    # create new dataset in destination project
    dst_dataset = api.dataset.create(dst_project.id, dst_dataset_name)

    print("Processing: project = {!r}, dataset = {!r} \n".format(project.name, dataset.name))
    # add images and annotations from source dataset to destination dataset
    for image in tqdm(api.image.get_list(dataset.id)):
        # add image to destination dataset 
        dst_image = api.image.add(dst_dataset.id, image.name, image.hash)

        # get image annotation
        ann_info = api.annotation.download(image.id)
        ann_json = ann_info.annotation
        ann = sly.Annotation.from_json(ann_json, meta)
        
        dst_ann = convert_classes(ann)
        
        # upload annotation to destination image
        api.annotation.upload(dst_image.id, dst_ann.to_json())
Out [8]:
 33%|███▎      | 1/3 [00:00<00:00,  6.17it/s]
Processing: project = 'tutorial_project', dataset = 'dataset_01' 

100%|██████████| 3/3 [00:00<00:00,  6.92it/s]
 50%|█████     | 1/2 [00:00<00:00,  8.80it/s]
Processing: project = 'tutorial_project', dataset = 'dataset_02' 

100%|██████████| 2/2 [00:00<00:00,  8.58it/s]

Done!

More Info

ID
24
First released
4 days ago
Last updated
3 hours ago

Owner

s