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 "cmtkAffineRegistrationCommandLine.h"
00034
00035 #include <System/cmtkConsole.h>
00036 #include <System/cmtkThreads.h>
00037 #include <System/cmtkTimers.h>
00038 #include <System/cmtkCommandLine.h>
00039 #include <System/cmtkExitException.h>
00040 #include <System/cmtkCompressedStream.h>
00041 #include <System/cmtkMountPoints.h>
00042
00043 #include <Base/cmtkTypes.h>
00044 #include <Base/cmtkAnatomicalOrientation.h>
00045 #include <Base/cmtkTransformChangeToSpaceAffine.h>
00046 #include <Base/cmtkTransformChangeFromSpaceAffine.h>
00047
00048 #include <IO/cmtkVolumeIO.h>
00049 #include <IO/cmtkClassStream.h>
00050 #include <IO/cmtkClassStreamAffineXform.h>
00051 #include <IO/cmtkXformIO.h>
00052 #include <IO/cmtkAffineXformITKIO.h>
00053
00054 #include <Registration/cmtkRegistrationCallback.h>
00055 #include <Registration/cmtkProtocolCallback.h>
00056
00057 #ifdef CMTK_USE_SQLITE
00058 # include <Registration/cmtkImageXformDB.h>
00059 #endif
00060
00061 #ifdef HAVE_SYS_TYPES_H
00062 # include <sys/types.h>
00063 #endif
00064
00065 #ifdef HAVE_SYS_STAT_H
00066 # include <sys/stat.h>
00067 #endif
00068
00069 #ifdef HAVE_SYS_UTSNAME_H
00070 # include <sys/utsname.h>
00071 #endif
00072
00073 #include <iostream>
00074 #include <stdio.h>
00075 #include <string.h>
00076
00077 #ifdef _MSC_VER
00078 # include <direct.h>
00079 #endif
00080
00081 namespace
00082 cmtk
00083 {
00084
00087
00088 AffineRegistrationCommandLine
00089 ::AffineRegistrationCommandLine
00090 ( const int argc, const char* argv[] )
00091 : m_InitialXformPath( NULL ),
00092 m_ReformattedImagePath( NULL ),
00093 m_OutputPathITK( NULL ),
00094 #ifdef CMTK_USE_SQLITE
00095 m_UpdateDB( NULL ),
00096 #endif
00097 Protocol( NULL )
00098 {
00099 this->m_Metric = 0;
00100
00101 this->m_AutoMultiLevels = 0;
00102 CoarsestResolution = -1;
00103 this->m_Exploration = 8;
00104 this->m_Accuracy = 0.1;
00105 this->m_Sampling = 1.0;
00106 OutParametersName = OutMatrixName = Studylist = Time = NULL;
00107 InitXlate = 0;
00108 this->m_NoSwitch = 0;
00109
00110 Verbose = 0;
00111
00112 const char *InitialStudylist = NULL;
00113 Study1 = Study2 = NULL;
00114
00115 const char* clArg1 = NULL;
00116 const char* clArg2 = NULL;
00117
00118 try
00119 {
00120 CommandLine cl( CommandLine::PROPS_XML );
00121 cl.SetProgramInfo( CommandLine::PRG_TITLE, "Rigid and affine registration" );
00122 cl.SetProgramInfo( CommandLine::PRG_DESCR, "This program performs rigid and affine image registration using multi-resolution optimization of voxel-based image similarity measures." );
00123 cl.SetProgramInfo( CommandLine::PRG_CATEG, "CMTK.Registration" );
00124
00125 typedef CommandLine::Key Key;
00126 cl.AddSwitch( Key( 'v', "verbose" ), &Verbose, true, "Verbose mode" )->SetProperties( CommandLine::PROPS_NOXML );
00127 cl.AddSwitch( Key( 'q', "quiet" ), &Verbose, false, "Quiet mode" )->SetProperties( CommandLine::PROPS_NOXML );
00128
00129 cl.BeginGroup( "Automation", "Automation Options" );
00130 cl.AddOption( Key( "auto-multi-levels" ), &this->m_AutoMultiLevels, "Automatic optimization and resolution parameter generation for <n> levels" );
00131
00132 cl.BeginGroup( "Optimization", "Optimization settings" );
00133 cl.AddOption( Key( 'e', "exploration" ), &this->m_Exploration, "Exploration [initial optimizer step size]" );
00134 cl.AddOption( Key( 'a', "accuracy" ), &this->m_Accuracy, "Accuracy [final optimizer step size]" );
00135 cl.AddOption( Key( 'f', "stepfactor" ), &this->OptimizerStepFactor, "Factor for search step size reduction. Must be > 0.0 and < 1.0" );
00136 cl.AddOption( Key( "delta-f-threshold" ), &this->m_DeltaFThreshold, "Optional threshold to terminate optimization (level) if relative change of target function drops below this value." );
00137 cl.EndGroup();
00138
00139 cl.BeginGroup( "Resolution", "Image resolution parameters" );
00140 cl.AddOption( Key( 's', "sampling" ), &this->m_Sampling, "Image sampling (finest resampled image resolution)" );
00141 cl.AddOption( Key( "coarsest" ), &this->CoarsestResolution, "Upper limit for image sampling in multiresolution hierarchy" );
00142
00143 cl.AddSwitch( Key( "omit-original-data" ), &this->m_UseOriginalData, false, "Do not use original data in full resolution, omit final stage in multiresolution hierarchy, thus reducing computation time." );
00144 cl.EndGroup();
00145
00146 cl.BeginGroup( "Transformation", "Transformation parameters" );
00147 cl.AddVector( Key( "dofs" ), this->NumberDOFs, "Add number of degrees of freedom [can be repeated]" );
00148 cl.AddVector( Key( "dofs-final" ), this->NumberDOFsFinal, "Add number of degrees of freedom for final level only [can be repeated]" );
00149
00150 cl.AddSwitch( Key( 'n', "no-switch" ), &this->m_NoSwitch, true, "Do not auto-switch reference and floating image for improved computational performance" );
00151 cl.AddSwitch( Key( 'i', "initxlate" ), &InitXlate, true, "Initialized transformation by translating floating image FOV center onto reference image FOV center" );
00152
00153 cl.AddOption( Key( "initial" ), &InitialStudylist, "Initialize transformation from given path" )->SetProperties( CommandLine::PROPS_XFORM );
00154 cl.AddSwitch( Key( "initial-is-inverse" ), &this->m_InitialXformIsInverse, true, "Invert initial transformation before initializing registration" );
00155 cl.EndGroup();
00156
00157 cl.BeginGroup( "Image data", "Image data" );
00158 CommandLine::EnumGroup<int>::SmartPtr
00159 metricGroup = cl.AddEnum( "registration-metric", &this->m_Metric, "Registration metric for motion estimation by image-to-image registration." );
00160 metricGroup->AddSwitch( Key( "nmi" ), 0, "Normalized Mutual Information metric" );
00161 metricGroup->AddSwitch( Key( "mi" ), 1, "Standard Mutual Information metric" );
00162 metricGroup->AddSwitch( Key( "cr" ), 2, "Correlation Ratio metric" );
00163 metricGroup->AddSwitch( Key( "msd" ), 4, "Mean Squared Difference metric" );
00164 metricGroup->AddSwitch( Key( "ncc" ), 5, "Normalized Cross Correlation metric" );
00165
00166 cl.AddSwitch( Key( "match-histograms" ), &this->m_MatchFltToRefHistogram, true, "Match floating image histogram to reference image histogram." );
00167
00168 this->m_PreprocessorRef.AttachToCommandLine( cl );
00169 this->m_PreprocessorFlt.AttachToCommandLine( cl );
00170
00171 cl.BeginGroup( "Output", "Output parameters" )->SetProperties( CommandLine::PROPS_NOXML );
00172 cl.AddOption( Key( 'o', "outlist" ), &this->Studylist, "Output path for final transformation" );
00173 cl.AddOption( Key( "out-matrix" ), &this->OutMatrixName, "Output path for final transformation in matrix format" );
00174 cl.AddOption( Key( "out-parameters" ), &this->OutParametersName, "Output path for final transformation in plain parameter list format" );
00175 cl.AddOption( Key( 'p', "protocol" ), &this->Protocol, "Optimization protocol output file name" );
00176 cl.AddOption( Key( 't', "time" ), &this->Time, "Computation time statistics output file name" );
00177 cl.EndGroup();
00178
00179 cl.BeginGroup( "SlicerImport", "Import Results into Slicer" );
00180 cl.AddOption( Key( "out-itk" ), &this->m_OutputPathITK, "Output path for final transformation in ITK format" )
00181 ->SetProperties( CommandLine::PROPS_XFORM | CommandLine::PROPS_OUTPUT )
00182 ->SetAttribute( "reference", "FloatingImage" );
00183 cl.AddOption( Key( "write-reformatted" ), &this->m_ReformattedImagePath, "Write reformatted floating image." )->SetProperties( CommandLine::PROPS_IMAGE | CommandLine::PROPS_OUTPUT );
00184 cl.EndGroup();
00185
00186 #ifdef CMTK_USE_SQLITE
00187 cl.BeginGroup( "Database", "Image/Transformation Database" );
00188 cl.AddOption( Key( "db" ), &this->m_UpdateDB, "Path to image/transformation database that should be updated with the new registration and/or reformatted image." );
00189 cl.EndGroup();
00190 #endif
00191
00192 cl.AddParameter( &clArg1, "ReferenceImage", "Reference (fixed) image path" )->SetProperties( CommandLine::PROPS_IMAGE );
00193 cl.AddParameter( &clArg2, "FloatingImage", "Floating (moving) image path" )->SetProperties( CommandLine::PROPS_IMAGE | CommandLine::PROPS_OPTIONAL );
00194
00195 cl.Parse( argc, argv );
00196 }
00197 catch ( const CommandLine::Exception& ex )
00198 {
00199 StdErr << ex << "\n";
00200 throw cmtk::ExitException( 1 );
00201 }
00202
00203 if ( (OptimizerStepFactor <= 0) || (OptimizerStepFactor >= 1) )
00204 {
00205 StdErr << "ERROR: step factor value " << OptimizerStepFactor << " is invalid. Must be in range (0..1)\n";
00206 throw cmtk::ExitException( 1 );
00207 }
00208
00209 this->SetInitialTransformation( AffineXform::SmartPtr( new AffineXform() ) );
00210
00211 if ( clArg2 )
00212 {
00213 Study1 = const_cast<char*>( clArg1 );
00214 Study2 = const_cast<char*>( clArg2 );
00215 }
00216 else
00217 {
00218 this->m_InitialXformPath = clArg1;
00219
00220 if ( InitialStudylist )
00221 {
00222 StdErr << "Transformation will be overriden by '--initial' list.\n";
00223 }
00224
00225 if ( Verbose )
00226 StdErr << "Reading input studylist " << this->m_InitialXformPath << ".\n";
00227
00228 ClassStream typedStream( MountPoints::Translate(this->m_InitialXformPath), "registration", ClassStream::READ );
00229 if ( ! typedStream.IsValid() )
00230 {
00231 StdErr << "Could not open studylist archive " << this->m_InitialXformPath << ".\n";
00232 throw cmtk::ExitException( 1 );
00233 }
00234
00235 typedStream.Seek ( "registration" );
00236 Study1 = typedStream.ReadString( "reference_study" );
00237 Study2 = typedStream.ReadString( "floating_study" );
00238 if ( Study2 )
00239 {
00240 AffineXform::SmartPtr affineXform;
00241 typedStream >> affineXform;
00242 this->SetInitialTransformation( affineXform );
00243 }
00244 else
00245 {
00246
00247 Study2 = typedStream.ReadString( "model_study" );
00248 AffineXform::SmartPtr affineXform;
00249 typedStream >> affineXform;
00250 this->SetInitialTransformation( affineXform->GetInverse() );
00251 }
00252
00253 typedStream.Close();
00254 }
00255
00256 if ( !Study1 )
00257 {
00258 StdErr << "ERROR: reference image path resolved to NULL.\n";
00259 throw cmtk::ExitException( 1 );
00260 }
00261
00262 if ( !Study2 )
00263 {
00264 StdErr << "ERROR: floating image path resolved to NULL.\n";
00265 throw cmtk::ExitException( 1 );
00266 }
00267
00268 UniformVolume::SmartPtr volume( VolumeIO::ReadOriented( Study1, Verbose ) );
00269 if ( !volume )
00270 {
00271 StdErr << "ERROR: volume " << this->Study1 << " could not be read\n";
00272 throw cmtk::ExitException( 1 );
00273 }
00274 this->SetVolume_1( UniformVolume::SmartPtr( this->m_PreprocessorRef.GetProcessedImage( volume ) ) );
00275
00276 volume = UniformVolume::SmartPtr( VolumeIO::ReadOriented( Study2, Verbose ) );
00277 if ( !volume )
00278 {
00279 StdErr << "ERROR: volume " << this->Study2 << " could not be read\n";
00280 throw cmtk::ExitException( 1 );
00281 }
00282 this->SetVolume_2( UniformVolume::SmartPtr( this->m_PreprocessorFlt.GetProcessedImage( volume ) ) );
00283
00284 if ( InitialStudylist )
00285 {
00286 this->m_InitialXformPath = InitialStudylist;
00287 Xform::SmartPtr xform( XformIO::Read( InitialStudylist, Verbose ) );
00288 if ( ! xform )
00289 {
00290 StdErr << "ERROR: could not read transformation from " << InitialStudylist << "\n";
00291 throw cmtk::ExitException( 1 );
00292 }
00293
00294 AffineXform::SmartPtr affine( AffineXform::SmartPtr::DynamicCastFrom( xform ) );
00295 if ( ! affine )
00296 {
00297 StdErr << "ERROR: transformation " << InitialStudylist << " is not affine.\n";
00298 throw cmtk::ExitException( 1 );
00299 }
00300
00301 if ( affine->m_MetaInformation[META_SPACE] != AnatomicalOrientation::ORIENTATION_STANDARD )
00302 {
00303 TransformChangeFromSpaceAffine toStandardSpace( *affine, *(this->m_Volume_1), *(this->m_Volume_2) );
00304 *affine = toStandardSpace.GetTransformation();
00305 affine->m_MetaInformation[META_SPACE] = AnatomicalOrientation::ORIENTATION_STANDARD;
00306 }
00307
00308 this->SetInitialTransformation( affine );
00309 }
00310
00311 if ( InitXlate )
00312 {
00313 if ( this->m_InitialXformPath )
00314 {
00315 StdErr << "WARNING: Initial transformation was taken from studylist. Switch --initxlate / -i will be ignored.\n";
00316 }
00317 else
00318 {
00319 this->SetInitialAlignCenters();
00320 }
00321 }
00322
00323 if ( this->m_AutoMultiLevels > 0 )
00324 {
00325 const Types::Coordinate minDelta = std::min( this->m_Volume_1->GetMinDelta(), this->m_Volume_2->GetMinDelta() );
00326 const Types::Coordinate maxDelta = std::max( this->m_Volume_1->GetMaxDelta(), this->m_Volume_2->GetMaxDelta() );
00327
00328 this->m_Accuracy = 0.1 * minDelta;
00329 this->m_Sampling = maxDelta;
00330 this->m_Exploration = maxDelta * (1<<(this->m_AutoMultiLevels-1));
00331 }
00332
00333 if ( Protocol )
00334 {
00335 RegistrationCallback::SmartPtr callback( new ProtocolCallback( Protocol ) );
00336 this->SetCallback( callback );
00337 }
00338 }
00339
00340 CallbackResult
00341 AffineRegistrationCommandLine::InitRegistration ()
00342 {
00343 CallbackResult Result = AffineRegistration::InitRegistration();
00344 return Result;
00345 }
00346
00347 void
00348 AffineRegistrationCommandLine::OutputResultMatrix( const char* matrixName ) const
00349 {
00350 Types::Coordinate matrix[4][4];
00351 this->GetTransformation()->GetMatrix( matrix );
00352
00353 FILE* mfile = fopen( matrixName, "w" );
00354 if ( mfile )
00355 {
00356 for ( int i = 0; i < 4; ++i )
00357 {
00358 fprintf( mfile, "%e\t%e\t%e\t%e\n", matrix[0][i], matrix[1][i], matrix[2][i], matrix[3][i] );
00359 }
00360 fclose( mfile );
00361 }
00362 }
00363
00364 void
00365 AffineRegistrationCommandLine::OutputResultParameters
00366 ( const char* paramsName, const CoordinateVector& v ) const
00367 {
00368 FILE* pfile = fopen( paramsName, "w" );
00369 if ( pfile )
00370 {
00371 for ( unsigned int idx=0; idx < v.Dim; ++idx )
00372 fprintf( pfile, "#%d: %f\n", idx, v.Elements[idx] );
00373 fclose( pfile );
00374 }
00375 }
00376
00377 void
00378 AffineRegistrationCommandLine::OutputResultList( const char* studyList ) const
00379 {
00380 ClassStream classStream( studyList, "studylist", ClassStream::WRITE );
00381 if ( !classStream.IsValid() ) return;
00382
00383 classStream.Begin( "studylist" );
00384 classStream.WriteInt( "num_sources", 2 );
00385 classStream.End();
00386
00387 classStream.Begin( "source" );
00388 classStream.WriteString( "studyname", CompressedStream::GetBaseName( Study1 ) );
00389 classStream.End();
00390
00391 classStream.Begin( "source" );
00392 classStream.WriteString( "studyname", CompressedStream::GetBaseName( Study2 ) );
00393 classStream.End();
00394
00395 classStream.Close();
00396
00397 classStream.Open( studyList, "registration", ClassStream::WRITE );
00398
00399 classStream.Begin( "registration" );
00400 classStream.WriteString( "reference_study", CompressedStream::GetBaseName( Study1 ) );
00401 classStream.WriteString( "floating_study", CompressedStream::GetBaseName( Study2 ) );
00402
00403 classStream << *(this->GetTransformation());
00404
00405 classStream.End();
00406 classStream.Close();
00407
00408 classStream.Open( studyList, "settings", ClassStream::WRITE );
00409 classStream.WriteDouble( "exploration", this->m_Exploration );
00410 classStream.WriteDouble( "accuracy", this->m_Accuracy );
00411 classStream.WriteDouble( "min_sampling", this->m_Sampling );
00412 classStream.WriteDouble( "coarsest_resolution", CoarsestResolution );
00413 classStream.WriteInt( "metric", this->m_Metric );
00414 classStream.WriteDouble( "optimizer_step_factor", OptimizerStepFactor );
00415 classStream.WriteBool( "no_switch", this->m_NoSwitch );
00416
00417 this->m_PreprocessorRef.WriteSettings( classStream );
00418 this->m_PreprocessorFlt.WriteSettings( classStream );
00419
00420 classStream.Close();
00421
00422 classStream.Open( studyList, "statistics", ClassStream::WRITE );
00423 classStream.WriteDouble( "time", this->GetTotalElapsedTime() );
00424 classStream.WriteDouble( "walltime", this->GetTotalElapsedWalltime() );
00425 #ifdef CMTK_USE_THREADS
00426 classStream.WriteDouble( "thread_time", this->GetThreadTotalElapsedTime() );
00427 #endif
00428
00429 #ifndef _MSC_VER
00430 struct utsname name;
00431 if ( uname( &name ) >= 0 )
00432 {
00433 classStream.WriteString( "host", name.nodename );
00434 classStream.WriteString( "system", name.sysname );
00435 }
00436 #endif
00437 classStream.Close();
00438 }
00439
00440 void
00441 AffineRegistrationCommandLine::OutputResult ( const CoordinateVector* v )
00442 {
00443 if ( Verbose )
00444 {
00445 fprintf( stderr, "\rResulting transformation parameters: \n" );
00446 for ( unsigned int idx=0; idx<v->Dim; ++idx )
00447 fprintf( stderr, "#%d: %f\n", idx, v->Elements[idx] );
00448 }
00449
00450 if ( this->OutMatrixName )
00451 {
00452 this->OutputResultMatrix( this->OutMatrixName );
00453 }
00454
00455 if ( this->OutParametersName )
00456 {
00457 this->OutputResultParameters( this->OutParametersName, *v );
00458 }
00459
00460 if ( this->Studylist )
00461 {
00462 this->OutputResultList( this->Studylist );
00463 }
00464
00465 if ( this->m_OutputPathITK )
00466 {
00467 TransformChangeToSpaceAffine toNative( *(this->GetTransformation()), *(this->m_Volume_1), *(this->m_Volume_2), AnatomicalOrientationBase::SPACE_ITK );
00468 AffineXformITKIO::Write( this->m_OutputPathITK, toNative.GetTransformation() );
00469 }
00470
00471 if ( this->m_ReformattedImagePath )
00472 {
00473 VolumeIO::Write( *(this->GetReformattedFloatingImage()), this->m_ReformattedImagePath, this->Verbose );
00474 }
00475
00476 #ifdef CMTK_USE_SQLITE
00477 if ( this->m_UpdateDB )
00478 {
00479 try
00480 {
00481 cmtk::ImageXformDB db( this->m_UpdateDB );
00482
00483 if ( this->m_ReformattedImagePath )
00484 {
00485 db.AddImage( this->m_ReformattedImagePath, this->m_ReferenceVolume->GetMetaInfo( META_FS_PATH ) );
00486 }
00487
00488 if ( this->Studylist )
00489 {
00490 if ( this->m_InitialXformPath )
00491 {
00492 db.AddRefinedXform( this->Studylist, true , this->m_InitialXformPath, this->m_InitialXformIsInverse );
00493 }
00494 else
00495 {
00496 db.AddImagePairXform( this->Studylist, true , this->m_ReferenceVolume->GetMetaInfo( META_FS_PATH ), this->m_FloatingVolume->GetMetaInfo( META_FS_PATH ) );
00497 }
00498 }
00499 }
00500 catch ( const cmtk::ImageXformDB::Exception& ex )
00501 {
00502 StdErr << "DB ERROR: " << ex.what() << " on database " << this->m_UpdateDB << "\n";
00503 }
00504 }
00505 #endif
00506 }
00507
00508 void
00509 AffineRegistrationCommandLine::EnterResolution
00510 ( CoordinateVector::SmartPtr& v, Functional::SmartPtr& f,
00511 const int index, const int total )
00512 {
00513 if ( Verbose )
00514 fprintf( stderr, "\rEntering resolution level %d out of %d...\n", index, total );
00515 this->Superclass::EnterResolution( v, f, index, total );
00516 }
00517
00518 CallbackResult
00519 AffineRegistrationCommandLine::Register ()
00520 {
00521 const double baselineTime = Timers::GetTimeProcess();
00522 CallbackResult Result = Superclass::Register();
00523 const int elapsed = static_cast<int>( Timers::GetTimeProcess() - baselineTime );
00524
00525 if ( Time )
00526 {
00527 FILE *tfp = fopen( Time, "w" );
00528
00529 if ( tfp )
00530 {
00531 fprintf( tfp, "%d\n", elapsed );
00532 fclose( tfp );
00533 }
00534 else
00535 {
00536 std::cerr << "Could not open time file " << Time << "\n";
00537 }
00538 }
00539 return Result;
00540 }
00541
00542 }
00543