ITK introductory tutorial (10) ITK imports image data from the buffer

Posted by linfidel on Thu, 04 Nov 2021 03:10:01 +0100

@TOC

1. General

This example illustrates how to import data into the itk::Image class. This is very useful when interfacing with other software systems. Many systems use continuous memory blocks as buffers for image pixel data. The current example assumes this and inserts an itk::ImportImageFilter into the buffer to generate an image as output.
Here, we create a composite image of the central sphere on the locally allocated buffer memory and pass this memory block to ImportImageFilter.

2. Process analysis

First, you must include the header file of the itk::ImportImageFilter class.

#include "itkImage.h"
#include "itkImportImageFilter.h"

Then we select the data type to represent the image pixels. We assume that external memory blocks use the same data type to represent pixels.

typedef unsigned char PixelType;
const unsigned int Dimension = 3;
typedef itk::Image< PixelType, Dimension > ImageType;

The following line is an instantiation of the ImportImageFilter class.

typedef itk::ImportImageFilter< PixelType, Dimension > ImportFilterType;

Use the New() method to create a filter object and assign it to a smart pointer.

ImportFilterType::Pointer importFilter = ImportFilterType::New();

The filter requires the user to specify the size of the output image. You can use the SetRgion() method. The image size must match the number of pixels of the locally allocated buffer.

ImportFilterType::SizeType size;
size[0] = 200; // size along X
size[1] = 200; // size along Y
size[2] = 200; // size along Z
ImportFilterType::IndexType start;
start.Fill( 0 );
ImportFilterType::RegionType region;
region.SetIndex( start );
region.SetSize( size );
importFilter->SetRegion( region );

Use the SetOrigin() method to specify the origin of the output image.

const itk::SpacePrecisionType origin[ Dimension ] = { 0.0, 0.0, 0.0 };
importFilter->SetOrigin( origin );

Use the SetSpacing() method to pass the spacing of the output image.

// spacing isotropic volumes to 1.0
const itk::SpacePrecisionType spacing[ Dimension ] = { 1.0, 1.0, 1.0 };
importFilter->SetSpacing( spacing );

Now we allocate a block of memory containing pixel data and pass it to ImportImageFilter. Note: we use the exact size specified by the SetRegion() method. In practical application, you can get this buffer from some other class libraries, which use different data structures to represent an image.

const unsigned int numberOfPixels = size[0] * size[1] * size[2];
PixelType * localBuffer = new PixelType[ numberOfPixels ];

Here we use a binary sphere to fill the buffer. Here we use a simple for() loop like C or FOTTRAN programming languages. Note: ITK does not use a for() loop in its internal code to access pixels. All pixel access tasks are performed using itk::ImageIterators that support processing n-dimensional images.

const double radius2 = radius * radius;
	PixelType * it = localBuffer;

	for (int z = 0; z < size[2]; z++)
	{
		for (int y = 0; y < size[1]; y++)
		{
			for (int x = 0; x < size[0]; x++)
			{
				double temp = (x-40 )*(x-40 ) + (y-40 )*(y-40) + (z-40)*(z-40);
				*it++ = PixelType(temp > radius2 ? 0 : 255);
			}
		}
	}

Pass the buffer to ImportImageFilter using the SetImportPointer() method. Note that the last parameter of this method indicates who is responsible for freeing memory when the memory block is no longer in use. When the return value is false, it indicates that the destructor of ImportImageFilter will not attempt to release the buffer when calling; On the other hand, when the return value is true, it means that the filter will be allowed to release the memory block during import filter deconstruction.

The C++ new() operator must be used to allocate memory so that ImportImageFilter can properly free memory blocks. Memory allocated by other memory allocation mechanisms, such as malloc and calloc in C, will not be properly released by ImportImageFilter. In other words, the application programmer is responsible for ensuring that only ImportImageFilter is allowed to free the memory allocated by the C++ new operator.

const bool importImageFilterWillOwnTheBuffer = true;
importFilter->SetImportPointer( localBuffer, numberOfPixels,
importImageFilterWillOwnTheBuffer );

Finally, we connect the output of this filter to a pipe. For simplicity, we only use one writer here. Of course, we can also use any other filter.

typedef itk::ImageFileWriter< ImageType > WriterType;
itk::ObjectFactoryBase::RegisterFactory(itk::PNGImageIOFactory::New());
WriterType::Pointer writer = WriterType::New();
writer->SetFileName( argv[1] );
writer->SetInput( importFilter->GetOutput() );

Note that we did not call delete on the buffer because we passed true as the last parameter of SetImportPointer(). The buffer is now owned by ImportImageFilter.

3. Code example

#include "itkImage.h"
#include "itkImportImageFilter.h" / / the header file containing the importimagefilter (image pixel data import buffer) class
#include "itkImageFileWriter.h"
#include <itkPNGImageIOFactory.h>
//This example illustrates how to enter data into the itk::Image class. This is more useful when connected to other software systems
//Use. Many systems use an adjacent memory block of memory as a buffer for image pixel data. The current example assumes
//In this case, an itk::ImportImageFilter is inserted into the buffer to produce an image as output.
//We call the memory center block to create a synchronized image and pass the memory block to ImportImageFilter. this
//The example is set based on running. The user must provide an output file name as a command-line variable.
int main(int argc, char * argv[])
{
	/*if( argc < 2 )
	  {
	  std::cerr << "Usage: " << std::endl;
	  std::cerr << argv[0] << "  outputImageFile" << std::endl;
	  return EXIT_FAILURE;
	  }*/
	  /*Select a data type to represent image pixels. We assume that the external memory block of memory uses the same data class
		Type to represent pixels*/
	typedef unsigned char   PixelType;
	const unsigned int Dimension = 3;

	typedef itk::Image< PixelType, Dimension > ImageType;
	//Instantiation of ImportImageFilter type
	typedef itk::ImportImageFilter< PixelType, Dimension >   ImportFilterType;
	//Use the New() method to create a filter object importFilter and point to a smart pointer
	ImportFilterType::Pointer importFilter = ImportFilterType::New();
	/*The filter requires the user to specify the size of the image as the output, which can be achieved by using the SetRgion() method. Image size must be
	 Match the number of pixel variables of the buffer that is currently invoked.*/
	ImportFilterType::SizeType  size;
	size[0] = 200;  // size along X
	size[1] = 200;  // size along Y
	size[2] = 200;  // size along Z
	ImportFilterType::IndexType start;
	start.Fill(0);

	ImportFilterType::RegionType region;
	region.SetIndex(start);
	region.SetSize(size);
	importFilter->SetRegion(region);
	//Use the SetOrigin() method to specify the origin of the output image
	const itk::SpacePrecisionType origin[Dimension] = { 0.0, 0.0, 0.0 };
	importFilter->SetOrigin(origin);
	//Use the SetSpacing() method to pass the spacing of the output image
	const itk::SpacePrecisionType  spacing[Dimension] = { 1.0, 1.0, 1.0 };
	importFilter->SetSpacing(spacing);
	/*Now we allocate memory blocks containing pixel data to pass information to ImportImageFilter. Note: we use and
	SetRegion() The size specified by the method is exactly the same size. In practical application, you can use a representative image
	Different data structures get this buffer from some other class libraries.*/
	const unsigned int numberOfPixels = size[0] * size[1] * size[2];
	PixelType * localBuffer = new PixelType[numberOfPixels];

	const double radius = 50.0;
	/*Here you can fill the buffer with a binary sphere. Here we like C or FOTTRAN programming language
	Use a simple for () loop as in. Note: ITK cannot use a for() loop in its internal encoding of access pixels.
	Use itk::ImageIterators that support processing n-dimensional images instead of performing all pixel access tasks.*/
	const double radius2 = radius * radius;
	PixelType * it = localBuffer;

	for (int z = 0; z < size[2]; z++)
	{
		for (int y = 0; y < size[1]; y++)
		{
			for (int x = 0; x < size[0]; x++)
			{
				double temp = (x-40 )*(x-40 ) + (y-40 )*(y-40) + (z-40)*(z-40);
				*it++ = PixelType(temp > radius2 ? 0 : 255);
			}
		}
	}

	/* The buffer is passed to ImportImageFilter under the action of SetImportPointer(). Note the last of this method
	The problem is to specify who will free memory when memory is no longer used. When the return value is false, it means that when the destructor is called
	ImportImageFilter Buffer is not released; On the other hand, when the return value is true, it indicates that the destructor is allowed to be released
	Enter the memory block on the filter.
	Since ImportImageFilter frees up the appropriate memory blocks, the C++ new() operation can call these memory blocks. use
	Memory allocated by other memory allocation mechanisms, such as malloc and calloc in C, will not be allocated by ImportImageFilter
	To free up the appropriate memory. In other words, the programming application needs to ensure that only the ImportImageFilter command is given to release
	C++ Allocate memory for the new operation.*/
	const bool importImageFilterWillOwnTheBuffer = true;
	importFilter->SetImportPointer(localBuffer, numberOfPixels,
		importImageFilterWillOwnTheBuffer);
	typedef itk::ImageFileWriter< ImageType > WriterType;
	itk::ObjectFactoryBase::RegisterFactory(itk::PNGImageIOFactory::New());
	WriterType::Pointer writer = WriterType::New();

	writer->SetFileName("lena2.png");
	/*Finally, we connect the output of the filter to a pipe. For simplicity, we only use one here
	writer ,Of course, any other filter can:*/
	writer->SetInput(importFilter->GetOutput());

	try
	{
		writer->Update();
	}
	catch (itk::ExceptionObject & exp)
	{
		std::cerr << "Exception caught !" << std::endl;
		std::cerr << exp << std::endl;
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}
//Note: we pass true as the last problem of SetImportPointer(), so we don't need to call the release operation on the buffer
//Make. The buffer is now owned by ImportImageFilter

4. Result display