A Bitmask is a type of annotation on the Encord platform that allows for pixel-wise segmentation of an image, which can be useful when bounding boxes and polygons don’t provide enough precision, or when topologically separate regions belonging to the same class need to be labeled.
Downloading Bitmask annotations from Encord
The Encord platform allows the creation of Bitmask annotations. After labelling is complete, it is possible to download these annotations using the SDK. The following code example illustrates how to download / export and save Bitmask labels:
import cv2
import numpy as np
from encord import EncordUserClient
user_client = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path="<private_key_path>"
)
project = user_client.get_project("<project_hash>")
label_row = project.list_label_rows_v2()[0]
label_row.initialise_labels()
object_with_bitmask_annotation = label_row.get_frame_views()[
0
].get_object_instances()[0]
bitmask_annotation = object_with_bitmask_annotation.get_annotations()[0]
bitmask = bitmask_annotation.coordinates.to_numpy_array().astype(np.uint8)
bitmask[bitmask == 1] = 255
cv2.imwrite("./mask_as_an_image.png", bitmask)
Uploading Bitmask annotations to Encord
If there are pre-existing Bitmask annotations, previously created in Encord or any other software, they can be uploaded to Encord using the SDK. The following code example illustrates how to read a bitmask from a file, and upload it:
import numpy as np
from encord import EncordUserClient
from encord.objects import Object, OntologyStructure
from encord.objects.coordinates import BitmaskCoordinates
numpy_coordinates = np.ones((512, 512))
numpy_coordinates = numpy_coordinates.astype(bool)
user_client = EncordUserClient.create_with_ssh_private_key("<your_private_key>")
project = user_client.get_project("<project_hash>")
label_row = project.list_label_rows_v2()[0]
label_row.initialise_labels()
ontology_structure: OntologyStructure = label_row.ontology_structure
bitmask_ontology_object: Object = ontology_structure.get_child_by_title(
title="My bitmask feature", type_=Object
)
bitmask_ontology_object_instance = bitmask_ontology_object.create_instance()
bitmask_ontology_object_instance.set_for_frames(
coordinates=BitmaskCoordinates(numpy_coordinates),
frames=0,
manual_annotation=True,
)
label_row.add_object_instance(bitmask_ontology_object_instance)
label_row.save()
Decoding Bitmask labels
Bitmask labels exported from Encord are encoded in an rleString
. This rleString
can be converted to an image or a binary array.
Creating Bitmask images from JSON export
The script below must only be used to decode bitmasks that are exported in the JSON format.
The following script takes your exported JSON file, finds the rleString
, and converts it into an image.
import json
from pathlib import Path
import numpy as np
from encord.objects.coordinates import BitmaskCoordinates
from PIL import Image
labels = json.loads(Path("labels.json").read_text())
output_folder = Path("output")
output_folder.mkdir(parents=True, exist_ok=True)
found_any = False
for label_row in labels:
for du in label_row["data_units"].values():
slice_nums = list(du["labels"].keys())
for slice_num in slice_nums:
for obj in du["labels"][slice_num]["objects"]:
bmc = BitmaskCoordinates.from_dict(obj)
nparr = np.array(bmc)
img = Image.fromarray(nparr)
img.save(output_folder / f"mask_{int(slice_num):05d}_{obj['objectHash']}.png")
print("Decoding complete.")
Creating a binary array
The rleString
can also be converted into an array of 1’s and 0’s using the script below.
from encord.objects.coordinates import BitmaskCoordinates
np_mask = BitmaskCoordinates.from_dict(obj).to_numpy_array()
print(np_mask.astype(int))
Creating Bitmask images from COCO export
Use the COCO API to decode Bitmasks when using the COCO export format.
from pycocotools.coco import COCO
import numpy as np
from pycocotools import mask as maskUtils
from PIL import Image
import json
coco_annotations_path = 'labels.json'
coco = COCO(coco_annotations_path)
annIds = coco.getAnnIds()
anns = coco.loadAnns(annIds)
for i, ann in enumerate(anns):
if 'segmentation' in ann and isinstance(ann['segmentation'], dict):
rle = ann['segmentation']
mask = maskUtils.decode(rle)
img = Image.fromarray(mask * 255).convert('L')
img.save("example.png")
print("Decoding complete.")