Getting started

How to fetch Point Clouds

In this example the BSL will first be installed (compiled) and then a connection to a Blickfeld LiDAR device will be established and a pointcloud stream created. Finally the Blickfeld Point Cloud Frames will be received.

Installing the BSL

To install the BSL, either the Debian package or the PyPI package can be used.

sudo apt update
sudo apt install -y wget libprotobuf-dev libprotobuf17
wget https://github.com/Blickfeld/blickfeld-scanner-lib/releases/latest/download/blickfeld-scanner-lib-dev-Linux.deb
sudo dpkg -i blickfeld-scanner-lib-dev-Linux.deb
pip install blickfeld_scanner

Note

For manual compilation instructions and further installation information refer to Installation.

Importing the library

To get a point cloud, the BSL has to be installed and the Blickfeld Scanner Library has to be imported.

#include <blickfeld/scanner.h>
>>> import blickfeld_scanner

Connecting

A connection to the device has to be created. In order to connect to a Blickfeld LiDAR device, a TCP connection must first be established. We can use an IP address or a hostname, depending on the connection to the device (see Network configuration).

std::string device_ip_or_hostname = "localhost";  // IP or hostname of the device
std::shared_ptr<blickfeld::scanner> scanner = blickfeld::scanner::connect(device_ip_or_hostname);
>>> device_ip_or_hostname = "localhost"  # IP or hostname of the device
>>> scanner = blickfeld_scanner.scanner(device_ip_or_hostname)
>>> scanner
<blickfeld_scanner.scanner.scanner object at 0x000002A611C1A710>

With this scanner object it is possible to perform different types of tasks, such as getting the status of a device (function get_status) and getting and setting the scan pattern (function get_scan_pattern and set_scan_pattern).

Streaming point clouds

To receive a pointcloud, a stream object has to be generated. With this point_cloud_stream object, point cloud frames can be received.

auto stream = scanner->get_point_cloud_stream();
>>> stream = scanner.get_point_cloud_stream()
>>> stream
<blickfeld_scanner.scanner.point_cloud_stream object at 0x000002A61289D1D0>

To receive a pointcloud frame, the recv_frame function of the point_cloud_stream object has to be called. This can be done in a while loop.

while (true) {
    const blickfeld::protocol::data::Frame frame = stream->recv_frame();
    // TODO: Process received frame
}
while True:
    frame = stream.recv_frame()
    # TODO: Process received frame

Note

The device will drop frames if the client is unable to fetch the frames quickly enough. For this reason, the network connection and frame processing in the while loop need to be fast enough. To detect missing frames, the frame ID can be used. If the IDs are not consecutive, i.e., if IDs are missing, the receiving and processing pipeline will be too slow.

Format

Each frame is a protobuf object that consists of several nested protobuf objects. The following figure provides an overview:

../../_images/protobuf-frame-visualisation.png

The following examples show how to get the values of protobuf fields. For further information on protobufs, see: https://developers.google.com/protocol-buffers

frame.start_time_ns() / 1e9  // Get a field value
frame.scan_pattern().frame_rate().maximum()  // Get a field in a nested protobuf object
frame.scanlines_size()  // Get the size of a repeated protobuf field
>>> frame.start_time_ns / 1e9  # Get a field value
1587462177.261129
>>> frame.scan_pattern.frame_rate.maximum  # Get a field in a nested protobuf object
1.2874246835708618
>>> frame.scanlines[0].points[0]  # Get a value of a repeated nested field
id: 0
start_offset_ns: 0
ambient_light_level: 5
direction {
  azimuth: -0.005621920805424452
  elevation: -0.0010442694183439016
}

To get values of the repeated nested protobuf objects, a loop can be used. For example, to get values of the single returns it is necessary to loop through the scanlines, points and returns in a frame.

// Iterate through all the scanlines in a frame
for (int s_ind = 0; s_ind < frame.scanlines_size(); s_ind++) {

    // Iterate through all the points in a scanline
    for (int p_ind = 0; p_ind < frame.scanlines(s_ind).points_size(); p_ind++) {
        auto& point = frame.scanlines(s_ind).points(p_ind);

        // Iterate through all the returns for each points
        for (int r_ind = 0; r_ind < point.returns_size(); r_ind++) {
            auto& ret = point.returns(r_ind);
            printf("coordinates: (%f, %f, %f)\n", ret.cartesian(0), ret.cartesian(1), ret.cartesian(2));
# Iterate through all the scanlines in a frame
for s_ind in range(len(frame.scanlines)):

    # Iterate through all the points in a scanline
    for p_ind in range(len(frame.scanlines[s_ind].points)):
        point = frame.scanlines[s_ind].points[p_ind]

        # Iterate through all the returns for each points
        for r_ind in range(len(point.returns)):
            ret = point.returns[r_ind]
            print(f"coordinates: ({ret.cartesian[0]}, {ret.cartesian[1]}, {ret.cartesian[2]})")

For example, the x, y, and z coordinates are saved in “ret.cartesian(0)”, which is equal to frame.scanlines(s_ind).points(p_ind).returns(r_ind).cartesian(0).

Closing a stream

To close a stream, the point_cloud_stream object must be deleted.

auto stream = scanner->get_point_cloud_stream();  // Create a stream
...  // Receive frames and do some fancy stuff
delete stream;  // Close the stream
>>> stream = scanner.get_point_cloud_stream()  # Create a stream
>>> stream
<blickfeld_scanner.scanner.point_cloud_stream object at 0x000002A61289D1D0>
>>> ...  # Receive frames and do some fancy stuff
>>> del stream  # Close the stream