00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
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
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
00132
00133
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";
00141 break;
00142 case cmtk::ANALYZE_AXIAL_FLIP:
00143 orientString = "RAI";
00144 break;
00145 case cmtk::ANALYZE_CORONAL:
00146 orientString = "RIP";
00147 break;
00148 case cmtk::ANALYZE_CORONAL_FLIP:
00149 orientString = "RSA";
00150 break;
00151 case cmtk::ANALYZE_SAGITTAL:
00152 orientString = "AIR";
00153 break;
00154 case cmtk::ANALYZE_SAGITTAL_FLIP:
00155 orientString = "AIL";
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
00191 volume->m_MetaInformation[META_SPACE] = volume->m_MetaInformation[META_SPACE_ORIGINAL] = orientString;
00192 volume->ChangeCoordinateSpace( AnatomicalOrientation::ORIENTATION_STANDARD );
00193
00194
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";
00278 }
00279 std::string originalOrientation = writeVolume->m_MetaInformation[META_IMAGE_ORIENTATION_ORIGINAL];
00280 if ( originalOrientation == "" )
00281 {
00282 originalOrientation = currentOrientation;
00283 }
00284
00285
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 );
00313 #else
00314 FileHeader header( buffer, false );
00315 #endif
00316
00317 header.StoreField<int>(0, 348 );
00318 header.StoreField<int>( 32, 16384 );
00319 header.StoreField<short>( 36, 0 );
00320 header.StoreField<char>( 38, 'r' );
00321
00322
00323 header.StoreField<short>( 40, 4 );
00324
00325
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 );
00330 header.StoreField<short>( 50, 0 );
00331 header.StoreField<short>( 52, 0 );
00332 header.StoreField<short>( 54, 0 );
00333 header.StoreField<short>( 56, 0 );
00334
00335 header.StoreField<float>( 68, 0.0 );
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 );
00375 header.StoreField<float>( 96, 1.0f );
00376
00377
00378 header.StoreField<float>( 108, 0.0f );
00379
00380
00381 const Types::DataItemRange dataRange = data->GetRange();
00382
00383 header.StoreField<float>( 124, static_cast<float>( dataRange.m_UpperBound ) );
00384 header.StoreField<float>( 128, static_cast<float>( dataRange.m_LowerBound ) );
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
00392 header.StoreField<byte>( 252, cmtk::ANALYZE_AXIAL );
00393 header.StoreField<byte>( 254, 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 );
00404
00405
00406 header.StoreFieldString( 344, "SRI1", 4 );
00407 }
00408
00409
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
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 }