The purpose of an image adaptor is to make one image appear like another image, possibly of a
different pixel type. A typical example is to take an image of pixel type unsigned char and
present it as an image of pixel type float. The motivation for using image adaptors in this
case is to avoid the extra memory resources required by using a casting filter. When we use the
itk::CastImageFilter for the conversion, the filter creates a memory buffer large enough to store
the float image. The float image requires four times the memory of the original image and
contains no useful additional information. Image adaptors, on the other hand, do not require
the extra memory as pixels are converted only when they are read using image iterators (see
Chapter 25).
Image adaptors are particularly useful when there is infrequent pixel access, since the actual conversion
occurs on the fly during the access operation. In such cases the use of image adaptors may reduce overall
computation time as well as reduce memory usage. The use of image adaptors, however, can be
disadvantageous in some situations. For example, when the downstream filter is executed multiple times, a
CastImageFilter will cache its output after the first execution and will not re-execute when
the filter downstream is updated. Conversely, an image adaptor will compute the cast every
time.
Another application for image adaptors is to perform lightweight pixel-wise operations replacing the need
for a filter. In the toolkit, adaptors are defined for many single valued and single parameter functions such as
trigonometric, exponential and logarithmic functions. For example,
The following examples illustrate common applications of image adaptors.
The source code for this example can be found in the file
Examples/DataRepresentation/Image/ImageAdaptor1.cxx.
This example illustrates how the itk::ImageAdaptor can be used to cast an image from one pixel type
to another. In particular, we will adapt an unsigned char image to make it appear as an image of pixel
type float.
We begin by including the relevant headers.
#include "otbImage.h" #include "itkImageAdaptor.h"
First, we need to define a pixel accessor class that does the actual conversion. Note that in general, the only
valid operations for pixel accessors are those that only require the value of the input pixel. As such,
neighborhood type operations are not possible. A pixel accessor must provide methods Set() and Get(),
and define the types of InternalPixelType and ExternalPixelType. The InternalPixelType
corresponds to the pixel type of the image to be adapted (unsigned char in this example). The
ExternalPixelType corresponds to the pixel type we wish to emulate with the ImageAdaptor (float in
this case).
class CastPixelAccessor { public: typedef unsigned char InternalType;
typedef float ExternalType; static void Set(InternalType& output, const ExternalType& input)
{ output = static_cast<InternalType>(input); } static ExternalType Get(const InternalType& input)
{ return static_cast<ExternalType>(input); } };
The CastPixelAccessor class simply applies a static_cast to the pixel values. We now use this
pixel accessor to define the image adaptor type and create an instance using the standard New()
method.
typedef unsigned char InputPixelType; const unsigned int Dimension = 2;
typedef otb::Image<InputPixelType, Dimension> ImageType;
typedef itk::ImageAdaptor<ImageType, CastPixelAccessor> ImageAdaptorType;
ImageAdaptorType::Pointer adaptor = ImageAdaptorType::New();
We also create an image reader templated over the input image type and read the input image from
file.
typedef otb::ImageFileReader<ImageType> ReaderType; ReaderType::Pointer reader = ReaderType::New();
The output of the reader is then connected as the input to the image adaptor.
adaptor->SetImage(reader->GetOutput());
In the following code, we visit the image using an iterator instantiated using the adapted image type and
compute the sum of the pixel values.
typedef itk::ImageRegionIteratorWithIndex<ImageAdaptorType> IteratorType;
IteratorType it(adaptor, adaptor->GetBufferedRegion()); double sum = 0.0; it.GoToBegin();
while (!it.IsAtEnd()) { float value = it.Get(); sum += value; ++it; }
Although in this example, we are just performing a simple summation, the key concept is that access to
pixels is performed as if the pixel is of type float. Additionally, it should be noted that the adaptor is used
as if it was an actual image and not as a filter. ImageAdaptors conform to the same API as the otb::Image
class.
The source code for this example can be found in the file
Examples/DataRepresentation/Image/ImageAdaptor2.cxx.
This example illustrates how to use the itk::ImageAdaptor to access the individual components of an
RGB image. In this case, we create an ImageAdaptor that will accept a RGB image as input and presents it
as a scalar image. The pixel data will be taken directly from the red channel of the original
image.
As with the previous example, the bulk of the effort in creating the image adaptor is associated with
the definition of the pixel accessor class. In this case, the accessor converts a RGB vector to a
scalar containing the red channel component. Note that in the following, we do not need to
define the Set() method since we only expect the adaptor to be used for reading data from the
image.
class RedChannelPixelAccessor { public: typedef itk::RGBPixel<float> InternalType;
typedef float ExternalType; static ExternalType Get(const InternalType& input)
{ return static_cast<ExternalType>(input.GetRed()); } };
The Get() method simply calls the GetRed() method defined in the itk::RGBPixel class.
Now we use the internal pixel type of the pixel accessor to define the input image type, and then proceed to
instantiate the ImageAdaptor type.
typedef RedChannelPixelAccessor::InternalType InputPixelType; const unsigned int Dimension = 2;
typedef otb::Image<InputPixelType, Dimension> ImageType; typedef itk::ImageAdaptor<ImageType,
RedChannelPixelAccessor> ImageAdaptorType; ImageAdaptorType::Pointer adaptor = ImageAdaptorType::New();
We create an image reader and connect the output to the adaptor as before.
typedef otb::ImageFileReader<ImageType> ReaderType; ReaderType::Pointer reader = ReaderType::New();
adaptor->SetImage(reader->GetOutput());
We create an itk::RescaleIntensityImageFilter and an otb::ImageFileWriter to rescale the
dynamic range of the pixel values and send the extracted channel to an image file. Note that the image type
used for the rescaling filter is the ImageAdaptorType itself. That is, the adaptor type is used in the same
context as an image type.
typedef otb::Image<unsigned char, Dimension> OutputImageType;
typedef itk::RescaleIntensityImageFilter<ImageAdaptorType, OutputImageType
> RescalerType; RescalerType::Pointer rescaler = RescalerType::New();
typedef otb::ImageFileWriter<OutputImageType> WriterType; WriterType::Pointer writer = WriterType::New();
Now we connect the adaptor as the input to the rescaler and set the parameters for the intensity
rescaling.
rescaler->SetOutputMinimum(0); rescaler->SetOutputMaximum(255);
rescaler->SetInput(adaptor); writer->SetInput(rescaler->GetOutput());
Finally, we invoke the Update() method on the writer and take precautions to catch any exception that may
be thrown during the execution of the pipeline.
try { writer->Update(); } catch (itk::ExceptionObject& excp) {
std::cerr << "Exception caught " << excp << std::endl; return 1; }
ImageAdaptors for the green and blue channels can easily be implemented by modifying the pixel accessor
of the red channel and then using the new pixel accessor for instantiating the type of an image adaptor. The
following define a green channel pixel accessor.
class GreenChannelPixelAccessor { public: typedef itk::RGBPixel<float> InternalType;
typedef float ExternalType; static ExternalType Get(const InternalType& input)
{ return static_cast<ExternalType>(input.GetGreen()); } };
A blue channel pixel accessor is similarly defined.
class BlueChannelPixelAccessor { public: typedef itk::RGBPixel<float> InternalType;
typedef float ExternalType; static ExternalType Get(const InternalType& input)
{ return static_cast<ExternalType>(input.GetBlue()); } };
The source code for this example can be found in the file
Examples/DataRepresentation/Image/ImageAdaptor3.cxx.
This example illustrates the use of itk::ImageAdaptor to obtain access to the components of a vector
image. Specifically, it shows how to manage pixel accessors containing internal parameters. In this example
we create an image of vectors by using a gradient filter. Then, we use an image adaptor to extract
one of the components of the vector image. The vector type used by the gradient filter is the
itk::CovariantVector class.
We start by including the relevant headers.
#include "itkGradientRecursiveGaussianImageFilter.h"
A pixel accessors class may have internal parameters that affect the operations performed on input pixel
data. Image adaptors support parameters in their internal pixel accessor by using the assignment operator.
Any pixel accessor which has internal parameters must therefore implement the assignment operator. The
following defines a pixel accessor for extracting components from a vector pixel. The m_Index member
variable is used to select the vector component to be returned.
class VectorPixelAccessor { public: typedef itk::CovariantVector<float, 2> InternalType;
typedef float ExternalType; void operator =(const VectorPixelAccessor& vpa)
{ m_Index = vpa.m_Index; } ExternalType Get(const InternalType& input) const {
return static_cast<ExternalType>(input[m_Index]); } void SetIndex(unsigned int index)
{ m_Index = index; } private: unsigned int m_Index; };
The Get() method simply returns the i-th component of the vector as indicated by the index. The
assignment operator transfers the value of the index member variable from one instance of the pixel
accessor to another.
In order to test the pixel accessor, we generate an image of vectors using the
itk::GradientRecursiveGaussianImageFilter . This filter produces an output image of
itk::CovariantVector pixel type. Covariant vectors are the natural representation for gradients since
they are the equivalent of normals to iso-values manifolds.
typedef unsigned char InputPixelType; const unsigned int Dimension = 2;
typedef otb::Image<InputPixelType, Dimension> InputImageType;
typedef itk::CovariantVector<float, Dimension> VectorPixelType;
typedef otb::Image<VectorPixelType, Dimension> VectorImageType;
typedef itk::GradientRecursiveGaussianImageFilter<InputImageType, VectorImageType>
GradientFilterType; GradientFilterType::Pointer gradient = GradientFilterType::New();
We instantiate the ImageAdaptor using the vector image type as the first template parameter and the pixel
accessor as the second template parameter.
typedef itk::ImageAdaptor<VectorImageType, VectorPixelAccessor> ImageAdaptorType;
ImageAdaptorType::Pointer adaptor = ImageAdaptorType::New();
The index of the component to be extracted is specified from the command line. In the following, we create
the accessor, set the index and connect the accessor to the image adaptor using the SetPixelAccessor()
method.
VectorPixelAccessor accessor; accessor.SetIndex(atoi(argv[3])); adaptor->SetPixelAccessor(accessor);
We create a reader to load the image specified from the command line and pass its output as the input to the
gradient filter.
typedef otb::ImageFileReader<InputImageType> ReaderType; ReaderType::Pointer reader = ReaderType::New();
gradient->SetInput(reader->GetOutput()); reader->SetFileName(argv[1]); gradient->Update();
We now connect the output of the gradient filter as input to the image adaptor. The adaptor
emulates a scalar image whose pixel values are taken from the selected component of the vector
image.
adaptor->SetImage(gradient->GetOutput());
The source code for this example can be found in the file
Examples/DataRepresentation/Image/ImageAdaptor4.cxx.
Image adaptors can also be used to perform simple pixel-wise computations on image data. The following
example illustrates how to use the itk::ImageAdaptor for image thresholding.
A pixel accessor for image thresholding requires that the accessor maintain the threshold value. Therefore,
it must also implement the assignment operator to set this internal parameter.
class ThresholdingPixelAccessor { public: typedef unsigned char InternalType;
typedef unsigned char ExternalType; ExternalType Get(const InternalType& input) const {
return (input > m_Threshold) ? 1 : 0; } void SetThreshold(const InternalType threshold)
{ m_Threshold = threshold; } void operator =(const ThresholdingPixelAccessor& vpa)
{ m_Threshold = vpa.m_Threshold; } private: InternalType m_Threshold; };
The Get() method returns one if the input pixel is above the threshold and zero otherwise. The assignment
operator transfers the value of the threshold member variable from one instance of the pixel accessor to
another.
To create an image adaptor, we first instantiate an image type whose pixel type is the same as the internal
pixel type of the pixel accessor.
typedef ThresholdingPixelAccessor::InternalType PixelType; const unsigned int Dimension = 2;
typedef otb::Image<PixelType, Dimension> ImageType;
We instantiate the ImageAdaptor using the image type as the first template parameter and the pixel accessor
as the second template parameter.
typedef itk::ImageAdaptor<ImageType, ThresholdingPixelAccessor> ImageAdaptorType;
ImageAdaptorType::Pointer adaptor = ImageAdaptorType::New();
The threshold value is set from the command line. A threshold pixel accessor is created and connected to
the image adaptor in the same manner as in the previous example.
ThresholdingPixelAccessor accessor; accessor.SetThreshold(atoi(argv[3])); adaptor->SetPixelAccessor(accessor);
We create a reader to load the input image and connect the output of the reader as the input to the
adaptor.
typedef otb::ImageFileReader<ImageType> ReaderType; ReaderType::Pointer reader = ReaderType::New();
reader->SetFileName(argv[1]); reader->Update(); adaptor->SetImage(reader->GetOutput());
As before, we rescale the emulated scalar image before writing it out to file. Figure 26.2 illustrates
the result of applying the thresholding adaptor to a typical gray scale image using two
different threshold values. Note that the same effect could have been achieved by using the
itk::BinaryThresholdImageFilter but at the price of holding an extra copy of the image in
memory.
Image adaptors will not behave correctly when connected directly to a writer. The reason is that writers tend
to get direct access to the image buffer from their input, since image adaptors do not have a real buffer their
behavior in this circumstances is incorrect. You should avoid instantiating the ImageFileWriter or the
ImageSeriesWriter over an image adaptor type.