Image module
#include "diplib.h"
dip::Image class

Represents an image with all associated information.

A dip::Image object is the core of the DIPlib library, as all functionality revolves around images. Some image manipulation is provided as class methods, but most image processing and analysis functionality is provided in functions defined in the dip namespace.

Image representation

An dip::Image object can have any number of dimensions (limited by the integer representation used), though 2D and 3D are the most often used dimensionalities. Most functions in the library accept images with any number of dimensions, for the functions that are limited in this respect there is a note in the documentation. A 0D image is an image with a single pixel.

We use the term pixel to refer to the collection of samples taken at the same spatial location, irrespective of the number of dimensions that the image has (that is, we don’t use the term voxel for pixels in 3D images). A pixel is represented by a tensor. We have limited the tensors, in the current implementation, to have no more than two dimensions (a matrix), as there doesn’t seem to be much use for higher-dimensional tensors in image analysis. However, there is no limit to the number of tensor elements (other than available memory and the integer representation used).

Each element of the tensor at a pixel is referred to as a sample. A tensor element is synonymous with sample, and we use the one or the other term in the documentation and code depending on context. We say that an image with 1 million pixels and 3 samples per pixel has a total of 3 million samples.

If the tensor is 0D (a single sample), the image is a standard grey-value image (which we refer to as scalar image). A 1D tensor (a vector) can be used to represent color images (e.g. an RGB image has three samples per pixel), but also for example the image gradient (see dip::Gradient). With a 2D tensor (a matrix) it is possible to represent concepts such as the Hessian and the structure tensor (see Why tensors?). For example, the Hessian of a 3D image has 9 samples per pixel. For more details on how tensor elements are stored, see the section on Tensor images.

A dip::Image object can contain samples of a wide variety of numeric types, including binary, unsigned and signed integers, floating point, and complex. For a complete list see Pixel data types. All the image’s samples must have the same type.

All of these image properties are dynamic. That is, they can be determined and changed at runtime, they are not fixed at compile time. An Image object has two states: raw and forged. When an image is raw, it has no associated data segment. In the raw state, all image properties can be changed. The dip::Image::Forge method allocates the data segment (the memory block that holds the pixel values). Once the image is forged, its properties are fixed. It is possible to call the dip::Image::Strip method to revert to the raw state. The reason behind this dynamic image structure is that it allows flexibility: one can read the data in a file without knowing what the file’s data type is going to be; the file reading function can adjust the Image object’s data type (and dimensionality) at run time to accommodate any data that the file might contain. Another advantage is that the programmer does not need to think about, for example, what data type is appropriate as output of a specific function. However, when desired, it is possible to control the data types of images.

Strides

For maximum flexibility in the relationship between image coordinates and how the samples are stored in memory, a dip::Image object specifies a stride array (dip::Image::Strides). This array indicates, for each dimension, how many samples to skip to get to the neighboring pixel in the given dimension. For example, to go from a pixel at coordinates (x,y) to the neighbor at coordinates (x+1,y), you would need to increment the data pointer with strides[0]. In a 2D image, the pixel at coordinates (x,y) can be reached by (assuming dip::DT_UINT8 data type):

dip::uint8* origin = img.Origin();
dip::IntegerArray strides = img.Strides();
dip::uint8 value = *( origin + x * strides[0] + y * strides[1] );

This concept naturally scales with image dimensionality. Strides can be negative, and need not be ordered in any particular way. This allows a dip::Image object to contain a regular subset of the pixels of another image, but still point to the same physical memory block. Here are some examples:

  • An Image object can contain a region of interest (ROI), a smaller region within a larger image. In this case, the strides are identical to those of the larger image, but the origin and the image size differs.

  • An Image object can contain a subsampled image, where the strides are a multiple of the strides of the original image.

  • An Image object can contain a single slice of a 3D image, where the strides are identical to the 3D image’s strides, but the origin and the image size and dimensionality are different.

  • An Image object can contain a mirrored image, where the stride in the mirrored dimension is negative, and the origin is different.

  • An Image object can contain a rotated image. For example, rotating over 90 degrees involves swapping the two dimensions (i.e. swapping the sizes and strides associated to these dimensions), and inverting one of the dimensions (as in the case of the mirrored image).

Arbitrary strides also allow data segments from other software to be encapsulated by an Image object. For example, MATLAB stores images with columns contiguous in memory, requiring strides[1] == 1.

All routines in the library support images with arbitrary strides.

The various elements of a tensor are also accessed through a stride, which can be obtained through dip::Image::TensorStride. Even for a 2D tensor, all tensor elements can be visited using a single stride value. See the section Tensor images for more information on accessing tensor elements. And see the section On pixel coordinates, indices, offsets and data pointers for more information about accessing samples. See the section Normal strides for information on the default strides.

Tensor images

A tensor image (generalization of the vector and matrix image) has a tensor for each pixel. A tensor collects all samples corresponding to the same spatial location into a specific shape. dip::Image::TensorElements indicates how many samples per pixel the image has.

A tensor image is stored into a single memory block. In the same way that strides indicate how to skip from one pixel to another (as described in the section Strides), the dip::Image::TensorStride indicates how to skip from one tensor element to another. This allows e.g. a multi-channel image to be stored as either interleaved per pixel, per line or per plane, and as long as an algorithm uses the strides, it does not need to know how the channels are interleaved.

All tensor elements are stored as if they composed a single spatial dimension. Therefore, it is possible to change the image such that the tensor elements form a new spatial dimension (dip::Image::TensorToSpatial), or such that one spatial dimension is converted to a tensor (dip::Image::SpatialToTensor), without moving the samples. It is also possible to change the shape of the tensor without moving data (dip::Image::ReshapeTensorAsVector, dip::Image::Transpose).

The shape of the tensor is represented by the enumerator dip::Tensor::Shape (obtained through dip::Image::TensorShape). The chosen way of storing tensor elements allows us, for example, to store a symmetric 2D tensor such as the Hessian matrix without repeating the repeating the duplicated values. We also have a specific shape for diagonal matrices and triangular matrices.

On pixel coordinates, indices, offsets and data pointers

Given

dip::Image img( { 10, 12, 20, 8, 18 }, 1, dip::DT_UINT16 );

Then img.Origin() is a void* pointer to the first pixel (or rather the first sample of in the image). This pointer needs to be cast to the type given by img.DataType() to be used, as in:

(dip::uint16*)img.Origin() = 0;

A pixel’s offset is the number of samples to move away from the origin to access that pixel:

dip::uint16* ptr = (dip::uint16*)img.Origin();
ptr + offset = 1;

Alternatively, it is possible to compute the pixel’s pointer without casting to the right data type (this leads to a more generic algorithm) by using the dip::DataType::SizeOf operator (we cast to dip::uint8 pointer to do pointer arithmetic in bytes):

(dip::uint8*)img.Origin() + offset * img.DataType().SizeOf();

This computation is performed by img.Pointer( offset ).

Note that the offset is a signed integer, and can be negative, because strides can be negative also. The offset is computed from coordinates using the image’s strides:

dip::UnsignedArray coords { 1, 2, 3, 4, 5 };
dip::sint offset = 0;
for( dip::uint ii = 0; ii < img.Dimensionality(); ++ii ) {
  offset += coords[ii] * img.Stride( ii );
}

This computation is performed by img.Offset( coords ). img.Pointer( coords ) simply chains this operation with the previous one. The inverse operation is performed by img.OffsetToCoordinates( offset ). Two images of the same size do not necessarily share offset values. Both the dimensions and the strides must be identical for the offset to be compatible between the images.

The coordinates to a pixel simply indicate the number of pixels to skip along each dimension. The first dimension (dimension 0) is typically x, but this is not evident anywhere in the library, so it is the application using the library that would make this decision. Coordinates start at 0, and should be smaller than the img.Sizes() value for that dimension. They are encoded using a dip::UnsignedArray. However, some functions take coordinates as a dip::IntegerArray. These are the functions that do not expect the coordinates to indicate a pixel inside the image domain.

The index to a pixel (a.k.a. “linear index”) is a value that increases monotonically as one moves from one pixel to the next, first along dimension 0, then along dimension 1, etc. The index computed from a pixel’s coordinates is as follows:

dip::UnsignedArray coords { 1, 2, 3, 4, 5 };
dip::uint dd = img.Dimensionality();
dip::uint index = 0;
while( dd > 0 ) {
  --dd;
  index *= img.Size( dd );
  index += coords[dd];
}

This computation is performed by img.Index( coords ). It is the nD equivalent to x + y * width. An index, as opposed to an offset, is always non-negative, and therefore stored in an unsigned integer. The index is shared among any images with the same dimensions.

It is not efficient to use indices to access many pixels, as the relationship between the index and the offset is non-trivial. One can determine the coordinates corresponding to an index through img.IndexToCoordinates( index ), which then leads to an offset or a pointer. The function dip::Image::At with a scalar argument uses linear indices, and consequently is not efficient for images with dimensionality of 2 or more.

Oftentimes it is possible to determine a simple stride that will allow you to access every pixel in an image. When an image is a view into another image, this is not necessarily possible, but any default image (i.e. with Normal strides) has this possibility. This simple stride allows one to view the image as a 1D image. The function dip::Image::Flatten will create this 1D image (without copying any data, if there exists such a simple stride). Walking along this one dimension will, however, not necessarily access the pixels in the same order as given by the linear index. This order is only consistent if the image has normal strides. See dip::Image::HasNormalStrides, dip::Image::HasSimpleStride, dip::Image::GetSimpleStrideAndOrigin.

To walk along all pixels in an arbitrary image (i.e. arbitrary dimensionality and strides) in the order given by the linear index, use the image iterators defined in diplib/iterators.h (see Using iterators to implement filters):

dip::ImageIterator< dip::uint16 > it( img );
dip::uint16 ii = 0;
do {
  *it = ii++;
} while( ++it );

The functionality in the dip::Framework namespace is the recommended way of building generic functions that access all pixels in an image. These functions allow you to loop over multiple images simultaneously, using multi-threading, while taking care of different data types, checking input images, allocating output images, etc. Different framework functions do pixel-based processing, line-based processing (e.g. separable filters, projections) and neighborhood-based processing (i.e. non-separable filters). There is currently no plans for framework functionality to support priority queue algorithms, if you figure out how to make such a function generic enough please contribute!

For images with more than one sample per pixel, the above discussion shows only how to access the first sample in each pixel. Coordinates always indicate a pixel, and therefore dip::Image::Offset always gives the offset to the first sample of a pixel. The other samples can be accessed by adding n * img.TensorStride() to the offset, where n is the tensor element. Tensor elements are then accessed in a specific order, depending on the shape of the tensor. See dip::Tensor::Shape for a description of the order of the tensor elements in memory. For the iterators, use it[n] to access the nth tensor element.

Creation, assignment and copy

To create a new image with specific properties, one can either set each of the properties individually, or use one of the constructors. For example, the two following images are the same:

dip::Image img1;
img1.SetSizes( { 256, 256 } );
img1.SetDataType( dip::DT_UINT8 );
img1.SetTensorSizes( 1 );
img1.Forge();

dip::Image img2( dip::UnsingedArray{ 256, 256 }, 1, dip::DT_UINT8 );

The first method is more flexible, as it allows to set all properties before forging the image (such as strides). Note that the created image has uninitialized pixel data. You can use the dip::Image::Fill method to set all pixels to a specific value.

To create a new image with same sizes and tensor shape as another one, use the dip::Image::Similar method:

dip::Image img2 = img1.Similar();

Again, the new image will have uninitialized pixel data. An optional second argument can be used to specify the data type of the new image:

dip::Image img2 = img1.Similar( dip::DT_SCOMPLEX );

Both methods copy all image properties, including the strides array and the external interface; see dip::Image::CopyProperties.

A similar method is dip::Image::ReForge, which modifies the properties of an image and creates a new data segment if the old one is of the wrong size to support the new properties. In function, these three sets of statements are equivalent:

img2 = img1.Similar();

img2.Strip();
img2.CopyProperties( img1 );
img2.Forge();

img2.ReForge( img1 );

However, ReForge might not strip and forge if it is not necessary (also, it does not use the source image’s strides), and so is the recommended way of modifying an image to match another one. dip::Image::ReForge has two other forms that can be useful, see the documentation.

Lastly, it is possible to create a 0D image (an image with a single pixel) with the constructor that takes a scalar value (integer, float or complex), or an initializer list containing scalar values of the same type. With the initializer list, the image will be a vector image with as many samples as elements in the initializer list:

dip::Image img1( 10 );
dip::Image img2( { 0, 1, 2, 3 } );

The assignment operator creates a copy of the image, but does not actually copy the data. Instead, the new copy will share the data segment with the original image:

img2 = img1;

Both img1 and img2 point at the same data, meaning that changing one image’s pixel values also affects the other image. The data segment will exist as long as one image references it. That is, if img1 goes out of scope, img2 will still point at a valid data segment, which will not be freed until img2 goes out of scope (or is stripped). This is useful behavior, but can cause unexpected results at times. See Handling input and output images that alias each other for how to write image filters that are robust against images with shared data. However, if the image assigned into is protected or has an external interface set, a data copy might be triggered, see The “protect” flag and Define an image’s allocator.

dip::Image::QuickCopy can be used here if the image copy does not need any of the image metadata (color space and pixel size). The overhead of copying the metadata information is small, but we often use this function internally when the input image to a function needs to be reshaped for processing, but we do not want to modify the input image itself:

dip::Image tmp = img1.QuickCopy();
tmp.Squeeze();
...

The copy constructor behaves the same way as the assignment operator, making the new image share data with the input image. The following three statements all invoke the copy constructor:

img2 = dip::Image( img1 );
dip::Image img3( img1 );
dip::Image img4 = img1;

To make a copy of an image with its own copy of the data segment, use the dip::Image::Copy method:

img2.Copy( img1 );

or equivalently the dip::Copy function:

img2 = dip::Copy( img1 );

In both cases, img2 will be identical to img1, with identical pixel values, and with its own data segment.

When the dip::Image::Copy method is used on a forged image, it is expected to be of the same size as the image to be copied. Pixel values will be copied to the existing data segment, casting to the target image’s data type with clamping (see diplib/library/clamp_cast.h):

img2 = img1.Similar( dip::DT_UINT8 );
img2.Copy( img1 );

Or equivalently:

img2 = dip::Convert( img1, dip::DT_UINT8 );

The dip::Image::Convert method, as opposed to the dip::Convert function, converts the image itself to a new data type. This process creates a new data segment if the data type is of different size, and works in place if the data type is of the same size (e.g. dip::DT_SINT32 and dip::DT_SFLOAT have the same size, as do dip::DT_DFLOAT and dip::DT_SCOMPLEX). However, if the data segment is shared, it will never work in place, as that could cause important problems.

Assigning a constant to an image is equivalent to calling the dip::Image::Fill method, writing that constant to each sample in the image. The constant is cast to the image’s data type with saturation (see dip::clamp_cast). Note that the image must be forged:

img1 = 10;
img2.Fill( 0 );

Additionally, one can assign an initializer list to an image. The list should have one element for each tensor element. Each pixel in the image will then be set to the tensor values in the initializer list:

img2 = dip::Image( dip::UnsignedArray{ 256, 256 }, 10, dip::DT_SFLOAT );
img2 = { 1, 2, 3, 4 };

img2 will have all pixels set to the same vector [ 1, 2, 3, 4 ].

Indexing

There are three main modes of indexing pixels in a dip::Image object: single pixel indexing, regular grid indexing, and irregular pixel indexing. All three modes have similar, consistent syntax, but differ by the return type and properties. Additionally, one can index into the tensor dimension. The next four sub-sections describe these different indexing modes.

All indexing modes share in common that the returned object can be assigned to, modifying the original image. When indexing a single pixel, the return type is a dip::Image::Pixel, which references a single pixel in the original image. When indexing multiple pixels (either by indexing the tensor dimension or by regular or irregular indexing), the return type is a dip::Image::View, which references multiple pixels in the original image. Both these objects have some overloaded methods and can be iterated over. Additionally, the dip::Image::View object implicitly casts back to a dip::Image, so it can be used as arguments to functions that take an Image as input. Here is where the difference between regular and irregular indexing comes into play: the Image that results from regular indexing (and from tensor indexing) shares the data with the original image, whereas when the indexing was irregular, the data needs to be copied to create a new image with only those pixels.

The following table summarizes the various indexing types discussed in detail in this section.

  Single pixel Tensor Regular Mask image Set of pixels
Syntax .At(dip::uint, ...)
.At(dip::UnsignedArray)
[dip::uint]
[dip::UnsignedArray]
[dip::Range]
.Diagonal()
.TensorRow(dip::uint)
.TensorColumn(dip::uint)
.At(dip::Range, ...)
.At(dip::RangeArray)
.At(dip::Image) .At(dip::CoordinateArray)
.AtIndices(dip::UnsignedArray)
Output dip::Image::Pixel dip::Image::View dip::Image::View dip::Image::View dip::Image::View
Implicitly casts to dip::Image No Yes, with shared data Yes, with shared data Yes, with data copy Yes, with data copy

Tensor dimensions

To address one channel in a color image is a rather common operation, and therefore we have delegated the C++ indexing operator ([]) to the task of extracting a tensor component from an image (remember that a color channel is a tensor component). For example:

dip::Image red = colorIm[ 0 ];

For a two-dimensional matrix it is possible to index using two values in an array:

dip::Image out = tensorIm[ dip::UnsignedArray{ i, j } ];

The indexing operation, as explained above, returns a dip::Image::View object, which can be assigned to to modify the referenced pixels:

colorIm[ 0 ] = colorIm[ 1 ];

When cast back to an image, the image created shares the pixels with the original image, meaning that it is possible to write to a channel in this way:

dip::Image blueChannel = colorIm[ 2 ];
blueChannel.Protect();                         // Prevent `dip::Gauss` from reforging this image.
dip::Gauss( blueChannel, blueChannel, { 4 } ); // Modifies `colorIm`.

Note that the single-index version of the tensor indexing uses linear indexing into the tensor element list: if the tensor is, for example, a 2x2 symmetric matrix, only three elements are actually stored, with indices 0 and 1 representing the two diagonal elements, and index 2 representing the two identical off-diagonal elements. See dip::Tensor for information on how the tensor is stored.

To extract multiple tensor elements one can use a dip::Range to index. Other useful methods here are dip::Image::Diagonal, dip::Image::TensorRow and dip::Image::TensorColumn, which all yield a vector image.

The methods dip::Image::Real and dip::Image::Imaginary are somewhat related to these last three methods in that they return a view over a subset of the data, except that they reference only the real or imaginary component of the complex sample values.

Single-pixel indexing

Some forms of the function dip::Image::At extract a single pixel from the image. It has different forms, accepting either a dip::UnsignedArray representing the coordinates of the pixel, one to three indices for 1D to 3D images, or a (linear) index to the pixel. The latter form is less efficient because the linear index needs to be translated to coordinates, as the linear index is not necessarily related to the order in which pixels are stored in memory.

image1D.At( 5 );                           // Indexes pixel at coordinate 5
image2D.At( 0, 10 );                       // Indexes the pixel at coordinates (0, 10)
image2D.At( dip::UnsignedArray{ 0, 10 } ); // Indexes the pixel at coordinates (0, 10)
image2D.At( 20 );                          // Indexes the pixel with linear index 20

These forms result in an object of type dip::Image::Pixel. The object contains a reference to the original image pixels, so writing to the reference changes the pixel values in the image:

image.At( 0, 10 ) += 1;

The single-pixel forms of dip::Image::At have an alternative, templated form. In that form, they return a dip::Image::CastPixel instead, which is identical to a dip::Image::Pixel, but implicitly casts to any chosen type. Thus:

int v1 = image.At( 0, 10 );             // Does not compile, no implicit conversion to `int`.
int v2 = image.At< int >( 0, 10 );      // OK
int v3 = image.At( 0, 10 ).As< int >(); // Same as `v2`.

This implicit cast makes its use a little simpler in a setting combined with numbers of that type. However, the object itself is not tied to the type, and it is still possible to access (read or write) the pixel as other types. For example, this code will print “( 1.0, -3.0 )” to the standard output, the template argument to At has no influence on the results:

dip::Image image( { 256, 256 }, 1, dip::DT_SCOMPLEX );
image.At< dip::uint8 >( 85, 43 ) = dip::dcomplex{ 1.0, -3.0 );
std::cout << image.At< dip::sint32 >( 85, 43 );

Tensor and spatial indexing can be combined in either order, but the results are not identical:

image.At( 0, 10 )[ 1 ];
image[ 1 ].At( 0, 10 );

The first line above uses [] to index into the dip::Image::Pixel object, yielding a dip::Image::Sample. Again, this is a reference to the sample in the image, and can be written to to change the image. The second line first uses [] to create a scalar image view, and then extracts a pixel from it. The result is a dip::Image::Pixel object, not a dip::Image::Sample, though the pixel object references a single sample in the input image. In practice, these are equally useful, though the first form is slightly more efficient because the intermediate object generated has less overhead.

See the documentation to dip::Image::Sample and dip::Image::Pixel for more information on how these objects can be used. Note however, that this is not an efficient way of accessing pixels in an image. It is much more efficient to use pointers into the data segment. However, pointers require knowledge of the data type at compile time. The indexing described here is a convenient way to read a particular value in a data-type agnostic way. To access all pixels in a data-type agnostic way, use the dip::GenericImageIterator.

Regular indexing (windows, ROI processing, subsampling)

The function dip::Image::At is also used for creating views that represent a subset of pixels. In this form, it accepts a set of dip::Range objects, or a dip::RangeArray, representing regular pixel intervals along each dimension through a start, stop and step value. That is, a range indicates a portion of an image line, with optional subsampling. For example, indexing into a 1D image:

image1D.At( dip::Range{ 5 } );        // Indexes pixel at coordinate 5
image1D.At( dip::Range{ 0, 10 } );    // Indexes the first 11 pixels
image1D.At( dip::Range{ 0, -1, 2 } ); // Indexes every second pixel

Note that negative values index from the end, without needing to know the exact size of the image. Note also that the stop value is always included in the range.

For indexing into multidimensional images, simply provide one range per dimension. For more than 3 dimensions, use a dip::RangeArray.

As is the case with the [] indexing, these operations yield a dip::Image::View that can be assigned to to change the referenced pixels; and when cast back to a dip::Image, yields an image that shares data with the original image. For example:

image.At( dip::Range{ 0, 10 }, dip::Range{ 20, 30 } ) += 1;

Tensor and spatial indexing can be combined in either order, with identical results:

image.At( { 0, 10 }, { 20, 30 } )[ 1 ];
image[ 1 ].At( { 0, 10 }, { 20, 30 } );

The method dip::Image::Cropped is a convenience function to extract a view to a rectangular widow of a given size and with a given anchor (image center or corner). The dip::Image::Pad method does the opposite operation, but creates a new image and copies the data over.

Irregular indexing (mask image, arbitrary set of pixels)

An arbitrary subset of pixels can be indexed in three ways, using one of:

  • a mask image
  • a list of pixel coordinates
  • a list of linear indices into the image

The first two forms are again available through the dip::Image::At method, the third one through the dip::Image::AtIndices method (since the input argument would not be distinguishable from indexing a single pixel through its coordinates). As with regular indexing, the returned object is a dip::Image::View, which can be assigned to to change the referenced pixels. But as opposed to regular indexing, when cast back to a dip::Image, the operation results in a 1D image with a copy of the sample values. Thus, once cast back to an Image, pixels are no longer shared and can be modified without affecting the original image. This is by necessity, as the Image object cannot reference samples that are not stored in memory on a regular grid.

image.At( mask ) += 1;
dip::Image out = image.At( mask );
out.Fill( 0 ); // Careful! Does not affect `image`.

Another typical example:

image.At( mask ) = otherImage.At( mask );

Reshaping

There is a large collection of methods to reshape the image without physically moving samples around in memory. These functions accomplish their goal simply by modifying the sizes and strides arrays, and the tensor representation values. Thus, they are very cheap. All of these functions modify the object directly, and return a reference so that they can be chained.

For example, dip::Image::Rotation90 rotates the image in 90 degree increments by mirroring and swapping dimensions. More generic methods are dip::Image::Mirror, and dip::Image::SwapDimensions. dip::Image::PermuteDimensions reorders the image’s dimensions, an optionally adds or removes singleton dimensions. Singleton dimensions are dimensions with a size of 1, and can be added or removed without affecting the data layout in memory.

Several of these methods are meant to manipulate singleton dimensions. dip::Image::AddSingleton and dip::Image::Squeeze add and remove singleton dimensions. dip::Image::ExpandDimensionality adds singleton dimensions at the end to increase the number of image dimensions. It is also possible to expand singleton dimensions so they no longer are singleton, by replicating the data along that dimensions, again without physically replicating or modifying the data in memory. See Singleton expansion. The methods dip::Image::ExpandSingletonDimension, dip::Image::ExpandSingletonDimensions and dip::Image::ExpandSingletonTensor are used for this. dip::Image::UnexpandSingletonDimensions does the opposite operation.

The method dip::Image::StandardizeStrides undoes all rotation, mirroring, dimension reordering, and singleton expansion, by making all strides positive and sorting them from smallest to largest. It also removes singleton dimensions (as dip::Image::Squeeze). The dip::Image::Flatten method converts the image to 1D (though if the image does not have contiguous data, it will have to be copied to form a 1D image). dip::Image::FlattenAsMuchAsPossible does the same thing, but never copies. If the data is not contiguous, the image will not be 1D, though it will hopefully have fewer dimensions than before the method call. dip::Image::SplitDimension splits a dimension into two.

A group of methods manipulate the image’s tensor shape: dip::Image::ReshapeTensor requires the target tensor shape to have as many tensor elements as the image already has. For example, a 3-vector image can be converted into a 2x2 symmetric tensor image. dip::Image::ReshapeTensorAsVector and dip::Image::ReshapeTensorAsDiagonal turn the image into the given tensor shapes. The dip::Image::Transpose method belongs in this set, though it has a mathematical meaning. Transpose changes the row-major matrix into a column-major matrix and vice-versa, thereby transposing the tensor.

It is also possible to turn the tensor dimension into a spatial dimension and back, using dip::Image::TensorToSpatial and dip::Image::SpatialToTensor. Turning the tensor dimension into a spatial dimension can be useful for example to apply the same operation to all samples in a vector image: it is easier to loop over a scalar image with a single dip::ImageIterator loop than to loop over a tensor image using a double loop over pixels and over tensor elements.

Similar in purpose and function to dip::Image::TensorToSpatial are functions that split the complex-valued samples into two floating-point–valued samples, and present these as either a new spatial dimension, or the tensor dimension: dip::Image::SplitComplex, and dip::Image::SplitComplexToTensor. The inverse operation is accomplished with dip::Image::MergeComplex and dip::Image::MergeTensorToComplex.

The “protect” flag

An image carries a “protect” flag. When set, the dip::Image::Strip function throws an exception. That is, when the flag is set, the data segment cannot be stripped (freed) or reforged (reallocated). Furthermore, when the protect flag is set, the assignment operator will perform a deep copy. For example:

dip::Image img1( dip::UnsignedArray{ 256, 256 }, 3, dip::DT_SFLOAT );
img1.Protect();
//img1.Strip();  // Throws!
img1 = img2;     // Equivalent to: `img1.Copy( img2 )`.

The protect flag has two purposes:

First: To prevent an image from being reforged, for example when the data segment is allocated in a special way and one needs to ensure it stays that way. In this case, it functions as a warning.

Second: To provide a simple means of specifying the data type for the output image or a filter. Most filters and operations in DIPlib choose a specific data type for their output based on the input data type, and in such a way that little precision is lost. For example, the Gaussian filter will produce a single-precision floating point output image by default when the input image is an 8-bit unsigned integer. Under the assumption that this default choice is suitable most of the time, we have chosen to not give all these functions an additional input argument to specify the data type for the output image. Instead, if the output image has the protect flag set, these functions will not modify its data type. Thus, you can set the output image’s data type, protect it, and receive the result of the filter in that data type:

dip::Image img = ...
dip::Image out;
out.SetDataType( dip::DT_SINT16 );
out.Protect();
dip::Gauss( img, out, { 4 } );
// `out` is forged with correct sizes to receive filter result, and as 16-bit integer.

This is especially suitable for in-place operations where we want to receive the output in the same data segment as the input:

dip::Image img = ...
img.Protect();
dip::Gauss( img, img, { 4 } );

If the filter is called with a protected, forged image as output, as is the case in the last code snipped above, the filter will not be able to strip and re-forge the image, meaning that the image must have the correct sizes and tensor elements to receive the output. However, if the image is not forged, as in the first code snippet, then the filter can set its sizes and forge it.

Note, however, that some functions require specific data types for their output image, and will throw an error if the wrong output data type is requested.

Const correctness

When an image object is marked const, the compiler will prevent modifications to it, it cannot be assigned to, and it cannot be used as the output argument to a filter function. However, the indexing operators, the copy assignment operator, and dip::Image::QuickCopy all allow the user to make a non-const object that points to the same data, making it possible to modify the pixel values of a const image (see Const correctness for our reasons to allow this). Because of that, it did not really make sense either to have dip::Image::Data, dip::Image::Origin, and dip::Image::Pointer return const pointers when applied to a const image.

Thus, there is nothing prevent you from modifying the pixel values of a const image. However, none of the functions in DIPlib will do so. A const image (usually the input images to functions are marked const) will not be modified.

Arithmetic and comparison operators

Arithmetic operations, logical operations, and comparisons can all be performed using operators, but there are also functions defined that perform the same function with more flexibility. See Arithmetic operators and Comparison operators for a full list of these functions.

For example, to add two images a and b, one can simply do:

dip::Image c = a + b;

But it is also possible to control the output image by using the dip::Add function:

dip::Image c;
dip::Add( a, b, c, dip::DT_SINT32 );

The fourth argument specifies the data type of the output image. The computation is performed in that data type, meaning that both inputs are first cast to that data type (with clamping). The operation is then performed with saturation. This means that adding -5 and 10 in an unsigned integer format will not yield 5, but 10, because the -5 is first cast to unsigned, becoming 0. Also, adding 200 and 200 in an 8-bit unsigned integer format will yield 255, there is no dropping of the higher-order bit as in standard C++ arithmetic.

As is true for most image processing functions in DIPlib (see Function signatures), the statement above is identical to

dip::Image c = dip::Add( a, b, dip::DT_SINT32 );

However, the former version allows for writing to already-allocated memory space in image c, or to an image with an external interface (see Define an image’s allocator).

For in-place addition, use

dip::Add( a, b, a, a.DataType() );

or simply

a += b;

All dyadic operations (arithmetic, logical, comparison) perform Singleton expansion. They also correctly handle tensor images of any shape. For example, it is possible to add a vector image and a tensor image, but it is not possible to add two vector images of different lengths. The multiplication operation always performs matrix multiplication on the tensors, use the dip::MultiplySampleWise function to do sample-wise multiplication. Other operators are sample-wise by definition (including the division, which is not really defined for tensors).

Color images

A color image is a vector image where each vector element represents a color channel. dip::Image::SetColorSpace assigns a string into the image object that flags it as a color image. The string identifies the color space. An empty string indicates that the image is not a color image.

There are no checks made when marking the image as a color image. For example, and RGB image is expected to have three channels (img.TensorElements() == 3). However, the call img.SetColorSpace("RGB") will mark img as an RGB image no matter how many tensor elements it has (this is because the function has no knowledge of color spaces). If the image has other than three tensor elements, errors will occur when the color space information is used, for example when trying to convert the image to a different color space.

Nonetheless, when manipulating an image in such a way that the number of tensor elements changes, the color space information in the image is reset, turning it into a non-color image.

An object of type dip::ColorSpaceManager is used to convert an image from one known color space to another. This object is the only place in the library where there is knowledge about color spaces. Create one of these objects for any application that requires color space knowledge. It is possible to register new color spaces and color space conversion functions with this object. Other functions that use specific color spaces will have knowledge only of those specific color spaces, and will expect their input to be in one of those color spaces.

Pixel size

Each image carries with it the size of its pixels as a series of physical quantities (dip::PhysicalQuantity), one for each image dimension. The advantage of keeping the pixel size with the image rather than as a separate value taken manually from the image file metadata is that it is automatically updated through manipulations such as scaling (zoom), dimension permutations, etc. When the pixel size in a particular dimension is not set, it is always presumed to be of size 1 (a dimensionless unit). See dip::PixelSize for details.

There are three ways in which the pixel size can be used:

  • The measurement function will return its measurements as physical quantities, using the pixel sizes, if known, to derive those from measurements in pixels.

  • The dip::Image::PhysicalToPixels method converts a filter size in physical units to one in pixels, suitable to pass to a filtering function. For example, to apply a filter with a sigma of 1 micron to an image:

    dip::PhysicalQuantityArray fsz_phys{ 1 * dip::PhysicalQuantity::Micrometer() };
    dip::Filter( img, img, img.PhysicalToPixels( fsz_phys ));
    
  • The dip::Image::PixelsToPhysical method converts coordinates in pixels to coordinates in physical units. For example, to determine the position in physical units of a pixel w.r.t. the top left pixel:

    dip::FloatArray pos_pix{ 40, 24 };
    dip::PhysicalQuantityArray pos_phys = img.PixelsToPhysical( pos_pix );
    

It is currently possible to add, subtract, multiply and divide two physical quantities, and elevate a physical quantity to an integer power. Other operations should be added as necessary.

Controlling data segment allocation

It is possible to create dip::Image objects whose pixels are not allocated by DIPlib using two different methods:

  1. Create an image around an existing data segment. This is used in the case when passing data from other imaging libraries, or from interpreted languages that have their own array type. Just about any data can be encapsulated by a a dip::Image without copy.

  2. Define an image’s allocator, so that when DIPlib forges the image, the user’s allocator is called instead of std::malloc. This is used in the case when the result of DIPlib functions will be passed to another imaging library, or to an interpreted language.

In both cases, dip::Image::IsExternalData returns true.

Note that when the image needs to be reforged (sizes and/or data type do not match what is required for output by some function), but the data segment is of the correct size, dip::Image::ReForge would typically re-use the data segment if it is not shared with another image. However, when the data segment is external, it is always reforged.

Create an image around existing data

One of the dip::Image constructors takes a pointer to the first pixel of a data segment (i.e. pixel buffer), and image sizes and strides, and creates a new image that references that data. The only requirement is that all pixels are aligned on boundaries given by the size of the sample data type. That is, DIPlib strides are not in bytes but in samples. The resulting image is identical to any other image in all respects, except the behavior of dip::Image::ReForge, as mentioned in the previous paragraph.

For example, in this bit of code we take the data of a std::vector and build an image around it, which we can use as both an input or an output image:

std::vector<unsigned char> src( 256 * 256, 0 ); // existing data
dip::Image img(
   NonOwnedRefToDataSegment( src.data() ),
   src.data(),    // origin
   dip::DT_UINT8, // dataType
   { 256, 256 },  // sizes
   { 1, 256 }     // strides
);
img.Protect();          // Prevent `dip::Gauss` from reallocating its output image.
dip::Gauss( img, img ); // Apply Gaussian filter, output is written to input array.

The first argument to this constructor is a dip::DataSegment object, which is just a shared pointer to void. If you want the dip::Image object to own the resources, pass a shared pointer with an appropriate deleter function. Otherwise, use the function dip::NonOwnedRefToDataSegment to create a shared pointer without a deleter function (as in the example above), indicating that ownership is not to be transferred. In the example below, we do the same as above, but we transfer ownership of the std::vector to the image. When the image goes out of scope (or is reallocated) the std::vector is deleted:

auto src = new std::vector<unsigned char>( 256 * 256, 0 ); // existing data
dip::Image img(
   dip::DataSegment{ src }, // transfer ownership of the data
   src->data(),   // origin
   dip::DT_UINT8, // dataType
   { 256, 256 },  // sizes
   { 1, 256 }     // strides
);
dip::Gauss( img, img ); // Apply Gaussian filter, output is written to a different data segment.

After the call to dip::Gauss, *src no longer exists. dip::Gauss has reforged img to be of type dip::DT_SFLOAT, triggering the deletion of object pointed to by src.

Another form of the constructor simplifies the above in the case where ownership is not transferred. It takes two or three input arguments: a pointer to the data and an array with sizes, and optionally the number of tensor elements (channels). The strides are assumed to be normal (see Normal strides). The data pointer must be of any of the allowed data types:

std::vector<unsigned char> src( 256 * 256, 0 ); // existing data
dip::Image img(
   src.data(),    // origin
   { 256, 256 }   // sizes
);

Define an image’s allocator

The the previous sub-section we saw how to encapsulate external data. The resulting image object can be used as input and output, but most DIPlib functions will reforge their output images to be of appropriate size and data type. Unless the allocated image is of suitable size and data type, and the image is protected, a new data segment will be allocated. This means that the output of the function call will not be written into the buffer we had provided.

Instead we can define an allocator class, derived from dip::ExternalInterface, and assign a pointer to an object of the allocator class to an image using dip::Image::SetExternalInterface. When that image is forged or reforged, the dip::ExternalInterface::AllocateData method is called. This method is free to allocate whatever objects it needs, and sets the image’s origin pointer and strides (much like in the previous section). As before, it is possible to set the dip::DataSegment also, so that ownership of the allocated objects is transferred to the image object. As can be seen in the DIPlib–MATLAB interface (see dml::MatlabInterface in dip_matlab_interface.h), the dip::DataSegment can contain a custom deleter function that again calls the external interface object to take care of proper object cleaning.

The external interface remains owned by the calling code, and can be linked to many images.

Note that an image with an external interface behaves differently when assigned to: usual assignment causes the source and destination images to share the data segment (i.e. no copy of samples is made); if the destination has an external interface that is different from the source’s, the samples are copied. Additionally, these images behave differently when reforged, as mentioned above.

The example below is a simple external interface that allocates data using a std::vector. The AllocateData method returns without setting the origin, indicating to the calling dip::Image::Forge function that it cannot allocate such an image. Forge will then allocate the data itself. If you prefer the forging to fail, simply throw an exception.

class VectorInterface : public dip::ExternalInterface {
   public:
      virtual dip::DataSegment AllocateData(
            void*& origin,
            dip::DataType datatype,
            dip::UnsignedArray const& sizes,
            dip::IntegerArray& strides,
            dip::Tensor const& tensor,
            dip::sint& tstride
      ) override {
         if(( sizes.size() != 2 ) || ( !tensor.IsScalar() )) {
            return nullptr; // We do not want to handle such images.
         }
         auto data = new std::vector< unsigned char >( sizes[ 0 ] * sizes[ 1 ], 0 );
         origin = data.data();
         strides = dip::UnsignedArray{ 1, sizes[ 0 ] };
         tstride = 1;
         return dip::DataSegment{ data };
      }
}

See the source code to dml::MatlabInterface for a more realistic example of this feature. That class stores the data in such a way that ownership can be retrieved from the shared pointer, so that the data array can be used by MATLAB after the dip::Image object that originally owned it has been destroyed.

Singleton expansion

When two images need to be of the same size, a process we refer to as singleton expansion is performed. First, singleton dimensions (dimensions with a size of 1) are added to the image with the fewer dimensions. This process does not require a data copy. Next, all singleton dimensions are expanded to match the size of the corresponding dimension in the other image. This expansion simply repeats the value all along that dimension, and is accomplished by setting the image’s size in that dimension to the expected value, and the corresponding stride to 0 (again, no data are physically copied). This will cause an algorithm to read the same value, no matter how many steps it takes along this dimension. For example:

dip::Image img1( dip::UnsignedArray{ 50, 1, 60 } );
dip::Image img2( dip::UnsignedArray{ 50, 30 } );
dip::Image img3 = img1 + img2;

Here, the dimension array for img2 will be extended to { 50, 30, 1 } in the first step. In the second step, the arrays for both images will be changed to { 50, 30, 60 }. img1 gets its second dimension expanded, whereas img2 will get its new third dimension expanded. The output image img3 will thus have 50x30x60 pixels.

Constructors and assignment operators

Image() defaulted
The default-initialized image is 0D (an empty sizes array), one tensor element, dip::DT_SFLOAT, and raw (it has no data segment).
Image(dip::Image&& rhs)
Move constructor, rhs ends up in default-initialized state, this even robs the external interface from rhs.
auto operator=(dip::Image const& rhs) -> dip::Image&
Copy assignment
auto operator=(dip::Image&& rhs) -> dip::Image&
Move assignment
Image(dip::UnsignedArray sizes, dip::uint tensorElems = 1, dip::DataType dt = DT_SFLOAT) explicit
Forged image of given sizes and data type. The data is left uninitialized.
Image(dip::Image::Pixel const& pixel) explicit
Create a 0-D image with the data type, tensor shape, and values of pixel.
Image(dip::Image::Pixel const& pixel, dip::DataType dt) explicit
Create a 0-D image with data type dt, and tensor shape and values of pixel.
Image(dip::Image::Sample const& sample) explicit
Create a 0-D image with the data type and value of sample.
Image(dip::Image::Sample const& sample, dip::DataType dt) explicit
Create a 0-D image with data type dt and value of sample.
Image(dip::FloatArray const& values, dip::DataType dt = DT_SFLOAT) explicit
Create a 0-D vector image with data type dt, and values of values.
Image(dip::Image::View const& view)
A dip::Image::View implicitly converts to a dip::Image.
Image(dip::Image::View&& view)
A dip::Image::View implicitly converts to a dip::Image.
Image(dip::DataSegment const& data, void* origin, dip::DataType dataType, dip::UnsignedArray sizes, dip::IntegerArray strides = {}, dip::Tensor const& tensor = {}, dip::sint tensorStride = 1, dip::ExternalInterface* externalInterface = nullptr)
Create an image around existing data.
template<typename T, typename <SFINAE>>
Image(T const* data, dip::UnsignedArray sizes, dip::uint nTensorElements = 1)
Create an image around existing data. No ownership is transferred.
auto Similar() const -> dip::Image
Create a new forged image similar to this. The data is not copied.
auto Similar(dip::DataType dt) const -> dip::Image
Create a new forged image similar to this, but with different data type. The data is not copied.

Sizes

auto Dimensionality() const -> dip::uint
Get the number of spatial dimensions.
auto Sizes() const -> dip::UnsignedArray const&
Get a const reference to the sizes array (image size).
auto Size(dip::uint dim) const -> dip::uint
Get the image size along a specific dimension, without test for dimensionality.
auto NumberOfPixels() const -> dip::uint
Get the number of pixels. Works also for a raw image, using current values of sizes.
auto NumberOfSamples() const -> dip::uint
Get the number of samples. Works also for a raw image, using current values of sizes and tensor elements.
void SetSizes(dip::UnsignedArray d)
Set the image sizes. The image must be raw.

Strides

auto Strides() const -> dip::IntegerArray const&
Get a const reference to the strides array.
auto Stride(dip::uint dim) const -> dip::sint
Get the stride along a specific dimension, without test for dimensionality.
auto TensorStride() const -> dip::sint
Get the tensor stride.
void SetStrides(dip::IntegerArray s)
Set the strides array. The image must be raw.
void SetTensorStride(dip::sint ts)
Set the tensor stride. The image must be raw.
static auto ComputeStrides(dip::UnsignedArray const& sizes, dip::uint tensorElements) -> dip::IntegerArray
Computes Normal strides given the sizes array and the number of tensor elements. Note that the tensor stride is presumed to be 1. If tensor dimension is to be sorted at the end, set tensorElements to 1.
void SetNormalStrides()
Set the strides array and tensor stride so strides are normal (see Normal strides). The image must be raw, but its sizes should be set first.
void MatchStrideOrder(dip::Image const& src)
Set the strides array and tensor stride to match the dimension order of src. The image must be raw, but its sizes should be set first.
auto HasContiguousData() const -> bool
Test if all the pixels are contiguous.
auto HasNormalStrides() const -> bool
Test if strides are as by default (see Normal strides). The image must be forged.
auto HasSingletonDimension() const -> bool
Test if any of the image dimensions is a singleton dimension (size is 1). Singleton expanded dimensions are not considered. The image must be forged.
auto IsSingletonExpanded() const -> bool
Test if the image has been singleton expanded.
auto HasSimpleStride() const -> bool
Test if the whole image can be traversed with a single stride value.
auto GetSimpleStrideAndOrigin() const -> std::pair<dip::sint, void *>
Return a single stride to walk through all pixels and pointer to the start of the data.
auto HasSameDimensionOrder(dip::Image const& other) const -> bool
Checks to see if other and this have their dimensions ordered in the same way.

Tensor

auto TensorSizes() const -> dip::UnsignedArray
Get the tensor sizes. The array returned can have 0, 1 or 2 elements, as those are the allowed tensor dimensionalities.
auto TensorElements() const -> dip::uint
Get the number of tensor elements (i.e. the number of samples per pixel), the product of the elements in the array returned by TensorSizes.
auto TensorColumns() const -> dip::uint
Get the number of tensor columns.
auto TensorRows() const -> dip::uint
Get the number of tensor rows.
auto TensorShape() const -> dip::Tensor::Shape
Get the tensor shape.
auto Tensor() const -> dip::Tensor const&
Get the tensor shape.
auto IsScalar() const -> bool
True for non-tensor (grey-value) images.
auto IsVector() const -> bool
True for vector images, where the tensor is one-dimensional.
auto IsSquare() const -> bool
True for square matrix images, independent from how they are stored.
void SetTensorSizes(dip::UnsignedArray const& tdims)
Set tensor sizes. The image must be raw.
void SetTensorSizes(dip::uint nelems)
Set tensor sizes. The image must be raw.

Data type

auto DataType() const -> dip::DataType
Get the image’s data type.
void SetDataType(dip::DataType dt)
Set the image’s data type. The image must be raw.

Color space

auto ColorSpace() const -> dip::String const&
Get the image’s color space name.
auto IsColor() const -> bool
Returns true if the image is in color, false if the image is grey-valued.
void SetColorSpace(dip::String cs)
Sets the image’s color space name. This causes the image to be a color image, but will cause errors to occur (eventually, not immediately) if the number of tensor elements does not match the expected number of channels for the given color space.
void ResetColorSpace()
Resets the image’s color space information, turning the image into a non-color image.

Pixel size

auto PixelSize() -> dip::PixelSize&
Get the pixels’ size in physical units, by reference, allowing to modify it at will.
auto PixelSize() const -> dip::PixelSize const&
Get the pixels’ size in physical units.
auto PixelSize(dip::uint dim) const -> dip::PhysicalQuantity
Get the pixels’ size along the given dimension in physical units.
void SetPixelSize(dip::PixelSize ps)
Set the pixels’ size in physical units.
void SetPixelSize(dip::uint dim, dip::PhysicalQuantity sz)
Set the pixels’ size along the given dimension in physical units.
void ResetPixelSize()
Reset the pixels’ size, so that HasPixelSize returns false.
auto HasPixelSize() const -> bool
Returns true if the pixel has physical dimensions.
auto IsIsotropic() const -> bool
Returns true if the pixel has the same size in all dimensions.
auto AspectRatio() const -> dip::FloatArray
Returns an array with aspect ratios: [1, y/x, z/x, …]. If dimensions don’t match, returns 0 for that dimension.
auto PixelsToPhysical(dip::FloatArray const& in) const -> dip::PhysicalQuantityArray
Converts a size in pixels to a size in physical units.
auto PhysicalToPixels(dip::PhysicalQuantityArray const& in) const -> dip::FloatArray
Converts a size in physical units to a size in pixels.

Utility functions

auto CompareProperties(dip::Image const& src, dip::Option::CmpPropFlags cmpProps, dip::Option::ThrowException throwException = Option::ThrowException::DO_THROW) const -> bool
Compare properties of an image against a template, either returns true/false or throws an error.
auto CheckProperties(dip::uint ndims, dip::DataType::Classes dts, dip::Option::ThrowException throwException = Option::ThrowException::DO_THROW) const -> bool
Check image properties, either returns true/false or throws an error.
auto CheckProperties(dip::uint ndims, dip::uint tensorElements, dip::DataType::Classes dts, dip::Option::ThrowException throwException = Option::ThrowException::DO_THROW) const -> bool
Check image properties, either returns true/false or throws an error.
auto CheckProperties(dip::UnsignedArray const& sizes, dip::DataType::Classes dts, dip::Option::ThrowException throwException = Option::ThrowException::DO_THROW) const -> bool
Check image properties, either returns true/false or throws an error.
auto CheckProperties(dip::UnsignedArray const& sizes, dip::uint tensorElements, dip::DataType::Classes dts, dip::Option::ThrowException throwException = Option::ThrowException::DO_THROW) const -> bool
Check image properties, either returns true/false or throws an error.
auto CheckIsMask(dip::UnsignedArray const& sizes, dip::Option::AllowSingletonExpansion allowSingletonExpansion = Option::AllowSingletonExpansion::DONT_ALLOW, dip::Option::ThrowException throwException = Option::ThrowException::DO_THROW) const -> bool
Check image properties for a mask image, either returns true/false or throws an error.
void CopyProperties(dip::Image const& src)
Copy all image properties from src, including strides. The image must be raw.
void CopyNonDataProperties(dip::Image const& src)
Copy non-data image properties from src.
void ResetNonDataProperties()
Reset non-data image properties.
void swap(dip::Image& other) noexcept
Swaps this and other.

Data

auto Data() const -> void*
Get pointer to the data segment.
auto IsShared() const -> bool
Check to see if the data segment is shared with other images.
auto ShareCount() const -> dip::uint
Get the number of images that share their data with this image.
auto SharesData(dip::Image const& other) const -> bool
Determine if this shares its data pointer with other.
auto IsExternalData() const -> bool
Returns true if the data segment was not allocated by DIPlib. See Controlling data segment allocation.
auto Aliases(dip::Image const& other) const -> bool
Determine if this shares any samples with other.
auto IsIdenticalView(dip::Image const& other) const -> bool
Determine if this and other offer an identical view of the same set of pixels.
auto IsOverlappingView(dip::Image const& other) const -> bool
Determine if this and other offer different views of the same data segment, and share at least one sample.
auto IsOverlappingView(dip::ImageConstRefArray const& other) const -> bool
Determine if this and any of those in other offer different views of the same data segment, and share at least one sample.
auto IsOverlappingView(dip::ImageArray const& other) const -> bool
Determine if this and any of those in other offer different views of the
void Forge()
Allocate data segment.
void ReForge(dip::Image const& src, dip::Option::AcceptDataTypeChange acceptDataTypeChange = Option::AcceptDataTypeChange::DONT_ALLOW)
Modify image properties and forge the image.
void ReForge(dip::Image const& src, dip::DataType dt, dip::Option::AcceptDataTypeChange acceptDataTypeChange = Option::AcceptDataTypeChange::DONT_ALLOW)
Modify image properties and forge the image.
void ReForge(dip::UnsignedArray const& sizes, dip::uint tensorElems = 1, dip::DataType dt = DT_SFLOAT, dip::Option::AcceptDataTypeChange acceptDataTypeChange = Option::AcceptDataTypeChange::DONT_ALLOW)
Modify image properties and forge the image.
void Strip()
Disassociate the data segment from the image. If there are no other images using the same data segment, it will be freed. Throws if the image is protected and has a data segment.
auto IsForged() const -> bool
Test if forged.
auto Protect(bool set = true) -> bool
Set protection flag.
auto Unprotect() -> bool
Reset protection flag. Alias for Protect(false).
auto IsProtected() const -> bool
Test if protected. See dip::Image::Protect for information.
void SetExternalInterface(dip::ExternalInterface* ei)
Set external interface pointer. The image must be raw. See Define an image’s allocator.
void ResetExternalInterface()
Remove external interface pointer. The image behaves like a native one (for assignment, reforging, etc.), but the current pixel buffer (if forged) is not affected. See Define an image’s allocator.
auto ExternalInterface() const -> dip::ExternalInterface*
Get external interface pointer. See Define an image’s allocator
auto HasExternalInterface() const -> bool
Test if an external interface is set. See Define an image’s allocator

Pointers, offsets, indices

auto Origin() const -> void*
Get pointer to the first sample in the image, the first tensor element at coordinates (0,0,0,…). The image must be forged.
auto Pointer(dip::sint offset) const -> void*
Get a pointer to the pixel given by the offset.
auto Pointer(dip::UnsignedArray const& coords) const -> void*
Get a pointer to the pixel given by the coordinates index.
auto Pointer(dip::IntegerArray const& coords) const -> void*
Get a pointer to the pixel given by the coordinates index.
auto IsOnEdge(dip::UnsignedArray const& coords) const -> bool
Return true if the coordinates are on the image edge.
template<typename CoordType>
auto IsInside(dip::DimensionArray const& coords) const -> bool
Returns whether the coordinates are inside the image
static auto Offset(dip::UnsignedArray const& coords, dip::IntegerArray const& strides, dip::UnsignedArray const& sizes) -> dip::sint
Compute offset given coordinates and strides.
static auto Offset(dip::IntegerArray const& coords, dip::IntegerArray const& strides) -> dip::sint
Compute offset given coordinates.
auto Offset(dip::UnsignedArray const& coords) const -> dip::sint
Compute offset given coordinates.
auto Offset(dip::IntegerArray const& coords) const -> dip::sint
Compute offset given coordinates.
auto OffsetToCoordinates(dip::sint offset) const -> dip::UnsignedArray
Compute coordinates given an offset.
auto OffsetToCoordinatesComputer() const -> dip::CoordinatesComputer
Returns a functor that computes coordinates given an offset.
static auto Index(dip::UnsignedArray const& coords, dip::UnsignedArray const& sizes) -> dip::uint
Compute linear index (not offset) given coordinates and image sizes.
auto Index(dip::UnsignedArray const& coords) const -> dip::uint
Compute linear index (not offset) given coordinates.
auto IndexToCoordinates(dip::uint index) const -> dip::UnsignedArray
Compute coordinates given a linear index.
auto IndexToCoordinatesComputer() const -> dip::CoordinatesComputer
Returns a functor that computes coordinates given a linear index.
auto GetCenter(dip::String const& mode = "right") const -> dip::FloatArray
Returns the coordinates for the center of the image.

Reshaping forged image

auto PermuteDimensions(dip::UnsignedArray const& order) -> dip::Image&
Permute dimensions.
auto SwapDimensions(dip::uint dim1, dip::uint dim2) -> dip::Image&
Swap dimensions d1 and d2. This is a simplified version of PermuteDimensions.
auto ReverseDimensions() -> dip::Image&
Reverses the dimensions, such that indexing switches from (x,y,z) to (z,y,x).
auto Flatten() -> dip::Image&
Make image 1D.
auto FlattenAsMuchAsPossible() -> dip::Image&
Make image have as few dimensions as possible.
auto SplitDimension(dip::uint dim, dip::uint size) -> dip::Image&
Splits a dimension into two.
auto Squeeze(dip::UnsignedArray& dims) -> dip::Image&
Remove singleton dimensions (dimensions with size==1).
auto Squeeze() -> dip::Image&
Remove singleton dimensions (dimensions with size==1).
auto Squeeze(dip::uint dim) -> dip::Image&
Remove singleton dimension dim (has size==1).
auto AddSingleton(dip::uint dim) -> dip::Image&
Add a singleton dimension (with size==1) to the image.
auto AddSingleton(dip::UnsignedArray const& dims) -> dip::Image&
Add a singleton dimensions (with size==1) to the image.
auto ExpandDimensionality(dip::uint dim) -> dip::Image&
Append singleton dimensions to increase the image dimensionality.
auto ExpandSingletonDimension(dip::uint dim, dip::uint sz) -> dip::Image&
Expand singleton dimension dim to sz pixels, setting the corresponding stride to 0.
auto ExpandSingletonDimensions(dip::UnsignedArray const& newSizes) -> dip::Image&
Performs singleton expansion.
auto UnexpandSingletonDimensions() -> dip::Image&
Unexpands singleton-expanded dimensions.
auto UnexpandSingletonDimension(dip::uint dim) -> dip::Image&
Unexpands a singleton-expanded dimension.
auto IsSingletonExpansionPossible(dip::UnsignedArray const& newSizes) const -> bool
Tests if the image can be singleton-expanded to size.
auto ExpandSingletonTensor(dip::uint sz) -> dip::Image&
Expand singleton tensor dimension sz samples, setting the tensor stride to 0.
auto UnexpandSingletonTensor() -> dip::Image&
Unexpands the singleton-expanded tensor dimension.
auto Mirror(dip::uint dimension) -> dip::Image&
Mirror the image about a single axes.
auto Mirror(dip::BooleanArray process = {}) -> dip::Image&
Mirror the image about selected axes.
auto Rotation90(dip::sint n, dip::uint dimension1, dip::uint dimension2) -> dip::Image&
Rotates the image by n times 90 degrees, in the plane defined by dimensions dimension1 and dimension2.
auto Rotation90(dip::sint n, dip::uint axis) -> dip::Image&
Rotates the 3D image by n times 90 degrees, in the plane perpendicular to dimension axis.
auto Rotation90(dip::sint n = 1) -> dip::Image&
Rotates the image by n times 90 degrees, in the x-y plane.
auto StandardizeStrides() -> dip::Image&
Undo the effects of Mirror, Rotation90, PermuteDimensions, and singleton expansion. Also removes singleton dimensions.
static auto StandardizeStrides(dip::IntegerArray& strides, dip::UnsignedArray& sizes) -> std::pair<UnsignedArray, dip::sint>
Transforms input arrays and outputs ordering required to standardize an image’s strides.
auto ReshapeTensor(dip::uint rows, dip::uint cols) -> dip::Image&
Change the tensor shape, without changing the number of tensor elements.
auto ReshapeTensor(dip::Tensor const& example) -> dip::Image&
Change the tensor shape, without changing the number of tensor elements.
auto ReshapeTensorAsVector() -> dip::Image&
Change the tensor to a vector, without changing the number of tensor elements.
auto ReshapeTensorAsDiagonal() -> dip::Image&
Change the tensor to a diagonal matrix, without changing the number of tensor elements.
auto Transpose() -> dip::Image&
Transpose the tensor.
auto TensorToSpatial(dip::uint dim) -> dip::Image&
Convert tensor dimensions to spatial dimension.
auto SpatialToTensor(dip::uint dim, dip::uint rows, dip::uint cols) -> dip::Image&
Convert spatial dimension to tensor dimensions. The image must be scalar.
auto SplitComplex(dip::uint dim) -> dip::Image&
Split the two values in a complex sample into separate samples, creating a new spatial dimension of size 2.
auto MergeComplex(dip::uint dim) -> dip::Image&
Merge the two samples along dimension dim into a single complex-valued sample.
auto SplitComplexToTensor() -> dip::Image&
Split the two values in a complex sample into separate samples of a tensor. The image must be scalar and forged.
auto MergeTensorToComplex() -> dip::Image&
Merge the two samples in the tensor into a single complex-valued sample.
auto ReinterpretCast(dip::DataType dataType) -> dip::Image&
Changes the data type of this without changing or copying the data.
auto ReinterpretCastToSignedInteger() -> dip::Image&
Changes the data type of this to a signed integer of the same size, without changing the data.
auto ReinterpretCastToUnsignedInteger() -> dip::Image&
Changes the data type of this to an unsigned integer of the same size, without changing the data.
auto Crop(dip::UnsignedArray const& sizes, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER) -> dip::Image&
Reduces the size of the image by cropping off the borders.
auto Crop(dip::UnsignedArray const& sizes, dip::String const& cropLocation) -> dip::Image&
Reduces the size of the image by cropping off the borders.
auto CropWindow(dip::UnsignedArray const& sizes, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER) const -> dip::RangeArray
Returns the dip::RangeArray indexing data that corresponds to the result of dip::Image::Crop.
auto CropWindow(dip::UnsignedArray const& sizes, dip::String const& cropLocation) const -> dip::RangeArray
Returns the dip::RangeArray indexing data that corresponds to the result of dip::Image::Crop.
static auto CropWindow(dip::UnsignedArray const& imageSizes, dip::UnsignedArray const& windowSizes, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER) -> dip::RangeArray
Returns the dip::RangeArray indexing data that corresponds to the result of dip::Image::Crop, for an image of size imageSizes.
static auto CropWindow(dip::UnsignedArray const& imageSizes, dip::UnsignedArray const& windowSizes, dip::String const& cropLocation) -> dip::RangeArray
Returns the dip::RangeArray indexing data that corresponds to the result of dip::Image::Crop, for an image of size imageSizes.

Indexing without data copy

auto operator[](dip::UnsignedArray const& indices) const -> dip::Image::View
Extract a tensor element, indices must have one or two elements. The image must be forged.
template<typename T, typename <SFINAE>>
auto operator[](T index) const -> dip::Image::View
Extract a tensor element using linear indexing. Negative indices start at the end. The image must be forged.
auto operator[](dip::Range range) const -> dip::Image::View
Extract tensor elements using linear indexing. The image must be forged.
auto Diagonal() const -> dip::Image::View
Extracts the tensor elements along the diagonal. The image must be forged.
auto TensorRow(dip::uint index) const -> dip::Image::View
Extracts the tensor elements along the given row. The image must be forged and the tensor representation must be full (i.e. no symmetric or triangular matrices). Use dip::Image::ExpandTensor to obtain a full representation.
auto TensorColumn(dip::uint index) const -> dip::Image::View
Extracts the tensor elements along the given column. The image must be forged and the tensor representation must be full (i.e. no symmetric or triangular matrices). Use dip::Image::ExpandTensor to obtain a full representation.
auto At(dip::UnsignedArray const& coords) const -> dip::Image::Pixel
Extracts the pixel at the given coordinates. The image must be forged.
template<typename T>
auto At(dip::UnsignedArray const& coords) const -> dip::Image::CastPixel
Same as above, but returns a type that implicitly casts to T.
auto At(dip::uint index) const -> dip::Image::Pixel
Extracts the pixel at the given linear index (inefficient if image is not 1D!). The image must be forged.
template<typename T>
auto At(dip::uint index) const -> dip::Image::CastPixel
Same as above, but returns a type that implicitly casts to T.
auto At(dip::uint x_index, dip::uint y_index) const -> dip::Image::Pixel
Extracts the pixel at the given coordinates from a 2D image. The image must be forged.
template<typename T>
auto At(dip::uint x_index, dip::uint y_index) const -> dip::Image::CastPixel
Same as above, but returns a type that implicitly casts to T.
auto At(dip::uint x_index, dip::uint y_index, dip::uint z_index) const -> dip::Image::Pixel
Extracts the pixel at the given coordinates from a 3D image. The image must be forged.
template<typename T>
auto At(dip::uint x_index, dip::uint y_index, dip::uint z_index) const -> dip::Image::CastPixel
Same as above, but returns a type that implicitly casts to T.
auto begin() -> dip::GenericImageIterator
Returns an iterator to the first pixel in the image. Include diplib/generic_iterators.h to use this.
static auto end() -> dip::GenericImageIterator
Returns an iterator to the end of the iterator range. It cannot be dereferenced or manipulated, and is meant solely as an end-of-iteration marker.
auto At(dip::Range const& x_range) const -> dip::Image::View
Extracts a subset of pixels from a 1D image. The image must be forged.
auto At(dip::Range const& x_range, dip::Range const& y_range) const -> dip::Image::View
Extracts a subset of pixels from a 2D image. The image must be forged.
auto At(dip::Range const& x_range, dip::Range const& y_range, dip::Range const& z_range) const -> dip::Image::View
Extracts a subset of pixels from a 3D image. The image must be forged.
auto At(dip::RangeArray ranges) const -> dip::Image::View
Extracts a subset of pixels from an image. The image must be forged.
auto At(dip::Image mask) const -> dip::Image::View
Creates a 1D image view containing the pixels selected by mask.
auto At(dip::CoordinateArray const& coordinates) const -> dip::Image::View
Creates a 1D image view containing the pixels selected by coordinates.
auto AtIndices(dip::UnsignedArray const& indices) const -> dip::Image::View
Creates a 1D image view containing the pixels selected by indices.
auto Cropped(dip::UnsignedArray const& sizes, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER) const -> dip::Image::View
Extracts a subset of pixels from an image.
auto Cropped(dip::UnsignedArray const& sizes, dip::String const& cropLocation) const -> dip::Image::View
Extracts a subset of pixels from an image.
auto Real() const -> dip::Image::View
Extracts the real component of a complex-typed image. The image must be forged.
auto Imaginary() const -> dip::Image::View
Extracts the imaginary component of a complex-typed image. The image must be forged and complex-valued.
auto AsScalar(dip::uint dim) const -> dip::Image::View
Creates a scalar view of the image, where the tensor dimension is converted to a new spatial dimension. See dip::Image::TensorToSpatial.
auto AsScalar() const -> dip::Image::View
\overload
auto QuickCopy() const -> dip::Image
Quick copy, returns a new image that points at the same data as this, and has mostly the same properties.

Setting pixel values, copying

auto Pad(dip::UnsignedArray const& sizes, dip::Image::Pixel const& value, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER) const -> dip::Image
Extends the image by padding with value.
auto Pad(dip::UnsignedArray const& sizes, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER) const -> dip::Image
Extends the image by padding with zeros, overload for function above with value equal to 0.
auto Pad(dip::UnsignedArray const& sizes, dip::Image::Pixel const& value, dip::String const& cropLocation) const -> dip::Image
Extends the image by padding with value.
auto Pad(dip::UnsignedArray const& sizes, dip::String const& cropLocation) const -> dip::Image
Extends the image by padding with zeros, overload for function above with value equal to 0.
void Copy(dip::Image const& src)
Deep copy, this will become a copy of src with its own data.
void Copy(dip::Image::View const& src)
Idem as above, but with a dip::Image::View as input.
auto Copy() const -> dip::Image
Deep copy, returns a copy of this with its own data.
void Convert(dip::DataType dt)
Converts the image to another data type.
void SwapBytesInSample()
Swaps bytes in each sample, converting from little endian to big endian or vice versa.
void ExpandTensor()
Expands the image’s tensor, such that the tensor representation is a column-major matrix.
void ForceNormalStrides()
Copies pixel data over to a new data segment if the strides are not normal.
void ForceContiguousData()
Copies pixel data over to a new data segment if the data is not contiguous.
void Separate()
If the image shares its data segment with another image, create a data copy so it no longer shares data.
void Fill(dip::Image::Pixel const& pixel)
Sets all pixels in the image to the value pixel.
void Fill(dip::Image::Sample const& sample)
Sets all samples in the image to the value sample.
auto operator=(dip::Image::Pixel const& pixel) -> dip::Image&
Sets all pixels in the image to the value pixel.
auto operator=(dip::Image::Sample const& sample) -> dip::Image&
Sets all samples in the image to the value sample.
template<typename T, typename <SFINAE>>
auto As() const -> T
Returns the value of the first sample in the first pixel in the image as the given numeric type.
auto operator FloatArray() const -> dip::FloatArray
Returns a FloatArray containing the sample values of the first pixel in the image. For a complex-valued image, the modulus (absolute value) is returned.
void Mask(dip::Image const& mask)
Sets all pixels not in mask to zero. img.Mask(mask) is equivalent to img.At(~mask).Fill(0), but without creating an intermediate copy of mask. Can also be expressed as img *= mask.

Classes

class Sample
A sample represents a single numeric value in an image, see Image representation.
class Pixel
A pixel represents a set of numeric value in an image, see Image representation.
template<typename T>
class CastSample
Derived from dip::Image::Sample, works identically except it implicitly converts to type T. \relates dip::Image::Sample
template<typename T>
class CastPixel
Derived from dip::Image::Pixel, works identically except it implicitly converts to type T. \relates dip::Image::Pixel
class View
A view represents a subset of samples in an image. It can be assigned to to change those samples.

Functions

void CopyDataToNewDataSegment() private
Allocates a new data segment and copies the data over. The image will be the same as before, but have Normal strides and not share data with another image.

Function documentation

dip::Image& operator=(dip::Image const& rhs)

Copy assignment

Copies the data if the LHS (this) is protected or has an external interface set, and this external interface is different from the one in rhs (see The “protect” flag and Define an image’s allocator). In this case, rhs will not be modified.

Otherwise, this and rhs will share the data segment. See Creation, assignment and copy.

The protect flag will not be copied over.

dip::Image& operator=(dip::Image&& rhs)

Move assignment

Copies the data if the LHS (this) is protected or has an external interface set, and this external interface is different from the one in rhs (see The “protect” flag and Define an image’s allocator). In this case, rhs will not be modified. Note that this copy can throw.

Otherwise, this will become exactly what rhs was, and rhs will become raw.

Image(dip::UnsignedArray sizes, dip::uint tensorElems = 1, dip::DataType dt = DT_SFLOAT) explicit

Forged image of given sizes and data type. The data is left uninitialized.

Note that to call this constructor with a single parameter, you need to explicitly type the parameter, an initializer list by itself will be considered a pixel, see the constructor below.

The data segment is not initialized, use dip::Image::Fill to set it to constant value.

Image(dip::Image::Pixel const& pixel) explicit

Create a 0-D image with the data type, tensor shape, and values of pixel.

Note that pixel can be created through an initializer list. Thus, the following is a valid way of creating a 0-D tensor image with 3 tensor components:

dip::Image image( { 10.0f, 1.0f, 0.0f } );

The image in the example above will be of type dip::DT_SFLOAT.

Image(dip::Image::Pixel const& pixel, dip::DataType dt) explicit

Create a 0-D image with data type dt, and tensor shape and values of pixel.

Note that pixel can be created through an initializer list. Thus, the following is a valid way of creating a 0-D tensor image with 3 tensor components:

dip::Image image( { 10, 1, 0 }, dip::DT_SFLOAT );

The image in the example above will be of type dip::DT_SFLOAT.

Image(dip::Image::Sample const& sample) explicit

Create a 0-D image with the data type and value of sample.

Note that sample can be created by implicit cast from any numeric value. Thus, the following are valid ways of creating a 0-D image:

dip::Image image( 10.0f );
dip::Image complex_image( dip::dcomplex( 3, 4 ));

The images in the examples above will be of type dip::DT_SFLOAT and dip::DT_DCOMPLEX.

Image(dip::Image::Sample const& sample, dip::DataType dt) explicit

Create a 0-D image with data type dt and value of sample.

Note that sample can be created by implicit cast from any numeric value. Thus, the following is a valid way of creating a 0-D image:

dip::Image image( 10, dip::DT_SFLOAT );

The image in the example above will be of type dip::DT_SFLOAT.

Image(dip::FloatArray const& values, dip::DataType dt = DT_SFLOAT) explicit

Create a 0-D vector image with data type dt, and values of values.

Note that if values is specified as an initializer list, the constructor dip::Image::Image is called instead.

Note also that this constructor is specifically with a FloatArray. If the array is of type UnsignedArray, a different constructor will be called, and the array will be interpreted as image sizes, not sample values.

Image(dip::DataSegment const& data, void* origin, dip::DataType dataType, dip::UnsignedArray sizes, dip::IntegerArray strides = {}, dip::Tensor const& tensor = {}, dip::sint tensorStride = 1, dip::ExternalInterface* externalInterface = nullptr)

Create an image around existing data.

data is a shared pointer used to manage the lifetime of the data segment. If the image is supposed to take ownership, put a pointer to the data segment or the object that owns it in data, with a deleter function that will delete the data segment or object when the image is stripped or deleted. Otherwise, use dip::NonOwnedRefToDataSegment to create a shared pointer without a deleter function, implying ownership is not transferred.

origin is the pointer to the first pixel. It must be a valid pointer. This is typically, but not necessarily, the same pointer as used in data.

dataType and sizes must be set appropriately. strides must either have the same number of elements as sizes, or be an empty array. If strides is an empty array, Normal strides will be assumed. In this case, tensorStride will be ignored. tensor defaults to scalar (i.e. a single tensor element). No tests will be performed on the validity of the values passed in, except to enforce a few class invariants.

See Define an image’s allocator for information about the externalInterface parameter.

See Create an image around existing data for more information on how to use this constructor.

See the next constructor for a simplified interface to this constructor.

template<typename T, typename <SFINAE>>
Image(T const* data, dip::UnsignedArray sizes, dip::uint nTensorElements = 1)

Create an image around existing data. No ownership is transferred.

data is a raw pointer to the data that will be encapsulated by the output image. Normal strides will be assumed. That is, the data is contiguous and in row-major order, with the channels interleaved. sizes indicates the size of each dimension in the data, and nTensorElements the number of channels. data must point to a buffer that is at least sizes.product() * nTensorElements elements long.

To encapsulate data in a different format, or to transfer ownership of the data to the image, see the previous constructor.

See Create an image around existing data for more information on how to use this function.

dip::Image Similar() const

Create a new forged image similar to this. The data is not copied.

The data segment is not initialized, use dip::Image::Fill to set it to constant value.

dip::Image Similar(dip::DataType dt) const

Create a new forged image similar to this, but with different data type. The data is not copied.

The data segment is not initialized, use dip::Image::Fill to set it to constant value.

bool HasContiguousData() const

Test if all the pixels are contiguous.

If all pixels are contiguous, you can traverse the whole image, accessing each of the pixels, using a single stride with a value of 1. To do so, you don’t necessarily start at the origin: if any of the strides is negative, the origin of the contiguous data will be elsewhere. Use dip::Image::GetSimpleStrideAndOrigin to get a pointer to the origin of the contiguous data.

The image must be forged.

bool IsSingletonExpanded() const

Test if the image has been singleton expanded.

If any dimension is larger than 1, but has a stride of 0, it means that a single pixel is being used across that dimension. The methods dip::Image::ExpandSingletonDimension and dip::Image::ExpandSingletonTensor create such dimensions.

The image must be forged.

bool HasSimpleStride() const

Test if the whole image can be traversed with a single stride value.

This is similar to dip::Image::HasContiguousData, but the stride value can be larger than 1. Use dip::Image::GetSimpleStrideAndOrigin to get a pointer to the origin of the contiguous data. Note that this only tests spatial dimensions, the tensor dimension must still be accessed separately.

The image must be forged.

std::pair<dip::sint, void *> GetSimpleStrideAndOrigin() const

Return a single stride to walk through all pixels and pointer to the start of the data.

If this is not possible, the function returns nullptr for the pointer. Note that this only tests spatial dimensions, the tensor dimension must still be accessed separately.

dip::sint stride;
void* origin;
std::tie( stride, origin ) = img.GetSimpleStrideAndOrigin();

The stride returned is always positive.

The image must be forged.

bool HasSameDimensionOrder(dip::Image const& other) const

Checks to see if other and this have their dimensions ordered in the same way.

Traversing more than one image using simple strides is only possible if they have their dimensions ordered in the same way, otherwise the simple stride does not visit the pixels in the same order in the various images.

The images must be forged.

dip::PixelSize& PixelSize()

Get the pixels’ size in physical units, by reference, allowing to modify it at will.

There are other Image methods that can be used to modify the pixel size, and might be simpler. For example:

img.PixelSize() = ps;                   img.SetPixelSize(ps);
img.PixelSize().Set(dim,sz);            img.SetPixelSize(dim,sz);
img.PixelSize().Clear();                img.ResetPixelSize();

Also for querying the pixel size there are several Image methods:

pq = img.PixelSize()[dim];              pq = img.PixelSize(dim);
bd = img.PixelSize().IsDefined();       bd = img.HasPixelSize();
bi = img.PixelSize().IsIsotropic();     bi = img.IsIsotropic();
ar = img.PixelSize().AspectRatio(img.Dimensionality());
                                        ar = img.AspectRatio();

void CopyNonDataProperties(dip::Image const& src)

Copy non-data image properties from src.

The non-data image properties are those that do not influence how the data is stored in memory: tensor shape, color space, and pixel size. The number of tensor elements of the the two images must match. The image must be forged.

void ResetNonDataProperties()

Reset non-data image properties.

The non-data image properties are those that do not influence how the data is stored in memory: tensor shape, color space, and pixel size.

void* Data() const

Get pointer to the data segment.

This is useful to identify the data segment, but not to access the pixel data stored in it. Use dip::Image::Origin instead. The image must be forged.

The pointer returned could be tangentially related to the data segment, if dip::Image::IsExternalData is true.

bool IsShared() const

Check to see if the data segment is shared with other images.

dip::uint ShareCount() const

Get the number of images that share their data with this image.

For normal images the count is always at least 1. If the count is larger than 1, dip::Image::IsShared is true.

If this encapsulates external data (dip::Image::IsExternalData is true), then the share count is not necessarily correct, as it might not count the uses of the source data.

The image must be forged.

bool SharesData(dip::Image const& other) const

Determine if this shares its data pointer with other.

Note that sharing the data pointer does not imply that the two images share any pixel data, as it is possible for the two images to represent disjoint windows into the same data block. To determine if any pixels are shared, use dip::Image::Aliases.

bool Aliases(dip::Image const& other) const

Determine if this shares any samples with other.

If true, writing into this image will change the data in other, and vice-versa.

bool IsIdenticalView(dip::Image const& other) const

Determine if this and other offer an identical view of the same set of pixels.

If true, changing one sample in this image will change the same sample in other.

bool IsOverlappingView(dip::Image const& other) const

Determine if this and other offer different views of the same data segment, and share at least one sample.

If true, changing one sample in this image might change a different sample in other. An image with an overlapping view of an input image cannot be used as output to a filter, as it might change input data that still needs to be used. Use this function to test whether to use the existing data segment or allocate a new one.

Note that this function returns false if the two images offer the same view of the same data segment.

bool IsOverlappingView(dip::ImageConstRefArray const& other) const

Determine if this and any of those in other offer different views of the same data segment, and share at least one sample.

If true, changing one sample in this image might change a different sample in at least one image in other. An image with an overlapping view of an input image cannot be used as output to a filter, as it might change input data that still needs to be used. Use this function to test whether to use the existing data segment or allocate a new one.

bool IsOverlappingView(dip::ImageArray const& other) const

Determine if this and any of those in other offer different views of the

same data segment, and share at least one sample.

If true, changing one sample in this image might change a different sample in at least one image in other. An image with an overlapping view of an input image cannot be used as output to a filter, as it might change input data that still needs to be used. Use this function to test whether to use the existing data segment or allocate a new one.

void Forge()

Allocate data segment.

This function allocates a memory block to hold the pixel data. If the stride array is consistent with size array, and leads to a compact data segment, it is honored. Otherwise, it is ignored and a new stride array is created that leads to an image that has Normal strides. If an external interface is registered for this image, that interface may create whatever strides are suitable, may honor or not the existing stride array, and may or may not produce normal strides.

The data segment is not initialized, use dip::Image::Fill to set it to constant value.

void ReForge(dip::Image const& src, dip::Option::AcceptDataTypeChange acceptDataTypeChange = Option::AcceptDataTypeChange::DONT_ALLOW)

Modify image properties and forge the image.

ReForge has three signatures that match three image constructors. ReForge will try to avoid freeing the current data segment and allocating a new one. This version will cause this to be an identical copy of src, but with uninitialized data. The external interface of src is not used, nor are its strides.

If this doesn’t match the requested properties, it must be stripped and forged. If this is protected (see dip::Image::Protect) and forged, an exception will be thrown by dip::Image::Strip. However, if acceptDataTypeChange is dip::Option::AcceptDataTypeChange::DO_ALLOW, a protected image will keep its old data type, and no exception will be thrown if this data type is different from dt. Note that other properties much still match if this was forged. Thus, this flag allows this to control the data type of the image, ignoring any requested data type here.

The data segment is not initialized, use dip::Image::Fill to set it to constant value.

void ReForge(dip::Image const& src, dip::DataType dt, dip::Option::AcceptDataTypeChange acceptDataTypeChange = Option::AcceptDataTypeChange::DONT_ALLOW)

Modify image properties and forge the image.

ReForge has three signatures that match three image constructors. ReForge will try to avoid freeing the current data segment and allocating a new one. This version will cause this to be an identical copy of src, but with a different data type and uninitialized data. The external interface of src is not used, nor are its strides.

If this doesn’t match the requested properties, it must be stripped and forged. If this is protected (see dip::Image::Protect) and forged, an exception will be thrown by dip::Image::Strip. However, if acceptDataTypeChange is dip::Option::AcceptDataTypeChange::DO_ALLOW, a protected image will keep its old data type, and no exception will be thrown if this data type is different from dt. Note that other properties much still match if this was forged. Thus, this flag allows this to control the data type of the image, ignoring any requested data type here.

The data segment is not initialized, use dip::Image::Fill to set it to constant value.

void ReForge(dip::UnsignedArray const& sizes, dip::uint tensorElems = 1, dip::DataType dt = DT_SFLOAT, dip::Option::AcceptDataTypeChange acceptDataTypeChange = Option::AcceptDataTypeChange::DONT_ALLOW)

Modify image properties and forge the image.

ReForge has three signatures that match three image constructors. ReForge will try to avoid freeing the current data segment and allocating a new one. This version will cause this to be of the requested sizes and data type.

If this doesn’t match the requested properties, it must be stripped and forged. If this is protected (see dip::Image::Protect) and forged, an exception will be thrown by dip::Image::Strip. However, if acceptDataTypeChange is dip::Option::AcceptDataTypeChange::DO_ALLOW, a protected image will keep its old data type, and no exception will be thrown if this data type is different from dt. Note that other properties much still match if this was forged. Thus, this flag allows this to control the data type of the image, ignoring any requested data type here.

The data segment is not initialized, use dip::Image::Fill to set it to constant value.

bool Protect(bool set = true)

Set protection flag.

A protected image cannot be stripped or reforged. See The “protect” flag for more information.

Returns the old setting. This can be used as follows to temporarily protect an image:

bool wasProtected = img.Protect();
[...] // do your thing
img.Protect( wasProtected );

void* Pointer(dip::sint offset) const

Get a pointer to the pixel given by the offset.

Cast the pointer to the right type before use. No check is made on the index.

The image must be forged.

void* Pointer(dip::UnsignedArray const& coords) const

Get a pointer to the pixel given by the coordinates index.

Cast the pointer to the right type before use. This is not the most efficient way of indexing many pixels in the image.

If coords is not within the image domain, an exception is thrown.

The image must be forged.

void* Pointer(dip::IntegerArray const& coords) const

Get a pointer to the pixel given by the coordinates index.

Cast the pointer to the right type before use. This is not the most efficient way of indexing many pixels in the image.

coords can be outside the image domain.

The image must be forged.

bool IsOnEdge(dip::UnsignedArray const& coords) const

Return true if the coordinates are on the image edge.

Coordinates on the image edge are such that at least one neighboring coordinates (direct neighbor) is outside the image domain.

The image must be forged.

static dip::sint Offset(dip::UnsignedArray const& coords, dip::IntegerArray const& strides, dip::UnsignedArray const& sizes)

Compute offset given coordinates and strides.

The offset needs to be multiplied by the number of bytes of each sample to become a memory offset within the image.

If coords is not within the domain given by sizes, an exception is thrown. The size of coords is not verified.

static dip::sint Offset(dip::IntegerArray const& coords, dip::IntegerArray const& strides)

Compute offset given coordinates.

The offset needs to be multiplied by the number of bytes of each sample to become a memory offset within the image.

coords can have negative values, no domain assumptions are made.

dip::sint Offset(dip::UnsignedArray const& coords) const

Compute offset given coordinates.

The offset needs to be multiplied by the number of bytes of each sample to become a memory offset within the image.

If coords is not within the image domain, an exception is thrown.

The image must be forged.

dip::sint Offset(dip::IntegerArray const& coords) const

Compute offset given coordinates.

The offset needs to be multiplied by the number of bytes of each sample to become a memory offset within the image.

coords can be outside the image domain.

The image must be forged.

dip::UnsignedArray OffsetToCoordinates(dip::sint offset) const

Compute coordinates given an offset.

If the image has any singleton-expanded dimensions, the computed coordinate along that dimension will always be 0. This is an expensive operation, use dip::Image::OffsetToCoordinatesComputer to make it more efficient when performing multiple computations in sequence.

Note that the coordinates must be inside the image domain, if the offset given does not correspond to one of the image’s pixels, the result is meaningless.

The image must be forged.

dip::CoordinatesComputer OffsetToCoordinatesComputer() const

Returns a functor that computes coordinates given an offset.

This is more efficient than using dip::Image::OffsetToCoordinates when repeatedly computing offsets, but still requires complex calculations.

The image must be forged.

static dip::uint Index(dip::UnsignedArray const& coords, dip::UnsignedArray const& sizes)

Compute linear index (not offset) given coordinates and image sizes.

This index is not related to the position of the pixel in memory, and should not be used to index many pixels in sequence.

dip::uint Index(dip::UnsignedArray const& coords) const

Compute linear index (not offset) given coordinates.

This index is not related to the position of the pixel in memory, and should not be used to index many pixels in sequence.

The image must be forged.

dip::UnsignedArray IndexToCoordinates(dip::uint index) const

Compute coordinates given a linear index.

If the image has any singleton-expanded dimensions, the computed coordinate along that dimension will always be 0. This is an expensive operation, use dip::Image::IndexToCoordinatesComputer to make it more efficient when performing multiple computations in sequence.

Note that the coordinates must be inside the image domain, if the index given does not correspond to one of the image’s pixels, the result is meaningless.

The image must be forged.

dip::CoordinatesComputer IndexToCoordinatesComputer() const

Returns a functor that computes coordinates given a linear index.

This is more efficient than using dip::Image::IndexToCoordinates, when repeatedly computing indices, but still requires complex calculations.

The image must be forged.

dip::FloatArray GetCenter(dip::String const& mode = "right") const

Returns the coordinates for the center of the image.

mode specifies the origin of the coordinates. It can be one of the following strings:

  • "right": The origin is on the pixel right of the center (at integer division result of size/2). This is the default.
  • "left": The origin is on the pixel left of the center (at integer division result of (size-1)/2).
  • "true": The origin is halfway the first and last pixel, in between pixels if necessary (at floating-point division result of size/2).
  • "corner": The origin is on the first pixel.
  • "frequency": The coordinates used are as for the Fourier transform. Same results as for "right".

dip::Image& PermuteDimensions(dip::UnsignedArray const& order)

Permute dimensions.

This function allows to re-arrange the dimensions of the image in any order. It also allows to remove singleton dimensions (but not to add them, should we add that? how?). For example, given an image with size { 30, 1, 50 }, and an order array of { 2, 0 }, the image will be modified to have size { 50, 30 }. Dimension number 1 is not referenced, and was removed (this can only happen if the dimension has size 1, otherwise an exception will be thrown!). Dimension 2 was placed first, and dimension 0 was placed second.

The image must be forged. If it is not, you can simply assign any new sizes array through Image::SetSizes. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& SwapDimensions(dip::uint dim1, dip::uint dim2)

Swap dimensions d1 and d2. This is a simplified version of PermuteDimensions.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& Flatten()

Make image 1D.

The image must be forged. If HasSimpleStride, this is a quick and cheap operation, but if not, the data segment will be copied. Note that the order of the pixels in the resulting image depend on the strides, and do not necessarily follow the same order as linear indices.

dip::Image& FlattenAsMuchAsPossible()

Make image have as few dimensions as possible.

If the image has contiguous storage (or non-contiguous storage with a simple stride), then dip::Image::Flatten will convert it into a 1D image without copy. This method performs a similar function, but only merges the dimensions that are possible to merge without data copy. In the cases where dip::Image::Flatten doesn’t copy data, this method will yield the same result. In other cases, the output of this method will yield an image with more than one dimension, sometimes as many as the input image. Dimensions can be reordered and reversed.

The goal with reducing dimensions is to make it simpler to iterate through the image. Iterators will be more efficient on a flattened image.

The image must be forged. This is always a quick and cheap operation. Note that the order of the pixels in the resulting image depend on the strides, and do not necessarily follow the same order as linear indices.

dip::Image& SplitDimension(dip::uint dim, dip::uint size)

Splits a dimension into two.

Splits dimension dim into two: one with size size, and one with size Size( dim ) / size. The two new dimensions will be at dim and dim + 1, moving the previous dim + 1 and subsequent dimensions over by one. Size( dim ) must be evenly divisible by size for this to work, an exception will be thrown if this is not the case.

After this call, the image will have the same number of pixels, stored identically (no copy is made), but one more dimension. For example:

dip::Image image( { 43, 512, 21 } );
image.SplitDimension( 1, 32 );
std::cout << image.Sizes() << '\n'; // should print { 43, 32, 16, 21 }

dip::Image& Squeeze(dip::UnsignedArray& dims)

Remove singleton dimensions (dimensions with size==1).

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dims will be modified to contain the dimensions that were removed. AddSingleton can be used with dims to recover the original image sizes.

dip::Image& Squeeze()

Remove singleton dimensions (dimensions with size==1).

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& Squeeze(dip::uint dim)

Remove singleton dimension dim (has size==1).

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& AddSingleton(dip::uint dim)

Add a singleton dimension (with size==1) to the image.

Dimensions dim to last are shifted up, dimension dim will have a size of 1.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

Example: to an image with sizes { 4, 5, 6 } we add a singleton dimension dim == 1. The image will now have sizes { 4, 1, 5, 6 }.

dip::Image& AddSingleton(dip::UnsignedArray const& dims)

Add a singleton dimensions (with size==1) to the image.

The elements of dims will be applied in order. Dimensions dims[ii] to last are shifted up, dimension dim[ii] will have a size of 1.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& ExpandDimensionality(dip::uint dim)

Append singleton dimensions to increase the image dimensionality.

The image will have n dimensions. However, if the image already has n or more dimensions, nothing happens.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& ExpandSingletonDimension(dip::uint dim, dip::uint sz)

Expand singleton dimension dim to sz pixels, setting the corresponding stride to 0.

If dim is not a singleton dimension (size==1), an exception is thrown.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& ExpandSingletonDimensions(dip::UnsignedArray const& newSizes)

Performs singleton expansion.

The image is modified so that it has size as dimensions. It must be forged and singleton-expandable to size, otherwise an exception is thrown. See dip::Image::ExpandSingletonDimension. size is the array as returned by dip::Framework::SingletonExpandedSize.

dip::Image& UnexpandSingletonDimensions()

Unexpands singleton-expanded dimensions.

The image is modified so that each singleton-expanded dimension has a size of 1, including the tensor dimension. That is, the resulting image will no longer be dip::Image::IsSingletonExpanded.

dip::Image& UnexpandSingletonDimension(dip::uint dim)

Unexpands a singleton-expanded dimension.

The image is modified so that the singleton-expanded dimension dim has a size of 1. That is, this dimension will no longer be singleton-expanded. If dim was not singleton-expanded, throws an exception.

bool IsSingletonExpansionPossible(dip::UnsignedArray const& newSizes) const

Tests if the image can be singleton-expanded to size.

dip::Image& ExpandSingletonTensor(dip::uint sz)

Expand singleton tensor dimension sz samples, setting the tensor stride to 0.

If there is more than one tensor element, an exception is thrown.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& UnexpandSingletonTensor()

Unexpands the singleton-expanded tensor dimension.

Undoes the effect of dip::Image::ExpandSingletonTensor. If the tensor dimension was not singleton-expanded, throws an exception.

dip::Image& Mirror(dip::uint dimension)

Mirror the image about a single axes.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& Mirror(dip::BooleanArray process = {})

Mirror the image about selected axes.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

process indicates which axes to mirror. If process is an empty array, all axes will be mirrored.

dip::Image& Rotation90(dip::sint n, dip::uint dimension1, dip::uint dimension2)

Rotates the image by n times 90 degrees, in the plane defined by dimensions dimension1 and dimension2.

The image must be forged, and have at least two dimensions. The data will never be copied (i.e. this is a quick and cheap operation).

The rotation occurs in the direction of positive angles, as defined in the image coordinate system. That is, if dimension1 is 0 (x-axis) and dimension2 is 1 (y-axis), and considering the y-axis is positive in the down direction, then the rotation happens in clockwise direction. A negative value for n inverts the direction of rotation.

dip::Image& Rotation90(dip::sint n, dip::uint axis)

Rotates the 3D image by n times 90 degrees, in the plane perpendicular to dimension axis.

The image must be forged and have three dimensions. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& Rotation90(dip::sint n = 1)

Rotates the image by n times 90 degrees, in the x-y plane.

The image must be forged. The data will never be copied (i.e. this is a quick and cheap operation).

dip::Image& StandardizeStrides()

Undo the effects of Mirror, Rotation90, PermuteDimensions, and singleton expansion. Also removes singleton dimensions.

Modifies the image such that all strides are positive and sorted smaller to larger. The first dimension will have the smallest stride. Visiting pixels in linear indexing order (as is done through dip::ImageIterator) will be most efficient after calling this function.

Note that strides are not necessarily normal after this call, if the image is a view over a larger image, if singleton dimensions were created or expanded, etc. Use ForceNormalStrides to ensure that strides are normal.

static std::pair<UnsignedArray, dip::sint> StandardizeStrides(dip::IntegerArray& strides, dip::UnsignedArray& sizes)

Transforms input arrays and outputs ordering required to standardize an image’s strides.

strides and sizes are modified such that any negative strides (mirrored image dimensions) become positive, and expanded singleton dimensions become singletons again.

The output array can be used to permute the strides and the sizes arrays to reorder image dimensions such that linear indices match storage order.

The output signed integer is the offset that needs to be applied to the origin to account for any image dimensions that were reversed.

The non-static Image method with the same name uses this function to standardize the strides of the image:

dip::UnsignedArray order;
dip::sint offset;
std::tie( order, offset ) = dip::Image::StandardizeStrides( strides, sizes );
origin = origin + offset;
sizes = sizes.permute( order );
strides = strides.permute( order );

sizes and strides are assumed to be of the same length, this is not tested for.

dip::Image& TensorToSpatial(dip::uint dim)

Convert tensor dimensions to spatial dimension.

Works even for scalar images, creating a singleton dimension. dim defines the new dimension, subsequent dimensions will be shifted over. dim should not be larger than the number of dimensions. dim defaults to the image dimensionality, meaning that the new dimension will be the last one. The image must be forged.

dip::Image& SpatialToTensor(dip::uint dim, dip::uint rows, dip::uint cols)

Convert spatial dimension to tensor dimensions. The image must be scalar.

If rows or cols is zero, its size is computed from the size of the image along dimension dim. If both are zero (or not given), a default column tensor is created. dim defaults to the last spatial dimension. The image must be forged.

dip::Image& SplitComplex(dip::uint dim)

Split the two values in a complex sample into separate samples, creating a new spatial dimension of size 2.

dim defines the new dimension, subsequent dimensions will be shifted over. dim should not be larger than the number of dimensions. dim defaults to the image dimensionality, meaning that the new dimension will be the last one. The image must be forged.

dip::Image& MergeComplex(dip::uint dim)

Merge the two samples along dimension dim into a single complex-valued sample.

Dimension dim must have size 2 and a stride of 1. dim defaults to the last spatial dimension. The image must be forged.

dip::Image& MergeTensorToComplex()

Merge the two samples in the tensor into a single complex-valued sample.

The image must have two tensor elements, a tensor stride of 1, and be forged.

dip::Image& ReinterpretCast(dip::DataType dataType)

Changes the data type of this without changing or copying the data.

If the target dataType is smaller than the source data type, then the spatial dimension that has a stride of 1 will grow (for example, casting from 32-bit integer to 8-bit integer causes that dimension to have four times as many pixels). If no spatial dimension has a stride of 1, a new dimension with a stride of 1 will be created, this will be dimension number 0.

If the target dataType is larger than the source data type, then the spatial dimension that has a stride 1 will shrink. The input size along that dimension must be a multiple of the shrink factor, otherwise an exception will be thrown. If no spatial dimension has a stride of 1, an exception will be thrown. Furthermore, all strides in the image must be compatible with the new data size, if this is not the case, an exception will be thrown.

If the target and source data types have the same size, this operation will always succeed. For the special case of complex to real casting, see dip::Image::SplitComplex and dip::Image::MergeComplex.

The tensor dimension will never be used in the logic described above.

The pixel sizes for the modified dimension will not change, though they will likely be meaningless after this operation.

If the image shares data with other images, the other images will still view the pixels in their original data type. Interpreting data as a different type is inherently dangerous, the C++ standard considers it Undefined Behaviour. Use this function only if you know what you are doing.

The image must be forged.

dip::Image& ReinterpretCastToSignedInteger()

Changes the data type of this to a signed integer of the same size, without changing the data.

If the image shares data with other images, the other images will still view the pixels in their original data type. Interpreting data as a different type is inherently dangerous, but changing the signedness of an integer type is relatively benign. Thus, this function is safer to use than dip::Image::ReinterpretCast.

This function is always fast. The image must be forged and of an integer type.

dip::Image& ReinterpretCastToUnsignedInteger()

Changes the data type of this to an unsigned integer of the same size, without changing the data.

If the image shares data with other images, the other images will still view the pixels in their original data type. Interpreting data as a different type is inherently dangerous, but changing the signedness of an integer type is relatively benign. Thus, this function is safer to use than dip::Image::ReinterpretCast.

This function is always fast. The image must be forged and of an integer type.

dip::Image& Crop(dip::UnsignedArray const& sizes, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER)

Reduces the size of the image by cropping off the borders.

Crops the image to the given size. Which pixels are selected is controlled by the cropLocation parameter. The default is dip::Option::CropLocation::CENTER, which maintains the origin pixel (as defined in dip::FourierTransform and other other places) at the origin of the output image.

dip::Image::Cropped does the same thing, but returning a dip::Image::View instead of modifying this. dip::Image::Pad does the inverse operation.

The image must be forged.

dip::Image& Crop(dip::UnsignedArray const& sizes, dip::String const& cropLocation)

Reduces the size of the image by cropping off the borders.

This is an overloaded version of the function above. The string cropLocation is translated to one of the dip::Option::CropLocation values as follows:

String CropLocation constant
"center" dip::Option::CropLocation::CENTER
"mirror center" dip::Option::CropLocation::MIRROR_CENTER
"top left" dip::Option::CropLocation::TOP_LEFT
"bottom right" dip::Option::CropLocation::BOTTOM_RIGHT

dip::Image::View At(dip::Image mask) const

Creates a 1D image view containing the pixels selected by mask.

When cast to an image, the values will be copied, not referenced. The output is of the same data type and tensor shape as this, but has only one dimension. Pixels will be read from mask in the linear index order.

If mask is a non-scalar image, it must have the same number of tensor elements as this. The created View will be scalar, as we’re selecting individual samples, not pixels. Samples will be read in the linear index order, reading all samples for the first pixel, then all samples for the second pixel, etc.

this must be forged and be of equal size as mask. mask is a binary image.

If mask has no set pixels (i.e. it selects nothing) the View object created will cast to a raw image. For example:

dip::Image mask = img.Similar( dip::BIN );
mask.Fill( false );
img.At( mask ) = 0;  // This is valid, we write the value 0 to none of the pixels of img.
dip::Image out = img.At( mask );  // This is also valid, but out will be a raw image.

dip::Image::View At(dip::CoordinateArray const& coordinates) const

Creates a 1D image view containing the pixels selected by coordinates.

When cast to an image, the values will be copied, not referenced. The output is of the same data type and tensor shape as this, but have only one dimension. It will have as many pixels as coordinates are in coordinates, and be sorted in the same order.

Each of the coordinates must have the same number of dimensions as this.

this must be forged.

dip::Image::View AtIndices(dip::UnsignedArray const& indices) const

Creates a 1D image view containing the pixels selected by indices.

When cast to an image, the values will be copied, not referenced. The output is of the same data type and tensor shape as this, but have only one dimension. It will have as many pixels as indices are in indices, and be sorted in the same order.

indices contains linear indices into the image. Note that converting indices into offsets is not a trivial operation; prefer to use the version of this function that uses coordinates.

this must be forged.

Note that this function is not called At because of the clash with At( UnsignedArray const& ).

dip::Image::View Cropped(dip::UnsignedArray const& sizes, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER) const

Extracts a subset of pixels from an image.

Returns a view to a smaller area within the image. Which pixels are selected is controlled by the cropLocation parameter. The default is dip::Option::CropLocation::CENTER, which maintains the origin pixel (as defined in dip::FourierTransform and other other places) at the origin of the output image.

dip::Image::Crop does the same thing, but modifies the image directly instead of returning a view. dip::Image::Pad does the inverse operation.

The image must be forged.

dip::Image::View Cropped(dip::UnsignedArray const& sizes, dip::String const& cropLocation) const

Extracts a subset of pixels from an image.

This is an overloaded version of the function above. The string cropLocation is translated to one of the dip::Option::CropLocation values as follows:

String CropLocation constant
"center" dip::Option::CropLocation::CENTER
"mirror center" dip::Option::CropLocation::MIRROR_CENTER
"top left" dip::Option::CropLocation::TOP_LEFT
"bottom right" dip::Option::CropLocation::BOTTOM_RIGHT

dip::Image QuickCopy() const

Quick copy, returns a new image that points at the same data as this, and has mostly the same properties.

The color space and pixel size information are not copied, and the protect flag is reset. The external interface is not taken over either. This function is mostly meant for use in functions that need to modify some properties of the input images, without actually modifying the input images.

dip::Image::Copy is similar, but makes a deep copy of the image, such that the output image has its own data segment.

dip::Image Pad(dip::UnsignedArray const& sizes, dip::Image::Pixel const& value, dip::Option::CropLocation cropLocation = Option::CropLocation::CENTER) const

Extends the image by padding with value.

Pads the image to the given size. Where the original image data is located in the output image is controlled by the cropLocation parameter. The default is dip::Option::CropLocation::CENTER, which maintains the origin pixel (as defined in dip::FourierTransform and other other places) at the origin of the output image.

The object is not modified, a new image is created, with identical properties, but of the requested size.

dip::ExtendImageToSize does the same thing, but filling the new regions according to a boundary condition. dip::Image::Crop does the inverse operation.

The image must be forged.

dip::Image Pad(dip::UnsignedArray const& sizes, dip::Image::Pixel const& value, dip::String const& cropLocation) const

Extends the image by padding with value.

This is an overloaded version of the function above. The string cropLocation is translated to one of the dip::Option::CropLocation values as follows:

String CropLocation constant
"center" dip::Option::CropLocation::CENTER
"mirror center" dip::Option::CropLocation::MIRROR_CENTER
"top left" dip::Option::CropLocation::TOP_LEFT
"bottom right" dip::Option::CropLocation::BOTTOM_RIGHT

void Copy(dip::Image const& src)

Deep copy, this will become a copy of src with its own data.

If this is forged, and src has the same sizes and number of tensor elements, then the data is copied over from src to this. The copy will apply data type conversion, where values are clipped to the target range and/or truncated, as applicable. Complex values are converted to non-complex values by taking the absolute value.

If this is not forged, or its sizes or number of tensor elements don’t match those of src, then this will be forged or reforged to match src, and then the data from src will be copied over. this will retain its external interface, if it has one, and not inherit that of src. Strides will be copied only if the data is contiguous, and any external interface allows those strides.

src must be forged.

dip::Image Copy() const

Deep copy, returns a copy of this with its own data.

this must be forged.

Any external interface is not preserved. Use dip::Copy to control the data allocation for the output image.

void Convert(dip::DataType dt)

Converts the image to another data type.

The data type conversion clips values to the target range and/or truncates them, as applicable. Complex values are converted to non-complex values by taking the absolute value.

The data segment is replaced by a new one, unless the old and new data types have the same size and it is not shared with other images. If the data segment is replaced, strides are set to normal.

A binary image can be converted to an 8-bit integer type without copying or touching the data.

void SwapBytesInSample()

Swaps bytes in each sample, converting from little endian to big endian or vice versa.

This process works for any data type with more than one byte per sample. For 8-bit integer imgaes and binary images, nothing is node. The modification will affect all images with shared data. The image must be forged.

void ExpandTensor()

Expands the image’s tensor, such that the tensor representation is a column-major matrix.

If the image has a non-full tensor representation (diagonal, symmetric, triangular), or a row-major ordering, then the data segment is replaced by a new one. Otherwise, nothing is done.

After calling this method, the object always has dip::Tensor::HasNormalOrder equal true. This method simplifies manipulating tensors by normalizing their storage.

void ForceNormalStrides()

Copies pixel data over to a new data segment if the strides are not normal.

Will throw an exception if reallocating the data segment does not yield Normal strides. This can happen only if there is an external interface.

The image must be forged.

void ForceContiguousData()

Copies pixel data over to a new data segment if the data is not contiguous.

The image must be forged.

void Separate()

If the image shares its data segment with another image, create a data copy so it no longer shares data.

The image must be forged.

void Fill(dip::Image::Pixel const& pixel)

Sets all pixels in the image to the value pixel.

pixel must have the same number of tensor elements as the image, or be a scalar. Its values will be clipped to the target range and/or truncated, as applicable.

The image must be forged.

void Fill(dip::Image::Sample const& sample)

Sets all samples in the image to the value sample.

The value will be clipped to the target range and/or truncated, as applicable.

The image must be forged.

dip::Image& operator=(dip::Image::Pixel const& pixel)

Sets all pixels in the image to the value pixel.

pixel must have the same number of tensor elements as the image, or be a scalar. Its values will be clipped to the target range and/or truncated, as applicable.

The image must be forged.

dip::Image& operator=(dip::Image::Sample const& sample)

Sets all samples in the image to the value sample.

The value will be clipped to the target range and/or truncated, as applicable.

The image must be forged.

void CopyDataToNewDataSegment() private

Allocates a new data segment and copies the data over. The image will be the same as before, but have Normal strides and not share data with another image.

Don’t call this function if the image is not forged.

void dip::DefineROI(dip::Image const& src, dip::Image& dest, dip::UnsignedArray origin = {}, dip::UnsignedArray sizes = {}, dip::UnsignedArray spacing = {})

Makes a new image object pointing to same pixel data as src, but with different origin, strides and size.

This function does the same as dip::Image::At, but allows for more flexible defaults: If origin, sizes or spacing have only one value, that value is repeated for each dimension. For empty arrays, origin defaults to all zeros, sizes to src.Sizes() - origin, and spacing to all ones. These defaults make it easy to crop pixels from one side of the image, to subsample the image, etc. For example, the following code subsamples by a factor 2 in each dimension:

DefineROI( src, dest, {}, {}, { 2 } );

If dest is protected, or has an external interface set that is different from src’s, then the pixel data will be copied over. Otherwise, dest will share data with src.

void dip::CopyFrom(dip::Image const& src, dip::Image& dest, dip::Image const& srcMask)

Copies the pixels selected by srcMask in src over to dest. dest will be a 1D image.

If dest is already forged and has the right number of pixels and tensor elements, it will not be reforged. In this case, the copy will apply data type conversion, where values are clipped to the target range and/or truncated, as applicable, and complex values are converted to non-complex values by taking the absolute value.

If srcMask selects no pixels, dest will be a raw image.

void dip::CopyFrom(dip::Image const& src, dip::Image& dest, dip::IntegerArray const& srcOffsets)

Copies the pixels selected by srcOffsets over from src to dest. dest will be a 1D image.

If dest is already forged and has the right number of pixels and tensor elements, it will not be reforged. In this case, the copy will apply data type conversion, where values are clipped to the target range and/or truncated, as applicable, and complex values are converted to non-complex values by taking the absolute value.

void dip::ExpandTensor(dip::Image const& src, dip::Image& dest)

Copies samples over from src to dest, expanding the tensor so it’s a standard, column-major matrix.

If the tensor representation in src is one of those that do not save symmetric or zero values, to save space, a new data segment will be allocated for dest, where the tensor representation is a column-major matrix (dest will have dip::Tensor::HasNormalOrder be true). Otherwise, dest will share the data segment with src (unless it’s protected or has an external interface, in which case data must be copied). This function simplifies manipulating tensors by normalizing their storage.

void dip::Convert(dip::Image const& src, dip::Image& dest, dip::DataType dt)

Copies samples over from src to dest, with data type conversion.

If dest is forged, has the same size as number of tensor elements as src, and has data type dt, then its data segment is reused. If src and dest are the same object, its dip::Image::Convert method is called instead.

The data type conversion clips values to the target range and/or truncates them, as applicable. Complex values are converted to non-complex values by taking the absolute value.