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 "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
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
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
00177 volume->m_MetaInformation[META_IMAGE_ORIENTATION] = volume->m_MetaInformation[META_IMAGE_ORIENTATION_ORIGINAL] = niftiSpace;
00178 }
00179 }
00180
00181
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
00272 size_t suffixPosGz = pathImg.rfind( ".gz" );
00273 if ( suffixPosGz != std::string::npos )
00274 {
00275
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;
00298 header.dim_info = 0;
00299
00300
00301 header.dim[0] = 4;
00302
00303
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
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 }