/** Computed and return the affine transform that aligns image to template. */
 public AffineTransform align(
     final RandomAccessibleInterval<T> image,
     final int maxIterations,
     final double minParameterChange) {
   currentTransform.set(new AffineTransform(n));
   int i = 0;
   while (i < maxIterations) {
     ++i;
     if (alignStep(image) < minParameterChange) break;
   }
   System.out.println("computed " + i + " iterations.");
   return currentTransform;
 }
  double alignStep(final RandomAccessibleInterval<T> image) {
    // compute error image = warped image - template
    computeDifference(Views.extendBorder(image), currentTransform, template, error);

    // compute transform parameter update
    final double[] gradient = new double[numParameters];
    for (int p = 0; p < numParameters; ++p) {
      final Cursor<T> err = Views.flatIterable(error).cursor();
      for (final T t : Views.flatIterable(Views.hyperSlice(descent, n, p)))
        gradient[p] += t.getRealDouble() * err.next().getRealDouble();
    }
    final double[] dp = new double[numParameters];
    LinAlgHelpers.mult(Hinv, gradient, dp);

    // udpate transform
    currentTransform.preConcatenate(warpFunction.getAffine(dp));

    // return norm of parameter update vector
    return LinAlgHelpers.length(dp);
  }