Create an Instance Segmentation Project#
When your project uses instance segmentation, the uploaded dataset needs to contain the required instance polygons information alongside each image as part of a single datapoint.
First we will create a dataset, and later on, a dummy model that returns a polygon information.
Create dataset#
A convenient approach for creating an instance segmentation dataset is by ensuring that the local format is in COCO-compatible format.
import efemarai as ef
# Create a project
project = ef.Session().create_project(
name="Example Instance Segmentation Project (COCO)",
description="Example project using the COCO dataset format.",
exists_ok=True,
)
dataset = project.create_dataset(
name="Instance Segmentation dataset",
data_url="./data/coco/val2017",
annotations_url="./data/coco/annotations/instances_val2017.json",
stage=ef.DatasetStage.Validation,
format=ef.DatasetFormat.COCO,
)
If your dataset is remote or part of an existing database with custom formats, you can easily upload it to the system by (1) iterating over the dataset and (2) creating a datapoint containing the image and required targets.
In upload_dataset.py
add the following script to perform the above steps.
import itertools
import numpy as np
import efemarai as ef
from pycocotools.coco import COCO
from efemarai.formats.dataset_coco import create_mask
from efemarai.fields import create_polygons_from_mask
def upload_dataset():
project = ef.Session().create_project(
name="Example Instance Segmentation Project",
description="Example project with custom datapoint upload.",
exists_ok=True,
)
# Create an empty dataset
dataset = project.create_dataset(
name="Example Custom COCO Dataset",
stage=ef.DatasetStage.Validation,
format=ef.DatasetFormat.Custom,
)
# Define an annotations URL
root = "./data/coco"
# Load your custom dataset
coco = COCO(f"{root}/annotations/instances_val2017.json")
# Create the labels and add them to the dataset
for category in coco.loadCats(coco.getCatIds()):
dataset.add_annotation_class(
id=category["id"],
name=category["name"],
category=category["supercategory"],
)
# Iterate over your dataset
for cocoImg in coco.loadImgs(coco.getImgIds()):
# Instantiate the image with an ef.Image object
image = ef.Image(
file_path=f"{root}/val2017/{cocoImg['file_name']}",
width=cocoImg["width"],
height=cocoImg["height"],
)
# Create a datapoint and add the image as input.
datapoint = ef.Datapoint(
dataset=dataset,
inputs={"image": image},
)
# Get the AnnotationClass from the dataset
annotations = coco.loadAnns(coco.getAnnIds(imgIds=cocoImg["id"]))
for instance_id, cocoAnn in enumerate(annotations):
# Search for the true label object in the dataset by either `name` or `id`.
# Alternatively: label = dataset.get_annotation_class(id=cocoAnn["name"])
label = dataset.get_annotation_class(id=cocoAnn["category_id"])
if isinstance(cocoAnn["segmentation"], dict):
# RLE encoding -> create polygon
mask_img = np.zeros((cocoImg["width"], cocoImg["height"]))
mask_img = ef.formats.dataset_coco.create_mask(mask_img.shape, cocoAnn)
polygons, polygons_area = create_polygons_from_mask(mask_img)
polygons = [list(itertools.chain(*polygon)) for polygon in polygons]
cocoAnn["segmentation"] = polygons
cocoAnn["area"] = polygons_area
vertices = []
for polygon in cocoAnn["segmentation"]:
vertices.append(list(zip(polygon[:-1][::2], polygon[1:][::2])))
# Add a bounding box to the image
datapoint.add_target(
ef.BoundingBox(
xyxy=[
cocoAnn["bbox"][0],
cocoAnn["bbox"][1],
cocoAnn["bbox"][0] + cocoAnn["bbox"][2],
cocoAnn["bbox"][1] + cocoAnn["bbox"][3],
],
label=label,
instance_id=cocoAnn["id"],
ref_field=image,
)
)
datapoint.add_target(
ef.Polygon(
vertices=vertices,
label=label,
instance_id=cocoAnn["id"],
ref_field=image,
)
)
# Upload a single datapoint
datapoint.upload()
# Finalize dataset
dataset.finalize()
if __name__ == "__main__":
upload_dataset()
Run python upload_dataset.py
and wait for it to be completed.
After wrapping up any processing, you can confirm the status in the UI and explore the inputs and annotations.
Create a model#
A model that works with bounding boxes dataset will need to return a list of
ef.Polygons
objects that will be matched to the ones stored in the dataset.
In a file dummy_model.py
save the following code:
import cv2
import numpy as np
import efemarai as ef
import numpy as np
class DummyModel:
"""A DummyModel returning a random polygon"""
def __init__(self, device):
self.device = device # Move model to device
def __call__(self, image):
num_points = 10
points = (np.random.rand(num_points, 2) * [640, 480]).astype(np.float32)
hull = cv2.convexHull(points, clockwise=True)
convex_hull_polygon = hull.squeeze().tolist()
return {
"class_id": np.random.randint(1, 4),
"polygon": [convex_hull_polygon],
"score": np.random.random(),
}
def predict_images(datapoints, model, device):
outputs = []
for datapoint in datapoints:
image = datapoint.get_input("image") # by default, the key name is `image`, this corresponds to the key from the creation dict
image_post_process = image.data / 255 - 0.5 # perform any pre-processing
output = model(image_post_process)
# Here again the label can be referenced by name or class
# label = ef.AnnotationClass(name=output["class_name"])
label = ef.AnnotationClass(id=output["class_id"])
outputs.append(
[
ef.Polygon(
vertices=output["polygon"],
confidence=output["score"], # Confidence of detection
ref_field=image, # Say which image this output refers to
label=label, # And what label it has
),
]
)
# If the model returns a mask, we can use InstanceMask().to_polygon() - e.g.
# mask = np.zeros((480, 640), dtype=np.uint8)
# cv2.polylines(mask, [np.array(convex_hull_polygon)], True, 255, thickness=cv.FILLED)
# ef.InstanceMask(data=mask, ref_field=image, label=label).to_polygon()
return outputs
def load_model(device):
model = DummyModel(device)
return model
def test():
# Load the model
device = "cpu"
model = load_model(device=device)
image_RGB = np.random.randint(0, 256, size=(480, 640, 3), dtype=np.uint8)
ef_datapoint = ef.Datapoint(
dataset=None,
inputs={
"image": ef.Image(data=image_RGB),
},
)
# Pass a list of datapoints to the predictor function
output = predict_images([ef_datapoint], model, device)
print(output)
assert isinstance(output, list)
assert isinstance(output[0][0], ef.Polygon)
if __name__ == "__main__":
test()
If you run it with python dummy_model.py
you’ll be able to confirm that the
output of the model is a list of detections per input datapoint.
efemarai.yaml
file#
To run the model, you need to have defined the loading and inference
capabilities in the efemarai.yaml
file.
Here’s the one corresponding to the dummy model.
project:
name: "Example Instance Segmentation Project"
models:
- name: Dummy Model
description: This is a dummy model to show consuming inputs and outputs
runtime:
image: python:3.10-slim-buster
device: "gpu"
batch:
max_size: 10
load:
entrypoint: dummy_model:load_model
inputs:
- name: device
value: ${model.runtime.device}
output:
name: model
predict:
entrypoint: dummy_model:predict_images
inputs:
- name: datapoints
value: ${datapoints}
- name: model
value: ${model.runtime.load.output.model}
- name: device
value: ${model.runtime.device}
output:
name: predictions
keys:
- masks
Register model#
To register the model, use the CLI to upload it by going into the root of the file directory, next to the efemarai.yaml.
ef model create .
Now you should be able to see the model uploaded and active with this project.