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.
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:
Copy
import cv2import numpy as npfrom encord import EncordUserClient# Instantiate Encord client by substituting the path to your private keyuser_client = EncordUserClient.create_with_ssh_private_key( ssh_private_key_path="<private_key_path>")# Specify a project using project hashproject = user_client.get_project("<project_hash>")# Obtain labels for the media of interestlabel_row = project.list_label_rows_v2()[0]label_row.initialise_labels()# Find annotation# In this example, it is just a first object on a first frameobject_with_bitmask_annotation = label_row.get_frame_views()[ 0].get_object_instances()[0]# Get a bitmask annotation# In this example, it is a first annotation on the objectbitmask_annotation = object_with_bitmask_annotation.get_annotations()[0]# Convert bitmask to a numpy array# Obtained array is a binary mask, so to work with it as an image,# it is necessary to convert it to a different datatype and scalebitmask = bitmask_annotation.coordinates.to_numpy_array().astype(np.uint8)bitmask[bitmask == 1] = 255# And now we can save the mask as a grayscale imagecv2.imwrite("./mask_as_an_image.png", bitmask)
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:
Copy
import numpy as npfrom encord import EncordUserClientfrom encord.objects import Object, OntologyStructurefrom encord.objects.coordinates import BitmaskCoordinates# Firstly, we need to prepare the mask itself.# For simplicity, we can just mask the whole image# Note, that the size of the mask must be identical to the size of the imagenumpy_coordinates = np.ones((512, 512))# we also need to make sure that the image is in boolean formatnumpy_coordinates = numpy_coordinates.astype(bool)# Now we can upload it with the following steps# Instantiate Encord client and get a project using project hashuser_client = EncordUserClient.create_with_ssh_private_key("<your_private_key>")project = user_client.get_project("<project_hash>")# Obtain labels for the media of interest# In this case, just a first image of the projectlabel_row = project.list_label_rows_v2()[0]label_row.initialise_labels()# Find a bitmask annotation object in the project ontologyontology_structure: OntologyStructure = label_row.ontology_structurebitmask_ontology_object: Object = ontology_structure.get_child_by_title( title="My bitmask feature", type_=Object)# Create the instance of this object - actual annotationbitmask_ontology_object_instance = bitmask_ontology_object.create_instance()# Set the bitmask as coordinates for the annotationbitmask_ontology_object_instance.set_for_frames( # Create coordinates from provided numpy bitmask coordinates=BitmaskCoordinates(numpy_coordinates), # Add the bounding box to the first frame frames=0, # There are multiple additional fields that can be set optionally: manual_annotation=True,)# And assign the object instance to the label rowlabel_row.add_object_instance(bitmask_ontology_object_instance)label_row.save()
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.
Copy
# Import dependenciesimport jsonfrom pathlib import Pathimport numpy as npfrom encord.objects.coordinates import BitmaskCoordinatesfrom PIL import Image# Read the JSON file.# Replace labels.json with the path to the JSON file containing your Bitmasklabels = json.loads(Path("labels.json").read_text())output_folder = Path("output")output_folder.mkdir(parents=True, exist_ok=True)# Finds the rleString and saves it to a .png imagefound_any = Falsefor 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.show() # Replace example.png with the name of the output file img.save(output_folder / f"mask_{int(slice_num):05d}_{obj['objectHash']}.png")print("Decoding complete.")
Use the COCO API to decode Bitmasks when using the COCO export format.
Copy
from pycocotools.coco import COCOimport numpy as npfrom pycocotools import mask as maskUtilsfrom PIL import Imageimport json# Read the COCO file. Replace 'labels.json' with the path to your COCO format annotations filecoco_annotations_path = 'labels.json'coco = COCO(coco_annotations_path)# Get all annotation IDs (you might want to filter or select specific ones)annIds = coco.getAnnIds()anns = coco.loadAnns(annIds)# Decode each rleString and save as a .png imagefor 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') # Convert to grayscale# Replace example.png with the name of the output file img.save("example.png")print("Decoding complete.")