PyDIP User Manual » Filtering

This section goes into details on using PyDIP functions.

To find a function in the documentation, type T here (or click the search icon at the top of the page) to bring up a search dialog box. There you can search for functions, modules and pages by name. For example, typing “watershed” in the search box will point you to a series of functions related to the watershed.

The signature of functions are mostly the same in the Python package as they are in the C++ library. Most DIPlib types exist in PyDIP as well, but a few types are translated to Python types, see Type correspondences.

Most DIPlib functions have two function signatures, the documentation will always list only one of them. The difference is the parameter called out, if it’s an image. There is a second function signature where this parameter is left out, and the function returns the output image. This alternative function signature is usually easier to use, and most example PyDIP code will use it.

A simple example

Let’s look at that watershed example. First look at the documentation for dip::Watershed. The function has three image arguments: in, mask and out. We can make out be the output argument. An image called mask is always a binary image, and usually optional (even if the documentation doesn’t say so explicitly as in this case). We can pass None to represent “no image” (where in C++ you’d use a raw image).

So we can call it with:

img = dip.ImageRead('examples/cermet')
out = dip.Watershed(img, None, maxDepth=40)

But None in this case is the default for the mask argument, so we can simplify further as

out = dip.Watershed(img, maxDepth=40)

The default values are always the same as they are in the documentation.

The out argument

Sometimes you want an operation to write its output in a pre-allocated buffer. This is when the out input argument is useful:

out = dip.Image(img.Sizes(), 1, "BIN")
dip.Watershed(img, out=out, maxDepth=40)

Here, dip.Watershed() can write its output in the existing out image, and will not reforge it (reallocate its data segment) or change its properties.

But in principle it is not necessary to get the properties right. If the image passed for out has the wrong sizes, number of tensor elements or data type, then it will be reforged to be correct:

out = dip.Image((10, 20), 3, "SINT32")
dip.Watershed(img, out=out, maxDepth=40)
print(out)  # will show how the image now is 256x256, scalar and binary

The protect flag

When a function is able to reforge the out image, it is not always clear why we should bother with it. Can we ever be sure that the image will not be reforged? Yes! We can protect the data segment, preventing the image from being reforged.

out = dip.Image((10, 20), 3, "SINT32")
out.Protect()
dip.Watershed(img, out=out, maxDepth=40)  # raises an exception, because the result cannot be written into out

The protect flag is useful when an image encapsulates data from a different source, and we want to ensure we overwrite that data.

It is also useful to change the output data type of a filter. For example, dip::Gauss will produce a single-precision floating-point image as output by default. But we can force it to produce an output of a different type:

out = dip.Image()
out.SetDataType('UINT8')
out.Protect()
dip.Gauss(img, out=out, sigmas=5)

Here, out was not forged (we never gave sizes, we only specified the data type). But by setting the protect flag, we indicated to dip.Gauss() not to change its data type, forcing the function to produce a 8-bit unsigned integer output.

Finally, the protect flag is useful to have a function work in-place. For example:

dip.Gauss(img, out=img, sigmas=5)

Without protecting img, dip.Gauss will reforge its output image, meaning that img is now an 'SFLOAT' image, no longer a 'UINT8' as it was before calling the function. The filter didn’t work in place, it reforged img after keeping a reference to the original data segment to be used as input to the computation. But if we protect the image before applying the filter, this is no longer the case:

img.Protect()
dip.Gauss(img, out=img, sigmas=5)
img.Protect(False)     # reset the protect flag

In this case, img is not reforged, and the filter works in-place. img still points to the same data segment as it did before the filter was applied.

Note that some functions cannot work in-place. If they receive the same image as input and output, they will first copy the input data before doing the computations that write to the output. To the user it looks like the operation is working in-place, but in reality there’s a temporary copy being made.

Filtering color images

With some functions, like dip.Gauss(), we can process color images normally. However, many functions require a scalar (gray-scale) image as input. This is mostly the case for functions where it doesn’t make sense to work on color (for example dip.Watershed()), or where the operation is ambiguous on color (for example dip.Dilation()). We’ll use this latter function as an example.

Let’s start with an example color image:

img = dip.ImageRead('examples/DIP.tif')
img.Show()

The DIP image

If we try to apply a dilation to this image, we get an exception:

se = dip.SE(25, "rectangular")
dip.Dilation(img, se=se)  # raises exception with text "Image is not scalar"

The dilation computes a local maximum, and the maximum over a set of RGB values is not uniquely defined, there’s no unique, correct way to order colors (not in RGB space, not in any other color space).

One approach is to apply the dilation to each channel independently:

res = img.Similar()
for ii in range(img.TensorElements()):
    dip.Dilation(img(ii), out=res(ii), se=se)
res.Show()

The filtered DIP image, false colors appeared

This is called “marginal ordering”, and can introduce new colors into the image. The maximum of yellow, sky blue and black apparently is pinkish.

Another approach is to sort pixels on intensity only, or on some other property. Then the dilation picks the pixel that has the largest value of this property. For example, we can determine the distance to pure yellow, and sort pixels based on that value. dip::SelectionFilter is a dilation of sorts, it picks pixels from one image based on the intensities of another (scalar) image:

select = -dip.Norm(img - [255, 255, 0])  # negate the distance, so that yellow gets larger values
res = dip.SelectionFilter(img, select, kernel=dip.Kernel(25, "rectangular"), mode="maximum")
res.Show()

The DIP image, with a meaningful dilation applied

Now we see that the yellow area has grown over the other colors, as one would expect for a dilation.