/**
  * Compute the steepest descent images of the template at the identity warp. Each steepest descent
  * image comprises the partial derivatives of template intensities with respect to one parameter
  * of the warp function.
  *
  * <p>The result is stored in the <em>n+1</em> dimensional {@link #target} image. Dimension
  * <em>n</em> is used to index the partial derivative. For example, the partial derivative by the
  * second parameter of the warp function is stored in slice <em>n=1</em>.
  *
  * @param gradients n+1 dimensional image of partial derivatives of the template. Dimension n is
  *     used to index the partial derivative. For example, the partial derivative by Y is stored in
  *     slice n=1.
  * @param warpFunction The warp function to be applied to the template. The partial derivatives of
  *     template intensities with respect to the parameters of this warp function are computed.
  * @param target Image of <em>n+1</em> dimensions to store the steepest descent Dimension
  *     <em>n</em> is used to index the parameters of the warp function. For example, the partial
  *     derivative of the template image intensity by parameter 2 of the warp function at pixel
  *     <em>(x,y)</em> is stored at position <em>(x,y,1)</em>.
  */
 public static <T extends NumericType<T>> void computeSteepestDescents(
     final RandomAccessibleInterval<T> gradients,
     final WarpFunction warpFunction,
     final RandomAccessibleInterval<T> target) {
   final int n = gradients.numDimensions() - 1;
   final int numParameters = warpFunction.numParameters();
   final T tmp = Util.getTypeFromInterval(gradients).createVariable();
   for (int p = 0; p < numParameters; ++p) {
     for (int d = 0; d < n; ++d) {
       final Cursor<T> gd =
           Views.flatIterable(Views.hyperSlice(gradients, n, d)).localizingCursor();
       for (final T t : Views.flatIterable(Views.hyperSlice(target, n, p))) {
         tmp.set(gd.next());
         tmp.mul(warpFunction.partial(gd, d, p));
         t.add(tmp);
       }
     }
   }
 }
  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);
  }