cmtkVolumeFromFileNifti.cxx

Go to the documentation of this file.
00001 /*
00002 //
00003 //  Copyright 1997-2009 Torsten Rohlfing
00004 //
00005 //  Copyright 2004-2010 SRI International
00006 //
00007 //  This file is part of the Computational Morphometry Toolkit.
00008 //
00009 //  http://www.nitrc.org/projects/cmtk/
00010 //
00011 //  The Computational Morphometry Toolkit is free software: you can
00012 //  redistribute it and/or modify it under the terms of the GNU General Public
00013 //  License as published by the Free Software Foundation, either version 3 of
00014 //  the License, or (at your option) any later version.
00015 //
00016 //  The Computational Morphometry Toolkit is distributed in the hope that it
00017 //  will be useful, but WITHOUT ANY WARRANTY; without even the implied
00018 //  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019 //  GNU General Public License for more details.
00020 //
00021 //  You should have received a copy of the GNU General Public License along
00022 //  with the Computational Morphometry Toolkit.  If not, see
00023 //  <http://www.gnu.org/licenses/>.
00024 //
00025 //  $Revision: 2695 $
00026 //
00027 //  $LastChangedDate: 2011-01-07 16:20:45 -0800 (Fri, 07 Jan 2011) $
00028 //
00029 //  $LastChangedBy: torstenrohlfing $
00030 //
00031 */
00032 
00033 #include "cmtkVolumeFromFile.h"
00034 
00035 #include <System/cmtkConsole.h>
00036 #include <System/cmtkCompressedStream.h>
00037 
00038 #include <IO/cmtkVolumeIO.h>
00039 #include <IO/cmtkAnalyze.h>
00040 #include <IO/cmtkFileHeader.h>
00041 
00042 #include <Base/cmtkUniformVolume.h>
00043 #include <Base/cmtkAnatomicalOrientation.h>
00044 
00045 #include "nifti1.h"
00046 
00047 #include <stdio.h>
00048 #include <string.h>
00049 #include <stdlib.h>
00050 
00051 #ifdef HAVE_ZLIB
00052 #  include <zlib.h>
00053 #endif
00054 
00055 #ifdef HAVE_SYS_STAT_H
00056 #  include <sys/stat.h>
00057 #endif
00058 
00059 namespace
00060 cmtk
00061 {
00062 
00065 
00066 const UniformVolume::SmartPtr
00067 VolumeFromFile::ReadNifti( const char* pathHdr, const bool detached, const bool readData )
00068 {
00069   CompressedStream hdrStream( pathHdr );
00070   if ( !hdrStream.IsValid() ) 
00071     {
00072     StdErr.printf( "ERROR: could not open Nifti header file %s\n", pathHdr );
00073     return UniformVolume::SmartPtr( NULL );
00074     }
00075   
00076   nifti_1_header buffer;
00077   if ( sizeof(buffer) != hdrStream.Read( &buffer, 1, sizeof(buffer) ) ) 
00078     {
00079     StdErr.printf( "ERROR: could not read %d bytes from header file %s\n", int( sizeof( buffer ) ), pathHdr );
00080     return UniformVolume::SmartPtr( NULL );
00081     }
00082   hdrStream.Close();
00083   
00084   // determine if we need to byte-swap
00085   const int dim0 = buffer.dim[0];
00086   const bool byteSwap = ((dim0>0) && (dim0<8)) ? false : true;
00087 
00088 #ifdef WORDS_BIGENDIAN
00089   FileHeader header( &buffer, !byteSwap );
00090 #else
00091   FileHeader header( &buffer, byteSwap );
00092 #endif
00093 
00094   short ndims = header.GetField<short>( 40 );
00095   if ( ndims < 3 ) 
00096     {
00097     StdErr.printf( "ERROR: image dimension %d is smaller than 3 in file %s\n", ndims, pathHdr );
00098     return UniformVolume::SmartPtr( NULL );
00099     }
00100   
00101   const DataGrid::IndexType::ValueType dims[3] = { header.GetField<short>( 42 ), header.GetField<short>( 44 ), header.GetField<short>( 46 ) };
00102   const int dims3 = header.GetField<short>( 48 );
00103 
00104   if ( (ndims > 3) && (dims3 > 1) ) 
00105     {
00106     StdErr.printf( "WARNING: dimension %d is greater than 3 in file %s\n", ndims, pathHdr );
00107     }
00108   
00109   float pixelDim[3];
00110   header.GetArray( pixelDim, 80, 3 );
00111 
00112   Types::Coordinate size[3] = { (dims[0] - 1) * fabs( pixelDim[0] ), (dims[1] - 1) * fabs( pixelDim[1] ), (dims[2] - 1) * fabs( pixelDim[2] ) };
00113 
00114   UniformVolume::SmartPtr volume( new UniformVolume( DataGrid::IndexType( dims ), UniformVolume::CoordinateVectorType( size ) ) );
00115   // Nifti is in RAS space.
00116   const char *const niftiSpace = "RAS";
00117   volume->m_MetaInformation[META_SPACE] = volume->m_MetaInformation[META_SPACE_ORIGINAL] = niftiSpace;
00118 
00119   const short qform_code = header.GetField<short>( offsetof(nifti_1_header,qform_code) );
00120   if ( qform_code > 0 )
00121     {
00122     const float qb = header.GetField<float>( offsetof(nifti_1_header,quatern_b) );
00123     const float qc = header.GetField<float>( offsetof(nifti_1_header,quatern_c) );
00124     const float qd = header.GetField<float>( offsetof(nifti_1_header,quatern_d) );
00125     const double qa = sqrt( std::max( 0.0, 1.0 - (qb*qb + qc*qc + qd*qd) ) );
00126 
00127     const float qfac = (header.GetField<float>( offsetof(nifti_1_header,pixdim) ) >= 0) ? 1.0f : -1.0f;
00128 
00129     const Types::Coordinate directions[3][3] = 
00130       {
00131         {        pixelDim[0] * (qa*qa+qb*qb-qc*qc-qd*qd),        pixelDim[0] * (2*qb*qc+2*qa*qd),                pixelDim[0] * (2*qb*qd-2*qa*qc) },
00132         {        pixelDim[1] * (2*qb*qc-2*qa*qd),                pixelDim[1] * (qa*qa+qc*qc-qb*qb-qd*qd),        pixelDim[1] * (2*qc*qd+2*qa*qb) },
00133         { qfac * pixelDim[2] * (2*qb*qd+2*qa*qc),         qfac * pixelDim[2] * (2*qc*qd-2*qa*qb),         qfac * pixelDim[2] * (qa*qa+qd*qd-qc*qc-qb*qb) }
00134       };
00135     
00136     const Matrix3x3<Types::Coordinate> m3( directions );
00137     Matrix4x4<Types::Coordinate> m4( m3 );
00138 
00139     m4[3][0] = header.GetField<float>( offsetof(nifti_1_header,qoffset_x) );
00140     m4[3][1] = header.GetField<float>( offsetof(nifti_1_header,qoffset_y) );
00141     m4[3][2] = header.GetField<float>( offsetof(nifti_1_header,qoffset_z) );
00142 
00143     volume->m_IndexToPhysicalMatrix = m4;
00144     
00145     char orientationImage[4];
00146     AnatomicalOrientation::GetOrientationFromDirections( orientationImage, m4, niftiSpace );
00147     volume->m_MetaInformation[META_IMAGE_ORIENTATION] = volume->m_MetaInformation[META_IMAGE_ORIENTATION_ORIGINAL] = orientationImage;
00148     }
00149   else
00150     {
00151     const short sform_code = header.GetField<short>( offsetof(nifti_1_header,sform_code) );
00152     if ( sform_code > 0 )
00153       {
00154       float srow_x[4], srow_y[4], srow_z[4];
00155       header.GetArray( srow_x, offsetof(nifti_1_header,srow_x), 4 );
00156       header.GetArray( srow_y, offsetof(nifti_1_header,srow_y), 4 );
00157       header.GetArray( srow_z, offsetof(nifti_1_header,srow_z), 4 );
00158 
00159       const Types::Coordinate directions[4][4] =        
00160       {
00161         { srow_x[0], srow_y[0], srow_z[0], 0 },
00162         { srow_x[1], srow_y[1], srow_z[1], 0 },
00163         { srow_x[2], srow_y[2], srow_z[2], 0 },
00164         { srow_x[3], srow_y[3], srow_z[3], 1 }
00165       };
00166       
00167       Matrix4x4<Types::Coordinate> m4( directions );      
00168       volume->m_IndexToPhysicalMatrix = m4;
00169     
00170       char orientationImage[4];
00171       AnatomicalOrientation::GetOrientationFromDirections( orientationImage, m4, niftiSpace );
00172       volume->m_MetaInformation[META_IMAGE_ORIENTATION] = volume->m_MetaInformation[META_IMAGE_ORIENTATION_ORIGINAL] = orientationImage;
00173       }
00174     else
00175       {
00176       // no orientation info, default to nifti standard space
00177       volume->m_MetaInformation[META_IMAGE_ORIENTATION] = volume->m_MetaInformation[META_IMAGE_ORIENTATION_ORIGINAL] = niftiSpace;
00178       }
00179     }
00180   
00181   // don't read data, we're done here.
00182   if ( ! readData )
00183     return volume;
00184 
00185   ScalarDataType dtype;
00186   switch ( header.GetField<short>( 70 ) ) 
00187     {
00188     case DT_NONE:
00189     case DT_BINARY:
00190     case DT_COMPLEX:
00191     case DT_COMPLEX128:
00192     case DT_COMPLEX256:
00193     case DT_INT64:
00194     case DT_UINT64:
00195     case DT_FLOAT128:
00196     case DT_RGB:
00197     case DT_ALL:
00198     default:
00199       StdErr.printf( "ERROR: unsupported data type %d in Nifti file %s\n", header.GetField<short>( 70 ), pathHdr );
00200       return volume;
00201     case DT_UNSIGNED_CHAR:
00202       dtype = TYPE_BYTE;
00203       break;
00204     case DT_INT8:
00205       dtype = TYPE_CHAR;
00206       break;
00207     case DT_SIGNED_SHORT:
00208       dtype = TYPE_SHORT;
00209       break;
00210     case DT_SIGNED_INT:
00211       dtype = TYPE_INT;
00212       break;
00213     case DT_FLOAT:
00214       dtype = TYPE_FLOAT;
00215       break;
00216     case DT_DOUBLE:
00217       dtype = TYPE_DOUBLE;
00218       break;
00219     case DT_UINT16:
00220       dtype = TYPE_USHORT;
00221       break;
00222     case DT_UINT32:
00223       dtype = TYPE_UINT;
00224       break;
00225     }  
00226   
00227   size_t offset = static_cast<size_t>( header.GetField<float>( 108 ) );
00228   char* pathImg = Memory::AllocateArray<char>(  4 + strlen( pathHdr )  );
00229   strcpy( pathImg, pathHdr );
00230 
00231   if ( detached )
00232     {
00233     char* suffix = strstr( pathImg, ".hdr" );
00234     if ( suffix ) *suffix = 0;
00235     strcat( pathImg, ".img" );
00236     offset = 0;
00237     }
00238   
00239   CompressedStream stream( pathImg );
00240   if ( stream.IsValid() ) 
00241     {
00242     stream.Seek( offset, SEEK_CUR );
00243     
00244     TypedArray::SmartPtr data( TypedArray::Create( dtype, volume->GetNumberOfPixels() ) );
00245     stream.Read( data->GetDataPtr(), data->GetItemSize(), data->GetDataSize() );
00246 
00247     if ( byteSwap ) 
00248       data->ChangeEndianness();
00249 
00250     volume->SetData( data );
00251     } 
00252   else
00253     {
00254     StdErr.printf( "WARNING: could not open Nifti image file %s\n", pathImg );
00255     }
00256   
00257   Memory::DeleteArray( pathImg );
00258   
00259   return volume;
00260 }
00261 
00262 void
00263 VolumeFromFile::WriteNifti
00264 ( const char* path, const UniformVolume& volume, const bool )
00265 {
00266   bool detachedHeader = false;
00267   bool forceCompressed = false;
00268 
00269   std::string pathImg( path );
00270 
00271   // first, look for .gz
00272   size_t suffixPosGz = pathImg.rfind( ".gz" );
00273   if ( suffixPosGz != std::string::npos )
00274     {
00275     // found: set force compression flag and remove .gz from path
00276     forceCompressed = true;
00277     pathImg = pathImg.substr( 0, suffixPosGz );
00278     }
00279   
00280   std::string pathHdr( pathImg );
00281   size_t suffixPos = pathHdr.rfind( ".img" );
00282   if ( suffixPos != std::string::npos )
00283     {
00284     detachedHeader = true;
00285     pathHdr.replace( suffixPos, 4, ".hdr" );
00286     }
00287   
00288   UniformVolume::SmartPtr writeVolume( volume.Clone() );
00289   writeVolume->ChangeCoordinateSpace( "RAS" );
00290 
00291   const TypedArray* data = writeVolume->GetData();
00292   if ( ! data ) return;
00293 
00294   nifti_1_header header;
00295   memset( &header, 0, sizeof( header ) );
00296 
00297   header.sizeof_hdr = 348; // header size
00298   header.dim_info = 0;
00299 
00300   // ndims
00301   header.dim[0] = 4;
00302 
00303   // dimensions
00304   header.dim[1] = writeVolume->GetDims()[AXIS_X];
00305   header.dim[2] = writeVolume->GetDims()[AXIS_Y];
00306   header.dim[3] = writeVolume->GetDims()[AXIS_Z];
00307   header.dim[4] = 1;
00308   header.dim[5] = 0;
00309   header.dim[6] = 0;
00310   header.dim[7] = 0;
00311 
00312   header.qform_code = 0;
00313   header.sform_code = 1;
00314 
00315   AffineXform::MatrixType m4 = volume.m_IndexToPhysicalMatrix;
00316   for ( int i = 0; i < 4; ++i )
00317     {
00318     header.srow_x[i] = static_cast<float>( m4[i][0] );
00319     header.srow_y[i] = static_cast<float>( m4[i][1] );
00320     header.srow_z[i] = static_cast<float>( m4[i][2] );
00321     }
00322   
00323   switch ( data->GetType() ) 
00324     {
00325     default:
00326       header.datatype = DT_UNKNOWN;
00327       header.bitpix = 0;
00328       break;
00329     case TYPE_BYTE:
00330       header.datatype = DT_UNSIGNED_CHAR;
00331       header.bitpix = 8 * sizeof(byte);
00332       break;
00333     case TYPE_CHAR:
00334       header.datatype = DT_INT8;
00335       header.bitpix = 8 * sizeof(char);
00336       break;
00337     case TYPE_SHORT:
00338       header.datatype = DT_INT16;
00339       header.bitpix = 8 * sizeof(short);
00340       break;
00341     case TYPE_USHORT:
00342       header.datatype = DT_UINT16;
00343       header.bitpix = 8 * sizeof(unsigned short);
00344       break;
00345     case TYPE_INT:
00346       header.datatype = DT_INT32;
00347       header.bitpix = 8 * sizeof(int);
00348       break;
00349     case TYPE_UINT:
00350       header.datatype = DT_UINT32;
00351       header.bitpix = 8 * sizeof(unsigned int);
00352       break;
00353     case TYPE_FLOAT:
00354       header.datatype = DT_FLOAT;
00355       header.bitpix = 8 * sizeof(float);
00356       break;
00357     case TYPE_DOUBLE:
00358       header.datatype = DT_DOUBLE;
00359       header.bitpix = 8 * sizeof(double);
00360       break;
00361     }  
00362   
00363   header.pixdim[0] = 1;
00364   header.pixdim[1] = static_cast<float>( writeVolume->m_Delta[AXIS_X] );
00365   header.pixdim[2] = static_cast<float>( writeVolume->m_Delta[AXIS_Y] );
00366   header.pixdim[3] = static_cast<float>( writeVolume->m_Delta[AXIS_Z] );
00367   header.pixdim[4] = 0.0;
00368   header.pixdim[5] = 0.0;
00369   
00370   // determine data range;
00371   const Types::DataItemRange dataRange = data->GetRange();
00372   header.cal_max = static_cast<float>( dataRange.m_UpperBound );
00373   header.cal_min = static_cast<float>( dataRange.m_LowerBound );
00374   
00375 #ifdef _MSC_VER
00376   const char *const modestr = "w9b";
00377 #else
00378   const char *const modestr = "w9";
00379 #endif
00380     
00381   if ( detachedHeader )
00382     {
00383     memcpy( &header.magic, "ni1\x00", 4 );
00384     header.vox_offset = 0;
00385     FILE *hdrFile = fopen( pathHdr.c_str(), modestr );
00386     if ( hdrFile ) 
00387       {
00388       fwrite( &header, 1, sizeof( header ), hdrFile );
00389       const int extension = 0;
00390       fwrite( &extension, 1, 4, hdrFile );
00391       fclose( hdrFile );
00392       }
00393     else
00394       {
00395       StdErr << "ERROR: NIFTI header file '" << pathHdr << "' could not be opened for writing!\n";
00396       }
00397     }
00398   else
00399     {
00400     memcpy( &header.magic, "n+1\x00", 4 );
00401     header.vox_offset = 352;
00402     }
00403   
00404   if ( VolumeIO::GetWriteCompressed() || forceCompressed )
00405     {
00406     struct stat buf;
00407     if ( ! stat( pathImg.c_str(), &buf ) )
00408       {
00409       StdErr << "WARNING: NIFTI file '" << path << "' will be written compressed, but uncompressed file exists!\n";
00410       }
00411     
00412     gzFile imgFile = gzopen( (pathImg+".gz").c_str(), modestr );
00413     if ( imgFile ) 
00414       {
00415       if ( ! detachedHeader )
00416         {
00417         gzwrite( imgFile, &header, sizeof( header ) );
00418         const int extension = 0;
00419         gzwrite( imgFile, &extension, 4 );
00420         }
00421       
00422       const size_t dataSize = data->GetItemSize() * data->GetDataSize();
00423       if ( dataSize != static_cast<size_t>( gzwrite( imgFile, data->GetDataPtr(), dataSize ) ) )
00424         {
00425         StdErr << "WARNING: gzwrite() returned error when writing to " << pathImg << "\n";
00426         }
00427       gzclose( imgFile );
00428       }
00429     }
00430   else
00431     {
00432     FILE *imgFile = fopen( pathImg.c_str(), modestr );
00433     if ( imgFile ) 
00434       {
00435       if ( ! detachedHeader )
00436         {
00437         fwrite( &header, 1, sizeof( header ), imgFile );
00438         const int extension = 0;
00439         fwrite( &extension, 1, 4, imgFile );
00440         }
00441 
00442       fwrite( data->GetDataPtr(), data->GetItemSize(), data->GetDataSize(), imgFile );
00443       fclose( imgFile );
00444       }
00445     }
00446 }
00447 
00448 } // namespace cmtk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines