Maverick/Creating new modules

From KitwarePublic
Jump to navigationJump to search

Image IO

The run-time (shared library) modules must read and write the same type of image that is passed to them. This is a requirement of the execution model we've borrowed from Slicer.

In ITK, the application determines the type of data returned by the reader or written by the writer. The idea is that the application knows what data representation (precision) is needed to carry-out a particular operation (e.g., to blur an image, you shouldn't operate on pixels of type unsigned char).

So, ITK and Slicer collide. ITK the app decides the pixel type. Slicer the image file decides the pixel type.

So, when you write a module in Maverick, that module must first determine the pixel type of the image being passed to it, then create a reader that matches that type, then convert to the image to a useful type, process the data, convert it back to the input type, and then write the results back.

This isn't as bad as it sounds. The code snippet is as follows:

First, create a function that returns the pixel type in a file:

void GetImageType (std::string fileName,
                  itk::ImageIOBase::IOPixelType &pixelType,
                  itk::ImageIOBase::IOComponentType &componentType)
{
 typedef itk::Image<short, 3> ImageType;
 itk::ImageFileReader<ImageType>::Pointer imageReader =
       itk::ImageFileReader<ImageType>::New();
 imageReader->SetFileName(fileName.c_str());
 imageReader->UpdateOutputInformation();

 pixelType = imageReader->GetImageIO()->GetPixelType();
 componentType = imageReader->GetImageIO()->GetComponentType();
} 


Create a templated function that does the reading, processing, and writing:

template< class PixelT >
int DoIt( int argc, char *argv[] )
{
 typedef itk::Image< PixelT, 3 >  ImageType;

 typedef itk::ImageFileReader< ImageType > ReaderType;
 typename ReaderType::Pointer reader = ReaderType::New():
 ...
 typedef itk::ImageFileWriter< ImageType > WriterType;
 typename WriterType::Pointer writer = WriterType::New();
 ...
}

Finally, have your main determine the pixel type and then call the proper templated instance of your function:

int main(int argc, char * argv )
{
 PARSE_ARGS;

 itk::ImageIOBase::IOPixelType pixelType;
 itk::ImageIOBase::IOComponentType componentType;
 // The following line MUST occur before the call to GetImageType!!! 
 itk::ObjectFactoryBase::RegisterFactory( itk::MRMLIDImageIOFactory::New() );
 try
   {
   GetImageType( inputVolume, pixelType, componentType ); 
   switch( componentType )
     {
     case itk::ImageIOBase::UCHAR:
       return DoIt< unsigned char >( argc, argv );
     case itk::ImageIOBase::CHAR:
       return DoIt< char >( argc, argv );
     case itk::ImageIOBase::USHORT:
       return DoIt< unsigned short >( argc, argv );
     case itk::ImageIOBase::SHORT:
       return DoIt< short >( argc, argv );
     case itk::ImageIOBase::UINT:
     case itk::ImageIOBase::INT:
     case itk::ImageIOBase::ULONG:
     case itk::ImageIOBase::LONG:
     case itk::ImageIOBase::FLOAT:
     case itk::ImageIOBase::DOUBLE:
       return DoIt< float >( argc, argv );
     case itk::ImageIOBase::UNKNOWNCOMPONENTTYPE:
     default:
       {
       std::cout << "unknown component type" << std::endl;
       return EXIT_SUCCESS;
       }
    }
}

Note that I cheat and don't do all of them. Just the ones we expect to encounter when running a Maverick application. The problem is that each new templateed instance increases the compile time :( We may need to address this better in the future (or hope the slicer folks do).