DIPimage User Manual » The dip_image Object

Images used by this toolbox are encapsulated in an object called dip_image. Objects of this type are unlike regular MATLAB arrays in some ways, but behave similarly most of the time. This chapter explains the usage of these objects.

For more information on the functions mentioned in this chapter (and elsewhere) use the MATLAB help system. At the MATLAB command prompt, type help <function_name>.

Creating a dip_image object

To create a dip_image object, the function dip_image must be used. It converts any numeric array into an image object. The optional second argument indicates the desired data type for the image. The pixel data will be converted to this type if possible, or else an error will be generated (for example, it is illegal to convert complex data to a real type, since there are many ways this can be accomplished; it is necessary to do this explicitly). The valid data types are listed in the table below. This table also lists some alternative names that are mapped to the names on the left; these are just to make specifying the data type easier.

Data type Description Other allowed names
bin binary (in 8-bit integer)
uint8 8-bit unsigned integer
uint16 16-bit unsigned integer
uint32 32-bit unsigned integer
uint64 64-bit unsigned integer
sint8 8-bit signed integer int8
sint16 16-bit signed integer int16
sint32 32-bit signed integer int32
sint64 64-bit signed integer int64
sfloat single precision float single
dfloat double precision float double
scomplex single precision complex
dcomplex double precision complex

For example,

a = dip_image(a,'sfloat');

will convert the data in a to single (4-byte) floats before creating the dip_image object. The variable a now behaves somewhat differently than you might be used to. The following sections explain its behavior.

To convert a dip_image object back to a MATLAB array use the function dip_array. It simply returns the data array stored inside the dip_image object. The functions double, single, uint8, etc. convert the dip_image object to a MATLAB array of the specified class.

There are also some commands to create an image from scratch. newim is equivalent to the zeros function, but returns a dip_image object.

a = newim(256,256);

creates an image with 256x256 pixels set to zero. An additional parameter (as in the table above) can be used to specify the data type of the new image. The default is 'sfloat'. If b is an object of type dip_image, then

a = newim(b);

creates an image of the same size (this is the same as newim(imsize(b))). The functions xx, yy, zz, rr and phiphi all create an image containing the coordinates of its pixels, and can be used in formulas that need them. For example, rr([256,256])<64 creates a binary image with a disk of radius 64. The expression

a = (yy('corner'))*sin((xx('corner'))^2/300)

generates a nice test pattern with increasing frequency along the x-axis, and increasing amplitude along the y-axis. All these functions have 256x256 pixels as the default output size, and allow as a parameter either the size of an image, or an image whose size is to be copied. For example, a*xx(a) is an image multiplied by its x-coordinates.

When calling a DIPimage function that expects a dip_image object as input, but using a numeric array as input instead, the array is silently converted to a dip_image object as if calling dip_image(argument). This behavior is slightly different for arithmetic operations, see Tensor images.

Displaying dip_image objects

When a MATLAB command does not end with a semicolon, the display method is called for the resulting values, if any. This method defaults to calling the disp method, which displays all the values in matrices. For the dip_image objects, the display method has been overloaded to call dipshow instead. dipshow displays the image in a figure window (see The dipshow function for more information on this function). Before display, dipshow first calls squeeze (see Dimensions and Indexing pixels), meaning that a 4x1x6 image will be displayed as if it were a 4x6 image.

The disp method shows only the image size and data type instead. If you want display to call disp instead of dipshow, you can change the 'DisplayToFigure' preference using dipsetpref (see Toolbox preferences: dipsetpref and dipgetpref and Other settings).

For images that cannot be displayed by dipshow, (e.g. zero-dimensional and empty images, tensor images, etc.), display always calls disp.

There exist overloaded methods to query image properties, such as size and ndims, and some methods that are specific to dip_image objects, such as imsize, ntensordims and datatype.

Operations on dip_image objects

All mathematical operations have been overloaded for the dip_image object. The matrix multiplication (*) does a pixel-by-pixel multiplication, just as the array multiplication (.*) (the difference between these two operators becomes relevant when we introduce tensor images, see Tensor images). The same applies to the other matrix operations. Relational operations return binary images. Binary operations on non-binary images treat any non-zero value in those images as true and zero as false. For example, to do a threshold we do not need a special function, since we have the relational operators:

b = a > 100;

A double threshold would be (note MATLAB’s operator precedence):

b = a > 50 & a < 200;

When the two images in the operation do not have the same number of dimensions, images are expanded to match each other. This is called singleton expansion. For example, if image a is 10x12x15, and image b is 10x12, then image b is expanded along the third dimension by replication to compute a+b, resulting in an image the same size as a. If image a is 10x1, and image b is 1x12, the result of a+b is 10x12. Only dimensions of size one (and non-existing dimensions) will be expanded. If image a is 10x12, and image b is 1x6, a+b will produce an error.

A note is required on the data types of the resulting images. The “higher” data type always determines this result, but we have chosen never to return an integer type after any arithmetic operation. Thus, adding two integer images will result in a 4-byte floating-point image; an 8-byte floating-point (double) image is returned only if any of the two inputs is double.

Many of the arithmetic functions have also been defined for objects of type dip_image (see the two tables below for a complete listing). The basic difference between these and their MATLAB counterpart is that by default they work on the image as a whole, instead of on a per-column basis. For example, the function sum returns a row vector with the sum over the columns when applied to a numeric matrix, but returns a single number when applied to an image. An additional argument can be provided to compute a sum projection. Besides these, there are some other functions that are only defined for objects of type dip_image. See Overloaded methods with different behavior to learn about these functions. That section also lists some functions that behave differently than usual when applied to images.

Arithmetic functions defined for objects of type dip_image (image in, image out)
abs acos and, & angle asin atan
atan2 besselj ceil complex conj cos
erf exp fix floor hypot imag
log log10 log2 mod not, ~ or, |
phase pow10 pow2 real round sign
sin sqrt tan xor - +
* .* ./ / \^ .^
== ~= > >= < <=
Arithmetic functions defined for objects of type dip_image (image in, scalar out)
all any max mean median min
percentile prod std sum var

Dimensions

MATLAB arrays have at least 2 dimensions. This is not true for an image in a dip_image object, which can also have 0 or 1 dimension. That is, for images there is an explicit distinction between a 2D image of size 256 by 1 pixels, and a 1D image of size 256. Even though both images have the same number of pixels and their MATLAB array representation is identical, these two images behave differently in many aspects. For example, the imsize method (which is specific to dip_image objects, and returns the image size) will return two numbers for the first image, but only one for the second; similarly, it will return an empty array for a 0D image (whereas the corresponding MATLAB matrix has a size of 1-by-1). The method size (which is overloaded from the common MATLAB function) behaves more similarly to what you’re used to, as it always returns at least two values. This method is implemented because MATLAB requires it for displaying information with whos, we recommend you always use imsize with dip_image objects.

Use the function ndims to obtain the number of dimensions in an image.

The 2D image in the example above has a singleton dimension. A singleton dimension is any dimension of size 1. In MATLAB arrays, trailing singleton dimensions are removed if the array has more than two dimensions. That is, an array of size 4x1x6x1 is silently converted to an array of size 4x1x6. This never happens with dip_image objects.

As in MATLAB, operations between two images require that both images have compatible sizes. Singleton expansion is applied to make the two images equal in size. Singleton trailing dimensions can be applied to one of the images before singleton expansion, meaning that the images do not need to have the same dimensionality.

Indexing pixels

In image processing, it is conventional to index images starting at (0,0) in the upper-right corner, and have the first index (usually x), index into the image horizontally. Unfortunately, MATLAB is based on matrices, which are indexed starting at one, and indicating the row number first. By encapsulating images in an object, we were allowed to redefine the indexing. We chose not to follow MATLAB’s default indexing method. This might be confusing at first, and special care must be taken to check the class of a variable before indexing.

dip_image objects are indexed from 0 to end in each dimension, the first being the horizontal. The imsize function also returns the image width as the first number in the array. Any portion of a dip_image object, when extracted, is still a dip_image object, and of the same dimensionality, even if it is just a single pixel. Thus, if a is a 3D dip_image object, a(0,0,0) is also a 3D dip_image object, even though it only has a single pixel. To get a pixel value as a MATLAB array, use double(a(0,0,0)). To remove these singleton dimensions use squeeze. For example, a(:,:,2) is a 3D image with a singleton dimensions, whereas squeeze(a(:,:,2)) is a 2D image.

Any numeric type can be assigned into a dip_image object, without changing the image data type (that is, the element assigned into the image is converted to the image data type). For example,

b(:,0) = 0;

sets the top row of the image in b to 0. Note that indexing expressions can become as complicated as you like. For example, to sub-sample the image by a factor 3, we could write

b = b(1:3:end,1:3:end);

Instead of using full indexing (indexing each dimension separately), it is also possible to index using a single (linear) index. As with standard MATLAB arrays, the indices increase in the vertical direction, which is how pixels are stored in memory. However they start at 0 for dip_image objects ( \(i = y + x \cdot \textrm{height}\) ). The output is always a 1D image.

Finally, it is also possible to index using a mask image. Any binary image (or logical array) can be used as mask, but it must be of the same size as the image into which is being indexed. For example,

a(m) = 0;

sets all pixels in a, where m is one, to zero. A very common expression is of the form

a(a<0) = 0;

(which sets all negative pixels to zero).

Note that the expression a(m) above returns a one-dimensional image, with all pixels selected by the mask. It is equivalent to a(find(m)), where find returns an array of indices where m is one. This array is then used as a linear index into a.

Tensor images

Some image data benefits from assigning multiple values to each pixel. The most common example is a multi-channel image, such as an RGB color image. The multi-channel image is a form of vector image, where each pixel is a vector of values. Similarly, you could think of each pixel being a matrix. This concept can be generalized using tensors. A tensor can be a scalar value (0-rank tensor), a vector (1-rank tensor), a matrix (2-rank tensor), etc. In DIPimage (and DIPlib) we currently limit the tensor rank to 2, simply because we never came across a use for higher-rank tensor images.

The function newtensorim creates a new tensor image filled with zeros:

A = newtensorim([2,2],[256,256])

creates a 2-by-2 tensor image of 256 by 256 pixels.

Note that a scalar image (with one component) is also a tensor image (istensor returns true). The function isscalar returns true when there is only one tensor component. Additionally, the function isvector returns true if the tensor has rank 1. Relevant similar functions are iscolumn, isrow.

Some arithmetic operations behave differently for non-scalar images than for scalar images. For example, the * operator, which behaves identically to the .* operator for scalar images, actually applies a matrix multiplication between each corresponding pair of pixels. For example, the following code applies a matrix multiplication to the 2-vector image b, yielding a 2-by-2 matrix image c:

a = readim('trui');
b = gradient(a)      % yields a 2-vector
c = b * b'           % yields a 2-by-2 matrix

The pixels of a tensor image can be indexed like a normal image, returning a new tensor image. It can also be indexed using curly braces ({}) to select one or more tensor elements (channels). Indexing into the tensor dimension is identical to indexing into a MATLAB matrix: the first index goes down, and indices start at 1. For example, c{1,1} is a scalar image with the first tensor element of each matrix in the image c. A single index uses linear indexing: c{1} also is the first tensor element.

It is possible to combine spatial and tensor indexing, but the curly braces have to come first (this is a limitation of the MATLAB parser). Thus, write c{1}(0,0), not c(0,0){1}.

Because of limitations in the MATLAB language, it is impossible to know, for the overloaded end method, if it is being used inside curly or round braces (i.e. whether the last element of the image array is requested, or the last pixel of the image is requested). The solution we have adopted is to always assume round braces (()). Never use end within curly braces ({}). You can use tensorsize or numtensorel to compute indices from the end for tensor indexing:

a{end};             % doesn't work!
a{numtensorel(a)};  % returns the last tensor component

Note here that the image c above is a special type of matrix image: it is symmetric. That is because c{1,2} and c{2,1} are the result of the same operation: b{1}*b{2}. The * operator recognizes that the two inputs are transposed versions of each other (because they point at the same data block), and thus knows not to compute the same thing twice. The image c actually only contains 3 tensor elements, even though it represents a 2-by-2 matrix. Therefore, c{4} is an out-of-bounds error, whereas c{2,2} returns the result of b{2}.^2, and is identical to c{3}. There are special representations for the column-major matrix (the default), row-major matrix (obtained by transposing a matrix, which therefore doesn’t need to copy any data), symmetric matrix, and upper and lower triangular matrices. How the elements are stored is described in the DIPlib API documentation, but if you always access matrix elements using the two-index form then there is no need to know how these are stored.

The method numtensorel returns the number of tensor elements, and the method tensorsize returns the size of the tensor.

To get the array at a single pixel, use the double function: c(0,0) is a tensor image with a single pixel, and double(c(0,0)) is a MATLAB array with the tensor values at the first pixel.

Functions defined specifically for tensor images are summarized in the following table. See Overloaded methods with different behavior.

Functions defined for tensor images
cross curl det diag divergence dot
eig eye inv norm pinv rotate
svd trace * .' '

Arithmetic operations between an image and a small numeric matrix cause this matrix to be converted to a 0D tensor image (a single pixel with a matrix as value), instead of a small scalar image. This mode is triggered by matrices with up to 5-by-5 elements. For example,

a = readim('trui');
b = gradient(a);                 % yields a 2-vector
c = b.' * [cosd(30); sind(30)]   % yields a scalar

Color images

A color image is represented in a dip_image object by a tensor image with some extra information on the color space in which the pixel values are to be interpreted. A color image must have more than one channel, so the tensor image that represents it should have at least two components. Use the colorspace function to add this color space information to a tensor image:

C = colorspace(A,'RGB')

A color space is any string recognized by the system. See help colorspace for currently known color spaces. Images with a color space will be displayed by dipshow, which will convert them to RGB for a correct representation.

To convert an image from one color space to another, use the colorspace function. Converting to a color-space-less tensor image is done by specifying the empty string as a color space. This action only changes the color space information, and does not change any pixel values. Thus, to change from one color space to another without converting the pixel values themselves, change first to a color-space-less tensor image, and then to the final color space.

The function joinchannels combines two or more images into a color image using the specified color space:

C = joinchannels('RGB',a,b,c)

The function newcolorim creates a new color image of the given color space, filled with zeros:

C = newcolorim([256,256],'RGB');

All operations that are defined for tensor images can be applied to color images. These operations simply ignore the color space. Thus, adding two images with different color spaces does not cause one to be converted to the other color space. Typically, the output image will have the color space of the first input image with a color space and whose number of tensor elements matches that of the output image.

Manipulating the image shape

Functions used in MATLAB to manipulate array dimensions have been overloaded to do the same thing with images. They are listed in the table below.

Dimension manipulation functions defined for objects of type dip_image
cat circshift expanddim flipdim fliplr flipud
permute repmat reshape rot90 shiftdim squeeze
swapdim tensortospatial spatialtotensor

A few of these functions are unique to dip_image objects. The function expanddim adds trailing singleton dimensions, and swapdim is a simpler interface to the more general permute, and allows to swap two image dimensions.

spatialtotensor takes a spatial dimension of a scalar image and converts it to the tensor dimension, returning a vector image. tensortospatial does the reverse, returning a scalar image. When not specifying which spatial dimension to use, both these functions pick the dimension that requires no copying of data: 2. If you specify any other dimension, the data must be copied (and reordered). Thus, by using dimension 2, it is possible to exchange image shapes very efficiently, for example to apply functions such as max or sum along the tensor dimension.

Note that reshape and squeeze never copy image data (but see A note on the reshape and squeeze methods), and thus preserve the linear indexing order (the linear indexing order is related to storage in memory). Because linear indexing order matches the MATLAB storage order, dimension 2 is the most rapidly changing dimension. This means that squeezing an image of size [1,10,20,30] leads to an image of size [20,10,30], not [10,20,30], as one would expect.

A note on the reshape and squeeze methods

reshape and squeeze have a different behavior in DIPimage 3 than they had in earlier versions of the toolbox. The behavior was changed for consistency, though the new behavior can be surprising at times.

In older versions of the toolbox, reshape and squeeze often reordered the data (i.e. incurred the cost of a data copy), whereas the methods applied to a normal array never do so, these methods are supposed to be essentially free. reshape was implemented to fill the output image row-wise with pixels taken row-wise from the input image. But because MATLAB stores matrices column-wise, the data copy was necessary. However, this behavior was inconsistent with linear indexing, which wasn’t translated to use that same ordering. That is, linear indexing used the memory order of the pixels to translate an index to pixel coordinates, in the same way that it works for normal array. Thus, applying reshape (or squeeze, which applies a reshape to remove singleton dimensions) would change the pixels accessed at a given linear index, which is counter-intuitive. For example, in the following program a and b are different values:

img = dip_image(rand(10,11,8));
a = img(200);
img = reshape(orig,[11,8,10]);
b = img(200);

DIPimage 3 changed this behavior, such that reshape and squeeze are essentially free like they are for normal MATLAB arrays. Reshaping or squeezing an image is consistent with linear indexing (i.e. a and b above have the same value). However, this causes a different surprising behavior: squeeze reorders dimensions!

MATLAB’s array memory layout is such that a dip_image’s dimensions are ordered in memory like so: [2, 1, 3, 4, …]. If squeeze were to remove dimension number 1, subsequent dimensions would move left, meaning that dimension number 3 ends up in the location of dimension 1, but 2 stays where it was. An image of size 1x20x30, when squeezed, becomes an image of size 30x20, not 20x30 as one would expect. Similarly, removing dimension 2 would move dimension 1 to its place, and dimension 3 to the place of 1. Thus, an image of size 20x1x30 becomes a image of size 30x20, not 20x30 as one would expect.

Setting the preference CheapSqueeze to 'off' changes the behavior of squeeze to match its old behavior, possibly incurring a data copy. In essence, when the setting is 'off', then squeeze is implemented through permute, whereas when it is 'on' (the default), it is implemented through reshape.

Overloaded methods with different behavior

Most overloaded methods behave in a consistent manner with the built-in MATLAB functions that they overload. However, due to differences of the dip_image object, some behave somewhat differently. We summarize these functions here.

find, findcoord

find works similarly to the base version, except it is not possible to obtain [I,J] indices as output. The indices returned are always linear indices. An optional second output argument receives the non-zero values. To obtain the coordinates of non-zero values, use findcoord instead. It returns the coordinates of the pixels with non-zero values as a single array, with as many columns as dimensions in the input image, and one row for every non-zero pixel. Note that this matrix cannot be used directly to index an image.

gradient

The overloaded version of gradient returns a vector image, instead of multiple outputs. The derivatives are computed using Gaussian derivatives by default.

ind2sub, sub2ind

These functions have the same function as their base counterparts, but instead of using subscripts specified with one array for each dimension, they take and return a single coordinate array, compatible to that returned by findcoord. Also, instead of a size array, they take an image as input.

isscalar, isvector, isrow, iscolumn, ismatrix

These functions examine the tensor shape, not the image shape. A scalar image (it has a single channel) tests true with isscalar, no matter how many spatial dimensions it has.

max, min, mean, median, std, var, prod, sum, all, any

The built-in MATLAB versions of these always operate along matrix columns, yielding a row vector where each element is the max/min/mean/etc. of the corresponding column. This is a max/min/mean/etc. projection. It is possible to have them work along a different array dimension, and only since R2018b along multiple dimensions.

The overloaded versions of these functions that operate on dip_image objects can work along any number of dimensions simultaneously. By default, they operate on all dimensions, such that max(a) returns the maximum value of the image a. And max(a,[],[2,3]), if a is a 3D image, returns a 3D image with two singleton dimensions, where each pixel i contains the maximum over a(i,:,:).

Note there is a second argument to max that we didn’t use above. The projection functions all take a mask image as an optional second argument. The projection is taken only over those pixels selected by the mask. For example,

mean(a,a>0,1)

computes the mean projection along the first dimension (x axis), but only computes the mean over the positive pixels.

The function percentile is also projection function, but does not have a counterpart for MATLAB arrays (unless you have the statistics toolbox).

ndims

This method can return 0 or 1 (for 0D and 1D images, respectively). For normal MATLAB arrays it always returns at least 2. Note that ndims(a) is not necessarily equal to length(size(a)), but it is equal to length(imsize(a)).

numel

The overloaded numel is the number of samples in the image. Note that prod(size(a)) is not equal to numel(a), as it is for regular arrays. Instead, the following relations hold:

  • prod(size(a)) == numpixels(a)

  • prod(tensorsize(a)) == numtensorel(a)

  • numpixels(a) * numtensorel(a) == numel(a)

rotate

The overloaded method rotate has nothing to with MATLAB’s rotate (a handle graphics function). Applied to a 3-vector image, it rotates the vectors around an axis given by a second vector image or vector.

Review of the differences between a dip_image and a MATLAB array

As we have seen, objects of type dip_image have some differences with respect of regular MATLAB arrays. The main difference is in indexing. We start counting pixels from 0, and the first index counts from left to right. This ordering is also used by functions such as imsize, in which the first number is the image width and the second one the height. Finally, ndims can return 0 or 1, which it never does for MATLAB arrays. The reason is that zero-dimensional and one-dimensional images are allowed, and are not seen as a special case of two-dimensional images. Furthermore, singleton dimensions at the end are not ignored.

Another major difference is that one of the dimensions is not a spatial dimension, and is not included in the result of imsize and ndims, or accessible using normal indexing. This dimension represents a tensor at each image pixel. tensorsize and numtensorel are relevant here, as is indexing using curly braces ({}) as in cell array indexing. Color images are images with multiple tensor elements (i.e. channels), and color space information.

When a MATLAB command results in an object of type dip_image, and it is not ended with a semicolon, the image is displayed to a figure window, instead of having its pixel values shown in the command window. This is the default behavior, but can be overridden.

All operators work on a pixel-by-pixel basis. For example, the transpose operators ' and .' transpose the vector or matrix at each pixel, not the image itself, and the multiplication operator * applies matrix multiplication to each of the corresponding pixel pairs. All functions that work on the columns of numeric arrays (such as sum and max) work on the image as a whole when applied to a dip_image object.

Objects of type dip_image cannot be used in functions of the MathWorks’ Image Processing Toolbox. Although most of MATLAB’s functions work on dip_image objects, not every function will work as expected. Use the functions dip_array, double or uint8 to convert the image to a format recognizable by these functions.