Skip to content

Commit fd43a6f

Browse files
committed
Add convolution example
1 parent 7a161b7 commit fd43a6f

File tree

8 files changed

+146
-0
lines changed

8 files changed

+146
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Convolution
2+
3+
Illustration of various concepts using a naive convolution algorithm
4+
as an example.
5+
6+
7+
## What is it?
8+
9+
1. `src`: source directory of C++ code.
10+
a. `convolution`: directory that contains the convolution and matrix code.
11+
1. `matrices.h`: definition of the Matrix class.
12+
1. `matrices.cpp` definition of methods for the Matrix class.
13+
1. `convolution.h`: decleartion of the convolution functions.
14+
1. `convolution.cpp`: implementation of the convolution functions.
15+
1. `CMakeLists.txt`: CMake file to build the shared library.
16+
a. `bindings`: directory that contains the pybind11 bindings
17+
for Matrix and convolve.
18+
1. `matrix_bind.cpp`: pybind11 bindings for the Matrix class.
19+
1. `convolve_bind.cpp`: pybind11 bindings for the convolve function.
20+
This uses the buffer protocol.
21+
1. `CMakeLists.txt`: CMake file to build the Python modules.
22+
a. `test_convolution.cpp`: C++ application to test the C++ convolution
23+
implementation.
24+
a. benchmark_convolution.cpp`: C++ application to benchmark the C++ convolution
25+
implementation.
26+
a. `CMakeLists.txt`: CMake file to build the library, modules and C++
27+
applications.
28+
1. `CMakeLists.txt`: CMake file to build the library, modules and C++
29+
applications.
30+
1. `test_convolution.py`: Python script to test and benchmark the generated
31+
modules.

source-code/interfacing-c-c++-fortran/Pybind11/Convolution/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
add_subdirectory(convolution)
2+
add_subdirectory(bindings)
23

34
add_executable(test_convolution.exe
45
test_convolution.cpp)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
find_package(pybind11 REQUIRED)
2+
3+
pybind11_add_module(matrices matrix_bind.cpp)
4+
target_include_directories(matrices PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../convolution)
5+
target_link_libraries(matrices PUBLIC convolution)
6+
7+
pybind11_add_module(convolve convolve_bind.cpp)
8+
target_include_directories(convolve PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../convolution)
9+
target_link_libraries(convolve PUBLIC convolution)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include <convolution.h>
2+
#include <pybind11/pybind11.h>
3+
#include <pybind11/numpy.h>
4+
5+
namespace py = pybind11;
6+
7+
void convolve_func(py::buffer image_buffer, py::buffer kernel_buffer,
8+
py::buffer new_image_buffer) {
9+
py::buffer_info image_info = image_buffer.request();
10+
if (image_info.format != py::format_descriptor<double>::format())
11+
throw std::runtime_error("Incompatible format: expected a double array");
12+
if (image_info.ndim != 2)
13+
throw std::runtime_error("Incompatible buffer dimension");
14+
py::buffer_info kernel_info = kernel_buffer.request();
15+
if (kernel_info.format != py::format_descriptor<double>::format())
16+
throw std::runtime_error("Incompatible format: expected a double array");
17+
if (kernel_info.ndim != 2)
18+
throw std::runtime_error("Incompatible buffer dimension");
19+
py::buffer_info result_info = new_image_buffer.request();
20+
if (result_info.format != py::format_descriptor<double>::format())
21+
throw std::runtime_error("Incompatible format: expected a double array");
22+
if (result_info.ndim != 2)
23+
throw std::runtime_error("Incompatible buffer dimension");
24+
if (result_info.shape[0] != image_info.shape[0] + kernel_info.shape[0] - 1 ||
25+
result_info.shape[1] != image_info.shape[1] + kernel_info.shape[1] - 1)
26+
throw std::runtime_error("Incompatible result buffer shape");
27+
Matrix image(image_info.shape[0], image_info.shape[1]);
28+
Matrix kernel(kernel_info.shape[0], kernel_info.shape[1]);
29+
std::memcpy(image.data(), image_info.ptr, sizeof(double)*image.rows()*image.cols());
30+
std::memcpy(kernel.data(), kernel_info.ptr, sizeof(double)*kernel.rows()*kernel.cols());
31+
Matrix result = convolve(image, kernel);
32+
std::memcpy(result_info.ptr, result.data(), sizeof(double)*result.rows()*result.cols());
33+
}
34+
35+
PYBIND11_MODULE(convolve, module) {
36+
module.doc() = "pybind11 wrapper module for convolution.h";
37+
module.def("convolve", &convolve_func, "compute convolution of image with kernel");
38+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <matrices.h>
2+
#include <pybind11/pybind11.h>
3+
#include <pybind11/numpy.h>
4+
5+
namespace py = pybind11;
6+
7+
PYBIND11_MODULE(matrices, module) {
8+
module.doc() = "pybind11 wrapper module for matrices.h";
9+
py::class_<Matrix>(module, "Matrix", py::buffer_protocol())
10+
.def_buffer([](Matrix &m) -> py::buffer_info {
11+
return py::buffer_info(
12+
m.data(), /* Pointer to buffer */
13+
sizeof(double), /* Size of one scalar */
14+
py::format_descriptor<double>::format(), /* Python struct-style format descriptor */
15+
2, /* Number of dimensions */
16+
{m.rows(), m.cols()}, /* Buffer dimensions */
17+
{sizeof(double)*m.cols(), /* Strides (in bytes) for each index */
18+
sizeof(double)});
19+
})
20+
.def(py::init<int, int>(), "initialize matrix with given number of rows and columns")
21+
.def(py::init([](const py::buffer b) {
22+
py::buffer_info info = b.request();
23+
if (info.format != py::format_descriptor<double>::format())
24+
throw std::runtime_error("Incompatible format: expected a double array");
25+
if (info.ndim != 2)
26+
throw std::runtime_error("Incompatible buffer dimension!");
27+
Matrix m(info.shape[0], info.shape[1]);
28+
std::memcpy(m.data(), info.ptr, sizeof(double)*m.rows()*m.cols());
29+
return m;
30+
}), "initialize matrix from a numpy array");
31+
}

source-code/interfacing-c-c++-fortran/Pybind11/Convolution/src/convolution/convolution.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33

44
#include "matrices.h"
55

6+
/**
7+
* @brief Compute the convolution of an image with a kernel.
8+
* @param image The image to convolve. This is a 2D matrix with m rows and n columns.
9+
* @param kernel The kernel to convolve with. This is a 2D matrix with k rows and l columns,
10+
* where k and l ard odd integers.
11+
* @return The result of the convolution. This is a 2D matrix with m + k - 1 rows
12+
* and n + l -1 columns.
13+
*/
614
Matrix convolve(const Matrix& image, const Matrix& kernel);
715

816
#endif
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
from build.src.bindings.convolve import convolve
5+
import numpy as np
6+
import timeit
7+
8+
9+
def main():
10+
parser = argparse.ArgumentParser()
11+
parser.add_argument('--img_size', type=int, default=10, help='number of rows in the image')
12+
parser.add_argument('--kernel_size', type=int, default=3, help='number of rows in the kernel')
13+
parser.add_argument('--num_iter', type=int, default=1, help='number of iterations to run')
14+
args = parser.parse_args()
15+
16+
# Create input image and kernel
17+
input_image = np.random.uniform(0.0, 1.0, size=(args.img_size, args.img_size))
18+
kernel = np.random.rand(args.kernel_size, args.kernel_size)
19+
result = np.empty((args.img_size + args.kernel_size - 1, args.img_size + args.kernel_size - 1))
20+
time = timeit.timeit(lambda: convolve(input_image, kernel, result), number=args.num_iter)
21+
print(f'Image size: {args.img_size}x{args.img_size}')
22+
print(f'Kernel size: {args.kernel_size}x{args.kernel_size}')
23+
print(f'Time: {time/args.num_iter:.6f} seconds')
24+
print(f'Sum: {np.sum(result)}')
25+
26+
if __name__ == '__main__':
27+
main()

source-code/interfacing-c-c++-fortran/Pybind11/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ pybind11 is a wrapper generator for C++ code that has a lot of nice features.
77
1. `Simple`: very simple illustration of wrapping C++ functions.
88
1. `Spectrum`: illustration of warpping to support the buffer protocol.
99
1. `Stats`: illustration of wrapping a C++ class.
10+
1. `Convolution`: illustration of using the buffer protocol.
1011
1. `environment.yml`: conda environment specification for this directory.

0 commit comments

Comments
 (0)