cmtkVolumeFromFileAnalyze.cxx

Go to the documentation of this file.
00001 /*
00002 //
00003 //  Copyright 1997-2010 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: 2519 $
00026 //
00027 //  $LastChangedDate: 2010-10-29 22:39:09 -0700 (Fri, 29 Oct 2010) $
00028 //
00029 //  $LastChangedBy: torsten_at_home $
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 <stdio.h>
00046 #include <string.h>
00047 #include <stdlib.h>
00048 
00049 #ifdef HAVE_ZLIB
00050 #  include <zlib.h>
00051 #endif
00052 
00053 #ifdef HAVE_SYS_STAT_H
00054 #  include <sys/stat.h>
00055 #endif
00056 
00057 namespace
00058 cmtk
00059 {
00060 
00062 const char* const CMTK_LEGACY_ANALYZE_IO = "CMTK_LEGACY_ANALYZE_IO";
00063 
00065 const char* const IGS_LEGACY_ANALYZE_IO = "IGS_LEGACY_ANALYZE_IO";
00066 
00069 
00070 const UniformVolume::SmartPtr
00071 VolumeFromFile::ReadAnalyzeHdr( const char* pathHdr, const bool bigEndian, const bool readData )
00072 {
00073 #ifdef _MSC_VER
00074   FILE *hdrFile = fopen( pathHdr, "rb" );
00075 #else
00076   FILE *hdrFile = fopen( pathHdr, "r" );
00077 #endif
00078   if ( !hdrFile ) 
00079     {
00080     StdErr.printf( "ERROR: could not open Analyze header file %s\n", pathHdr );
00081     return UniformVolume::SmartPtr( NULL );
00082     }
00083   
00084   char buffer[348];
00085   if ( 348 != fread( buffer, 1, 348, hdrFile ) ) 
00086     {
00087     StdErr.printf( "ERROR: could not read 348 bytes from header file %s\n", pathHdr );
00088     return UniformVolume::SmartPtr( NULL );
00089     }
00090   
00091   FileHeader header( buffer, bigEndian );
00092 
00093   short ndims = header.GetField<short>( 40 );
00094   if ( ndims < 3 ) 
00095     {
00096     StdErr.printf( "ERROR: image dimension %d is smaller than 3 in file %s\n", ndims, pathHdr );
00097     return UniformVolume::SmartPtr( NULL );
00098     }
00099   
00100   DataGrid::IndexType dims;
00101   dims[0] = header.GetField<short>( 42 );
00102   dims[1] = header.GetField<short>( 44 );
00103   dims[2] = header.GetField<short>( 46 );
00104   const int dims3 = header.GetField<short>( 48 );
00105 
00106   if ( (ndims > 3) && (dims3 > 1) ) 
00107     {
00108     StdErr.printf( "WARNING: dimension %d is greater than 3 in file %s\n", ndims, pathHdr );
00109     }
00110   
00111   float pixelDim[3];
00112   header.GetArray( pixelDim, 80, 3 );
00113 
00114 // use fabs to catch something weird in FSL's output
00115 #ifndef CMTK_REGRESSION
00116   const Types::Coordinate size[3] = { float((dims[0] - 1) * fabs( pixelDim[0] )), float((dims[1] - 1) * fabs( pixelDim[1] )), float((dims[2] - 1) * fabs( pixelDim[2] )) };
00117 #else
00118   const Types::Coordinate size[3] = { (dims[0] - 1) * fabs( pixelDim[0] ), (dims[1] - 1) * fabs( pixelDim[1] ), (dims[2] - 1) * fabs( pixelDim[2] ) };
00119 #endif
00120   
00121   fclose( hdrFile );
00122 
00123   const byte orient = header.GetField<byte>( 252 );
00124 
00125   const char* orientString = NULL;
00126 
00127   const bool legacyMode = !header.CompareFieldStringN( 344, "SRI1", 4 );
00128   if ( legacyMode )
00129     {
00130     // 
00131     // Legacy mode: read Analyze images with incorrect orientations to preserve backward compatibility
00132     // We do this by setting "apparent" anatomical axis orientation codes so that the new reorientation
00133     // function in DataGrid will perform the same reordering as its obsolete SetData() member used to.
00134     //
00135     switch ( orient ) 
00136       {
00137       default:
00138         StdErr.printf( "WARNING: unsupported slice orientation %d in Analyze file %s\n", orient, pathHdr );
00139       case cmtk::ANALYZE_AXIAL:
00140         orientString = "RAS"; // INCORRECT LEGACY ORIENTATION
00141         break;
00142       case cmtk::ANALYZE_AXIAL_FLIP:
00143         orientString = "RAI"; // INCORRECT LEGACY ORIENTATION
00144         break;
00145       case cmtk::ANALYZE_CORONAL:
00146         orientString = "RIP"; // INCORRECT LEGACY ORIENTATION
00147         break;
00148       case cmtk::ANALYZE_CORONAL_FLIP:
00149         orientString = "RSA"; // INCORRECT LEGACY ORIENTATION
00150         break;
00151       case cmtk::ANALYZE_SAGITTAL:
00152         orientString = "AIR"; // INCORRECT LEGACY ORIENTATION
00153         break;
00154       case cmtk::ANALYZE_SAGITTAL_FLIP:
00155         orientString = "AIL"; // INCORRECT LEGACY ORIENTATION
00156         break;
00157       }
00158     StdErr << "INFO: reading Analyze hdr/img in legacy orientation mode, assuming " << orientString << " axes\n";
00159     }
00160   else
00161     {
00162     switch ( orient ) 
00163       {
00164       default:
00165         StdErr.printf( "WARNING: unsupported slice orientation %d in Analyze file %s\n", orient, pathHdr );
00166       case cmtk::ANALYZE_AXIAL:
00167         orientString = "LAS";
00168         break;
00169       case cmtk::ANALYZE_AXIAL_FLIP:
00170         orientString = "LPS";
00171         break;
00172       case cmtk::ANALYZE_CORONAL:
00173         orientString = "LSA";
00174         break;
00175       case cmtk::ANALYZE_CORONAL_FLIP:
00176         orientString = "LIA";
00177         break;
00178       case cmtk::ANALYZE_SAGITTAL:
00179         orientString = "ASL";
00180         break;
00181       case cmtk::ANALYZE_SAGITTAL_FLIP:
00182         orientString = "AIL";
00183         break;
00184       }
00185     }
00186   
00187   UniformVolume::SmartPtr volume( new UniformVolume( dims, UniformVolume::CoordinateVectorType( size ) ) );
00188   volume->m_MetaInformation[META_IMAGE_ORIENTATION] = volume->m_MetaInformation[META_IMAGE_ORIENTATION_ORIGINAL] = orientString;
00189 
00190   // Analyze is medical data, which we always treat in RAS space.
00191   volume->m_MetaInformation[META_SPACE] = volume->m_MetaInformation[META_SPACE_ORIGINAL] = orientString;
00192   volume->ChangeCoordinateSpace( AnatomicalOrientation::ORIENTATION_STANDARD );
00193 
00194   // don't read data, we're done here.
00195   if ( ! readData )
00196     return volume;
00197 
00198   ScalarDataType dtype;
00199   switch ( header.GetField<short>( 70 ) ) 
00200     {
00201     case cmtk::ANALYZE_TYPE_NONE:
00202     case cmtk::ANALYZE_TYPE_BINARY:
00203     case cmtk::ANALYZE_TYPE_COMPLEX:
00204     case cmtk::ANALYZE_TYPE_RGB:
00205     case cmtk::ANALYZE_TYPE_ALL:
00206     default:
00207       StdErr.printf( "ERROR: unsupported data type %d in Analyze file %s\n", header.GetField<short>( 70 ), pathHdr );
00208       return volume;
00209     case cmtk::ANALYZE_TYPE_UNSIGNED_CHAR:
00210       dtype = TYPE_BYTE;
00211       break;
00212     case cmtk::ANALYZE_TYPE_SIGNED_SHORT:
00213       dtype = TYPE_SHORT;
00214       break;
00215     case cmtk::ANALYZE_TYPE_SIGNED_INT:
00216       dtype = TYPE_INT;
00217       break;
00218     case cmtk::ANALYZE_TYPE_FLOAT:
00219       dtype = TYPE_FLOAT;
00220       break;
00221     case cmtk::ANALYZE_TYPE_DOUBLE:
00222       dtype = TYPE_DOUBLE;
00223       break;
00224     case cmtk::ANALYZE_TYPE_USHORT:
00225       dtype = TYPE_USHORT;
00226       break;
00227     case cmtk::ANALYZE_TYPE_UINT:
00228       dtype = TYPE_UINT;
00229       break;
00230     }  
00231   
00232   size_t offset = static_cast<size_t>( header.GetField<float>( 108 ) );
00233   
00234   char* pathImg = Memory::AllocateArray<char>(  4 + strlen( pathHdr )  );
00235   strcpy( pathImg, pathHdr );
00236   char* suffix = strstr( pathImg, ".hdr" );
00237   if ( suffix ) *suffix = 0;
00238   strcat( pathImg, ".img" );
00239   
00240   CompressedStream stream( pathImg );
00241   if ( stream.IsValid() ) 
00242     {
00243     stream.Seek( offset, SEEK_CUR );
00244     
00245     TypedArray::SmartPtr data( TypedArray::Create( dtype, volume->GetNumberOfPixels() ) );
00246     stream.Read( data->GetDataPtr(), data->GetItemSize(), data->GetDataSize() );
00247 
00248 #ifdef WORDS_BIGENDIAN
00249     if ( ! bigEndian ) data->ChangeEndianness();
00250 #else
00251     if ( bigEndian ) data->ChangeEndianness();
00252 #endif
00253 
00254     volume->SetData( data );
00255     } 
00256   else
00257     {
00258     StdErr.printf( "WARNING: could not open Analyze image file %s\n", pathImg );
00259     }
00260   
00261   Memory::DeleteArray( pathImg );
00262   
00263   return volume;
00264 }
00265 
00266 void
00267 VolumeFromFile::WriteAnalyzeHdr
00268 ( const char* pathHdr, const UniformVolume& volume, const bool verbose )
00269 {
00270   UniformVolume::SmartPtr writeVolume( volume.Clone() );
00271   if ( writeVolume->MetaKeyExists( META_SPACE_ORIGINAL ) )
00272     writeVolume->ChangeCoordinateSpace( writeVolume->m_MetaInformation[META_SPACE_ORIGINAL] );
00273 
00274   std::string currentOrientation = writeVolume->m_MetaInformation[META_IMAGE_ORIENTATION];
00275   if ( currentOrientation == "" )
00276     {
00277     currentOrientation = "LAS"; // default: write as is, axial tag, no reorientation.
00278     }
00279   std::string originalOrientation = writeVolume->m_MetaInformation[META_IMAGE_ORIENTATION_ORIGINAL];
00280   if ( originalOrientation == "" )
00281     {
00282     originalOrientation = currentOrientation;
00283     }
00284   
00285   // try to write something as close as possible to original orientation
00286   const char *const supportedOrientations[] = { "LAS", "LSA", "ASL", NULL };
00287   const char* writeOrientation = AnatomicalOrientation::GetClosestOrientation( originalOrientation.c_str(), supportedOrientations );
00288 
00289   if ( getenv( CMTK_LEGACY_ANALYZE_IO ) || getenv( IGS_LEGACY_ANALYZE_IO ) )
00290     {
00291     const char *const supportedOrientationsLegacy[] = { "RAS", "RIP", "AIR", NULL };
00292     writeOrientation = AnatomicalOrientation::GetClosestOrientation( originalOrientation.c_str(), supportedOrientationsLegacy );
00293     }
00294   
00295   UniformVolume::SmartPtr reorientedVolume;
00296   if ( strcmp( writeOrientation, currentOrientation.c_str() ) )
00297     {
00298     if ( verbose )
00299       {
00300       StdErr << "INFO: WriteAnalyzeHdr will reorient output volume from '" << currentOrientation << "' to '" << writeOrientation << "'\n";
00301       }
00302     reorientedVolume = UniformVolume::SmartPtr( volume.GetReoriented( writeOrientation ) );
00303     writeVolume = reorientedVolume;
00304     }
00305   
00306   const TypedArray* data = writeVolume->GetData();
00307   if ( ! data ) return;
00308 
00309   char buffer[348];
00310   memset( buffer, 0, sizeof( buffer ) );
00311 #ifdef WORDS_BIGENDIAN
00312   FileHeader header( buffer, true /*bigEndian*/ );
00313 #else
00314   FileHeader header( buffer, false /*bigEndian*/ );
00315 #endif
00316 
00317   header.StoreField<int>(0, 348 ); // header size
00318   header.StoreField<int>( 32, 16384 ); // extents
00319   header.StoreField<short>( 36, 0 ); // session error
00320   header.StoreField<char>( 38, 'r' ); // regular
00321 
00322   // ndims
00323   header.StoreField<short>( 40, 4 );
00324 
00325   // dimensions
00326   header.StoreField<short>( 42, writeVolume->GetDims()[AXIS_X] );
00327   header.StoreField<short>( 44, writeVolume->GetDims()[AXIS_Y] );
00328   header.StoreField<short>( 46, writeVolume->GetDims()[AXIS_Z] );
00329   header.StoreField<short>( 48, 1 ); // write dims 3-7
00330   header.StoreField<short>( 50, 0 ); // just for safety
00331   header.StoreField<short>( 52, 0 ); // just for safety
00332   header.StoreField<short>( 54, 0 ); // just for safety
00333   header.StoreField<short>( 56, 0 ); // just for safety
00334 
00335   header.StoreField<float>( 68, 0.0 ); // vox_offset
00336   switch ( data->GetType() ) 
00337     {
00338     default:
00339       header.StoreField<short>( 70, cmtk::ANALYZE_TYPE_NONE );
00340       header.StoreField<short>( 72, 0 );
00341     case TYPE_BYTE:
00342       header.StoreField<short>( 70, cmtk::ANALYZE_TYPE_UNSIGNED_CHAR );
00343       header.StoreField<short>( 72, 8 * sizeof( byte ) );
00344       break;
00345     case TYPE_SHORT:
00346       header.StoreField<short>( 70, cmtk::ANALYZE_TYPE_SIGNED_SHORT );
00347       header.StoreField<short>( 72, 8 * sizeof( short ) );
00348       break;
00349     case TYPE_USHORT:
00350       header.StoreField<short>( 70, cmtk::ANALYZE_TYPE_USHORT );
00351       header.StoreField<short>( 72, 8 * sizeof( unsigned short ) );
00352       break;
00353     case TYPE_INT:
00354       header.StoreField<short>( 70, cmtk::ANALYZE_TYPE_SIGNED_INT );
00355       header.StoreField<short>( 72, 8 * sizeof( signed int ) );
00356       break;
00357     case TYPE_UINT:
00358       header.StoreField<short>( 70, cmtk::ANALYZE_TYPE_UINT );
00359       header.StoreField<short>( 72, 8 * sizeof( unsigned int ) );
00360       break;
00361     case TYPE_FLOAT:
00362       header.StoreField<short>( 70, cmtk::ANALYZE_TYPE_FLOAT );
00363       header.StoreField<short>( 72, 8 * sizeof( float ) );
00364       break;
00365     case TYPE_DOUBLE:
00366       header.StoreField<short>( 70, cmtk::ANALYZE_TYPE_DOUBLE );
00367       header.StoreField<short>( 72, 8 * sizeof( double ) );
00368       break;
00369     }  
00370   
00371   header.StoreField<float>( 80, (float)writeVolume->m_Delta[AXIS_X] );
00372   header.StoreField<float>( 84, (float)writeVolume->m_Delta[AXIS_Y] );
00373   header.StoreField<float>( 88, (float)writeVolume->m_Delta[AXIS_Z] );
00374   header.StoreField<float>( 92, 1.0f ); // write sizes in dims 3 and
00375   header.StoreField<float>( 96, 1.0f ); // 4 just to be safe
00376 
00377   // set zero offset for binary file.
00378   header.StoreField<float>( 108, 0.0f );
00379 
00380   // determine data range;
00381   const Types::DataItemRange dataRange = data->GetRange();
00382 
00383   header.StoreField<float>( 124, static_cast<float>( dataRange.m_UpperBound ) ); // cal_max
00384   header.StoreField<float>( 128, static_cast<float>( dataRange.m_LowerBound ) ); // cal_min
00385 
00386   header.StoreField<int>( 140, static_cast<int>( dataRange.m_UpperBound ) );
00387   header.StoreField<int>( 144, static_cast<int>( dataRange.m_LowerBound ) );
00388 
00389   if ( getenv( CMTK_LEGACY_ANALYZE_IO ) || getenv( IGS_LEGACY_ANALYZE_IO ) )
00390     {
00391     // slice orientation always axial from caudal to cranial
00392     header.StoreField<byte>( 252, cmtk::ANALYZE_AXIAL );
00393     header.StoreField<byte>( 254, 0 ); //set Nifti sform code to 0.
00394     }
00395   else
00396     {
00397     if ( !strcmp( writeOrientation, "LAS" ) )
00398       header.StoreField<byte>( 252, cmtk::ANALYZE_AXIAL );
00399     else if ( !strcmp( writeOrientation, "LSA" ) )
00400       header.StoreField<byte>( 252, cmtk::ANALYZE_CORONAL );
00401     else if ( !strcmp( writeOrientation, "ASL" ) )
00402       header.StoreField<byte>( 252, cmtk::ANALYZE_SAGITTAL );
00403     header.StoreField<byte>( 254, 0 ); //set Nifti sform code to 0.
00404 
00405     // mark this as "new" SRI Analyze image.
00406     header.StoreFieldString( 344, "SRI1", 4 );
00407     }
00408 
00409   // write binary data
00410   char* pathImg = Memory::AllocateArray<char>(  4 + strlen( pathHdr )  );
00411   strcpy( pathImg, pathHdr );
00412   char* suffix = strstr( pathImg, ".hdr" );
00413   if ( suffix ) *suffix = 0;
00414   strcat( pathImg, ".img" );
00415   
00416   if ( VolumeIO::GetWriteCompressed() )
00417     {
00418     struct stat buf;
00419     if ( ! stat( pathImg, &buf ) )
00420       {
00421       StdErr << "WARNING: Analyze img file '" << pathImg << "' will be written compressed, but uncompressed file exists!\n";
00422       }
00423     
00424 #ifdef _MSC_VER
00425     const char *const modestr = "w9b";
00426 #else
00427     const char *const modestr = "w9";
00428 #endif
00429     
00430     gzFile imgFile = gzopen( strcat( pathImg, ".gz" ), modestr );
00431     if ( imgFile ) 
00432       {
00433       const size_t dataSize = data->GetItemSize() * data->GetDataSize();
00434       if ( dataSize != static_cast<size_t>( gzwrite( imgFile, data->GetDataPtr(), dataSize ) ) )
00435         {
00436         StdErr << "WARNING: gzwrite() returned error when writing to " << pathImg << "\n";
00437         }
00438       gzclose( imgFile );
00439       }
00440     }
00441   else
00442     {
00443 #ifdef _MSC_VER
00444     const char *const modestr = "wb";
00445 #else
00446     const char *const modestr = "w";
00447 #endif
00448 
00449     FILE *imgFile = fopen( pathImg, modestr );
00450     if ( imgFile ) 
00451       {
00452       fwrite( data->GetDataPtr(), data->GetItemSize(), data->GetDataSize(), imgFile );
00453       fclose( imgFile );
00454       }
00455     }
00456 
00457   // write header info
00458 #ifdef _MSC_VER
00459   FILE *hdrFile = fopen( pathHdr, "wb" );
00460 #else
00461   FILE *hdrFile = fopen( pathHdr, "w" );
00462 #endif
00463   if ( hdrFile ) 
00464     {
00465     if ( 348 != fwrite( buffer, 1, 348, hdrFile ) ) 
00466       {
00467       StdErr.printf( "ERROR: could not write 348 bytes to header file %s\n", pathHdr );
00468       }
00469     fclose( hdrFile );
00470     }
00471 
00472   Memory::DeleteArray( pathImg );
00473 }
00474 
00475 } // namespace cmtk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines