extension.rst 4.35 KB
Newer Older
Baptiste Esteban's avatar
Baptiste Esteban committed
1
2
Writing an extension module
===========================
3

Baptiste Esteban's avatar
Baptiste Esteban committed
4
5
6
7
While writing an algorithm in C++ is great for performance issue, it is not easy
to prototype algorithms in this language. To solve this problem, Pylene provides
a library, ``Pylene-numpy``, which makes the automatic conversion between
Pylene images and Numpy arrays, thanks to the Pybind11's ``type_caster``.
8
9
10
11

In the following, it is explained how to make use of this library to create an
extension module.

Baptiste Esteban's avatar
Baptiste Esteban committed
12
13
Step 1: Setup the project
--------------------------
14

15
16
At first, a ``conanfile.txt`` is created.

17
18
19
20
21
22
.. code-block:: text

    [generators]
    cmake_find_package

    [requires]
Baptiste Esteban's avatar
Baptiste Esteban committed
23
24
    pylene/head@lrde/unstable
    pybind11/2.6.2
25
26

    [options]
27
28
    pylene:fPIC=True # or pylene:shared=True

Baptiste Esteban's avatar
Baptiste Esteban committed
29
30
The `fPIC` option is specified as an option for Pylene, which enable to have the
``Pylene-numpy`` library during the dependencies installation.
31

Baptiste Esteban's avatar
Baptiste Esteban committed
32
Then, below is the ``CMakeLists.txt``:
33
34
35

.. code-block:: cmake

Baptiste Esteban's avatar
Baptiste Esteban committed
36
37
    cmake_minimum_required(VERSION 3.14)
    project(pylene_extension)
38

Baptiste Esteban's avatar
Baptiste Esteban committed
39
    set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_BINARY_DIR}")
40
    find_package(Pylene REQUIRED)
Baptiste Esteban's avatar
Baptiste Esteban committed
41
42
    find_package(pybind11 REQUIRED)
    
Baptiste Esteban's avatar
Baptiste Esteban committed
43
44
45
    pybind11_add_module(pylene_extension)
    target_sources(pylene_extension PRIVATE pylene_extension.cpp)
    target_link_libraries(pylene_extension PUBLIC Pylene::Pylene-numpy)
46

47

Baptiste Esteban's avatar
Baptiste Esteban committed
48
49
Step 2: Writing an extension
----------------------------
50

Baptiste Esteban's avatar
Baptiste Esteban committed
51
Below is an exemple of an extension module:
52

53
54
.. code-block:: cpp

Baptiste Esteban's avatar
Baptiste Esteban committed
55
56
57
    #include <pln/core/image_cast.hpp>
    #include <mln/core/image/ndimage.hpp>
    #include <mln/core/range/foreach.hpp>
58

Baptiste Esteban's avatar
Baptiste Esteban committed
59
    #include <pybind11/pybind11.h>
60

Baptiste Esteban's avatar
Baptiste Esteban committed
61
    #include <stdexcept>
62

Baptiste Esteban's avatar
Baptiste Esteban committed
63
    void iota(mln::ndbuffer_image arg_img)
64
    {
Baptiste Esteban's avatar
Baptiste Esteban committed
65
66
67
68
69
70
71
72
73
74
        auto img = arg_img.cast_to<std::uint8_t, 2>();
        if (!img)
            throw std::invalid_argument("iota: input image should be a 2D uint8 image");

        std::uint8_t i = 0;
        mln_foreach(auto p, img->domain())
        {
            (*img)(p) = i;
            i = i == 255 ? 0 : i + 1;
        }
75
76
    }

Baptiste Esteban's avatar
Baptiste Esteban committed
77
78
79
80
81
    PYBIND11_MODULE(pylene_extension, m)
    {
        pln::init_pylena_numpy(m);
        m.def("iota", &iota);
    }
82

83
84
85
There are a few important things to note about this code. At first,
**the header** ``<pln/core/image_cast.hpp>`` 
**must be included in all the compilation unit of the extension module**.
Baptiste Esteban's avatar
Baptiste Esteban committed
86
In this header, there is the ``type_caster`` converting Pylene images into Numpy
87
88
arrays and inversely. If not included, some undefined behavior may happen.

Baptiste Esteban's avatar
Baptiste Esteban committed
89
90
91
92
93
94
Then, a ``iota`` function is defined. This function fills inplace a 2D image
with unsigned element on 8 bits. The argument of this function is a
``mln::ndbuffer_image``, the type-erased version of the Pylene images, in which
the type and the dimension of the image are stored dynamically. However, it is
not considered as an image by the library and cannot be manipulated directly: it
has to be casted to a Pylene image, as it is done in the first lines of the
95
96
function, thanks to the ``cast_to`` method. This method takes two template
parameters: the type of the image and its dimension. If it matches the ones
Baptiste Esteban's avatar
Baptiste Esteban committed
97
98
stored dynamically, a pointer to the converted image is returned. Else,
``nullptr`` is returned by the method.
99
100
101

Finally, the last lines define the Python module, thanks to Pybind11. It is
important to note that the ``pln::init_pylena_numpy`` is called at the first
Baptiste Esteban's avatar
Baptiste Esteban committed
102
103
line of the module extension.
**It must be called at the first line of the module definition for an extension module.**
104

Baptiste Esteban's avatar
Baptiste Esteban committed
105
106
107
Step 3: Using the extension
---------------------------

Baptiste Esteban's avatar
Baptiste Esteban committed
108
Finally, below is an example of how to use the simple module developed above.
109

Baptiste Esteban's avatar
Baptiste Esteban committed
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
>>> from pylene_extension import iota
>>> import numpy as np
>>> img = np.zeros((10, 10)).astype(np.float64)
>>> iota(img)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: iota: input image should be a 2D uint8 image
>>> img = img.astype(np.uint8)
>>> iota(img)
>>> img
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
       [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
       [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
       [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
       [70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
       [80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
       [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]], dtype=uint8)