LabelRowV2.is_event_based is True.
Do not use the frame-based
ObjectInstance methods, such as set_for_frames() and remove_from_frames(), on a continuous MCAP Scene. Use upsert_event(), delete_event(), and get_events() for 3D objects, and get_space() with put_object_instance() for point cloud segmentation and time-series ranges.Writing Labels
Each example below is a standalone script with two tabs: Single Scene labels one MCAP Scene, and Bulk labels several Scenes, using bundles to initialize and save the label rows. Replace the values in the# User input section, the labels dictionary in the Bulk scripts, and the coordinates, with your own values. The ontology must contain the corresponding shape: a cuboid, point cloud segmentation, or time range. Each script saves the labels when run.
3D Objects
Root 3D objects, such as cuboids, spheres, keypoints, and polylines, use events at scene-relative nanosecond timestamps. Each upsert sets the geometry until the next event; a delete ends the object at that timestamp. The SDK uses hold behavior rather than interpolating geometry between keyframes. The example below labels a cuboid over[2 s, 5 s), updating it at 3.5 s.
Encord’s 3D annotation event model follows Foxglove’s add/replace/delete model. See SceneUpdate for updates and deletions, and SceneEntity for object IDs, geometry, and lifetime.
# Import dependencies
import json
from encord import EncordUserClient, Project
from encord.objects import LabelRowV2, Object, ObjectInstance
from encord.objects.coordinates import CuboidCoordinates
# User input
SSH_PATH = "/Users/chris-encord/ssh-private-key.txt" # Replace with the file path to your SSH private key
PROJECT_ID = "00000000-0000-0000-0000-000000000000" # Replace with the unique ID for the Project
DATA_TITLE = "spot-walk-001.mcap" # Replace with the title of the MCAP Scene
OBJECT_TITLE = "Vehicle" # Replace with the title of a cuboid object in the Ontology
START_NS = 2_000_000_000 # Nanoseconds after the Scene start when the cuboid appears
UPDATE_NS = 3_500_000_000 # Nanoseconds after the Scene start when the cuboid geometry changes
END_NS = 5_000_000_000 # Nanoseconds after the Scene start when the cuboid ends
OUTPUT_JSON = "/Users/chris-encord/mcap-cuboids-labels.json" # Replace with the file path to save the exported labels
# Create user client using ssh key
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path=SSH_PATH,
# For US platform users use "https://api.us.encord.com"
domain="https://api.encord.com",
)
# Get the Project
project: Project = user_client.get_project(PROJECT_ID)
# Get and initialize the label row for the MCAP Scene
label_rows = project.list_label_rows_v2(data_title_eq=DATA_TITLE)
if len(label_rows) != 1:
raise ValueError(f"Expected one label row; found {len(label_rows)}")
label_row: LabelRowV2 = label_rows[0]
label_row.initialise_labels()
if not label_row.is_event_based:
raise ValueError("Expected a continuous MCAP Scene")
# Find the cuboid object in the Project Ontology
cuboid_ontology_object: Object = project.ontology_structure.get_child_by_title(title=OBJECT_TITLE, type_=Object)
if cuboid_ontology_object is None:
raise ValueError(f"No ontology object named {OBJECT_TITLE!r}")
# Add a cuboid over [START_NS, END_NS), with a geometry change at UPDATE_NS.
# Create a cuboid instance and add it to the label row
cuboid_object_instance: ObjectInstance = cuboid_ontology_object.create_instance()
label_row.add_object_instance(cuboid_object_instance)
# Set the cuboid geometry from START_NS
cuboid_object_instance.upsert_event(
CuboidCoordinates(
position=(1.0, 2.0, 0.8),
orientation=(0.0, 0.0, 0.25),
size=(4.2, 1.8, 1.6),
),
START_NS,
)
# Change the cuboid geometry from UPDATE_NS
cuboid_object_instance.upsert_event(
CuboidCoordinates(
position=(2.2, 2.1, 0.8),
orientation=(0.0, 0.0, 0.30),
size=(4.2, 1.8, 1.6),
),
UPDATE_NS,
)
# End the cuboid at END_NS
cuboid_object_instance.delete_event(END_NS)
# Print the cuboid events
for event in cuboid_object_instance.get_events():
if event.kind == "upsert":
print(event.frame, event.kind, event.coordinates)
else:
print(event.frame, event.kind)
# Export the in-memory labels to a JSON file
with open(OUTPUT_JSON, "w", encoding="utf-8") as file:
json.dump(label_row.to_encord_dict(), file, ensure_ascii=False, indent=2)
# Save the labels to Encord
label_row.save()
print(f"Saved label row for {label_row.data_title}")
# Import dependencies
import json
from encord import EncordUserClient, Project
from encord.objects import LabelRowV2, Object, ObjectInstance
from encord.objects.coordinates import CuboidCoordinates
# User input
SSH_PATH = "/Users/chris-encord/ssh-private-key.txt" # Replace with the file path to your SSH private key
PROJECT_ID = "00000000-0000-0000-0000-000000000000" # Replace with the unique ID for the Project
OBJECT_TITLE = "Vehicle" # Replace with the title of a cuboid object in the Ontology
BUNDLE_SIZE = 100
OUTPUT_JSON = "/Users/chris-encord/mcap-cuboids-labels-bulk.json" # Replace with the file path to save the exported labels
# Dictionary of cuboid events per MCAP Scene and per cuboid.
# "frame" is in nanoseconds after the Scene start.
# An "upsert" sets the cuboid geometry until the next event; a "delete" ends the cuboid.
mcap_cuboid_labels = {
"spot-walk-001.mcap": {
"vehicle_001": [
{
"frame": 2_000_000_000,
"kind": "upsert",
"coordinates": CuboidCoordinates(
position=(1.0, 2.0, 0.8),
orientation=(0.0, 0.0, 0.25),
size=(4.2, 1.8, 1.6),
),
},
{
"frame": 3_500_000_000,
"kind": "upsert",
"coordinates": CuboidCoordinates(
position=(2.2, 2.1, 0.8),
orientation=(0.0, 0.0, 0.30),
size=(4.2, 1.8, 1.6),
),
},
{"frame": 5_000_000_000, "kind": "delete"},
],
"vehicle_002": [
{
"frame": 1_000_000_000,
"kind": "upsert",
"coordinates": CuboidCoordinates(
position=(-3.0, 1.5, 0.8),
orientation=(0.0, 0.0, 0.10),
size=(4.5, 1.9, 1.7),
),
},
{"frame": 4_000_000_000, "kind": "delete"},
],
},
"spot-walk-002.mcap": {
"vehicle_003": [
{
"frame": 500_000_000,
"kind": "upsert",
"coordinates": CuboidCoordinates(
position=(5.0, -1.0, 0.8),
orientation=(0.0, 0.0, 1.57),
size=(4.0, 1.8, 1.5),
),
},
{"frame": 2_500_000_000, "kind": "delete"},
],
},
}
# Create user client using ssh key
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path=SSH_PATH,
# For US platform users use "https://api.us.encord.com"
domain="https://api.encord.com",
)
# Get the Project
project: Project = user_client.get_project(PROJECT_ID)
# Find the cuboid object in the Project Ontology
cuboid_ontology_object: Object = project.ontology_structure.get_child_by_title(title=OBJECT_TITLE, type_=Object)
if cuboid_ontology_object is None:
raise ValueError(f"No ontology object named {OBJECT_TITLE!r}")
# Initialize the label rows for all MCAP Scenes using a bundle
label_row_map = {}
with project.create_bundle(bundle_size=BUNDLE_SIZE) as bundle:
for data_title in mcap_cuboid_labels.keys():
label_rows = project.list_label_rows_v2(data_title_eq=data_title)
if not label_rows:
print(f"Skipping: No label row found for {data_title}")
continue
label_row: LabelRowV2 = label_rows[0]
label_row.initialise_labels(bundle=bundle)
label_row_map[data_title] = label_row
# Add a cuboid over each upsert/delete sequence, per MCAP Scene
label_rows_to_save = []
for data_title, cuboids in mcap_cuboid_labels.items():
label_row = label_row_map.get(data_title)
if not label_row:
continue
if not label_row.is_event_based:
print(f"Skipping: {data_title} is not a continuous MCAP Scene")
continue
for label_ref, events in cuboids.items():
# Create a cuboid instance and add it to the label row
cuboid_object_instance: ObjectInstance = cuboid_ontology_object.create_instance()
label_row.add_object_instance(cuboid_object_instance)
# Write the cuboid events in order
for event in events:
if event["kind"] == "upsert":
cuboid_object_instance.upsert_event(event["coordinates"], event["frame"])
else:
cuboid_object_instance.delete_event(event["frame"])
label_rows_to_save.append(label_row)
# Export the in-memory labels for all label rows to a JSON file
with open(OUTPUT_JSON, "w", encoding="utf-8") as file:
json.dump([label_row.to_encord_dict() for label_row in label_rows_to_save], file, ensure_ascii=False, indent=2)
# Save all label rows to Encord using a bundle
with project.create_bundle(bundle_size=BUNDLE_SIZE) as bundle:
for label_row in label_rows_to_save:
label_row.save(bundle=bundle)
print(f"Saved label row for {label_row.data_title}")
object_instance.get_events() returns stored upserts and deletes in timestamp order; only upserts have coordinates. object_instance.get_ranges() returns the inclusive ranges where the object exists. To read its geometry between events, use object_instance.get_annotation(frame=3_000_000_000).coordinates. Despite the parameter name frame, this is a nanosecond timestamp relative to the Scene start. The resolved annotation is read-only; use upsert_event() to write a change.
A delete ends the object immediately. An upsert at 2 s followed by a delete at 5 s means the object exists over [2 s, 5 s): it is absent at exactly 5 s, and reading it there raises LabelRowError. The corresponding inclusive SDK range ends at 4_999_999_999 ns. Every track must end with a delete before export or save; if the last included timestamp is t, delete at t + 1 ns. Point-index ranges and time-series ranges include both endpoints.
Point Cloud Segmentation
Address a point cloud message with its topic and absolute MCAPlog_time in nanoseconds. Point index ranges are inclusive and refer to the exact point order in that message. Filtering or reordering the point cloud changes those indices.
To read the log_time with the Go version of the MCAP CLI, extract the first column of its text output (integer nanoseconds). Replace the file and topic with your recording’s values:
mcap cat recording.mcap --topics /lidar/smooth_pointcloud | head -n 2 | awk '{print $1}'
# Import dependencies
import json
from encord import EncordUserClient, Project
from encord.objects import LabelRowV2, Object, ObjectInstance
from encord.objects.frames import Range
from encord.objects.spaces.range_space.point_cloud_space import PointCloudFileSpace
# User input
SSH_PATH = "/Users/chris-encord/ssh-private-key.txt" # Replace with the file path to your SSH private key
PROJECT_ID = "00000000-0000-0000-0000-000000000000" # Replace with the unique ID for the Project
DATA_TITLE = "spot-walk-001.mcap" # Replace with the title of the MCAP Scene
OBJECT_TITLE = "Road surface" # Replace with the title of a point cloud segmentation object in the Ontology
POINT_CLOUD_TOPIC = "/lidar/smooth_pointcloud" # Replace with the point cloud topic in the MCAP file
LOG_TIME_NS = 1_750_258_475_125_212_159 # Replace with the absolute MCAP log_time of the message, in nanoseconds
POINT_RANGES = [
Range(start=0, end=15_234),
Range(start=52_000, end=52_980),
] # Inclusive point index ranges in the message
OUTPUT_JSON = "/Users/chris-encord/mcap-segmentation-labels.json" # Replace with the file path to save the exported labels
# Create user client using ssh key
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path=SSH_PATH,
# For US platform users use "https://api.us.encord.com"
domain="https://api.encord.com",
)
# Get the Project
project: Project = user_client.get_project(PROJECT_ID)
# Get and initialize the label row for the MCAP Scene
label_rows = project.list_label_rows_v2(data_title_eq=DATA_TITLE)
if len(label_rows) != 1:
raise ValueError(f"Expected one label row; found {len(label_rows)}")
label_row: LabelRowV2 = label_rows[0]
label_row.initialise_labels()
if not label_row.is_event_based:
raise ValueError("Expected a continuous MCAP Scene")
# Find the segmentation object in the Project Ontology
segmentation_ontology_object: Object = project.ontology_structure.get_child_by_title(
title=OBJECT_TITLE, type_=Object
)
if segmentation_ontology_object is None:
raise ValueError(f"No ontology object named {OBJECT_TITLE!r}")
# Label inclusive point-index ranges on one point cloud message.
# Get the point cloud space for one message, using the <topic>@<log_time_ns> space ID
point_cloud_space: PointCloudFileSpace = label_row.get_space(
id=f"{POINT_CLOUD_TOPIC}@{LOG_TIME_NS}", type_="point_cloud"
)
# Create a segmentation instance and label the point index ranges
segmentation_object_instance: ObjectInstance = segmentation_ontology_object.create_instance()
point_cloud_space.put_object_instance(segmentation_object_instance, ranges=POINT_RANGES)
# Export the in-memory labels to a JSON file
with open(OUTPUT_JSON, "w", encoding="utf-8") as file:
json.dump(label_row.to_encord_dict(), file, ensure_ascii=False, indent=2)
# Save the labels to Encord
label_row.save()
print(f"Saved label row for {label_row.data_title}")
# Import dependencies
import json
from encord import EncordUserClient, Project
from encord.objects import LabelRowV2, Object, ObjectInstance
from encord.objects.frames import Range
from encord.objects.spaces.range_space.point_cloud_space import PointCloudFileSpace
# User input
SSH_PATH = "/Users/chris-encord/ssh-private-key.txt" # Replace with the file path to your SSH private key
PROJECT_ID = "00000000-0000-0000-0000-000000000000" # Replace with the unique ID for the Project
OBJECT_TITLE = "Road surface" # Replace with the title of a point cloud segmentation object in the Ontology
BUNDLE_SIZE = 100
OUTPUT_JSON = "/Users/chris-encord/mcap-segmentation-labels-bulk.json" # Replace with the file path to save the exported labels
# Dictionary of segmentations per MCAP Scene.
# "log_time_ns" is the absolute MCAP log_time of the point cloud message, in nanoseconds.
# "ranges" are inclusive point index ranges in that message.
mcap_segmentation_labels = {
"spot-walk-001.mcap": [
{
"topic": "/lidar/smooth_pointcloud",
"log_time_ns": 1_750_258_475_125_212_159,
"ranges": [Range(start=0, end=15_234), Range(start=52_000, end=52_980)],
},
{
"topic": "/lidar/smooth_pointcloud",
"log_time_ns": 1_750_258_475_225_198_004,
"ranges": [Range(start=0, end=14_987)],
},
],
"spot-walk-002.mcap": [
{
"topic": "/lidar/smooth_pointcloud",
"log_time_ns": 1_750_259_102_004_117_392,
"ranges": [Range(start=1_200, end=18_450)],
},
],
}
# Create user client using ssh key
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path=SSH_PATH,
# For US platform users use "https://api.us.encord.com"
domain="https://api.encord.com",
)
# Get the Project
project: Project = user_client.get_project(PROJECT_ID)
# Find the segmentation object in the Project Ontology
segmentation_ontology_object: Object = project.ontology_structure.get_child_by_title(
title=OBJECT_TITLE, type_=Object
)
if segmentation_ontology_object is None:
raise ValueError(f"No ontology object named {OBJECT_TITLE!r}")
# Initialize the label rows for all MCAP Scenes using a bundle
label_row_map = {}
with project.create_bundle(bundle_size=BUNDLE_SIZE) as bundle:
for data_title in mcap_segmentation_labels.keys():
label_rows = project.list_label_rows_v2(data_title_eq=data_title)
if not label_rows:
print(f"Skipping: No label row found for {data_title}")
continue
label_row: LabelRowV2 = label_rows[0]
label_row.initialise_labels(bundle=bundle)
label_row_map[data_title] = label_row
# Label inclusive point-index ranges on each point cloud message, per MCAP Scene
label_rows_to_save = []
for data_title, segmentations in mcap_segmentation_labels.items():
label_row = label_row_map.get(data_title)
if not label_row:
continue
if not label_row.is_event_based:
print(f"Skipping: {data_title} is not a continuous MCAP Scene")
continue
for item in segmentations:
# Get the point cloud space for one message, using the <topic>@<log_time_ns> space ID
point_cloud_space: PointCloudFileSpace = label_row.get_space(
id=f"{item['topic']}@{item['log_time_ns']}", type_="point_cloud"
)
# Create a segmentation instance and label the point index ranges
segmentation_object_instance: ObjectInstance = segmentation_ontology_object.create_instance()
point_cloud_space.put_object_instance(segmentation_object_instance, ranges=item["ranges"])
label_rows_to_save.append(label_row)
# Export the in-memory labels for all label rows to a JSON file
with open(OUTPUT_JSON, "w", encoding="utf-8") as file:
json.dump([label_row.to_encord_dict() for label_row in label_rows_to_save], file, ensure_ascii=False, indent=2)
# Save all label rows to Encord using a bundle
with project.create_bundle(bundle_size=BUNDLE_SIZE) as bundle:
for label_row in label_rows_to_save:
label_row.save(bundle=bundle)
print(f"Saved label row for {label_row.data_title}")
Time Series
Use the exact numeric sub-channel ID, such as/spot/cmd_vel.angular.x, rather than only the parent topic. Both endpoints of a time-series Range are inclusive and expressed in nanoseconds relative to the Scene start. See Time Series layouts to identify a sub-channel in your recording.
# Import dependencies
import json
from encord import EncordUserClient, Project
from encord.objects import LabelRowV2, Object, ObjectInstance
from encord.objects.frames import Range
from encord.objects.spaces.range_space.time_series_space import TimeSeriesSpace
# User input
SSH_PATH = "/Users/chris-encord/ssh-private-key.txt" # Replace with the file path to your SSH private key
PROJECT_ID = "00000000-0000-0000-0000-000000000000" # Replace with the unique ID for the Project
DATA_TITLE = "spot-walk-001.mcap" # Replace with the title of the MCAP Scene
OBJECT_TITLE = "Braking interval" # Replace with the title of a time range object in the Ontology
CHANNEL_ID = "/spot/cmd_vel.angular.x" # Replace with the numeric sub-channel ID in the MCAP file
START_NS = 2_250_000_000 # Nanoseconds after the Scene start when the range starts (inclusive)
END_NS = 2_900_000_000 # Nanoseconds after the Scene start when the range ends (inclusive)
OUTPUT_JSON = "/Users/chris-encord/mcap-time-series-labels.json" # Replace with the file path to save the exported labels
# Create user client using ssh key
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path=SSH_PATH,
# For US platform users use "https://api.us.encord.com"
domain="https://api.encord.com",
)
# Get the Project
project: Project = user_client.get_project(PROJECT_ID)
# Get and initialize the label row for the MCAP Scene
label_rows = project.list_label_rows_v2(data_title_eq=DATA_TITLE)
if len(label_rows) != 1:
raise ValueError(f"Expected one label row; found {len(label_rows)}")
label_row: LabelRowV2 = label_rows[0]
label_row.initialise_labels()
if not label_row.is_event_based:
raise ValueError("Expected a continuous MCAP Scene")
# Find the time range object in the Project Ontology
time_range_ontology_object: Object = project.ontology_structure.get_child_by_title(title=OBJECT_TITLE, type_=Object)
if time_range_ontology_object is None:
raise ValueError(f"No ontology object named {OBJECT_TITLE!r}")
# Add an inclusive range to one numeric MCAP channel.
# Get the time series space for the channel
time_series_space: TimeSeriesSpace = label_row.get_space(id=CHANNEL_ID, type_="time_series")
# Create a time range instance and add it to the channel
time_range_object_instance: ObjectInstance = time_range_ontology_object.create_instance()
time_series_space.put_object_instance(time_range_object_instance, ranges=Range(start=START_NS, end=END_NS))
# Export the in-memory labels to a JSON file
with open(OUTPUT_JSON, "w", encoding="utf-8") as file:
json.dump(label_row.to_encord_dict(), file, ensure_ascii=False, indent=2)
# Save the labels to Encord
label_row.save()
print(f"Saved label row for {label_row.data_title}")
# Import dependencies
import json
from encord import EncordUserClient, Project
from encord.objects import LabelRowV2, Object, ObjectInstance
from encord.objects.frames import Range
from encord.objects.spaces.range_space.time_series_space import TimeSeriesSpace
# User input
SSH_PATH = "/Users/chris-encord/ssh-private-key.txt" # Replace with the file path to your SSH private key
PROJECT_ID = "00000000-0000-0000-0000-000000000000" # Replace with the unique ID for the Project
OBJECT_TITLE = "Braking interval" # Replace with the title of a time range object in the Ontology
BUNDLE_SIZE = 100
OUTPUT_JSON = "/Users/chris-encord/mcap-time-series-labels-bulk.json" # Replace with the file path to save the exported labels
# Dictionary of time ranges per MCAP Scene.
# "channel_id" is the numeric sub-channel ID in the MCAP file.
# "start_ns" and "end_ns" are inclusive, in nanoseconds after the Scene start.
mcap_time_series_labels = {
"spot-walk-001.mcap": [
{"channel_id": "/spot/cmd_vel.angular.x", "start_ns": 2_250_000_000, "end_ns": 2_900_000_000},
{"channel_id": "/spot/cmd_vel.linear.x", "start_ns": 6_000_000_000, "end_ns": 7_250_000_000},
],
"spot-walk-002.mcap": [
{"channel_id": "/spot/cmd_vel.angular.x", "start_ns": 1_000_000_000, "end_ns": 1_800_000_000},
],
}
# Create user client using ssh key
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path=SSH_PATH,
# For US platform users use "https://api.us.encord.com"
domain="https://api.encord.com",
)
# Get the Project
project: Project = user_client.get_project(PROJECT_ID)
# Find the time range object in the Project Ontology
time_range_ontology_object: Object = project.ontology_structure.get_child_by_title(title=OBJECT_TITLE, type_=Object)
if time_range_ontology_object is None:
raise ValueError(f"No ontology object named {OBJECT_TITLE!r}")
# Initialize the label rows for all MCAP Scenes using a bundle
label_row_map = {}
with project.create_bundle(bundle_size=BUNDLE_SIZE) as bundle:
for data_title in mcap_time_series_labels.keys():
label_rows = project.list_label_rows_v2(data_title_eq=data_title)
if not label_rows:
print(f"Skipping: No label row found for {data_title}")
continue
label_row: LabelRowV2 = label_rows[0]
label_row.initialise_labels(bundle=bundle)
label_row_map[data_title] = label_row
# Add an inclusive range to each numeric MCAP channel, per MCAP Scene
label_rows_to_save = []
for data_title, time_ranges in mcap_time_series_labels.items():
label_row = label_row_map.get(data_title)
if not label_row:
continue
if not label_row.is_event_based:
print(f"Skipping: {data_title} is not a continuous MCAP Scene")
continue
for item in time_ranges:
# Get the time series space for the channel
time_series_space: TimeSeriesSpace = label_row.get_space(id=item["channel_id"], type_="time_series")
# Create a time range instance and add it to the channel
time_range_object_instance: ObjectInstance = time_range_ontology_object.create_instance()
time_series_space.put_object_instance(
time_range_object_instance, ranges=Range(start=item["start_ns"], end=item["end_ns"])
)
label_rows_to_save.append(label_row)
# Export the in-memory labels for all label rows to a JSON file
with open(OUTPUT_JSON, "w", encoding="utf-8") as file:
json.dump([label_row.to_encord_dict() for label_row in label_rows_to_save], file, ensure_ascii=False, indent=2)
# Save all label rows to Encord using a bundle
with project.create_bundle(bundle_size=BUNDLE_SIZE) as bundle:
for label_row in label_rows_to_save:
label_row.save(bundle=bundle)
print(f"Saved label row for {label_row.data_title}")
Reading Labels
JSON export
Initialize the label row to read its saved annotations. This example exports all loaded labels to JSON.export MCAP labels
import json
from encord import EncordUserClient
client = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path="file-path-to-ssh-key",
)
project = client.get_project("<project_id>")
rows = project.list_label_rows_v2(data_title_eq="<mcap_data_title>")
label_row = rows[0]
label_row.initialise_labels()
with open("mcap-labels.json", "w") as file:
json.dump(label_row.to_encord_dict(), file, ensure_ascii=False, indent=2)
Other SDK methods
For root 3D objects,frame means nanoseconds relative to the Scene start. obj below is an ObjectInstance:
| API | When to use it |
|---|---|
label_row.is_event_based | Check whether the row uses continuous events. |
label_row.get_object_instances(filter_frames=...) | Find root objects present at a timestamp or overlapping a Range. |
obj.get_events() | Inspect stored upserts and deletes in time order; only upserts have coordinates. |
obj.get_ranges() | Find inclusive object lifetimes, including gaps. Requires a closing delete. |
obj.get_annotation(frame=t) | Read geometry at any timestamp using hold behavior; raises LabelRowError if absent. |
annotation.keyframe, annotation.is_virtual | Identify the source upsert and whether the resolved view inherits an earlier keyframe. |
Read MCAP labels
from encord import EncordUserClient
client = EncordUserClient.create_with_ssh_private_key(
ssh_private_key_path="file-path-to-ssh-key",
)
project = client.get_project("<project_id>")
rows = project.list_label_rows_v2(data_title_eq="<mcap_data_title>")
label_row = rows[0]
label_row.initialise_labels()
# Root 3D objects
for object_instance in label_row.get_object_instances():
print(object_instance.object_hash)
for event in object_instance.get_events():
if event.kind == "upsert":
print(event.frame, event.kind, event.coordinates)
else:
print(event.frame, event.kind)
for lifetime in object_instance.get_ranges():
annotation = object_instance.get_annotation(frame=lifetime.start)
print(lifetime.start, lifetime.end, annotation.coordinates)
| API | When to use it |
|---|---|
label_row.get_space(id=..., type_=...) | Access a point cloud message (point_cloud) or numeric channel (time_series). |
label_row.get_spaces() | List known label spaces; this does not list every MCAP topic or message. |
space.get_object_instances(), space.get_object_ranges(obj) | Read labeled objects and their inclusive point-index or time ranges. |

