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 "cmtkImageSymmetryPlaneCommandLineBase.h"
00034
00035 #include <IO/cmtkClassStream.h>
00036 #include <IO/cmtkVolumeIO.h>
00037 #include <IO/cmtkXformIO.h>
00038
00039 #include <Registration/cmtkBestNeighbourOptimizer.h>
00040 #include <Registration/cmtkReformatVolume.h>
00041
00042 #include <System/cmtkProgress.h>
00043 #include <System/cmtkProgressConsole.h>
00044
00045 cmtk::ImageSymmetryPlaneCommandLineBase
00046 ::ImageSymmetryPlaneCommandLineBase()
00047 : m_Verbose( false ),
00048 m_MinValueSet( false ),
00049 m_MaxValueSet( false ),
00050 m_Sampling( 1.0 ),
00051 m_Accuracy( 0.1 ),
00052 m_Interpolation( Interpolators::LINEAR ),
00053 m_Levels( 4 ),
00054 m_DisableOptimization( false ),
00055 m_FixOffset( false ),
00056 m_MirrorOutFile( NULL ),
00057 m_AlignedOutFile( NULL ),
00058 m_MarkPlaneAligned( false ),
00059 m_MarkedOutFile( NULL ),
00060 m_DifferenceOutFile( NULL ),
00061 m_WriteXformPath( NULL ),
00062 m_MarkPlaneValue( 4095 ),
00063 m_PadOutValueSet( false ),
00064 m_SymmetryOutFileName( NULL ),
00065 m_SymmetryParameters( NULL ),
00066 m_SymmetryParametersFile( NULL ),
00067 m_InFileName( NULL ),
00068 m_InitialPlane( SYMPL_INIT_YZ ),
00069 m_CommandLine( CommandLine::PROPS_XML )
00070 {
00071 this->m_CommandLine.SetProgramInfo( CommandLine::PRG_TITLE, "Symmetry plane computation" );
00072 this->m_CommandLine.SetProgramInfo( CommandLine::PRG_DESCR, "Compute the approximate symmetry plane of an image to determine, for example, the mid-sagittal plane in human brain images. "
00073 "Various forms of output are supported, e.g., writing the input image with the symmetry plane drawn into it, or the input image realigned along the symmetry plane." );
00074 this->m_CommandLine.SetProgramInfo( CommandLine::PRG_CATEG, "CMTK.Registration" );
00075
00076 typedef CommandLine::Key Key;
00077 this->m_CommandLine.AddSwitch( Key( 'v', "verbose" ), &this->m_Verbose, true, "Turn on verbosity mode." )->SetProperties( CommandLine::PROPS_NOXML );
00078
00079 this->m_CommandLine.BeginGroup( "Optimization", "Optimization" );
00080 this->m_CommandLine.AddOption( Key( 'a', "accuracy" ), &this->m_Accuracy, "Accuracy (final optimization step size in [mm]." );
00081 this->m_CommandLine.AddOption( Key( 's', "sampling" ), &this->m_Sampling, "Resampled image resolution. This is the resolution [in mm] of the first (finest) resampled image in the multi-scale pyramid, "
00082 "which is derived directly from the original full-resolution images.");
00083 this->m_CommandLine.AddOption( Key( 'l', "levels" ), &this->m_Levels, "Number of resolution levels. The algorithm will create (levels-1) resampled images with increasingly coarse resolution and use these "
00084 "in successive order of increasing resolution before using the original images at the final level." );
00085 this->m_CommandLine.AddSwitch( Key( "fix-offset" ), &this->m_FixOffset, true, "Fix symmetry plane offset. Reduces computation time and forces symmetry plane to cross center of mass, but may lead to less-than-accurate result." );
00086 this->m_CommandLine.EndGroup();
00087
00088 this->m_CommandLine.BeginGroup( "Initial", "Initial approximate symmetry plane orientation" );
00089 CommandLine::EnumGroup<InitialPlaneEnum>::SmartPtr
00090 initialPlane = this->m_CommandLine.AddEnum( "initial-plane", &this->m_InitialPlane, "Initial orientation of symmetry plane. This should be the closest orthogonal plane to the expected actual symmetry plane." );
00091 initialPlane->AddSwitch( Key( "initial-axial" ), SYMPL_INIT_XY, "Approximately axial symmetry" );
00092 initialPlane->AddSwitch( Key( "initial-coronal" ), SYMPL_INIT_XZ, "Approximately coronal symmetry" );
00093 initialPlane->AddSwitch( Key( "initial-sagittal" ), SYMPL_INIT_YZ, "Approximately sagittal symmetry" );
00094 initialPlane->AddSwitch( Key( "initial-xy" ), SYMPL_INIT_XY, "Approximately XY plane symmetry" );
00095 initialPlane->AddSwitch( Key( "initial-xz" ), SYMPL_INIT_XZ, "Approximately XZ plane symmetry" );
00096 initialPlane->AddSwitch( Key( "initial-yz" ), SYMPL_INIT_YZ, "Approximately YZ plane symmetry" );
00097 this->m_CommandLine.EndGroup();
00098
00099 this->m_CommandLine.BeginGroup( "Pre-computed", "Pre-computed symmetry" )->SetProperties( CommandLine::PROPS_ADVANCED | CommandLine::PROPS_NOXML );
00100 this->m_CommandLine.AddOption( Key( "output-only" ), &this->m_SymmetryParameters, "Give symmetry parameters [Rho Theta Phi] as option, skip search.", &this->m_DisableOptimization );
00101 this->m_CommandLine.AddOption( Key( "output-only-file" ), &this->m_SymmetryParametersFile, "Read symmetry parameters from file, skip search.", &this->m_DisableOptimization );
00102 this->m_CommandLine.EndGroup();
00103
00104 this->m_CommandLine.BeginGroup( "Preprocessing", "Data pre-processing" )->SetProperties( CommandLine::PROPS_ADVANCED );
00105 this->m_CommandLine.AddOption( Key( "min-value" ), &this->m_MinValue, "Force minumum data value.", &this->m_MinValueSet );
00106 this->m_CommandLine.AddOption( Key( "max-value" ), &this->m_MaxValue, "Force maximum data value.", &this->m_MaxValueSet );
00107 this->m_CommandLine.EndGroup();
00108
00109 this->m_CommandLine.BeginGroup( "OutputImages", "Output of Images" );
00110 CommandLine::EnumGroup<Interpolators::InterpolationEnum>::SmartPtr interpGroup = this->m_CommandLine.AddEnum( "interpolation", &this->m_Interpolation, "Interpolation method used for reformatted output data" );
00111 interpGroup->AddSwitch( Key( 'L', "linear" ), Interpolators::LINEAR, "Use linear image interpolation for output." );
00112 interpGroup->AddSwitch( Key( 'C', "cubic" ), Interpolators::CUBIC, "Use cubic image interpolation for output." );
00113 interpGroup->AddSwitch( Key( 'S', "sinc" ), Interpolators::COSINE_SINC, "Use cosine-windowed sinc image interpolation for output." );
00114
00115 this->m_CommandLine.AddOption( Key( 'P', "pad-out" ), &this->m_PadOutValue, "Padding value for output images.", &this->m_PadOutValueSet )->SetProperties( CommandLine::PROPS_ADVANCED );
00116 this->m_CommandLine.AddOption( Key( "mark-value" ), &this->m_MarkPlaneValue, "Data value to mark (draw) symmetry plane." );
00117 this->m_CommandLine.AddOption( Key( "write-marked" ), &this->m_MarkedOutFile, "File name for output image with marked symmetry plane." )->SetProperties( CommandLine::PROPS_IMAGE | CommandLine::PROPS_OUTPUT );
00118 this->m_CommandLine.AddOption( Key( "write-aligned" ), &this->m_AlignedOutFile, "File name for symmetry plane-aligned output image." )->SetProperties( CommandLine::PROPS_IMAGE | CommandLine::PROPS_OUTPUT );
00119 this->m_CommandLine.AddSwitch( Key( "mark-aligned" ), &this->m_MarkPlaneAligned, true, "Mark symmetry plane in aligned output image." );
00120 this->m_CommandLine.AddOption( Key( "write-subtract" ), &this->m_DifferenceOutFile, "File name for mirror subtraction image." )->SetProperties( CommandLine::PROPS_IMAGE | CommandLine::PROPS_OUTPUT );
00121 this->m_CommandLine.AddOption( Key( "write-mirror" ), &this->m_MirrorOutFile, "File name for image mirrored w.r.t. symmetry plane." )->SetProperties( CommandLine::PROPS_IMAGE | CommandLine::PROPS_OUTPUT );
00122 this->m_CommandLine.EndGroup();
00123
00124 this->m_CommandLine.BeginGroup( "OutputParameters", "Output of Parameters" )->SetProperties( CommandLine::PROPS_ADVANCED );
00125 this->m_CommandLine.AddOption( Key( 'o', "outfile" ), &this->m_SymmetryOutFileName, "File name for symmetry plane parameter output." )->SetProperties( CommandLine::PROPS_FILENAME | CommandLine::PROPS_OUTPUT );
00126 this->m_CommandLine.AddOption( Key( "write-xform" ), &this->m_WriteXformPath, "Write affine alignment transformation to file" )
00127 ->SetProperties( CommandLine::PROPS_XFORM | CommandLine::PROPS_OUTPUT )
00128 ->SetAttribute( "reference", "InputImage" );
00129 this->m_CommandLine.EndGroup();
00130
00131 this->m_CommandLine.AddParameter( &this->m_InFileName, "InputImage", "Input image path" )->SetProperties( CommandLine::PROPS_IMAGE );
00132 }
00133
00134 int
00135 cmtk::ImageSymmetryPlaneCommandLineBase
00136 ::Run( const int argc, const char* argv[] )
00137 {
00138 if ( ! this->ParseCommandLine( argc, argv ) )
00139 return 2;
00140
00141 UniformVolume::SmartPtr originalVolume( VolumeIO::ReadOriented( this->m_InFileName, this->m_Verbose ) );
00142 if ( !originalVolume )
00143 {
00144 StdErr.printf( "Could not read image file %s\n", this->m_InFileName );
00145 return 1;
00146 }
00147
00148 CoordinateVector v( 6 );
00149
00150
00151 v[0] = 0;
00152
00153 switch ( this->m_InitialPlane )
00154 {
00155 case SYMPL_INIT_XY:
00156 v[1] = 0;
00157 v[2] = 0;
00158 break;
00159 case SYMPL_INIT_XZ:
00160 v[1] = 90;
00161 v[2] = 90;
00162 break;
00163 default:
00164 case SYMPL_INIT_YZ:
00165 v[1] = 0;
00166 v[2] = 90;
00167 break;
00168 }
00169
00170
00171 Vector3D center = originalVolume->GetCenterOfMass();
00172 v[3] = center[0]; v[4] = center[1]; v[5] = center[2];
00173
00174 if ( this->m_DisableOptimization )
00175 {
00176 v[0] = this->m_Rho;
00177 v[1] = this->m_Theta.Value();
00178 v[2] = this->m_Phi.Value();
00179 }
00180 else
00181 {
00182 BestNeighbourOptimizer optimizer;
00183
00184
00185 ProgressConsole progressIndicator( "Symmetry Plane Computation" );
00186 Progress::Begin( 0, this->m_Levels, 1, "Symmetry Plane Computation" );
00187
00188 for ( int level = 0; level < this->m_Levels; ++level )
00189 {
00190 UniformVolume::SmartPtr volume;
00191 if ( level < this->m_Levels-1 )
00192 {
00193 Types::Coordinate voxelSize = this->m_Sampling * pow( 2.0, (this->m_Levels-level-2) );
00194 if ( this->m_Verbose )
00195 fprintf( stderr, "Entering level %d out of %d (%.2f mm voxel size)\n", level+1, this->m_Levels, voxelSize );
00196
00197 volume = UniformVolume::SmartPtr( new UniformVolume( *originalVolume, voxelSize ) );
00198 }
00199 else
00200 {
00201 if ( this->m_Verbose )
00202 fprintf(stderr,"Entering level %d out of %d (original voxel size)\n", level+1, this->m_Levels );
00203 volume = originalVolume;
00204 }
00205
00206 ImageSymmetryPlaneFunctionalBase::SmartPtr functional;
00207 if ( this->m_MinValueSet || this->m_MaxValueSet )
00208 {
00209 Types::DataItemRange valueRange = volume->GetData()->GetRange();
00210
00211 if ( this->m_MinValueSet )
00212 valueRange.m_LowerBound = this->m_MinValue;
00213 if ( this->m_MaxValueSet )
00214 valueRange.m_UpperBound = this->m_MaxValue;
00215
00216 functional = this->CreateFunctional( volume, valueRange );
00217 }
00218 else
00219 {
00220 functional = this->CreateFunctional( volume );
00221 }
00222
00223 functional->SetFixOffset( this->m_FixOffset );
00224
00225 optimizer.SetFunctional( functional );
00226 optimizer.Optimize( v, pow( 2.0, this->m_Levels-level-1 ), this->m_Accuracy * pow( 2.0, this->m_Levels-level-1 ) );
00227
00228 Progress::SetProgress( level );
00229 }
00230
00231 Progress::Done();
00232
00233 if ( this->m_Verbose )
00234 fprintf( stdout, "rho=%f, theta=%f, phi=%f\n", v[0], v[1], v[2] );
00235 }
00236
00237 this->m_SymmetryPlane.SetParameters( v );
00238
00239 if ( this->m_SymmetryOutFileName )
00240 {
00241 ClassStream stream( this->m_SymmetryOutFileName, ClassStream::WRITE );
00242 stream << this->m_SymmetryPlane;
00243 stream.Close();
00244 }
00245
00246 if ( this->m_AlignedOutFile )
00247 this->WriteAligned( originalVolume );
00248
00249 if ( this->m_MarkedOutFile )
00250 this->WriteMarkPlane( originalVolume );
00251
00252 if ( this->m_DifferenceOutFile )
00253 this->WriteDifference( originalVolume );
00254
00255 if ( this->m_MirrorOutFile )
00256 WriteMirror( originalVolume );
00257
00258 if ( this->m_WriteXformPath )
00259 {
00260 AffineXform::SmartPtr alignment( this->m_SymmetryPlane.GetAlignmentXform( 0 ) );
00261 XformIO::Write( alignment, this->m_WriteXformPath, this->m_Verbose );
00262 }
00263
00264 return 0;
00265 }
00266
00267 bool
00268 cmtk::ImageSymmetryPlaneCommandLineBase
00269 ::ParseCommandLine( const int argc, const char* argv[] )
00270 {
00271 try
00272 {
00273 if ( ! this->m_CommandLine.Parse( argc, argv ) ) return false;
00274
00275 if ( this->m_SymmetryParameters )
00276 {
00277 double rho, theta, phi;
00278 if ( 3 == sscanf( this->m_SymmetryParameters, "%lf %lf %lf", &rho, &theta, &phi ) )
00279 {
00280 this->m_Rho = rho;
00281 this->m_Theta = Units::Degrees( theta );
00282 this->m_Phi = Units::Degrees( phi );
00283 }
00284 }
00285
00286 if ( this->m_SymmetryParametersFile )
00287 {
00288 ClassStream inStream( this->m_SymmetryParametersFile, ClassStream::READ );
00289 if ( inStream.IsValid() )
00290 {
00291 ParametricPlane *plane = NULL;
00292 inStream >> plane;
00293 this->m_Rho = plane->GetRho();
00294 this->m_Theta = plane->GetTheta();
00295 this->m_Phi = plane->GetPhi();
00296 delete plane;
00297 }
00298 else
00299 {
00300 StdErr.printf( "ERROR: Could not open symmetry parameter file %s\n", this->m_SymmetryParametersFile );
00301 }
00302 }
00303 }
00304 catch ( const CommandLine::Exception& ex )
00305 {
00306 StdErr << ex << "\n";
00307 return false;
00308 }
00309
00310 return true;
00311 }
00312
00313 void
00314 cmtk::ImageSymmetryPlaneCommandLineBase
00315 ::WriteDifference
00316 ( UniformVolume::SmartConstPtr& originalVolume ) const
00317 {
00318 UniformVolume::SmartPtr diffVolume( originalVolume->CloneGrid() );
00319 const TypedArray* originalData = originalVolume->GetData();
00320 TypedArray::SmartPtr diffData = TypedArray::SmartPtr( TypedArray::Create( GetSignedDataType( originalData->GetType() ), originalData->GetDataSize() ) );
00321 diffVolume->SetData( diffData );
00322
00323 Types::DataItem dataV, dataW;
00324
00325 const UniformVolumeInterpolatorBase::SmartPtr interpolator( ReformatVolume::CreateInterpolator( this->m_Interpolation, originalVolume ) );;
00326
00327 int offset = 0;
00328 for ( int z = 0; z < originalVolume->GetDims()[2]; ++z )
00329 for ( int y = 0; y < originalVolume->GetDims()[1]; ++y )
00330 for ( int x = 0; x < originalVolume->GetDims()[0]; ++x, ++offset )
00331 {
00332 if ( ! originalData->Get( dataV, offset ) )
00333 {
00334 diffData->SetPaddingAt( offset );
00335 continue;
00336 }
00337 UniformVolume::CoordinateVectorType w = originalVolume->GetGridLocation( x, y, z );
00338 this->m_SymmetryPlane.MirrorInPlace( w );
00339
00340 if ( interpolator->GetDataAt( w, dataW ) )
00341 {
00342 diffData->Set( fabs( dataV - dataW ), offset );
00343 }
00344 else
00345 {
00346 diffData->SetPaddingAt( offset );
00347 }
00348 }
00349
00350 VolumeIO::Write( *diffVolume, this->m_DifferenceOutFile );
00351 }
00352
00353 void
00354 cmtk::ImageSymmetryPlaneCommandLineBase
00355 ::WriteMirror
00356 ( UniformVolume::SmartConstPtr& originalVolume ) const
00357 {
00358 TypedArray::SmartPtr mirrorData = TypedArray::Create( originalVolume->GetData()->GetType(), originalVolume->GetData()->GetDataSize() );
00359
00360 Types::DataItem data;
00361
00362 const UniformVolumeInterpolatorBase::SmartPtr interpolator( ReformatVolume::CreateInterpolator( this->m_Interpolation, originalVolume ) );;
00363
00364 int offset = 0;
00365 for ( int z = 0; z < originalVolume->GetDims()[2]; ++z )
00366 {
00367 for ( int y = 0; y < originalVolume->GetDims()[1]; ++y )
00368 for ( int x = 0; x < originalVolume->GetDims()[0]; ++x, ++offset )
00369 {
00370 UniformVolume::CoordinateVectorType v = originalVolume->GetGridLocation( x, y, z );
00371 this->m_SymmetryPlane.MirrorInPlace( v );
00372
00373 if ( interpolator->GetDataAt( v, data ) )
00374 {
00375 mirrorData->Set( data, offset );
00376 }
00377 else
00378 {
00379 mirrorData->SetPaddingAt( offset );
00380 }
00381 }
00382 }
00383
00384 UniformVolume::SmartPtr mirrorVolume( originalVolume->CloneGrid() );
00385 mirrorVolume->SetData( mirrorData );
00386 VolumeIO::Write( *mirrorVolume, this->m_MirrorOutFile );
00387 }
00388
00389 void
00390 cmtk::ImageSymmetryPlaneCommandLineBase
00391 ::WriteMarkPlane
00392 ( UniformVolume::SmartConstPtr& originalVolume ) const
00393 {
00394 UniformVolume::SmartPtr markVolume( originalVolume->CloneGrid() );
00395 TypedArray::SmartPtr markData( originalVolume->GetData()->Clone() );
00396 markVolume->SetData( markData );
00397
00398 int offset = 0;
00399 for ( int z = 0; z < originalVolume->GetDims()[2]; ++z )
00400 {
00401 for ( int y = 0; y < originalVolume->GetDims()[1]; ++y )
00402 {
00403 int currentSideOfPlane = 0;
00404 for ( int x = 0; x < originalVolume->GetDims()[0]; ++x, ++offset )
00405 {
00406 int newSideOfPlane = this->m_SymmetryPlane.GetWhichSide( originalVolume->GetGridLocation( x, y, z ) );
00407 if ( ( newSideOfPlane != currentSideOfPlane ) && x )
00408 markData->Set( this->m_MarkPlaneValue, offset );
00409 currentSideOfPlane = newSideOfPlane;
00410 }
00411 }
00412 }
00413
00414 VolumeIO::Write( *markVolume, this->m_MarkedOutFile );
00415 }
00416
00417 void
00418 cmtk::ImageSymmetryPlaneCommandLineBase
00419 ::WriteAligned
00420 ( UniformVolume::SmartConstPtr& originalVolume ) const
00421 {
00422 const TypedArray* originalData = originalVolume->GetData();
00423
00424 TypedArray::SmartPtr alignData = TypedArray::Create( originalData->GetType(), originalData->GetDataSize() );
00425 if ( this->m_PadOutValueSet )
00426 {
00427 alignData->SetPaddingValue( this->m_PadOutValue );
00428 }
00429
00430 UniformVolume::SmartPtr alignVolume( originalVolume->CloneGrid() );
00431 alignVolume->SetData( alignData );
00432
00433 const Types::DataItem maxData = originalData->GetRange().m_UpperBound;
00434
00435 Types::DataItem data;
00436
00437 int normalAxis = 0;
00438 switch ( this->m_InitialPlane )
00439 {
00440 case SYMPL_INIT_XY: normalAxis = 2; break;
00441 case SYMPL_INIT_XZ: normalAxis = 1; break;
00442 case SYMPL_INIT_YZ: normalAxis = 0; break;
00443 }
00444
00445 const UniformVolumeInterpolatorBase::SmartPtr interpolator( ReformatVolume::CreateInterpolator( this->m_Interpolation, originalVolume ) );;
00446
00447 AffineXform::SmartPtr alignment( this->m_SymmetryPlane.GetAlignmentXform( normalAxis ) );
00448 int offset = 0;
00449 for ( int z = 0; z < originalVolume->GetDims()[2]; ++z )
00450 {
00451 for ( int y = 0; y < originalVolume->GetDims()[1]; ++y )
00452 {
00453 for ( int x = 0; x < originalVolume->GetDims()[0]; ++x, ++offset )
00454 {
00455 UniformVolume::CoordinateVectorType v = originalVolume->GetGridLocation( x, y, z );
00456 alignment->ApplyInPlace( v );
00457
00458 if ( interpolator->GetDataAt( v, data ) )
00459 {
00460 if ( this->m_MarkPlaneAligned && (x == ( originalVolume->GetDims()[0] / 2 )) )
00461 alignData->Set( 2 * maxData, offset );
00462 else
00463 alignData->Set( data, offset );
00464 }
00465 else
00466 {
00467 alignData->SetPaddingAt( offset );
00468 }
00469 }
00470 }
00471 }
00472
00473 VolumeIO::Write( *alignVolume, this->m_AlignedOutFile );
00474 }
00475