Assorted concepts used in DIPlib 3

This page describes assorted concepts used in DIPlib 3.

Connectivity

Traditionally, neighborhood connectivity is given as 4 or 8 in a 2D image, 6, 18 or 26 in a 3D image, etc. These numbers indicate the number of neighbors one obtains when using the given connectivity. Since this way of indicating connectivity does not naturally lead to dimensionality-independent code, DIPlib uses the distance to the neighbors in city-block distance instead (the L1 norm). Thus, the connectivity is a number between 1 and N, where N is the image dimensionality. For example, in a 2D image, a connectivity of 1 leads to 4 nearest neighbors (the edge neighbors), and a connectivity of 2 leads to 8 nearest neighbors (the edge and vertex neighbors).

We use negative values for connectivity in some algorithms, in e.g. the binary dilation. These indicate alternating connectivities, which leads to more isotropic shapes than using the same connectivity for all iterations. These alternating connectivities are available only if the function takes a dip::sint as connectivity parameter.

In terms of the classical connectivity denominations we have, in 2D:

Connectivity Classical denominations Structuring element shape
1 4 connectivity diamond
2 8 connectivity square
-1 4-8 connectivity octagon
-2 8-4 connectivity octagon

And in 3D:

Connectivity Classical denominations Structuring element shape
1 6 connectivity octahedron
2 18 connectivity cuboctahedron
3 26 connectivity cube
-1 6-26 connectivity small rhombicuboctahedron
-3 26-6 connectivity small rhombicuboctahedron

Some functions will interpret a connectivity of 0 to mean the maximum connectivity (i.e. equal to the image dimensionality). This is an easy way to define a default value that changes depending on the image dimensionality.

Handling input and output images that alias each other

Many of the old DIPlib 2 functions (the ones that cannot work in-place) used a function dip_ImagesSeparate() to create temporary images when output images are also input images. The resource handler takes care of moving the data blocks from the temporary images to the output images when the function ends. With the current design of shared pointers to the data, this is no longer necessary. Say a function is called with

dip::Image A;
dip::Filter( A, A, params );

Then the function dip::Filter() does this:

void dip::Filter( const dip::Image &in_c, dip::Image &out, ... ) {
   Image in = in_c.QuickCopy();
   out.Strip();
   // do more processing ...
}

What happens here is that the new image in is a copy of the input image, A, pointing at the same data segment. The image out is a reference to image A. When we strip A, the new image in still points at the original data segment, which will not be freed until in goes out of scope. Thus, the copy in preserves the original image data, leaving the output image, actually the image A in the caller’s space, available for modifying.

However, if out is not stripped, and data is written into it, then in is changed during processing. So if the function cannot work in place, it should always test for aliasing of image data, and strip/forge the output image if necessary:

void dip::Filter( const dip::Image &in_c, dip::Image &out, ... ) {
   Image in = in_c.QuickCopy();
   if( in.Aliases( out )) {
      out.Strip();       // Force out to not point at data we still need
   }
   out.ReForge( ... );   // create new data segment for output
   // do more processing ...
}

Note that the dip::Framework functions take care of this.

Coordinate system origin

Some functions, such as dip::FourierTransform, dip::Rotation and dip::AffineTransform, use a coordinate system where the origin is a pixel in the middle of the image. The indices of this pixel are given by index[ ii ] = img.Size( ii ) / 2. This pixel is exactly in the middle of the image for odd-sized images, and to the right of the exact middle for even-sized images.

The function dip::FillCoordinates and related functions can be used to obtain the coordinates for each pixel. These all have a mode parameter that determines which coordinate system to use. The value "right" (the default) places the origin in the same location as dip::FourierTransform, dip::Rotation, etc.

The function dip::Image::GetCenter (using the default value for its input argument) returns the coordinates of the central pixel as a floating-point array.

Normal strides

As discussed in Strides, images in DIPlib can be stored in different orders. The stride array specifies how many samples to skip to find the neighboring pixel along each image dimension. Likewise, the tensor stride indicates how many samples to skip to find the value for the next channel of the current pixel. When an image is first forged, the sample ordering is normal, unless an external interface is set (see Define an image’s allocator). Normal strides are defined as follows:

  1. The tensor stride is set to 1. That is, image channels are interleaved.
  2. The first dimension’s stride is set to the number of tensor elements. That is, pixels are stored consecutively in memory along the first dimension (x).
  3. Other image dimension’s strides are set to the previous dimension’s stride times the previous dimension’s size. That is, image rows are stored consecutively without padding, as are image planes, etc.