/*
 * Copyright (c) 2012-2017, Peter Abeles. All Rights Reserved.
 *
 * This file is part of DDogleg (http://ddogleg.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ddogleg.solver;

import org.ejml.data.DMatrixRMaj;
import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
import org.ejml.interfaces.linsol.LinearSolverDense;

/**
 * <p>
 * Fit observations to a 2D quadratic around a 3x3 region.  Observations are specified in a local
 * coordinate system with indexes from -1 to 1.  Typically the maximum value is at the local center,
 * (0,0) coordinate.
 * </p>
 *
 * <p>
 * Even if the center pixel is the max value, it is still possible for peak to be found outside the
 * 3x3 region.  In that situation what happened is that it fit the points inside 3x3 region to one side
 * of the curve.  In many applications the estimate should then be ignored.
 * If the minimum number of points was being considered then it would be impossible to go outside since
 * it must pass through each sample point.
 * </p>
 *
 * @author Peter Abeles
 */
public class FitQuadratic3by3 {
	
	LinearSolverDense<DMatrixRMaj> solver = LinearSolverFactory_DDRM.leastSquares(9, 6);
	DMatrixRMaj X = new DMatrixRMaj(6,1);
	DMatrixRMaj Y = new DMatrixRMaj(9,1);
	
	double deltaX;
	double deltaY;
	
	public FitQuadratic3by3() {
		DMatrixRMaj M = new DMatrixRMaj(9,6);

		int index = 0;
		for( int i = -1; i <= 1; i++ ) {
			for( int j = -1; j <= 1; j++ , index++ ) {
				M.set(index,0,j*j);
				M.set(index,1,i*j);
				M.set(index,2,i*i);
				M.set(index,3,j);
				M.set(index,4,i);
				M.set(index,5,1);
			}
		}
		
		if( !solver.setA(M) )
			throw new RuntimeException("Solver is broken");
	}

	/**
	 * Sets the value by index.  A row-major matrix is used.
	 * index = (y+1)*3+x+1
	 *
	 * @param index Array index from 0 to 8
	 * @param value value at index
	 */
	public void setValue( int index , double value ) {
		Y.set(index,value);
	}

	/**
	 * Sets the observed value by coordinate point.
	 *
	 * @param x x-coordinate.-1 to 1
	 * @param y y-coordinate -1 to 1
	 * @param value Observed value at that coordinate
	 */
	public void setValue( int x , int y , double value ) {
		setValue((y+1)*3+x+1,value);
	}

	/**
	 * Computes the maximum.
	 */
	public void process() {
		solver.solve(Y,X);

		double a = X.data[0];
		double b = X.data[1];
		double c = X.data[2];
		double d = X.data[3];
		double e = X.data[4];

		double bottom = 4*a*c - b*b;
		deltaX = (b*e - 2*d*c)/bottom;
		deltaY = (b*d - 2*a*e)/bottom;
	}

	public double getDeltaX() {
		return deltaX;
	}

	public double getDeltaY() {
		return deltaY;
	}
}
