is pretty neat. Give it a function and a filter footprint, and it will apply
the function at every footprint in the image. The trouble is that it can
be a bit slow:
Yikes. But how about
Better, but still not great, especially when compared against the existing maximum filter:
I want to use this simple maximum filter example to show to build a low
(i.e., a lightly wrapped compiled C function) to be used with
in order to get better performance from custom filters (beyond just max).
The scipy docs
provide a number of examples using
geometric_transform to illustrate
low level callbacks. I highly recommend reading those.
Building the low level callback
If we read the Notes section in the docstring for
generic_filter we see:
This function also accepts low-level callback functions with one of the following signatures and wrapped in scipy.LowLevelCallable:
int callback(double *buffer, npy_intp filter_size, double *return_value, void *user_data) int callback(double *buffer, intptr_t filter_size, double *return_value, void *user_data)
The docs go on to provide more details, but here is the summary: we’ll write a
C function having the given signature that transforms
buffer (the array of
image values currently under the filter footprint) to produce a return value.
Let’s write some C.
Then compile it to a shared library:
OK. Now let’s load it with ctypes and wrap it as a low level callable:
max_filter_llc can be used in
generic_filter. Let’s time it!
Not bad. Although given that scipy’s
maximum_filter still runs in half the
time, there’s apparently some overhead to the
Does it work?
Note that we haven’t talked about the
user_data parameter. This parameter
allows for adding extra arguments (e.g., a global threshold value) to be used
by the callback. The scipy docs
provide some example usage that’s worth checking out.
Although, not all extra arguments can be plugged in as user-provided data. For example, as far as I can tell, it’s not possible to get the current position of the filter as an argument to the callback, which would be nice to optionally have in order to create position dependent filters. Maybe I will dive into the ndimage source to see if this sort of extension is feasible when I have some free time.
Bonus: sliding window standard deviation filter
We went through all the rigmarole, so let’s at least write a filter that might
not be readily available in pre-written routines. The following computes the
standard deviation of the values under the filter footprint and uses that as
the filter response. So, using this with
generic_filter computes a sliding
window standard deviation filter. This produces something not unlike a gradient
magnitude image, i.e., it produces large (bright) values where the image
changes intensity and small (dark) values where the image is homogeneous.