Extending and Developing the Medial C++ API for Python
This document outlines the low-level C++ details required to further develop the Python bindings for our API.
Objectives
The goals are to:
- Use our API from Python.
- Employ Python as a "Glue Language".
- Enable prototyping, exploration, and discovery in Python.
- Ensure interoperability with other frameworks.
- Minimize maintenance overhead when updating code.
Implementation Approach
For maintainability, SWIG is chosen as the binding solution. Other options considered:
- Cython: Adds another language to the process.
- Boost::python: Limited support/resources; requires special definitions for each exported member.
- ctypes: Direct C calls, but parameter definitions are error-prone and may lack binary compatibility across platforms.
Why SWIG?
- Mature project with extensive resources. TensorFlow uses SWIG for example.
- Handles memory and ownership complexities.
- Good NumPy support.
Our Implementation: "MedPyExport"
- Located in
MR_LIBS/Internal/MedPyExport. - Wraps classes and exports only necessary functions for Python.
- Minimal learning curve for contributors familiar with C++.
- SWIG interface files (
.i) are auto-generated during build.
Notes & Pitfalls
- Always use
std::vectorfor vectors. - Restart Jupyter Notebook after adding new functions/classes to reload wrappers.
- Use distinct names for NumPy parameters of different types.
- Method overloading is not supported (may be possible to implement).
- Avoid "big-data" loops; use NumPy/Pandas for vectorized operations.
- Build system is separate from the main CMake files.
- No direct Pandas DataFrame conversion yet.
- SWIG scans only simple C++ headers; keep implementation in
.cppfiles. - Compilation works on both Windows and Linux (tested execution on Linux/Jupyter).
Build System
- Targeted for Linux machines
- Windows compilation works but does not produce a Python-loadable binary.
- Uses a specialized CMake file for SWIG integration.
Directory Structure
Located in MR_LIBS/Internal/MedPyExport:
MedPyExport: Source files.generate_binding: CMake and binding generation files.MedPython: SWIG interface files.scripts: Helper scripts for compilation.
Key Files
make.sh: Bash script to start CMake build.MedPython/MedPython.i: Main SWIG interface.MedPython/medial-numpy.i: NumPy SWIG interface.MedPython/MedPython.h: Entry point for headers scanned by SWIG.MedPython/MedPython.c: Entry point for C files scanned by SWIG.MedPython/pythoncode.i: Python code for the binding.MedPython/scripts/make_apply.py: Script to generate NumPy apply directives.MedPython/apply_directives.i: Auto-generated SWIG directives.
Extending the Code
Example: Exporting a Class
- Include
MedPyCommon.hfor utilities/macros. - Exported class names start with
MPto avoid conflicts. - Use a pointer to the wrapped object.
- Keep headers simple for SWIG; implementation in
.cppfiles.
Add all exported headers to MedPyExport.h:
Python Usage:
Adding a New Class
- Basic types are mapped automatically.
- Use
std::vectorfor vectors.
Implementation Example:
Properties
Implement getter/setter methods with MEDPY_GET_ and MEDPY_SET_ prefixes:
Python Usage:
- Omit the setter for read-only properties.
Static Const Variables
Mapped to class variables in Python:
Implementation:
Python Usage:
Iterators
Array Iterator Example:
- Class name ends with
VectorAdaptor. - Implements
__len__and__getitem__.
Map Iterator Example:
- Class name ends with
MapAdaptor. - Implements
__len__,__getitem__, and optionally__setitem__, etc.
NumPy Arrays
Input Arrays:
In-place Arrays:
Output Arrays:
Variant Output Example:
Supported NumPy Types:
Helper Functions & Macros
- Docstrings:
MEDPY_DOC(function_or_class_name, docstring) - Ignore for SWIG:
MEDPY_IGNORE(...)or#ifdef SWIG ... #endif - Buffer to Vector:
buf_to_vector(int_in_buf, int_in_buf_len, idx); - Vector to Buffer:
vector_to_buf(o->weights, float_out_buf, float_out_buf_len);