Indexing & Slicing

One of the features of the FrameSetSource API, is the ability to use indexing and slicing when accessing the stored frames within the LidarFrame source. Currently, this capability is only supported for indexable sources. That is to say, the functionality we are discussing can only be used when accessing a pcap or an OSF file with indexing turned on. To turn on indexing simply add the index flag and set it True when opening a pcap or OSF file:

Imports

from ouster import sdk
using namespace ouster::sdk;

Read input source using open_source with indexing enabled

source = sdk.open_source(pcap_path,
                         index=True)
auto source = sdk::open_source(pcap_path, [](sdk::FrameSetSourceOptions& opts) { 
                               opts.index = true; });

Depending on the file size and the underlying file format there can be some delay before the file is fully indexed (OSF file take much less time than pcap file to index). A progress bar will appear to indicate progress of the indexing.

Once the index is built up, then we can start using utilizing and interact with the FrameSetSource object to access frames in the same manner we are dealing with a python list that holds reference to LidarFrame objects.

To access the 10th LidarFrame and print its frame id, we can do the following:

frame_set = source[9]
for frame in frame_set:
    if frame is not None:
        print(frame.frame_id)
# To directly access 9th frame of sensor with index 0.
frame = source[9][0]
assert frame is not None
print(frame.frame_id)
auto frame_set = source[9];
for (auto& frame : frame_set) {
    if (frame) {
        std::cout << frame->frame_id << '\n'; } }
// To directly access the 9th frame from sensor index 0 without looping.
auto frame = source[9][0];
std::cout << frame->frame_id << '\n';

One can access a specific frame and sensor in one go — e.g., source[-1][0].frame_id picks the last frame and the first sensor.

In C++, write source[{start, stop, step}]— leaving any entry out with nonstd::nullopt— to get a FrameSetSource restricted to that window but keeping the same iterators, indexes, and metadata. Negative steps are not allowed; for reverse iteration, collect indices first and iterate backwards manually.

Similarly we can access the last LidarFrame object and print its frame_id using:

frame = source[-1][0]
assert frame is not None
print(frame.frame_id)
auto frame = source[-1][0];
std::cout << frame->frame_id << '\n';

Alternatively we can instead request a range of frames using the python slice operator. For example, to request the first 9 frames from a FrameSetSource and print their frame ids, we can do the following:

for frame_set in source[0:9]:
    for frame in frame_set:
        if frame is not None:
            print(frame.frame_id)
for (const auto& frame_set : source[{0, 9}]) {
    for (auto& frame : frame_set) {
        if (frame) { 
            std::cout << frame->frame_id << '\n'; } } }

Note: we don’t need to add any break here since the operation source[0:9] will only yield the first 9 LidarFrame(s).

To print frame_id of the last 8 LidarFrames we do:

for frame_set in source[-9:-1]:
    for frame in frame_set:
        if frame is not None:
            print(frame.frame_id)
for (const auto& frame_set : source[{-9, -1}]) {
    for (const auto& frame : frame_set) {
        if (frame) { 
            std::cout << frame->frame_id << '\n'; } } }

Finally, as you would expect from a typical slice operation you can also use a step value, though reversed iteration is not supported.

# prints every second frame in the first ten
for frame_set in source[0:10:2]:
    for frame in frame_set:
        if frame is not None:
            print(frame.frame_id)
// prints every second frame in the first ten
for (const auto& frame_set : source[{0, 10, 2}]) {
    for (const auto& frame : frame_set) {
        if (frame) { std::cout << frame->frame_id << '\n'; } 
    } }

Note

Reverse iteration (negative step sizes) is not supported by FrameSetSource such as source[10:0:-1]. Collect indices into a vector and iterate in reverse order if needed.

Slicing operator as a FrameSetSource

The FrameSetSource slice operator [::] returns a FrameSetSource scoped to the indicated slice range. This means that the users can pass the object returned by the slice operator [::] to any function or code that expects a FrameSetSource object.

The following snippet shows few examples to demonstrate this capability:

source2 = source[5:10]
# This should print 5 since source2 is scoped to the range [5, 10]
print("source2 length:", len(source2))
# This is equivalent to calling source[5][0]
print(source2[0][0].frame_id)  # type: ignore[union-attr]

# This is equivalent to calling source[9][0]
print(source2[4][0].frame_id)  # type: ignore[union-attr]


# Use source2 as an iterator similar to the main source, assumes only one sensor
source_iter = iter(source2)
for frame, in source_iter:
    assert frame is not None
    print(frame.frame_id)

# it is possible to sub slice, meaning take the result of a previous slice operation and slice it
# Thus, the following statement is valid
source3 = source2[2:4]  # equivalent to source[7:9]
print("source3 length:", len(source3))
auto source2 = source[{5, 10}];
// This should print 5 since source2 is scoped to the range [5, 10]
std::cout << "source2 length: " << source2.size() << '\n';
// This is equivalent to calling source[5][0]
const auto first_frame = source2[0][0];
std::cout << first_frame->frame_id << '\n';
// This is equivalent to calling source[9][0]
const auto fifth_frame = source2[4][0];
std::cout << fifth_frame->frame_id << '\n';

// Use source2 as an iterator similar to the main source, assumes only one sensor
for (const auto& frame_set : source2) {
    if (frame_set.size() > 0 && frame_set[0]) {
        std::cout << frame_set[0]->frame_id << '\n'; } }

// it is possible to sub slice, meaning take the result of a previous slice operation and slice it
// Thus, the following statement is valid
auto source3 = source2[{2, 4}];
std::cout << "source3 length: " << source3.size() << '\n';  // equivalent to source[7:9]

Note

Invoking source2[10][0].frame_id would result in an out of range exception` since source2 is scoped to 5 frames. Also, invoking source2[4].frame_id would result in an AttributeError` since source2 is a list of LidarFrame objects.

Validating LidarFrames

The SDK provides utilities to validate data integrity at both the high-level (assembled frames) and low-level (packet streams).

These functions help validate the integrity of an assembled LidarFrame, allowing you to programmatically identify issues like empty columns or missing poses.

Python

The ouster.sdk.core module provides helpers that return the pose directly:

  • first_valid_column_pose(frame): Returns the 4x4 pose matrix of the first valid column.

  • last_valid_column_pose(frame): Returns the 4x4 pose matrix of the last valid column.

  • frame.body_to_world[column_index]: Returns the 4x4 body-to-world matrix for the given column.

  • frame.body_to_world: Returns the full (W, 4, 4) array of per-column body-to-world transforms.

C++

The core::LidarFrame class provides methods to find the indices, which can then be used to look up the pose:

  • frame.get_first_valid_column(): Returns the index of the first valid column. Raises std::runtime_error / RuntimeError if no valid columns are available.

  • frame.get_last_valid_column(): Returns the index of the last valid column. Raises std::runtime_error / RuntimeError if no valid columns are available.

  • frame.get_column_pose(index): Returns the body-to-world matrix for the given column.

  • frame.body_to_world(): Returns a reference to the full per-column body-to-world field.

frame = source[0][0]
assert frame is not None
print(frame.get_first_valid_column())
auto frame = source[0][0]; 
std::cout << frame->get_first_valid_column() << '\n';
frame = source[0][0]
assert frame is not None
print(frame.get_last_valid_column())
auto frame = source[0][0]; 
std::cout << frame->get_last_valid_column() << '\n';

Together, these helpers make it easier to programmatically identify issues like empty columns, missing poses, or incomplete frames before processing.