void 
cmtk::FitSplineWarpToDeformationField::FitSpline( SplineWarpXform& splineWarp, const int nLevels )
{
  // loop until final control point spacing
  for ( int level = 0; level < nLevels; ++level )
    {
    DebugOutput( 5 ) << "Multi-resolution spline fitting level " << level+1 << " out of " << nLevels << "\n";
    // refine control point grid unless this is first iteration
    if ( level )
      {
      splineWarp.Refine();
      }

    DebugOutput( 6 ) << "  Control point grid is " << splineWarp.m_Dims[0] << "x" << splineWarp.m_Dims[1] << "x" << splineWarp.m_Dims[2] << "\n";

    // compute residuals
    splineWarp.RegisterVolumePoints( this->m_DeformationField->m_Dims, this->m_DeformationField->m_Spacing, this->m_DeformationField->m_Offset );
    this->CreateGridLookupTables( splineWarp );
    this->ComputeResiduals( splineWarp );
    
    // loop over all control points to compute deltas as the spline coefficients that fit current residuals
    std::vector< FixedVector<3,Types::Coordinate> > delta( splineWarp.m_NumberOfControlPoints, FixedVector<3,Types::Coordinate>( FixedVector<3,Types::Coordinate>::Init( 0.0 ) ) );
    std::vector< Types::Coordinate > weight( splineWarp.m_NumberOfControlPoints, 0.0 );
    
    for ( RegionIndexIterator<WarpXform::ControlPointRegionType> voxelIt( this->m_DeformationField->GetAllControlPointsRegion() ); voxelIt != voxelIt.end(); ++voxelIt )
      {
      const DataGrid::IndexType voxelIdx = voxelIt.Index();

      Types::Coordinate sumOfSquares = 0;
      for ( int m = 0; m < 4; ++m )
	for ( int l = 0; l < 4; ++l )
	  for ( int k = 0; k < 4; ++k )
	    {
	    sumOfSquares += MathUtil::Square( splineWarp.m_GridSpline[0][4*voxelIdx[0]+k] * splineWarp.m_GridSpline[1][4*voxelIdx[1]+l] * splineWarp.m_GridSpline[2][4*voxelIdx[2]+m] );
	    }
      
      for ( int m = 0; m < 4; ++m )
	for ( int l = 0; l < 4; ++l )
	  for ( int k = 0; k < 4; ++k )
	    {
	    const size_t cpOfs = splineWarp.m_GridIndexes[0][voxelIdx[0]] + k + splineWarp.m_Dims[0] * ( splineWarp.m_GridIndexes[1][voxelIdx[1]] + l + splineWarp.m_Dims[1] * ( splineWarp.m_GridIndexes[2][voxelIdx[2]] + m ) );
	    const Types::Coordinate wklm = splineWarp.m_GridSpline[0][4*voxelIdx[0]+k] * splineWarp.m_GridSpline[1][4*voxelIdx[1]+l] * splineWarp.m_GridSpline[2][4*voxelIdx[2]+m];

	    delta[cpOfs] += MathUtil::Square( wklm ) * wklm / sumOfSquares * this->m_Residuals[this->m_DeformationField->GetOffsetFromIndex( voxelIdx )/3];
	    weight[cpOfs] += MathUtil::Square( wklm );
	    }
      }
    
    // apply delta
    for ( size_t cp = 0; cp < splineWarp.m_NumberOfControlPoints; ++cp )
      {
      if ( weight[cp] != 0 )
	{
	delta[cp] /= weight[cp];
	splineWarp.SetShiftedControlPointPositionByOffset( splineWarp.GetShiftedControlPointPositionByOffset( cp ) + delta[cp], cp );
	}
      else
	{
	// nothing to do - keep control point where it is.
	}
      }
    }
}
