How to import annotations on DICOM data and sample import formats.
You can use the Python SDK to import DICOM annotations.
This page shows how to declare different annotation types (as Python dictionaries and NDJSON objects) and demonstrates the import process.
A Python notebook demonstrates these steps and can be run directly with Google CoLab.
Supported annotations
To import annotations in Labelbox, you need to create an annotation payload. This section shows how to declare the payload for each supported annotation type.
Labelbox supports two formats for the annotations payload:
- Python annotation types (recommended)
- NDJSON
Polyline
polyline_annotation = [
lb_types.DICOMObjectAnnotation(
name="line_dicom",
group_key=lb_types.GroupKey.AXIAL,
frame=1,
value=lb_types.Line(points=[
lb_types.Point(x=10, y=10),
lb_types.Point(x=200, y=20),
lb_types.Point(x=250, y=250)
]),
segment_index=0,
keyframe=True,
),
lb_types.DICOMObjectAnnotation(
name="line_dicom",
group_key=lb_types.GroupKey.AXIAL,
frame=20,
value=lb_types.Line(points=[
lb_types.Point(x=10, y=10),
lb_types.Point(x=200, y=10),
lb_types.Point(x=300, y=300)
]),
segment_index=1,
keyframe=True,
),
]
polyline_annotation_ndjson = {
'name': 'line_dicom',
'groupKey': 'axial', # should be 'axial', 'sagittal', or 'coronal'
'segments': [
{
'keyframes': [{
'frame': 1,
'line': [
{'x': 10, 'y': 10},
{'x': 200, 'y': 20},
{'x': 250, 'y': 250},
]
}]},
{
'keyframes' : [{
'frame': 20,
'line': [
{'x': 10, 'y': 10},
{'x': 200, 'y': 10},
{'x': 300, 'y': 300},
]
}]}
],
}
Segmentation Masks
MaskData is mask data declared as a uint8 array of [H, W, 3]. You can also convert a polygon annotation or a 2D array to MaskData. You can also specify a URL to a cloud-hosted mask (can be hosted on any cloud provider).
Masks cannot be larger than 9,000 in height or 9,000 pixels in width; otherwise, they will not import.
mask_annotation = [
lb_types.DICOMMaskAnnotation(
group_key='axial',
frames=[
lb_types.MaskFrame(
index=1,
instance_uri="https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-mask-1.png"
),
lb_types.MaskFrame(
index=5,
instance_uri="https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-mask-1.png"
)
],
instances=[
lb_types.MaskInstance(
color_rgb=(255, 255, 255),
name="segmentation_mask_dicom"
)
])
]
mask_annotation_ndjson = {
'groupKey': 'axial',
'masks': {
'frames': [{
'index': 1,
'instanceURI': "https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-mask-1.png"
}, {
'index': 5,
'instanceURI': "https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-mask-1.png"
}],
'instances': [
{
'colorRGB': (255, 255, 255),
'name': 'segmentation_mask_dicom'
}
]
}
}
Example: Import prelabels or ground truth
The steps for importing annotations as prelabels (for model assisted learning) or as ground truth labels are very similar. Steps 5 and 6 explain these differences in detail, when you create the annotation payload and perform the import.
Before you start
You will need to import these libraries to use the code examples in this section.
import labelbox as lb
import labelbox.types as lb_types
import uuid
Replace API key
API_KEY = ""
client = lb.Client(API_KEY)
Step 1: Import data rows into Catalog
global_key = "sample-dicom-1.dcm"
asset = {
"row_data": "https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-dicom-1.dcm",
"global_key": global_key,
}
dataset = client.create_dataset(name="dicom_demo_dataset")
task = dataset.create_data_rows([asset])
task.wait_till_done()
print("Errors :",task.errors)
print("Failed data rows:" ,task.failed_data_rows)
Step 2: Set up ontology
Your project should have an ontology set up to support your annotations. To ensure schema matches, the tool names and classification instructions should match the name
fields in your annotations.
To illustrate, the earlier polyline example set the name
field to line_dicom
.
We use the same name when defining the tools
for the ontology. The same pattern must be followed for other tools and classifications used in the ontology.
ontology_builder = lb.OntologyBuilder(
tools=[
lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="segmentation_mask_dicom"),
lb.Tool(tool=lb.Tool.Type.LINE, name="line_dicom"),
]
)
ontology = client.create_ontology("Ontology DICOM Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Dicom)
Step 3: Create labeling project
Connect the ontology to the labeling project
project = client.create_project(name="dicom_project_demo", media_type=lb.MediaType.Dicom)
## connect ontology to your project
project.setup_editor(ontology)
Step 4: Send data rows to project
# Create a batch to send to your MAL project
batch = project.create_batch(
"first-batch-dicom-demo", # Each batch in a project must have a unique name
global_keys=[global_key], # a list of data row objects, data row ids or global keys
priority=5 # priority between 1(Highest) - 5(lowest)
)
print("Batch: ", batch)
Step 5: Create annotations payload
Create the annotations payload using earlier examples .
You can create annotations as Python dictionaries or NDJSON objects. The following examples show each format and show how to compose annotations into Labels attached to the data rows.
The resulting labels
and label_ndjson
from each approach will include every annotation (created earlier) supported by the respective method.
annotations_list = polyline_annotation + mask_annotation
labels = [
lb_types.Label(
data=lb_types.DicomData(global_key=global_key),
annotations=annotations_list
)
]
label_ndjson = []
for annotation in [
polyline_annotation_ndjson,
mask_annotation_ndjson
]:
annotation.update({
'dataRow': {
'globalKey': global_key
}
})
label_ndjson.append(annotation)
Step 6: Upload annotations project as prelabels or ground truth
For both options, pass either the labels
or label_ndjson
payload as the value for the predictions
or labels
parameter.
Option A: Upload as prelabels (model-assisted labeling)
upload_job_mal = lb.MALPredictionImport.create_from_objects(
client = client,
project_id = project.uid,
name="mal_import_job-" + str(uuid.uuid4()),
predictions=labels)
upload_job_mal.wait_until_done();
print("Errors:", upload_job_mal.errors)
print("Status of uploads: ", upload_job_mal.statuses)
Option B: Upload as ground truth
upload_job_label_import = lb.LabelImport.create_from_objects(
client = client,
project_id = project.uid,
name = "label_import_job-" + str(uuid.uuid4()),
labels=labels
)
upload_job_label_import.wait_until_done()
print("Errors:", upload_job_label_import.errors)
print("Status of uploads: ", upload_job_label_import.statuses)