DGtal 1.4.0
Loading...
Searching...
No Matches
Images
Authors
Tristan Roussillon, David Coeurjolly, Martial Tola, Roland Denis
Date
2012/12/05

Overview

The concept of point functor describes a mapping between the points of a digital space and a set of values. The only method of a point functor is the operator(), which must take a point as argument and must return a value.

The concept of constant image is a point functor bounded by a domain. It is thus a refinement of the concept of point functor, describing a mapping between points and values, but having in addition a domain, returned by the domain method, and a range of values (for each point of the domain), returned by the constRange method.

The concept of image, which is a refinement of the concept of constant image, provides extra services to update values.
Any model of image must have a method setValue taking a point and a value as input parameters and updating the value associated with the given point with the given value.
In addition, they must have a range, returned by the range method, providing output iterators.

Different models of images are available: ImageContainerBySTLVector, ImageContainerBySTLMap, ImageContainerByITKImage (a wrapper for ITK images) and — coming soon — experimental::ImageContainerByHashTree.

Let us go into details

In this section, the concepts and the main services to read and write values in images are detailed.

Concepts

Any model of the concept concepts::CPointFunctor must have two nested types:

  • Point, which specifies the type for a point.
  • Value, which specifies the type for a value.

Moreover, it must have the following method:

  • operator(), which takes a point as argument and returns a value, like a function.

The concept concepts::CConstImage is a refinement of concepts::CPointFunctor. Its models must have two extra nested types:

Obviously, there are two methods that return instances of these two types:

  • domain, which returns a constant reference on the image domain
  • constRange, which returns a range providing constant bidirectional iterators on the image values (associated to each point of the image domain)

You can see Digital Spaces, Points, Vectors and Domains for more details about spaces and domains and Ranges of values for more details about ranges in images.

The concept concepts::CImage is a refinement of concepts::CConstImage.
Images, instead of constant ones, provide services to update values. The main way of assigning values to points is the following method:

  • setValue, which updates a given value at a given point.

Moreover, in addition to the ConstRange, images must have the following inner type:

Obviously, you can get an instance of this type using the following method:

  • range, which returns a range providing both constant bidirectional iterators and output iterators.

Lastly, note that the Value type in the (constant) images is expected to be at least a model of concepts::CLabel, ie.
to be default-constructible, assignable and equality comparable.

Note
In the snippets of the following subsections, the type of image used is Image and its instances are image, image1, image2.

Main methods

All models of images have a domain returned by the method domain. This domain is the set of points for which the image is defined and has values. Since a domain is a range, you can straightforwardly use it in order to iterate over the points of an image.

//iterate over the points
Image::Domain d = image.domain();
for (Image::Domain::ConstIterator it = d.begin(), itEnd = d.end();
it != itEnd; ++it)
{}

Models of images have also two main methods in order to read or write values at a given point:

  • operator() to get the value associated to a given point
  • setValue to assign a value to a given point.
//process the values
Image::Domain d = image.domain();
for (Image::Domain::ConstIterator it = d.begin(), itEnd = d.end();
it != itEnd; ++it)
{
Image::Value v = image( *it );
v += 5; //adding 5 to all values
image.setValue( *it, v );
}

Note that this method of iterating over the values of an image is not always the fastest and that is why we also provide ranges of values.

Ranges of values

Constant images provide a constant range of values returned by the constRange method. As every model of concepts::CConstBidirectionalRange, it provides begin, end, rbegin and rend methods returning constant iterators to iterate over the values in the forward or backward direction.

//iterate over the values
Image::ConstRange r = image.constRange();
for (Image::ConstRange::ConstIterator it = r.begin(), itEnd = r.end();
it != itEnd; ++it)
{}
Aim: model of CConstBidirectionalRangeFromPoint that adapts any range of elements bounded by two iter...

However, this range is also a model of concepts::CConstBidirectionalRangeFromPoint, which is a refinement of concepts::CConstBidirectionalRange. That is why it also has overloaded versions of the begin and rbegin methods taking a point as input argument. This provides a way of iterating on sub-ranges defined from points.

//iterator on the origin (0, ... , 0)
Image::ConstRange::ConstIterator it = r.begin( Image::Point::diagonal(0) )

Note that if the point does not belong to the domain, the returned iterators (resp. reverse iterators) must be equal to the end (resp. rend) methods.

ASSERT( image.constRange().begin( image.domain().end() ) == image.constRange().end() )

Images provide in addition to a constant range, a richer range returned by the range method. This range is not only a model of concepts::CBidirectionalRangeFromPoint, but also a model of concepts::CBidirectionalRangeWithWritableIteratorFromPoint. That is why, it must have two methods:
outputIterator and routputIterator returning output iterators. Moreover, it must have overloaded versions of these methods taking a point as input argument. Thus, these output iterators are useful in order to incrementaly fill (a part of) an image. For instance, you can fill an image from the values of another one (assumed to have the same domain) as follows:

Image::Range r1 = image1.range();
Image::ConstRange r2 = image2.constRange();
std::copy( r2.begin(), r2.end(), r1.outputIterator() );
SimpleRandomAccessRangeFromPoint< ConstIterator, Iterator, DistanceFunctorFromPoint< Self > > Range

Main models

Different models of images are available: ImageContainerBySTLVector, ImageContainerBySTLMap, experimental::ImageContainerByHashTree and ImageContainerByITKImage, a wrapper for ITK images.

ImageContainerBySTLVector

ImageContainerBySTLVector is a model of concepts::CImage that inherits the STL vector class. The hyper-rectangular domain, which the only model of domain accepted, is linearized so that
each point is mapped, from its coordinates, into an index and each index is mapped into a unique value, as in any one-dimensional array.

Let \( n \) be the domain size (the number of points). At construction all the needed space is allocated and filled with a default value (0) in \( O(n) \) space and time. After that, you can access to the value associated to any point at any time. Each access for reading (operator()) or writing ('setValue`) values is in \( O(1) \).

The (constant) range of this class only used the built-in iterators of the underlying STL vector. It is therefore a fast way of iterating over the values of the image.

ImageContainerBySTLMap

ImageContainerBySTLMap is a model of concepts::CImage that inherits the STL map class. The domain can be any set of points. Values are stored and associated to points in pairs point-value. The set of points stored in this way may be any domain subset. A default value (user-defined) is automatically associated to each point of the domain that does not belong the subset for which values are known. Once constructed (in \( O(1) \)), the image is valid and every point of the image domain has a value, which can be read and overwritten.

The pairs point-value are stored in a red-black tree, where the points are used as keys, so that
each access for reading (operator()) or writing (setValue) values is in \( O(log m) \), where \( m \) is the cardinal of the subset for which values are known (less or equal to the domain size \( n \)).

The (constant) range of this class adapts the domain iterators in order to deal with values instead of points. The operator* of the iterators provided by the range calls the operator() and use the setValue method of the class.

ImageContainerByHashTree

experimental::ImageContainerByHashTree is an experimental image container implementing a pointerless nD-tree structure. In dimension 2 and 3, this structure is similar to quadtree and octree repsectively in which hierarchical links between a node and its children is given by prefix of a binary representation of the node coordinates using Morton keys. Finally, data values are stored in the structure in a hash table whose hash function is a suffix on the Morton key code.

Such container is well adapted for high resolution sparse images.

For more details, please refer to [83]

Image Adapter classes

ImageAdapter, ConstImageAdapter are perfect swiss-knifes to transform and adapt images (change their domain definition, change their value types, ...). These classses are parametrized by several types and functor in order to adapt the behavior of image getters/setters (operator() and setValue methods). These adapted behaviors are computed on-the-fly when calling these methods.

ConstImageAdapter requires several types and functors to create a "read-only" adapted image (operator(), constRange(), ...)

ImageAdapter requires a supplementary functor and allows write access to the image (setValue methods, range(), ... )

Note
Functors used to convert domains or values may be complex. Keep in mind that each time operator() or setValue() methods are called, such functors are evaluated (with potential side-effects depending on the functors).

In addition, ArrayImageAdapter allows to convert any iterable storage (like a C-style array or some DGtal image models), to a concepts::CImage (or concepts::CConstImage) model with the possibility to restrict his visibility to a sub-domain of the definition domain.

ConstImageAdapter

ConstImageAdapter is a small class that adapts any (constant or not) image into a constant one, which provides a virtual view (so read-only) of the true values contained in the adapted image. The class is parametrized by several template arguments: TImageContainer: the type of image to adapt. TNewDomain: the type of the new domain. TFunctorD: type of functor used to convert image domain points to new domain points TNewValue: type of the new image value type. TFucntorV: functor to convert values.

The values associated to access the point values are adapted
with a functor g and a functor f given at construction so that operator() calls f(img(g(aPoint))), instead of calling directly img.operator() of the underlying image img.

Functor g (and/or functor f) can be a default functor, i.e. a simple functor that just returns its argument.

In order to illustrate the next ConstImageAdapter usage samples, we are going a) to use these includes:

#include "DGtal/io/colormaps/HueShadeColorMap.h"
#include "DGtal/io/colormaps/GrayscaleColorMap.h"
#include "DGtal/images/ImageContainerBySTLVector.h"
#include "DGtal/images/ConstImageAdapter.h"

b) then define these types and variables:

typedef HueShadeColorMap<unsigned char> HueShade; // a simple HueShadeColorMap varying on 'unsigned char' values
typedef HueShadeColorMap<double> HueShadeDouble; // a simple HueShadeColorMap varying on 'double' values
typedef GrayscaleColorMap<unsigned char> Gray; // a simple GrayscaleColorMap varying on 'unsigned char' values
functors::Identity df; // a simple functor that just returns its argument

c) then define a simple 16x16 (1,1) to (16,16) image (of 'unsigned char' type):

filled with 0 to 255 values like that:

unsigned char i = 0;
for (Image::Iterator it = image.begin(); it != image.end(); ++it)
*it = i++;

which looks like that with a simple HueShadeColorMap varying from 0 to 255 and with (1,1) the first bottom-left point:

(1) simple 16x16 image: (1,1) to (16,16) drawn with a simple HueShadeColorMap varying from 0 to 255.

Here is now the construction of a simple image adapter that use a subdomain of the initial image domain to access the first bottom-left 8x8 image:

Domain subDomain(Point(1,1), Point(8,8));
ConstImageAdapterForSubImage subImage(image, subDomain, df, df);

and here is the result:

(2) first bottom-left 8x8 image: (1,1) to (8,8) adapted from image (1) with a subdomain.

Here is then the construction of an image adapter that use a specific domain: here, only one pixel on two in x and y coordinates, created like that:

for( unsigned int y=0; y < 17; y++)
for( unsigned int x=0; x < 17; x++)
if ((x%2) && (y%2))
set.insertNew(Point(x,y));
DigitalSetDomain<DigitalSet> specificDomain(set);

from the initial image domain.

ConstImageAdapterForSpecificImage specificImage(image, specificDomain, df, df);

Here is the result:

(3) 16x16 image: (1,1) to (16,16) adapted from image (1) with a specific domain.

Here is now the construction of an image adapter that is a thresholded view of the initial scalar image:

ConstImageAdapterForThresholderImage thresholderImage(image, domain, df, t);

and here is the result with a simple GrayscaleColorMap varying from 0 to 1:

(4) 16x16 image: (1,1) to (16,16) adapted from image (1) with a thresholder set to 127.

Here is finally the construction of an image adapter that use a functor to change 'unsigned char' values to 'double' values using a log scale functor defined like that:

template <typename Scalar>
struct LogScaleFunctor {
LogScaleFunctor() {};
double operator()(const Scalar &a) const
{
return std::log( 1 + NumberTraits<Scalar>::castToDouble(a) );
}
};

defined from the initial image:

LogScaleFunctor<Image::Value> logScale;
ConstImageAdapterForLogScale logImage(image, domain, df, logScale);

and here is the result with a simple HueShadeColorMap varying from 0. to logScale(255):

(5) 16x16 image: (1,1) to (16,16) adapted from image (1) with a log scale functor.

ImageAdapter

ImageAdapter is a small class that adapts an image (like ConstImageAdapter) but provides a virtual access (reading and writing) of the true values contained in the adapted image. It uses a given Domain (i.e. a subdomain) but work directly (for reading and writing processes) thanks to an alias (i.e. a pointer) on the original Image given in argument.

This class requires an additional templare paremter: TFunctoVm1: functor to convert adapted image values to the original image values.

The values associated to accessing the point values are adapted
with a functor g and a functor f given at construction so that operator() calls f(img(g(aPoint))), instead of calling directly operator() of the underlying image img.

The values associated to writing the points are adapted
with a functor g and a functor \( f^{-1}\) given at construction so that setValue() is img.setValue(g(aPoint), f-1(aValue)).

The use is the same that for ConstImageAdapter.

ArrayImageAdapter

The ArrayImageAdapter class is less generic than ImageAdapter but is able to adapt any storage that have a random-access iterator to a concepts::CImage model. It is thus usable on C-style array but also on STL container like std::vector (and therefore on ImageContainerBySTLVector) and on another ArrayImageAdapter instances.

In addition, this class allows to restrict the visibility to a sub-domain of the definition domain and provides a read-write random-access iterator (depending on the mutability of the storage's iterator) with fast access to the underlying point (no need to iterate over the domain).

A common usage of this last feature is for padded raw data.

In order to illustrate the following ArrayImageAdapter usages, we need some common includes:

#include <new>
#include <cmath>
#include <algorithm>
#include <DGtal/io/colormaps/HueShadeColorMap.h>
#include <DGtal/images/ImageContainerBySTLVector.h>
#include <DGtal/images/ArrayImageAdapter.h>

and the next definitions:

using Value = double; // value type of the image
using HueShadeDouble = HueShadeColorMap<Value>; // a simple HueShadeColorMap varying on 'double' values

From a C-style array, we can create an image that spans the full definition domain:

const Domain domain(Point(1,1), Point(16,16));
Value* data = new Value[ domain.size() ];
ArrayImageAdapter< Value*, Domain > image( data, domain );

and fill it using common iterator syntax:

Value i = 0;
for ( auto & value : image )
value = i++;

that gives us the following result:

(6) simple 16x16 image: (1,1) to (16,16) drawn with a simple HueShadeColorMap varying from 0 to 255.

We can now create a read-only view of the same image on a sub-domain:

Domain subDomain(Point(1,1), Point(8,8));
ArrayImageAdapter< Value const*, Domain > constSubImage( data, domain, subDomain );

that gives us:

(7) first bottom-left 8x8 image: (1,1) to (8,8) adapted from image (1) with a subdomain.

If we want to modify the image through this adapter, we must create a read-write instance using the following syntax:

ArrayImageAdapter< Value*, Domain > subImage( data, domain, subDomain );

or the alternate method using the helpers:

auto subImage = makeArrayImageAdapterFromIterator( data, domain, subDomain );

and we can then modify it using the common syntax with a domain iterator:

for ( auto point : subImage.domain() )
{
Value coord = (point - Point(4,4)).norm();
subImage.setValue( point, 25*(cos(coord)+1) );
}
(8) modifying the first bottom-left 8x8 image through the sub-domain view with a domain iterator.

Alternatively, there is a computationally faster syntax (no need to linearize the point) using the method getPoint featured by the ArrayImageAdapter iterators (see ArrayImageIterator):

for ( auto it = subImage.begin(), it_end = subImage.end(); it != it_end; ++it )
{
Value coord = (it.getPoint() - Point(4,4)).norm();
*it = 25*(sin(coord)+1);
}
(9) modifying the first bottom-left 8x8 image through the sub-domain view with an image iterator.

It is also possible to use ArrayImageAdater on any image model that provides a random-access iterator, like ImageContainerBySTLVector:

for (auto& value : anIterableImage)
value = 0;

and adapt it using:

{
ArrayImageAdapter< ImageContainerBySTLVector<Domain,Value>::Iterator, Domain > subImageSTL( anIterableImage.begin(), domain, subDomain );
}

or, thanks to the helpers:

auto subImageSTL = makeArrayImageAdapterFromImage( anIterableImage, subDomain );

From there, we can use all the previous features or, for example, use available STL algorithms:

std::copy( subImage.cbegin(), subImage.cend(), subImageSTL.begin() );
(10) modifying the first bottom-left 8x8 image of a ImageContainerBySTLVector through the sub-domain view.

Useful classes and functions

In addition to the image containers and the image adapters described in the previous sections, there are also image proxys:

Image is a light proxy on image containers based on a COW pointer. It can be constructed, copied, assigned, deleted without any special care.

Moreover, in ImageHelper.h, many useful functions are provided.

  1. Conversely, others convert images into digital sets with value comparators: setFromPointsRangeAndPredicate, setFromPointsRangeAndFunctor, setFromImage.
  2. Some of them convert digital sets into images, imageFromRangeAndValue assigns a given value in an image to each point of a given range.
  3. Some functions are available to fastly fill images from point functors or other images: imageFromFunctor and imageFromImage.
  4. Lastly, some functor like the Projector from BasicPointFunctors can be useful to manipulate domain points and permits to extract N-1 images from ND images (see example extract2DImagesFrom3D.cpp).

Subsampling functor

In association with ConstImageAdapter or ImageAdapter you can apply image subsampling using a domain subsampler (BasicDomainSubSampler from the BasicPointFunctors class). The example imageBasicSubsampling.cpp illustrates such a simple image subsampling (in 2D and 3D).

To apply the subsampling, you first have to import the headers associated with the ConstImageAdapter and to the BasicDomainSubSampler:

#include "DGtal/kernel/BasicPointFunctors.h"
#include "DGtal/images/ConstImageAdapter.h"

Then you can define some image types including the ConstImageAdapter:

typedef ImageContainerBySTLVector < Z2i::Domain, unsigned char> Image2D;
typedef ConstImageAdapter<Image2D, Image2D::Domain,
Image2D::Value,
functors::Identity > ConstImageAdapterForSubSampling;

A subsampling functor can be constructed from a given grid size and a shift vector:

DGtal::functors::BasicDomainSubSampler<Image2D::Domain> subSampler2D(image2D.domain(), aGridSize2D, Z2i::Point(0 ,0));

Afterwards the ConstImageAdapter can be defined as follows:

Image2D::Domain subSampledDomain2D = subSampler2D.getSubSampledDomain();
ConstImageAdapterForSubSampling subsampledImage2D (image2D, subSampledDomain2D, subSampler2D, df);

The resulting image can be exported with the GenericWriter class:

GenericWriter<ConstImageAdapterForSubSampling>::exportFile(outputname.str(), subsampledImage2D );

You will obtain such a result (with also the result on 3D images):

(11) Illustration of the resulting subsampling given by the imageBasicSubsampling.cpp example

.

Constant image from a function, functor or lambda

If you need to define an image as the result of a callable object (function, functor or lambda) and you don't want to precalculate it or to store it, you should consider using the functors::ConstImageFunctorHolder class.

An instance of this class must be constructed through the associated helper functors::holdConstImageFunctor and you can use any callable object that accepts a point alone or a point and a domain (in order to have a domain dependent image definition).

A first usage example should be composed of the appropriate include:

#include "DGtal/images/ConstImageFunctorHolder.h"

followed by the image definition:

const Domain domain1(Point(1,1), Point(16,16));
domain1,
[] (Point const& pt) { return 25 * ( std::cos( (pt - Point(4,4)).norm() ) + 1 ); }
);

that gives us the following result:

(12) Image generated from a point-dependent lambda.

You can also use a functor that depends on the point and the image's domain:

const Domain domain2(Point(-1,1), Point(18,18));
domain2,
[] (Point const& pt, Domain const& d) { // we could also capture the domain
return 2 * std::min( ( pt - d.lowerBound() ).norm(), ( pt - d.upperBound() ).norm() );
}
);

resulting in:

(13) Image generated from a lambda that depends on the point and the domain.

Going further, you can also capture other images in the lambda so that to define a mix:

domain1,
[&image1, &image2] (Point const& pt) { return image1(pt) + image2(pt); }
);

resulting in:

(14) Image generated as the sum of two images.
See also
FunctorHolder, Using functions, functors and lambdas in DGtal