Preprocessing Events to Tensors

Mostly for ML algorithms, but also some Model-based ones, it can be interesting to build tensors from events. These tensors can take the form of a time surface, event cube, histogram or differential frame. Each serves a different purpose, but all allow us to represent events into a frame-like data structure which can be fed to traditional or AI processing pipelines.

Preprocessing events with the C++ API

The Metavision C++ API provides preprocessing classes to build automatically those different types of tensors, with C++ bindings providing Python equivalent. The different tensor types are described in the Knowledge Center and the Legacy Python Event Preprocessing Tutorial. The classes described below provide both a C++ API and Python bindings to these C++ implementations, which implies that there is consistency between both languages, while the legacy version is only available in pure Python programming. Also, the following API is more flexible to use, as it allows us to process events into any structure, as Tensors can hold the data pointer of another structure’s data. All in all, we recommend using the classes described below for event preprocessing.

These are available in the Core module of the SDK, through the preprocessors folder:

The DiffProcessor and HardwareDiffProcessor follow the same mathematics but the first one produces a float representation of the differential frame, while the second one follows the hardware Differential frame generation of the GenX320 sensor, with in particular integer values manipulation. The same applies to Histogram generation.

You can use the EventPreprocessorFactory to create the desired instance, by providing a map with the desired preprocessing parameters, as well as the event input shape (typically defined by the sensor used). The required parameters are described in the EventPreprocessorFactory function documentation. The way events are provided also needs to be specified in the create function as a template parameter. It will define the type of input expected by the process_events method which is provided by the EventPreprocessor interface (raw pointer to EventCD, constant iterator to EventCD, etc).

std::unordered_map<std::string, Metavision::PreprocessingParameters> preprocess_map;
// Fill the map with desired parameters, here for
preprocess_map["type"] = Metavision::EventPreprocessorType::HARDWARE_HISTO;
preprocess_map["neg_saturation"] = (std::uint8_t)255;
preprocess_map["pos_saturation"] = (std::uint8_t)255;
// Instantiate the preprocessor
auto evt_input_shape = Metavision::TensorShape({{"H", height}, {"W", width}, {"C", 2}});
auto event_preprocessor = Metavision::EventPreprocessorFactory::create<const Metavision::EventCD *>(
    preprocess_maps, evt_input_shape);

Then, you can use the process_events method to apply the preprocessing of a batch of events to a pre-defined Tensor. This Tensor can either allocate itself the memory of the tensor data, as shown on the first example below, or be used as a wrapper to another structure’s data (particularly useful to avoid data copy) as shown in the second example.

// Example 1: Standalone tensor
Tensor processed_data(evt_input_shape, Metavision::BaseType::UINT8);
event_preprocessor_->process_events(ref_time, events_vector_.data(),
                                    events_vector_.data() + events_vector_.size(), processed_data);

// Example 2: Tensor wrapped around a cv::Mat data
cv::Mat allocated_matrix(rows, cols, CV_8UC1);
bool copy = false;
Tensor processed_data(evt_input_shape, Metavision::BaseType::UINT8,
                      static_cast<std::byte*>(allocated_matrix.data()), copy);
event_preprocessor_->process_events(ref_time, events_vector_.data(),
                                    events_vector_.data() + events_vector_.size(), processed_data);

In fact, this tensor can represent any type of ‘tensor-like’ data: a histogram, a differential frame, an event-cube, etc, which makes it a generic data structure suitable for a variety of needs, including ML pipelines.

Finally, you can use this data in a dedicated algorithm, ML model, display, etc. For example, the ML module Model class takes a map of tensors as input, which you can fill with such preprocessors. The interface of the Tensor class also allows you to get a pointer to the tensor’s data, and wrap it around your own data structure if needed, with cv::Mat for instance.

cv::Mat display_mat(height, width, CV_8UC2, processed_data.data<std::uint8_t>())

You can also directly create your own preprocessor if you don’t need to go through the factory function.

Metavision::HardwareHistoProcessor<const Metavision::EventCD *> event_preprocessor(width, height, 255, 255);

Preprocessing events with the Python API

There are two ways in the Python API to preprocess events.

The first method involves using the bindings of the C++ classes mentioned above, which are bound through the EventPreprocessor class. See an example with the HardwareHistoProcessor below.

from metavision_sdk_core import EventPreprocessor

event_preprocessor = EventPreprocessor.create_HardwareHistoProcessor(input_event_width=1280,
                                                                     input_event_height=720,
                                                                     neg_saturation=255,
                                                                     pos_saturation=255)

All C++ preprocessing classes are available through these bindings with factory methods of this Python class:

A numpy array can be provided in place of the C++ tensor. An example applied to the TimeSurface use-case can be found in the source code of this sample

Another way to preprocess events in Python is through the legacy Python implementation of preprocessors. Examples can be found here.

Depending on the desired application and usage, the first or second one may be more relevant. If you are working on a Python PoC, which you will then want to port to C++, the Python bindings should be preferred, as they will provide same results as the C++ classes given same inputs, which is not guaranteed if the Python implementation is used. Also the bindings to the C++ classes should be more efficient: when processing an important amount of data, it might bring some efficiency gains to your pipeline.