Image class
Represents an image with all associated information.
Contents
- Image representation
- Strides
- Tensor images
- On pixel coordinates, indices, offsets and data pointers
- Creation, assignment and copy
- Indexing
- Reshaping
- The “protect” flag
- Const correctness
- Arithmetic and comparison operators
- Color images
- Pixel size
- Controlling data segment allocation
- Singleton expansion
- Reference
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 n
th 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:
-
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. -
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) noexcept
- Move constructor,
rhs
ends up in default-initialized state,this
even robs the external interface fromrhs
. - 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 ofpixel
. - 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 ofsample
. - Image(dip::FloatArray const& values, dip::DataType dt = DT_SFLOAT) explicit
- Create a 0-D vector image with data type
dt
, and values ofvalues
. - Image(dip::Image::View const& view)
- A
dip::Image::View
implicitly converts to adip::Image
. - Image(dip::Image::View&& view)
- A
dip::Image::View
implicitly converts to adip::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
andthis
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
andother
.
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 withother
. - 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 withother
. - auto IsIdenticalView(dip::Image const& other) const -> bool
- Determine if
this
andother
offer an identical view of the same set of pixels. - auto IsOverlappingView(dip::Image const& other) const -> bool
- Determine if
this
andother
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 inother
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 inother
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
tosz
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 dimensionsdimension1
anddimension2
. - auto Rotation90(dip::sint n, dip::uint axis) -> dip::Image&
- Rotates the 3D image by
n
times 90 degrees, in the plane perpendicular to dimensionaxis
. - 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 ofdip::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 ofdip::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 ofdip::Image::Crop
, for an image of sizeimageSizes
. - 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 ofdip::Image::Crop
, for an image of sizeimageSizes
.
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 ofsrc
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 toimg.At(~mask).Fill(0)
, but without creating an intermediate copy ofmask
. Can also be expressed asimg *= 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 typeT
. \relates dip::Image::Sample -
template<typename T>class CastPixel
- Derived from
dip::Image::Pixel
, works identically except it implicitly converts to typeT
. \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 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::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 ofsize/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 ofsize/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.