Subsections


20.10 Extending nd_image in C

20.10.1 C callback functions

A few functions in the numarray.nd_image take a call-back argument. This can be a python function, but also a CObject containing a pointer to a C function. To use this feature, you must write your own C extension that defines the function, and define a python function that returns a CObject containing a pointer to this function.

An example of a function that supports this is geometric_transform (see section ). You can pass it a python callable object that defines a mapping from all output coordinates to corresponding coordinates in the input array. This mapping function can also be a C function, which generally will be much more efficient, since the overhead of calling a python function at each element is avoided.

For example to implement a simple shift function we define the following function:

static int 
_shift_function(int *output_coordinates, double* input_coordinates,
                int output_rank, int input_rank, void *callback_data)
{
  int ii;
  /* get the shift from the callback data pointer: */
  double shift = *(double*)callback_data;
  /* calculate the coordinates: */
  for(ii = 0; ii < irank; ii++)
    icoor[ii] = ocoor[ii] - shift;
  /* return OK status: */
  return 1;
}
This function is called at every element of the output array, passing the current coordinates in the output_coordinates array. On return, the input_coordinates array must contain the coordinates at which the input is interpolated. The ranks of the input and output array are passed through output_rank and input_rank. The value of the shift is passed through the callback_data argument, which is a pointer to void. The function returns an error status, in this case always 1, since no error can occur.

A pointer to this function and a pointer to the shift value must be passed to geometric_transform. Both are passed by a single CObject which is created by the following python extension function:

static PyObject *
py_shift_function(PyObject *obj, PyObject *args)
{
  double shift = 0.0;
  if (!PyArg_ParseTuple(args, "d", &shift)) {
    PyErr_SetString(PyExc_RuntimeError, "invalid parameters");
    return NULL;
  } else {
    /* assign the shift to a dynamically allocated location: */
    double *cdata = (double*)malloc(sizeof(double));
    *cdata = shift;
    /* wrap function and callback_data in a CObject: */
    return PyCObject_FromVoidPtrAndDesc(_shift_function, cdata,
                                        _destructor);
  }
}
The value of the shift is obtained and then assigned to a dynamically allocated memory location. Both this data pointer and the function pointer are then wrapped in a CObject, which is returned. Additionally, a pointer to a destructor function is given, that will free the memory we allocated for the shift value when the CObject is destroyed. This destructor is very simple:
static void
_destructor(void* cobject, void *cdata)
{
  if (cdata)
    free(cdata);
}
To use these functions, an extension module is build:
static PyMethodDef methods[] = {
  {"shift_function", (PyCFunction)py_shift_function, METH_VARARGS, ""},
  {NULL, NULL, 0, NULL}
};

void
initexample(void)
{
  Py_InitModule("example", methods);
}
This extension can then be used in Python, for example:
>>> import example
>>> array = arange(12, shape=(4,3), type = Float64)
>>> fnc = example.shift_function(0.5)
>>> print geometric_transform(array, fnc)
[[ 0.      0.      0.    ]
 [ 0.      1.3625  2.7375]
 [ 0.      4.8125  6.1875]
 [ 0.      8.2625  9.6375]]

C Callback functions for use with nd_image functions must all be written according to this scheme. The next section lists the nd_image functions that acccept a C callback function and gives the prototype of the callback function.

20.10.2 Functions that support C callback functions

The nd_image functions that support C callback functions are described here. Obviously, the prototype of the function that is provided to these functions must match exactly that what they expect. Therefore we give here the prototypes of the callback functions. All these callback functions accept a void callback_data pointer that must be wrapped in a CObject using the Python PyCObject_FromVoidPtrAndDesc function, which can also accept a pointer to a destructor function to free any memory allocated for callback_data. If callback_data is not needed, PyCObject_FromVoidPtr may be used instead. The callback functions must return an integer error status that is equal to zero if something went wrong, or 1 otherwise. If an error occurs, you should normally set the python error status with an informative message before returning, otherwise, a default error message is set by the calling function.

The function generic_filter (see section 20.2.5) accepts a callback function with the following prototype:

int FilterFunction(double *buffer, int filter_size, double *return_value, void *callback_data)
The calling function iterates over the elements of the input and output arrays, calling the callback function at each element. The elements within the footprint of the filter at the current element are passed through the buffer parameter, and the number of elements within the footprint through filter_size. The calculated valued should be returned in the return_value argument.

The function generic_filter1d (see section 20.2.5) accepts a callback function with the following prototype:

int FilterFunction1D(double *input_line, int input_length, double *output_line, int output_length, void *callback_data)
The calling function iterates over the lines of the input and output arrays, calling the callback function at each line. The current line is extended according to the border conditions set by the calling function, and the result is copied into the array that is passed through the input_line array. The length of the input line (after extension) is passed through input_length. The callback function should apply the 1D filter and store the result in the array passed through output_line. The length of the output line is passed through output_length.

The function geometric_transform (see section 20.4.2) expects a function with the following prototype:

int MapCoordinates(int *output_coordinates, double* input_coordinates, int output_rank, int input_rank, void *callback_data)
The calling function iterates over the elements of the output array, calling the callback function at each element. The coordinates of the current output element are passed through output_coordinates. The callback function must return the coordinates at which the input must be interpolated in input_coordinates. The rank of the input and output arrays are given by input_rank and output_rank respectively.
Send comments to the NumArray community.