File: unwarp_image_phantom.cxx

package info (click to toggle)
cmtk 3.3.1p2%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 10,524 kB
  • sloc: cpp: 87,098; ansic: 23,347; sh: 3,896; xml: 1,551; perl: 707; makefile: 334
file content (253 lines) | stat: -rw-r--r-- 10,672 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*
//
//  Copyright 1997-2010 Torsten Rohlfing
//
//  Copyright 2004-2014 SRI International
//
//  This file is part of the Computational Morphometry Toolkit.
//
//  http://www.nitrc.org/projects/cmtk/
//
//  The Computational Morphometry Toolkit is free software: you can
//  redistribute it and/or modify it under the terms of the GNU General Public
//  License as published by the Free Software Foundation, either version 3 of
//  the License, or (at your option) any later version.
//
//  The Computational Morphometry Toolkit is distributed in the hope that it
//  will be useful, but WITHOUT ANY WARRANTY; without even the implied
//  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License along
//  with the Computational Morphometry Toolkit.  If not, see
//  <http://www.gnu.org/licenses/>.
//
//  $Revision: 5436 $
//
//  $LastChangedDate: 2018-12-10 19:01:20 -0800 (Mon, 10 Dec 2018) $
//
//  $LastChangedBy: torstenrohlfing $
//
*/

#include <cmtkconfig.h>

#include <System/cmtkConsole.h>
#include <System/cmtkCommandLine.h>
#include <System/cmtkExitException.h>
#include <System/cmtkDebugOutput.h>

#include <IO/cmtkPhantomIO.h>
#include <IO/cmtkVolumeIO.h>
#include <IO/cmtkXformIO.h>

#include <Base/cmtkDetectedPhantomMagphanEMR051.h>
#include <Base/cmtkLandmarkList.h>
#include <Base/cmtkLandmarkPairList.h>
#include <Base/cmtkFitPolynomialToLandmarks.h>
#include <Base/cmtkFitAffineToLandmarks.h>
#include <Base/cmtkFitSplineWarpToLandmarks.h>

#include <vector>

cmtk::Xform::SmartConstPtr
doFitPoly( const cmtk::LandmarkPairList& pairList, const byte polyDegree )
{
  cmtk::PolynomialXform::SmartConstPtr polyXform;
  try
    {
    // fit polynomial transformation to landmark pairs.
    polyXform = cmtk::FitPolynomialToLandmarks( pairList, polyDegree ).GetPolynomialXform();
    }
  catch ( cmtk::PolynomialHelper::DegreeUnsupported& ex )
    {
    cmtk::StdErr << "ERROR: library does not support polynomial degree " << polyDegree << "\n";
    cmtk::StdErr << ex.what() << "\n";
    throw cmtk::ExitException( 1 );
    }

  return polyXform;
}

cmtk::Xform::SmartConstPtr
doFitSpline( const cmtk::LandmarkPairList& pairList, const cmtk::UniformVolume& unwarpImage, const std::string& gridDims, const cmtk::Types::Coordinate gridSpacing, const cmtk::FitSplineWarpToLandmarks::Parameters& fittingParameters, const bool affineFirst )
{
  // check for inconsistent parameters
  if ( (gridSpacing > 0)  && !gridDims.empty() )
    {
    cmtk::StdErr << "ERROR: must specify either output spline control point spacing or grid dimensions, but not both.\n";
    throw cmtk::ExitException( 1 );
    }

  // fit spline warp, potentially preceded by linear transformation, to landmark pairs.
  cmtk::AffineXform::SmartConstPtr affineXform;
  if ( affineFirst )
    {
    try
      {
      affineXform = cmtk::FitAffineToLandmarks( pairList ).GetAffineXform();
      }
    catch ( const cmtk::AffineXform::MatrixType::SingularMatrixException& )
      {
      cmtk::StdErr << "ERROR: fitted affine transformation has singular matrix\n";
      throw cmtk::ExitException( 1 );
      }
    }

  // fit by final spacing
  cmtk::SplineWarpXform::SmartConstPtr splineWarp;
  if ( gridSpacing )
    {
    splineWarp = cmtk::FitSplineWarpToLandmarks( pairList ).Fit( unwarpImage.m_Size, gridSpacing, affineXform.GetPtr(), fittingParameters );
    }
  else
    {
    // or fit by final control point grid dimension
    if ( !gridDims.empty() )
      {
      double dims[3];
      if ( 3 != sscanf( gridDims.c_str(), "%20lf,%20lf,%20lf", &(dims[0]), &(dims[1]), &(dims[2]) ) )
	{
	cmtk::StdErr << "ERROR: grid dimensions must be specified as dimsX,dimsY,dimsZ\n";
	throw cmtk::ExitException( 1 );
	}
      
      splineWarp = cmtk::FitSplineWarpToLandmarks( pairList ).Fit( unwarpImage.m_Size, cmtk::FixedVector<3,double>::FromPointer( dims ), affineXform.GetPtr(), fittingParameters );
      }
    else
      {
      cmtk::StdErr << "ERROR: must specify either output spline control point spacing or grid dimensions.\n";
      throw cmtk::ExitException( 1 );
      }
    }
  return splineWarp;
}

int
doMain( const int argc, const char* argv[] )
{
  // input file system paths
  std::string inputPhantomPath;
  std::string inputImagePath;

  // landmark exclusion threshold
  cmtk::Types::Coordinate residualThreshold = 5.0;
 
  // fitting options
  bool fitInverse = false;
  bool fitSpline = false;

  // polynomial fitting options
  byte polyDegree = 4;

  // B-spline fitting options
  std::string gridDims;
  cmtk::Types::Coordinate gridSpacing = 0;
  cmtk::FitSplineWarpToLandmarks::Parameters splineFittingParameters;  
  bool affineFirst = true;

  // output path
  std::string outputXform;

  try
    {
    cmtk::CommandLine cl;
    cl.SetProgramInfo( cmtk::CommandLine::PRG_TITLE, "Create transformation to unwarp an image based on a phantom description" );
    cl.SetProgramInfo( cmtk::CommandLine::PRG_DESCR, "This tool computes either a polynomial transformation or B-spline free-form deformation to unwarp an image. The transformation is based on expected and detected landmarks in an image of a structural phantom "
		       "acquired on the same scanner. Use the 'detect_adni_phantom' tool to detect landmarks of the ADNI Phantom in an image and generate a phantom description file suitable for use with this tool." );

    typedef cmtk::CommandLine::Key Key;    
    cl.BeginGroup( "Fitting", "Fitting Options" );
    cl.AddSwitch( Key( "fit-inverse" ), &fitInverse, true, "Fit inverse transformation (mapping actual to expected landmark locations). "
		  "This is useful for computing a Jacobian volume correction map (using 'reformatx') without having to numerically invert the fitted unwarping transformation." );
    cl.AddSwitch( Key( "fit-forward" ), &fitInverse, false, "Fit forward transformation (mapping expected to actual landmark locations). This is useful for rectifying the image via reslicing (using 'reformatx')." );
    cl.EndGroup();

    cl.BeginGroup( "Xform", "Transformation Models" );
    cl.AddSwitch( Key( "spline" ), &fitSpline, true, "Fit B-spline free-form deformation." );
    cl.AddSwitch( Key( "poly" ), &fitSpline, false, "Fit polynomial transformation." );
    cl.EndGroup();

    cl.BeginGroup( "PolyFitting", "Polynomial Fitting Options (with --poly)" );
    cl.AddOption( Key( "degree" ), &polyDegree, "Degree of the fitted polynomial transformation." );
    cl.EndGroup();

    cl.BeginGroup( "SplineFitting", "B-Spline Fitting Options (with --spline)" );
    cl.AddOption( Key( "final-cp-spacing" ), &gridSpacing, "Final control point grid spacing of the output B-spline transformation." );
    cl.AddOption( Key( "final-cp-dims" ), &gridDims, "Final control point grid dimensions (i.e., number of control points) of the output B-spline transformation. To be provided as 'dimX,dimY,dimZ'." );
    cl.AddOption( Key( "levels" ), &splineFittingParameters.m_Levels, "Number of levels in the multi-level B-spline approximation procedure." );
    cl.AddOption( Key( "iterations-per-level" ), &splineFittingParameters.m_IterationsPerLevel, "Maximum number of spline coefficient update iterations per level in the multi-level B-spline approximation procedure." );
    cl.AddOption( Key( "rms-threshold" ), &splineFittingParameters.m_ResidualThreshold, "Threshold for relative improvement of the RMS fitting residual. "
		  "The fitting iteration terminates if (rmsAfterUpdate-rmsBeforeUpdate)/rmsBeforeUpdate < threshold." );
    cl.AddSwitch( Key( "no-fit-affine" ), &affineFirst, false, "Disable fitting of affine transformation to initialize spline. Instead, fit spline directly. This usually gives worse results and is discouraged." );
    cl.EndGroup();

    cl.AddParameter( &inputPhantomPath, "InputPhantom", "Input path of the XML file describing a phantom previously detected in an image." );
    cl.AddParameter( &inputImagePath, "InputImage", "Input image path. This is the image that is unwarped. It is important that this image be acquired on the same scanner (not only the same model but the very machine) "
		     "on which the phantom image was also acquired, preferably in close temporal proximity. Also, both this and the phantom image must share and specify the same physical image coordinates, i.e., only images in "
		     "NIFTI or NRRD format can be used." )
      ->SetProperties( cmtk::CommandLine::PROPS_IMAGE );
    cl.AddParameter( &outputXform, "OutputXform", "Output transformation path. This is the affine phantom-to-image coordinate transformation fitted to the detected landmark spheres." )
      ->SetProperties( cmtk::CommandLine::PROPS_XFORM | cmtk::CommandLine::PROPS_OUTPUT );
    
    cl.Parse( argc, argv );
    }
  catch ( const cmtk::CommandLine::Exception& e )
    {
    cmtk::StdErr << e << "\n";
    return 1;
    }

  // read phantom description
  cmtk::DetectedPhantomMagphanEMR051::SmartPtr phantom( cmtk::PhantomIO::Read( inputPhantomPath ) );
  cmtk::DebugOutput( 5 ) << "INFO: read phantom with " << phantom->LandmarkPairsList().size() << " landmarks.\n";  

  cmtk::UniformVolume::SmartConstPtr unwarpImage = cmtk::VolumeIO::ReadOriented( inputImagePath );
  try
    {
    phantom->ApplyXformToLandmarks( cmtk::AffineXform( unwarpImage->GetImageToPhysicalMatrix().GetInverse() ) );
    }
  catch ( const cmtk::AffineXform::MatrixType::SingularMatrixException& )
    {
    cmtk::StdErr << "ERROR: singular image-to-physical space matrix cannot be inverted.\n";
    throw cmtk::ExitException( 1 );
    }

  cmtk::LandmarkPairList pairList;
  for ( std::list<cmtk::LandmarkPair>::const_iterator it = phantom->LandmarkPairsList().begin(); it != phantom->LandmarkPairsList().end(); ++it )
    {
    if ( it->m_Precise ) // exclude all unprecise landmarks
      {
      if ( it->m_Residual < residualThreshold ) // exclude outliers based on residual
	{
	if ( fitInverse )
	  {
	  pairList.push_back( it->GetSwapSourceTarget() );
	  }
	else
	  {
	  pairList.push_back( *it );
	  }
	}
      }
    }

  cmtk::DebugOutput( 2 ) << "INFO: using " << pairList.size() << " out of " << phantom->LandmarkPairsList().size() << " total phantom landmarks as fiducials.\n";
  
  cmtk::Xform::SmartConstPtr xform;
  if ( fitSpline )
    {
      xform = doFitSpline( pairList, *unwarpImage, gridDims, gridSpacing, splineFittingParameters, affineFirst );
    }
  else
    {
      xform = doFitPoly( pairList, polyDegree );
    }

  // writing resulting transformation
  cmtk::XformIO::Write( xform, outputXform );

  return 0;
}

#include "cmtkSafeMain"