/*========================================================================= Program: Insight Segmentation & Registration Toolkit Module: $RCSfile: itkInterfileImageIO.cxx,v $ Language: C++ Date: $Date: 2006/08/10 17:38:46 $ Version: $Revision: 1.1.1.1 $ Copyright (c) Insight Software Consortium. All rights reserved. See ITKCopyright.txt or http://www.itk.org/HTML/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "itkInterfileImageIO.h" #include #include #include #include #include #include #include #include namespace itk { /** * \class InterfileHeaderLine * \brief Private class to parse text lines in the header file. */ class InterfileHeaderLine { protected: /** \class whitespace * \brief Helper class for the interfile header parser to eliminate what * is considered white space in Interfile format. * * *Compatibility* : Underscore ('_') is also considered whitespace in * the Interfile specification, but some header files use it for the * name of data file key. */ struct whitespace : public std::unary_function { bool operator() (char c) const { if ((c == ' ') || (c == '\t') || (c == '!')) return true; else return false; } }; public: /* * Constructor * * Takes a line from the Interfile text header as an argument and * parses it into member variables. The expected format is: * key [index] := value * If this format was not found, the IsValid member return false. * Otherwise, if IsValid is true, then the key, index, and value * members have been initialized with valid values. */ InterfileHeaderLine(const char *inputLine) : m_parseSuccess(false), index(-1) { // Remove spaces from the input line std::string headerLine; headerLine.reserve(::strlen(inputLine)); std::remove_copy_if( inputLine, inputLine + ::strlen(inputLine), std::back_inserter(headerLine), whitespace() ); // Find ':=' std::string::iterator itKeyEnd = std::find( headerLine.begin(), headerLine.end(), ':' ); if (itKeyEnd == headerLine.end()) return; // Find the start of the value by advancing past ':=' std::string::iterator itValueStart( itKeyEnd ); ++itValueStart; if (( itValueStart == headerLine.end() ) || ( *itValueStart != '=' )) return; ++itValueStart; // Determine if there is an index std::string::iterator itIndexStart = std::find( headerLine.begin(), itKeyEnd, '[' ); if ( itIndexStart == itKeyEnd ) { // No index, so copy the entire range before ':=' as the key key.assign( headerLine.begin(), itKeyEnd ); } else { // Find ']' std::string::iterator itIndexEnd = std::find( itIndexStart, itKeyEnd, ']' ); if ( itIndexEnd == itKeyEnd ) return; // Copy the index into a temporary string for parsing to an int ++itIndexStart; std::string strIndex( itIndexStart, itIndexEnd ); // Check that the index is an integer for ( std::string::iterator it = strIndex.begin(); it != strIndex.end(); ++it ) { if ( !::isdigit( *it ) ) return; } index = ::atoi( strIndex.c_str() ); // Copy the key --itIndexStart; key.assign( headerLine.begin(), itIndexStart ); } // Copy the part past the ':=' as the value value.assign( itValueStart, headerLine.end() ); m_parseSuccess = true; } // True on successful initialization bool IsValid() { return m_parseSuccess; } /* Public variables */ std::string key; std::string value; int index; protected: bool m_parseSuccess; }; /*------------------------------------------------------------------------- * InterfileImageIO Implementation -------------------------------------------------------------------------*/ /* * Constructor */ InterfileImageIO::InterfileImageIO() { } /* * Destructor */ InterfileImageIO::~InterfileImageIO() { } /* * CanReadFile * * Inspects the file at path file to see if it is in Interfile format. */ bool InterfileImageIO::CanReadFile(const char* file) { // Check if we got a file name std::string filename = file; if( filename == "" ) { itkDebugMacro(<<"No filename specified."); return false; } // First check the extension bool extensionFound = false; std::string::size_type pngPos = filename.rfind(".h33"); if ((pngPos != std::string::npos) && (pngPos == filename.length() - 4)) { extensionFound = true; } if( !extensionFound ) { itkDebugMacro(<<"The filename extension is not recognized: " << file); return false; } // Now check the file header std::ifstream inputStream; inputStream.open( file, std::ios::in ); if ( inputStream.fail() ) { itkDebugMacro(<<"Could open file " << file); return false; } if ( inputStream.eof() ) { return false; } // Check the first 2 lines for Interfile identifying tags const int kLineLength = 255; char line[kLineLength]; inputStream.getline( line, kLineLength ); InterfileHeaderLine header(line); if (!header.IsValid()) return false; if ( itksys::SystemTools::Strucmp( header.key.c_str(), "Interfile" ) == 0 ) { inputStream.getline( line, kLineLength ); InterfileHeaderLine header2(line); if (!header.IsValid()) return false; if ( itksys::SystemTools::Strucmp( header2.key.c_str(), "versionofkeys" ) == 0 ) return true; } return false; } /* * ReadImageInformation * * Read the image header and populate member variables. */ void InterfileImageIO::ReadImageInformation() { // Open header file std::ifstream inputStream; inputStream.open( m_FileName.c_str(), std::ios::in ); if ( inputStream.fail() ) itkExceptionMacro(<< "Could not open file: " << m_FileName); // Start out a default of a 2D image and let the header override SetNumberOfDimensions( 2 ); // Parse each line of text in the header const int kLineLength = 255; char line[kLineLength]; bool fIsInt = false; bool fIsSigned = false; while ( !inputStream.eof() ) { inputStream.getline( line, kLineLength ); InterfileHeaderLine header(line); if ( !header.IsValid() ) continue; if ( itksys::SystemTools::Strucmp(header.key.c_str(), "nameofdatafile" ) == 0 ) { // Name of the image data file m_imageFileName = header.value; } else if ( itksys::SystemTools::Strucmp( header.key.c_str(), "numberofdimensions" ) == 0 ) { // Number of dimensions -- not found in Interfile spec v3.3 (see .h file for URL) int nDims = ::atoi(header.value.c_str()); if ( nDims <= 0 ) itkExceptionMacro(<< "Number of dimensions must be positive" ); SetNumberOfDimensions( nDims ); } else if ( itksys::SystemTools::Strucmp( header.key.c_str(), "matrixsize" ) == 0 ) { // Image size in a dimension if (( static_cast(header.index) > GetNumberOfDimensions() ) || ( header.index <= 0 )) itkExceptionMacro(<< "Matrix index " << header.index << " out of range" ); SetDimensions( header.index - 1, ::atoi(header.value.c_str()) ); } else if ( itksys::SystemTools::Strucmp(header.key.c_str(), "numberformat" ) == 0 ) { // Pixel number format -- only float and signed integer supported for now if (( itksys::SystemTools::Strucmp( header.value.c_str(), "float" ) == 0 ) || ( itksys::SystemTools::Strucmp( header.value.c_str(), "shortfloat" ) == 0 )) { SetPixelType( SCALAR ); SetComponentType( FLOAT ); } else if ( itksys::SystemTools::Strucmp( header.value.c_str(), "longfloat" ) == 0 ) { SetPixelType( SCALAR ); SetComponentType( DOUBLE ); } else if (( itksys::SystemTools::Strucmp( header.value.c_str(), "signedinteger" ) == 0 ) || ( itksys::SystemTools::Strucmp( header.value.c_str(), "integer" ) == 0 )) { fIsInt = true; fIsSigned = true; SetPixelType( SCALAR ); SetComponentType( SHORT ); } else if ( itksys::SystemTools::Strucmp( header.value.c_str(), "unsignedinteger" ) == 0 ) { fIsInt = true; fIsSigned = false; SetPixelType( SCALAR ); SetComponentType( USHORT ); } else { itkExceptionMacro(<< "pixel type " << header.value << " not supported" ); } } else if ( itksys::SystemTools::Strucmp( header.key.c_str(), "numberofbytesperpixel" ) == 0 ) { int size = ::atoi( header.value.c_str() ); if (fIsInt) { IOComponentType dataType; switch (size) { case 1: dataType = fIsSigned ? CHAR : UCHAR; break; case 2: dataType = fIsSigned ? SHORT : USHORT; break; case 4: dataType = fIsSigned ? LONG : ULONG; break; default: itkExceptionMacro(<< header.value << "-byte integer not supported" ); } SetPixelType( SCALAR ); SetComponentType( dataType ); } else { if ((size != 4) && (size != 8)) itkExceptionMacro(<< header.value << "-byte float not supported" ); } } else if ( itksys::SystemTools::Strucmp( header.key.c_str(), "imagedatabyteorder" ) == 0 ) { // Image data byte order if ( itksys::SystemTools::Strucmp( header.value.c_str(), "bigendian" ) == 0 ) { SetByteOrder( BigEndian ); } else // *Compatibility* : we should check for "littleendian" but some files we have seen mispell this tag { SetByteOrder( LittleEndian ); } } else if ( itksys::SystemTools::Strucmp( header.key.c_str(), "scalingfactor(mm/pixel)" ) == 0 ) { // Scaling factor for each pixel -- not found in Interfile spec v3.3 (see .h file for URL) if (( static_cast(header.index) > GetNumberOfDimensions() ) || ( header.index <= 0 )) itkExceptionMacro(<< "Scaling factor index " << header.index << " out of range" ); SetSpacing( header.index - 1, ::atof( header.value.c_str()) ); } } // Checks for valid data file path if ( m_imageFileName.length() == 0 ) itkExceptionMacro(<< "Input data file not specified" ); inputStream.close(); } /* * Read * * Read the image portion of the Interfile into buffer. Assumes * the header has already been read or this object otherwise contains * valid values describing the image buffer to read. */ void InterfileImageIO::Read(void* buffer) { // Check to see if we have anything to read if ( m_imageFileName.size() == 0 ) return; // Find the path to the header file, since the image file is assumed // to be in the same directory. MET_GetFilePath copies the contents of // its first parameter into its second parameter. It then finds the // last directory separator ("/" or "\") and sets the end of string // marker at that position + 1. Thus, we need to allocate a buffer // the length of the input string + 1. std::vector< char > vPath( m_FileName.size() + 1 ); MET_GetFilePath( m_FileName.c_str(), &vPath[0] ); vPath.resize( ::strlen( &vPath[0] ) ); // Construct the full path to the image file std::string imageFullpath( vPath.begin(), vPath.end() ); imageFullpath.append( m_imageFileName.begin(), m_imageFileName.end() ); // Open image file std::ifstream inputStream; inputStream.open( imageFullpath.c_str(), std::ios::in | std::ios::binary ); if ( inputStream.fail() ) itkExceptionMacro(<< "Could not open file: " << imageFullpath); // Read all bytes in at once inputStream.read( static_cast(buffer), GetImageSizeInBytes() ); // Byte swap if needed const std::type_info &pixelType = GetComponentTypeInfo(); if ( m_ByteOrder == LittleEndian ) { if ( pixelType == typeid( double )) ByteSwapper::SwapRangeFromSystemToLittleEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if ( pixelType == typeid( float )) ByteSwapper::SwapRangeFromSystemToLittleEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if (( pixelType == typeid( long )) || ( pixelType == typeid( unsigned long ))) ByteSwapper::SwapRangeFromSystemToLittleEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if (( pixelType == typeid( int )) || ( pixelType == typeid( unsigned int ))) ByteSwapper::SwapRangeFromSystemToLittleEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if (( pixelType == typeid( short )) || ( pixelType == typeid( unsigned short ))) ByteSwapper::SwapRangeFromSystemToLittleEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if (( pixelType == typeid( char )) || ( pixelType == typeid( unsigned char ))) ByteSwapper::SwapRangeFromSystemToLittleEndian( static_cast( buffer ), GetImageSizeInComponents() ); } else if ( m_ByteOrder == BigEndian ) { if ( pixelType == typeid( double ) ) ByteSwapper::SwapRangeFromSystemToBigEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if ( pixelType == typeid( float ) ) ByteSwapper::SwapRangeFromSystemToBigEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if (( pixelType == typeid( long )) || ( pixelType == typeid( unsigned long ))) ByteSwapper::SwapRangeFromSystemToBigEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if (( pixelType == typeid( int )) || ( pixelType == typeid( unsigned int ))) ByteSwapper::SwapRangeFromSystemToBigEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if (( pixelType == typeid( short )) || ( pixelType == typeid( unsigned short ))) ByteSwapper::SwapRangeFromSystemToBigEndian( static_cast( buffer ), GetImageSizeInComponents() ); else if (( pixelType == typeid( char )) || ( pixelType == typeid( unsigned char ))) ByteSwapper::SwapRangeFromSystemToBigEndian( static_cast( buffer ), GetImageSizeInComponents() ); } inputStream.close(); } /* * PrintSelf * * Print diagnostics to the output stream. */ void InterfileImageIO::PrintSelf(std::ostream& os, Indent indent) const { Superclass::PrintSelf( os, indent ); } /* * CanWriteFile * * Check that there is a valid file name for writing. */ bool InterfileImageIO::CanWriteFile(const char* file) { std::string filename(file); if( filename == "" ) { return false; } std::string::size_type extPos = filename.rfind( ".h33" ); if (( extPos != std::string::npos ) && ( extPos == filename.length() - 4 )) { return true; } return false; } /* * WriteImageInformation * * Write Interfile image header. */ void InterfileImageIO::WriteImageInformation(void) { // Open the header text file std::ofstream outfile( m_FileName.c_str(), std::ios::out ); if ( outfile.fail() ) itkExceptionMacro(<< "Could not create file: " << m_FileName); // Generate an image file name based on the header file name. // Replace the extension with ".img" or if there is not // existing extension, then append ".img". m_imageFileName = m_FileName; std::string::size_type extPos = m_imageFileName.rfind( ".h33" ); if (( extPos != std::string::npos ) && ( extPos == m_imageFileName.length() - 4 )) { m_imageFileName.resize( m_imageFileName.length() - 4 ); } m_imageFileName.append( ".img" ); // Strip out the directory path for writing into the header file std::string imageFileName; std::string::size_type dirPos = m_imageFileName.find_last_of( "\\/" ); if ( dirPos != std::string::npos ) imageFileName = m_imageFileName.substr( dirPos + 1 ); else imageFileName = m_imageFileName; // Write headers outfile << "!Interfile :=" << std::endl; outfile << "version of keys := 3.3" << std::endl; outfile << "name of data file := " << imageFileName << std::endl; outfile << "number of dimensions := " << GetNumberOfDimensions() << std::endl; unsigned int i; for ( i = 0; i < GetNumberOfDimensions(); i++ ) outfile << "matrix size [" << i + 1 << "] := " << GetDimensions( i ) << std::endl; WriteDataType(outfile); outfile << "imagedata byte order := "; #ifdef CMAKE_WORDS_BIGENDIAN outfile << "BIGENDIAN"; #else outfile << "LITTLEENDIAN"; #endif outfile << std::endl; outfile << std::setprecision( 7 ); for ( i = 0; i < GetNumberOfDimensions(); i++ ) outfile << "scaling factor (mm/pixel) [" << i + 1 << "] := " << GetSpacing( i ) << std::endl; outfile.close(); } /* * WriteDataType * * Write data type tags out. */ void InterfileImageIO::WriteDataType(std::ofstream& outfile) { const std::type_info &pixelType = GetComponentTypeInfo(); const char *typeStr; int typeSize; if ( pixelType == typeid( double )) { typeStr = "long float"; typeSize = 8; } else if ( pixelType == typeid( float )) { typeStr = "float"; // *Compatibility* : This should be short float, but examples we have seen so far use float typeSize = 4; } else if (( pixelType == typeid( long )) || ( pixelType == typeid( int ))) { typeStr = "signed integer"; typeSize = 4; } else if (( pixelType == typeid( unsigned long )) || ( pixelType == typeid( unsigned int ))) { typeStr = "unsigned integer"; typeSize = 4; } else if ( pixelType == typeid( short )) { typeStr = "signed integer"; typeSize = 2; } else if ( pixelType == typeid( unsigned short )) { typeStr = "unsigned integer"; typeSize = 2; } else if ( pixelType == typeid( char )) { typeStr = "signed integer"; typeSize = 1; } else if ( pixelType == typeid( unsigned char )) { typeStr = "unsigned integer"; typeSize = 1; } else itkExceptionMacro(<< pixelType.name() << " output not supported" ); outfile << "number format := " << typeStr << std::endl; outfile << "number of bytes per pixel := " << typeSize << std::endl; } /* * Write * * Write Interfile image pair (text header and binary image files). */ void InterfileImageIO::Write(const void* buffer) { WriteImageInformation(); // seems to not be called automatically // Open the file std::ofstream outfile(m_imageFileName.c_str(), std::ios::out | std::ios::binary ); if ( outfile.fail() ) itkExceptionMacro(<< "Could not create file: " << m_imageFileName); // Write binary image outfile.write( static_cast( buffer ), GetImageSizeInBytes() ); outfile.close(); } } // end namespace itk