Field and ArrayView¶
In the C++ SDK, channel data inside a LidarFrame is represented with Field, a contiguous multidimensional container.
A field is described by a FieldDescriptor which describes the memory stored by the field and FieldClass, an enumeration giving LidarFrame some information about how to categorize the field.
On the python side, fields are directly mapped to NumPy arrays so this document will only cover the C++ side.
Constructing a Field¶
This section describes constructing a standalone field. To construct a field inside the LidarFrame, refer to LidarFrame documentation.
The simplest pattern is to describe an array with fd_array (or a FieldDescriptor) and pass it
to the Field constructor:
// templated version
Field field(fd_array<uint32_t>(128, 1024));
// ChanFieldType version
Field field_cft(fd_array(ChanFieldType::UINT32, 128, 1024));
Type restrictions¶
Standalone fields support any primitive and plain-old-data types when used directly. The mechanism that provides type safety when accessing the data inside fields, however, is not cross platform so for serialization purposes we mirror commonly used field types with ChanFieldType. Fields inside LidarFrame should only be stored with types available with ChanFieldType.
Newly created fields are zeroed out over the entire content of the field. Neither constructors nor destructors are called for stored elements, so fields are best suited for storage of primitive types.
Accessing data¶
Fields are not designed to provide data access directly. To facilitate that, we use a conversion
mechanism instead - fields support converting to multiple wrapper classes and check type and
dimensional validity when the conversion happens, otherwise throwing std::invalid_argument.
As Eigen¶
One- and two-dimensional fields can be accessed as Eigen arrays:
Field field(fd_array<uint32_t>(128, 1024));
Eigen::Ref<img_t<uint32_t>> img = field;
As ArrayView¶
Alternatively, data can be accessed as ArrayView without Eigen’s dimension limits:
Field field(fd_array<uint32_t>(128, 1024));
ArrayView<uint32_t, 2> array_view = field;
As pointer¶
Data can also be directly accessed as a pointer of respective type (in such case, no dimension checks are made):
Field field(fd_array<uint32_t>(128, 1024));
uint32_t* ptr = field;
ArrayView¶
ArrayView is a lightweight non-owning view over multidimensional data. The class’s main motivation is to provide slicing functionality similar to that of NumPy ndarrays and avoid dimensional limitations imposed by Eigen matrices.
Typical sources include implicit conversion from a Field /
FieldView, or constructing directly from T* and shape.
Read-only access uses ConstArrayView (an alias for ArrayView<const T, Dim>).
Convenience aliases ArrayView1 … ArrayView4 (and matching ConstArrayView*) name common
ranks.
Element access¶
Scalar reads and writes use the operator() with exactly Dim integer
indices, outer dimension first, the same convention as indexing an Eigen::Array with
a(i, j, …). Arity is enforced at compile time. Out-of-bound indices produce undefined behavior
(there is no runtime check).
std::vector<float> data(100 * 100);
ArrayView2<float> img(data.data(), {100, 100});
const int row = 10;
const int col = 20;
float x = img(row, col); // read
img(row, col) = 3.14f; // write
Slicing and subview¶
subview mirrors NumPy-style indexing:
std::vector<int> data(100 * 100 * 100);
ArrayView3<int> cube{data.data(), {100, 100, 100}};
// Equivalent to NumPy arr[10]
ArrayView2<int> square_slice = cube.subview(10);
// Equivalent to NumPy arr[10, 20]
ArrayView1<int> line_slice = cube.subview(10, 20);
Calling subview with keep() keeps the full extent of that axis without changing rank.
std::vector<int> data(100 * 100 * 100);
ArrayView3<int> cube{data.data(), {100, 100, 100}};
const int fixed_row = 10;
const int fixed_col = 20;
// Equivalent to NumPy arr[:, fixed_row, fixed_col]
ArrayView1<int> line = cube.subview(keep(), fixed_row, fixed_col);
Calling subview with keep(start, end) slices the extent of the axis with half-open range [start, end):
std::vector<int> data(100 * 100);
ArrayView2<int> matrix{data.data(), {100, 100}};
// Equivalent to NumPy arr[:, 10:50]
ArrayView2<int> band = matrix.subview(keep(), keep(10, 50));
reshape¶
reshape(dims...) returns a new ArrayView over the same data range with a potentially different
compile-time rank and a new shape. After reshaping, indexing follows the usual compact layout for the
new dimensions. Reshaping only works for non-sparse views and will throw an std::invalid_argument
error otherwise.
std::vector<int> data(12);
ArrayView1<int> flat{data.data(), {12}};
// Same storage, new rank: 12 -> 3 x 4 row-major, then 2 x 2 x 3
ArrayView2<int> mat = flat.reshape(3, 4);
ArrayView3<int> box = mat.reshape(2, 2, 3);
FieldView¶
FieldView is a non-owning equivalent of Field, allowing the use of the slicing and reshaping functionality of ArrayView before converting into a concrete data accessor.
Field and FieldView share the same implicit conversions (typed pointer, Eigen, ArrayView). For
usage and type rules, see Accessing data.
Slicing and subview¶
FieldView::subview uses the same slicing model as ArrayView slicing.
Each argument is either an index (that dimension is fixed and dropped from the logical rank) or a slice
descriptor. subview returns a new FieldView sharing the same allocation.
Example:
std::vector<int> data(100 * 100);
FieldView view(data.data(), fd_array<int>(100, 100));
FieldView column = view.subview(keep(), 42);
reshape¶
For FieldView, reshape(dims...) follows the same model as ArrayView reshape:
it keeps the same underlying data pointer. Sparse views cannot be reshaped.
std::vector<int> data(12);
FieldView view(data.data(), fd_array<int>(3, 4));
// Same allocation and element count; descriptor gets a new shape + row-major strides
FieldView box = view.reshape(2, 2, 3);