Boundary problems using a numpy kernel and padding on a matrix within a C functions called from python

Question:

I want to use an external c function a numpy kernel operation on an image. For this the goal is to go through the image image only within the padding so that the kernel does not go outside the image. In the following debugging code, I first generate a matrix filled with 1 in the python script. I then pass this matrix with its dimension to the c function. I iterate over rows and column and want to set the value to 9 if I am on the border of the matrix (in the padding) and to 8 if I am within the part of the matrix where I want to do the computation.

The eventual goal of the computation is to do a computation for every entry of the matrix taking all the neighbours (e.g. on a 3×3 matrix pattern) for that computation. For a 3×3 matrix, the search has to start before the stop after the first row/column and stop before the last row column.

The result I want to have is the following:

,0,1,2,3
0,9,9,9,9
1,9,8,8,9
2,9,8,8,9
3,9,9,9,9

Instead I get the following:

,0,1,2,3
0,9,9,9,9
1,8,8,8,8
2,8,8,8,8
3,8,1,1,1

Here is the .sh script debug_scriptsh.sh:

a=debug_scriptc && cc -fPIC -shared -o $a.so $a.c && python debug_scriptpy.py && open debug_table.csv

the .py script debug_scriptc.c:

import ctypes
import pandas as pd
import numpy as np
import sys
import pdb

DLL_NAME = "./debug_scriptc.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")

def np_mat_type(rows, cols, element_type=float):
  return np.ctypeslib.ndpointer(dtype=element_type, shape=(rows, cols), flags="C_CONTIGUOUS")

rows0 = 4
cols0 = 4
kernel_size = 3

dll = ctypes.CDLL(DLL_NAME)
matrix_func = dll.matrixFunc4matin
matrix_func.argtypes = (
    ctypes.c_size_t,
    np_mat_type(rows0, cols0), 
    ctypes.c_size_t, 
    ctypes.c_size_t
    )
matrix_func.restype = np_mat_type(rows0, cols0)

dealloc_array = dll.deallocArray
dealloc_array.argtypes = (np_mat_type(rows0, cols0),)
dealloc_array.restype = None
u_rms_array = np.zeros((rows0, cols0))

velocity_image_ones = np.empty((rows0, cols0))
velocity_image_ones[:] = 1

u_rms_array = matrix_func(
    kernel_size, 
    velocity_image_ones,
    rows0, 
    cols0 
    )
debug_DF_u_rms_array_C = pd.DataFrame(u_rms_array.astype(int))
debug_DF_u_rms_array_C.to_csv("debug_table.csv")
dealloc_array(u_rms_array)

the .c script debug_scriptc.c:


#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif

#define ELEMENT_TYPE double

#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API ELEMENT_TYPE* matrixFunc4matin(
  size_t kernel_size,
  const ELEMENT_TYPE *pvelocity_image_ones, 
  size_t rows0, 
  size_t cols0
  );
DLL00_EXPORT_API void deallocArray(ELEMENT_TYPE *pMat);

#if defined(__cplusplus)
}
#endif

ELEMENT_TYPE* matrixFunc4matin(
  size_t kernel_size,
  const ELEMENT_TYPE *pvelocity_image_ones, 
  size_t rows0, 
  size_t cols0 
  )

{
  size_t matSize = sizeof(ELEMENT_TYPE) * cols0 * rows0;
  ELEMENT_TYPE *pRet = (ELEMENT_TYPE*)(malloc(matSize));

  memcpy(pRet, pvelocity_image_ones, matSize);  

  int start_kern_search = kernel_size - 2;
  int end_kern_search_rows = rows0 - (kernel_size-1)/2;
  int end_kern_search_cols = cols0 - (kernel_size-1)/2;

  for (int i_velmap = 0; i_velmap < rows0; i_velmap++){  
    for (int j_velmap = 0; j_velmap < cols0; j_velmap++){  
      if (i_velmap < start_kern_search | i_velmap > end_kern_search_rows | j_velmap < start_kern_search | j_velmap > end_kern_search_cols){
        pRet[i_velmap * cols0 + j_velmap] = 9;
        continue;
      }
      else
      {
        pRet[i_velmap * end_kern_search_cols + j_velmap] = 8;
      }
    }
  }
  return pRet;
}


void deallocArray(ELEMENT_TYPE *pMat)
{
    if (pMat)
        free(pMat);
}

Asked By: ecjb

||

Answers:

Continuation of [SO]: How to write a function in C which takes as input two matrices as numpy array.

The problem is index calculation.
When framing (centering) a (small) rectangle (inner), into a larger one (outer), the edge formula (on each axis (dimension)) is:

edge = (outer_dim - inner_dim) / 2

dll00.c:

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif

#define ELEMENT_TYPE double

#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API ELEMENT_TYPE* matrixFormat(const ELEMENT_TYPE *pMat, size_t rows, size_t cols, size_t kernel);
DLL00_EXPORT_API void deallocArray(ELEMENT_TYPE *pMat);

#if defined(__cplusplus)
}
#endif

#define MIN_EDGE 1
#define PAD_VAL 9
#define KERNEL_VAL 8


static size_t edge(size_t dim, size_t kernel, size_t minEdge)
{
    if (kernel >= dim)
        return minEdge;
    size_t ret = (dim - kernel) / 2;
    return (ret > minEdge) ? ret : minEdge;
}


ELEMENT_TYPE* matrixFormat(
    const ELEMENT_TYPE *pMat,
    size_t rows,
    size_t cols,
    size_t kernel)
{
    size_t matSize = sizeof(ELEMENT_TYPE) * cols * rows;
    ELEMENT_TYPE *pRet = (ELEMENT_TYPE*)(malloc(matSize));

    printf("nC: - matrix (%zu X %zu (%zu)):n", rows, cols, kernel);
    size_t minRow = edge(rows, kernel, MIN_EDGE),
           minCol = edge(cols, kernel, MIN_EDGE);
    size_t maxRow = rows - minRow - 1,
           maxCol = cols - minCol - 1;
    for (size_t i = 0; i < rows; ++i) {
        for (size_t j = 0; j < cols; ++j) {
            pRet[i * cols + j] = ((i >= minRow) && (i <= maxRow) && (j >= minCol) && (j <= maxCol)) ? KERNEL_VAL : PAD_VAL;
        }
    }
    return pRet;
}


void deallocArray(ELEMENT_TYPE *pMat)
{
    if (pMat)
        free(pMat);
}

code00.py:

#!/usr/bin/env python

import ctypes as cts
import sys

import numpy as np


DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")

ELEMENT_TYPE = cts.c_double


def np_mat_type(rows, cols, element_type=ELEMENT_TYPE):
    return np.ctypeslib.ndpointer(dtype=element_type, shape=(rows, cols), flags="C_CONTIGUOUS")


def main(*argv):
    np.set_printoptions(precision=3)

    dll = cts.CDLL(DLL_NAME)
    matrix_format = dll.matrixFormat
    dealloc_array = dll.deallocArray
    dealloc_array.restype = None

    values = (
        (4, 4, 3),
        (6, 6, 3),
        #(3, 3, 3),
    )

    for rows, cols, kernel in values:
        print("nRows: {:d}, Cols: {:d}, Kernel: {:d}".format(rows, cols, kernel))
        mat = np.ones((rows, cols), dtype=ELEMENT_TYPE)

        matrix_format.argtypes = (np_mat_type(rows, cols), cts.c_size_t, cts.c_size_t, cts.c_size_t)
        matrix_format.restype = np_mat_type(rows, cols)
        dealloc_array.argtypes = (np_mat_type(rows, cols),)

        print("mat:")
        print(mat)
        mat_res = matrix_format(mat, rows, cols, kernel)
        print("result:")
        print(mat_res)

        dealloc_array(mat_res)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}n".format(" ".join(elem.strip() for elem in sys.version.split("n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("nDone.n")
    sys.exit(rc)

Output:

(qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q074171783]> ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[064bit prompt]> ls
code00.py  dll00.c
[064bit prompt]> gcc -fPIC -shared -o dll00.so dll00.c
[064bit prompt]> ls
code00.py  dll00.c  dll00.so
[064bit prompt]> python ./code00.py
Python 3.8.10 (default, Jun 22 2022, 20:18:18) [GCC 9.4.0] 064bit on linux


Rows: 4, Cols: 4, Kernel: 3
mat:
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]

C: - matrix (4 X 4 (3)):
result:
[[9. 9. 9. 9.]
 [9. 8. 8. 9.]
 [9. 8. 8. 9.]
 [9. 9. 9. 9.]]

Rows: 6, Cols: 6, Kernel: 3
mat:
[[1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]]

C: - matrix (6 X 6 (3)):
result:
[[9. 9. 9. 9. 9. 9.]
 [9. 8. 8. 8. 8. 9.]
 [9. 8. 8. 8. 8. 9.]
 [9. 8. 8. 8. 8. 9.]
 [9. 8. 8. 8. 8. 9.]
 [9. 9. 9. 9. 9. 9.]]

Done.
Answered By: CristiFati
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.