Cepton

Cepton SDK Guide

SDK

The following is an overview of the functionality provided by the Cepton SDK. We will start at a high-level overview of the architecture, then dive deeper into some of the specific functions provided.

At a glance

The SDK is based around the ideas of parsers and callbacks.

  • Parsers are registered to interpret the incoming data, for example STDV, INFZ, or custom data types.
  • Callbacks are registered to be invoked when certain types of data are received. For example, we might have one callback that is triggered when new point data comes in, and another callback that is triggered when info data is received.

Data Types

Let's start by taking a closer look at the different data being output from the sensors and SDK.

CeptonPointEx

The primary data type used by the SDK is CeptonPointEx, which provides extended range and precision:

struct CeptonPointEx {
  int32_t x;  // Unit is 1/65536 m or ~0.015mm
  int32_t y;
  int32_t z;
  uint16_t reflectivity;        // Unit is 1%, no lookup table
  uint16_t relative_timestamp;  // Unit is 1 us
  uint16_t flags;
  uint16_t channel_id;
};

Fields description:

  • x: int32_t: the x position component, that is, the horizontal axis. Negative x are to the left, positive x are to the right. Unit is 1/65536 m (~0.015mm).
  • y: int32_t: the y position component, that is, the depth axis. Negative y points out of the screen, positive y points into the screen. Unit is 1/65536 m (~0.015mm).
  • z: int32_t: the z position component, that is, the vertical axis. Negative z points down, positive z points up. Unit is 1/65536 m (~0.015mm).
  • reflectivity: uint16_t: the reflectivity as a percentage (unit is 1%, no lookup table needed). For example, a value of 150 means 150% reflectivity.
  • relative_timestamp: uint16_t: relative timestamp in microseconds (1 us).
  • channel_id: uint16_t: identifies the laser that fired this point.
  • flags: uint16_t: The bits can be masked with the following enum to get each flag. Some of these flags may be sensor dependent.
enum {
  CEPTON_POINT_SATURATED = 1 << 0,
  CEPTON_POINT_BLOOMING = 1 << 1,
  CEPTON_POINT_FRAME_PARITY = 1 << 2,
  CEPTON_POINT_FRAME_BOUNDARY = 1 << 3,
  CEPTON_POINT_SECOND_RETURN = 1 << 4,
  CEPTON_POINT_NO_RETURN = 1 << 5,
  CEPTON_POINT_NOISE = 1 << 6,
  CEPTON_POINT_BLOCKED = 1 << 7,
};

To convert position values to meters:

CeptonPointEx point = ...
float x_meters = point.x / 65536.0f;
float y_meters = point.y / 65536.0f;
float z_meters = point.z / 65536.0f;
float reflectivity_percent = point.reflectivity;

Parsers

In general, you don't need worry about using parsers for Cepton lidars. The SDK has implemented parsers for all our data types.

APIs to be aware of:

CeptonRegisterParser
CeptonUnregisterParser

Callbacks

Callbacks are the part of the API that you will be using.

APIs to be aware of:

CeptonListenFramesEx()
CeptonUnlistenFramesEx()

The SDK provides frame aggregation callbacks that deliver complete frames of point cloud data using the CeptonPointEx data type. These callbacks work with all Cepton sensors and provide extended range and precision.

Callbacks - Frame Aggregation

APIs to be aware of:

CeptonListenFramesEx
CeptonUnlistenFramesEx

Most useful processing will be done on completed frames, containing tens of thousands of points. Let's look at a couple ways to get these completed frames.

1. Using natural frames

Each lidar has its own notion of a "natural frame". This is determined by the firmware and is exposed by the parity bit flag in each point.

To check the parity for a given point, you can do:

CeptonPointEx point;
bool parity = (point.flags & CEPTON_POINT_FRAME_PARITY) == 0;

You don't need to worry about this because it's taken care of by the SDK frame aggregators, but the idea is that each time the parity changes, the frame is completed and published. So if we have a stream of incoming points, the logic looks like this:

0 0 0 0 0 1 <publish frame with parity 0> 1 1 1 1 0 <publish frame with parity 1>

Again, you don't need to worry about this.

To subscribe to these natural frames, the code looks like this:

void frameCallback(CeptonSensorHandle handle,
                   int64_t start_timestamp, size_t n_points,
                   const CeptonPointEx *points,
                   void *user_data)
{
  cout << "Got " << n_points << " points" << endl;

  // Access point data
  float x_meters = points[0].x / 65536.0f;
  float reflectivity_percent = points[0].reflectivity;
}

int main() {
  // ... other initialization code
  CeptonListenFramesEx(CEPTON_AGGREGATION_MODE_NATURAL, frameCallback, nullptr);
}

2. Using timed frames

The second option, is to used timed frames. Generally this would not be used for modern sensors, but it is an option in case you want the SDK to take care of accumulating longer frames, rather than doing so yourself.

Rather than using CEPTON_AGGREGATION_MODE_NATURAL, we instead pass in an integer for the frame duration we want, measured in microseconds. So if we wanted to accumulate 0.2-second frames, the API call would be:

int main() {
  // .. other initialization code
  CeptonListenFramesEx(200000, frameCallback, nullptr);
}

Note that the duration must be at least 1000 microseconds, anything less will return an SDK error.

A complete example with a live sensor

// basic.c
#include <stdio.h>
#include <stdlib.h>

#include "cepton_sdk3.h"

void check_api_error(int err, char const *api) {
  if (err != CEPTON_SUCCESS) {
    printf("API Error for %s: %s\n", api, CeptonGetErrorCodeName(err));
    exit(1);
  }
}

int n_frames = 0;
void frameCallbackEx(CeptonSensorHandle handle, int64_t start_timestamp,
                     size_t n_points, const struct CeptonPointEx *points,
                     void *user_data) {
  printf("Got %d frames: %d points\n", ++n_frames, (int)n_points);

  // Example: convert first point position to meters
  if (n_points > 0) {
    float x_meters = points[0].x / 65536.0f;
    float y_meters = points[0].y / 65536.0f;
    float z_meters = points[0].z / 65536.0f;
  }
}

int main() {
  int ret;

  // Initialize
  ret = CeptonInitialize(CEPTON_API_VERSION, NULL);
  check_api_error(ret, "CeptonInitialize");

  // Start networking listener thread
  ret = CeptonStartNetworking();
  check_api_error(ret, "CeptonStartNetworking");

  // Listen for frames
  ret =
      CeptonListenFramesEx(CEPTON_AGGREGATION_MODE_NATURAL, frameCallbackEx, 0);
  check_api_error(ret, "CeptonListenFramesEx");

  // Sleep
  while (n_frames < 10);

  // Deinitialize
  ret = CeptonDeinitialize();
  check_api_error(ret, "CeptonDeinitialize");
  return 0;
}

A couple notes on this sample:

  • See the call to CeptonStartNetworking(). This starts an internal socket listener to receive the UDP data. If running with a live sensor, this function must be called after CeptonInitialize() in order to receive any data.
  • See the first parameter to CeptonInitialize, CEPTON_API_VERSION. This variable is defined in cepton_sdk3.h, and is used to ensure consistency between the included header file and the SDK binary. If the two do not match, an error is returned.

Building the example

To compile and link the basic.c example against the Cepton SDK on linux, use one of the following commands:

gcc basic.c -o basic -I/path/to/sdk/include -L/path/to/sdk/lib -lcepton_sdk3

Replace /path/to/sdk/include and /path/to/sdk/lib with the actual paths to your SDK installation.

Notes:

  • On Linux, you need to link against pthread and dl libraries
  • Make sure the SDK shared library (.so, .dylib, or .dll) is in your library path at runtime
  • You may need to set LD_LIBRARY_PATH (Linux), DYLD_LIBRARY_PATH (macOS), or add the DLL directory to PATH (Windows)