Categories
Integrated Design and Test

Integrated Design and Test: Part I, Python Binding of Realtime C++

Introduction

An integrated design and test (IDT) strategy involves executing design and development in a way that allows substantial reuse of engineering work for both offline design testing and online physical testing with minimal changes. Real-time signal processing building blocks inherently support this approach as illustrated in Fig. 1.

Figure 1: Integrated Design and Test Framework

The signal processing “devices” (C++ software) under test (DUTs) operate both offline on a PC and online in the target instrument hardware. If the shared test infrastructure is designed for real-time use, it can generate both simulated offline signals and physical signals—either through test hardware or direct communication with the instrument. Note that these tests are not software unit tests but typically far more substantial statistical tests that may run for many hours and provide important performance characterization.

This approach significantly reduces time and cost, as the design and development of complex statistical tests for offline characterization overlaps substantially with the engineering required for online implementation testing using physical hardware. By integrating C++ signal processing or algorithm test and verification for both design and hardware implementation, we maximize the efficiency of both technical infrastructures, saving time and resources.

For example, if one develops (a) a sophisticated simulation of realistic physiological signals that run iteratively to test an algorithm offline, and (b) a detailed statistical analysis to characterize the algorithm’s performance, a real-time design approach would allow reuse of these signal synthesis and performance analysis components for online performance testing. Using test hardware with digital-to-analog converters, or physical actuators in some cases, physical signal realizations can be generated and picked up by sensors connected to data acquisition hardware. The core application would then collect output responses from the hardware, which are fed back into the test infrastructure. Post-processing of the input/output signals would use the same statistical analysis components as the offline test to compare the results.

Python and PyBind11

Python is known for its versatility, simplicity, and power, enabling developers to quickly prototype ideas and iterate effectively. Its extensive ecosystem of standard and third-party libraries supports almost any kind of software development, including scientific programming, modeling, simulation, machine learning, and signal processing. Popular libraries include NumPy for scientific computing, Pandas for data analysis, TensorFlow and PyTorch for machine learning, and Flask and Django for web development. Many of these libraries are internally written in C++, leveraging Python’s ability to interface with C++ code. This makes Python an ideal language for programming simulations, models, and tests, as well as integrating the various components of an IDT system.

We integrate our C++ signal processing modules and classes with Python using PyBind11. From a command shell, install PyBind11 using

pip install pybind11.

Since PyBind11 doesn’t directly support binding templates as generic types, Python bindings must be generated for specific instantiations of a template class, but these instantiations can be quite general.

As an example, consider the C++ polynomial sharpening filter template FilterSharpenPoly10 from Efficient Signal Processing C++ Templates: Part I – Computational Pipelines. To bind to Python using PyBind11, first define the following PyBind11 wrapper function,

Then create a DLL by compiling the following:

Add as many different DSP types to this “dsp” module as desired. A CMakeLists.txt build file for the DLL would look something like:

This build file works with a higher level CMakeLists.txt file such as the following that specifies common build characteristics to support multiple bindings projects in addition to dsp.

Now the real-time C++ module can be called from Python. The following filter impulse and frequency response estimations and plots provide one example.

Running the above Python code generates the following plots from input/output data processed by the external C++ real-time filter.

Using vcpkg

vcpkg is a general option to simplify installation and management of C++ libraries on Windows, including configuring paths for CMake. In CMakeLists.txt, set CMAKE_TOOLCHAIN_FILE to the path to vcpkg.cmake as follows:

set(CMAKE_TOOLCHAIN_FILE "C:/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")

Then on the command line, install pybind11 as follows:

./vcpkg install pybind11

which should allow the following to work without any additional configuration:

find_package(pybind11 CONFIG REQUIRED)