//----------------------------------------------------------------------------//
// //
// B a s i c L i n e //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.math;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.Math.*;
/**
* Class {@code BasicLine} is a basic Line implementation which switches
* between horizontal and vertical equations when computing the points
* regression
*
* @author Hervé Bitteur
*/
public class BasicLine
implements Line
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(
BasicLine.class);
//~ Instance fields --------------------------------------------------------
/** Flag to indicate that data needs to be recomputed */
private boolean dirty;
/** Orientation indication */
private boolean isRatherVertical = false;
/** x coeff in Line equation */
private double a;
/** y coeff in Line equation */
private double b;
/** 1 coeff in Line equation */
private double c;
/** Sigma (x) */
private double sx;
/** Sigma (x**2) */
private double sx2;
/** Sigma (x*y) */
private double sxy;
/** Sigma (y) */
private double sy;
/** Sigma (y**2) */
private double sy2;
/** For regression : Number of points */
private int n;
//~ Constructors -----------------------------------------------------------
//-----------//
// BasicLine //
//-----------//
/**
* Creates a line, with no data. The line is no yet usable, except for
* including further defining points.
*/
public BasicLine ()
{
reset();
}
//-----------//
// BasicLine //
//-----------//
/**
* Creates a line, for which we already know the coefficients. The
* coefficients don't have to be normalized, the constructor takes care of
* this. This line is not meant to be modified by including additional
* points (although this is doable), since it contains no defining points.
*
* @param a xCoeff
* @param b yCoeff
* @param c 1Coeff
*/
public BasicLine (double a,
double b,
double c)
{
this();
this.a = a;
this.b = b;
this.c = c;
normalize();
dirty = false;
checkLineParameters();
}
//-----------//
// BasicLine //
//-----------//
/**
* Creates a line (and immediately compute its coefficients), as the least
* square fitted line on the provided points population.
*
* @param xVals abscissas of the points
* @param yVals ordinates of the points
*/
public BasicLine (double[] xVals,
double[] yVals)
{
this();
// Checks for parameter validity
if ((xVals == null) || (yVals == null)) {
throw new IllegalArgumentException(
"Provided arrays may not be null");
}
// Checks for parameter validity
if (xVals.length != yVals.length) {
throw new IllegalArgumentException(
"Provided arrays have different lengths");
}
// Checks for parameter validity
if (xVals.length < 2) {
throw new IllegalArgumentException("Provided arrays are too short");
}
// Include all defining points
for (int i = xVals.length - 1; i >= 0; i--) {
includePoint(xVals[i], yVals[i]);
}
checkLineParameters();
}
//~ Methods ----------------------------------------------------------------
//------------//
// distanceOf //
//------------//
@Override
public double distanceOf (double x,
double y)
{
checkLineParameters();
return (a * x) + (b * y) + c;
}
//------------------//
// getInvertedSlope //
//------------------//
@Override
public double getInvertedSlope ()
{
checkLineParameters();
return -b / a;
}
//-----------------//
// getMeanDistance //
//-----------------//
@Override
public double getMeanDistance ()
{
// Check we have at least 2 points
if (n < 2) {
throw new UndefinedLineException(
"Not enough defining points : " + n);
}
checkLineParameters();
// abs is used in case of rounding errors
return sqrt(
abs(
(a * a * sx2) + (b * b * sy2) + (c * c * n)
+ (2 * a * b * sxy) + (2 * a * c * sx) + (2 * b * c * sy)) / n);
}
//-------------------//
// getNumberOfPoints //
//-------------------//
@Override
public int getNumberOfPoints ()
{
return n;
}
//----------//
// getSlope //
//----------//
@Override
public double getSlope ()
{
checkLineParameters();
return -a / b;
}
//-------------//
// includeLine //
//-------------//
@Override
public Line includeLine (Line other)
{
if (other instanceof BasicLine) {
BasicLine o = (BasicLine) other;
n += o.n;
sx += o.sx;
sy += o.sy;
sx2 += o.sx2;
sy2 += o.sy2;
sxy += o.sxy;
} else {
throw new RuntimeException("Combining inconsistent lines");
}
dirty = true;
return this;
}
//--------------//
// includePoint //
//--------------//
@Override
public void includePoint (double x,
double y)
{
logger.debug("includePoint x={} y={}", x, y);
n += 1;
sx += x;
sy += y;
sx2 += (x * x);
sy2 += (y * y);
sxy += (x * y);
dirty = true;
}
//--------------//
// isHorizontal //
//--------------//
@Override
public boolean isHorizontal ()
{
checkLineParameters();
return a == 0d;
}
//------------//
// isVertical //
//------------//
@Override
public boolean isVertical ()
{
checkLineParameters();
return b == 0d;
}
//-------//
// reset //
//-------//
@Override
public void reset ()
{
a = b = c = Double.NaN;
n = 0;
sx = sy = sx2 = sy2 = sxy = 0d;
dirty = false;
}
//--------------------//
// swappedCoordinates //
//--------------------//
/**
* Return a new line whose coordinates are swapped with respect to this one
*
* @return a new X/Y swapped line
*/
@Override
public Line swappedCoordinates ()
{
BasicLine that = new BasicLine();
that.n = n;
that.sx = sy;
that.sy = sx;
that.sx2 = sy2;
that.sy2 = sx2;
that.sxy = sxy;
that.dirty = true;
return that;
}
//----------//
// toString //
//----------//
@Override
public String toString ()
{
try {
if (dirty) {
compute();
}
StringBuilder sb = new StringBuilder();
if (isRatherVertical) {
sb.append("{VLine ");
} else {
sb.append("{HLine ");
}
if (a >= 0) {
sb.append(" ");
}
sb.append((float) a)
.append("*x ");
if (b >= 0) {
sb.append("+");
}
sb.append((float) b)
.append("*y ");
if (c >= 0) {
sb.append("+");
}
sb.append((float) c)
.append("}");
return sb.toString();
} catch (UndefinedLineException ex) {
return "INVALID LINE";
}
}
//------//
// xAtY //
//------//
@Override
public double xAtY (double y)
{
if (n == 1) {
return sx;
}
checkLineParameters();
if (a != 0d) {
return -((b * y) + c) / a;
} else {
throw new NonInvertibleLineException("Line is horizontal");
}
}
//------//
// xAtY //
//------//
@Override
public int xAtY (int y)
{
return (int) rint(xAtY((double) y));
}
//------//
// yAtX //
//------//
@Override
public double yAtX (double x)
{
if (n == 1) {
return sy;
}
checkLineParameters();
if (b != 0d) {
return ((-a * x) - c) / b;
} else {
throw new NonInvertibleLineException("Line is vertical");
}
}
//------//
// yAtX //
//------//
@Override
public int yAtX (int x)
{
return (int) rint(yAtX((double) x));
}
//------//
// getA // Meant for test
//------//
double getA ()
{
checkLineParameters();
return a;
}
//------//
// getB // Meant for test
//------//
double getB ()
{
checkLineParameters();
return b;
}
//------//
// getC // Meant for test
//------//
double getC ()
{
checkLineParameters();
return c;
}
//---------------------//
// checkLineParameters //
//---------------------//
/**
* Make sure the line parameters are usable.
*/
private void checkLineParameters ()
{
// Recompute parameters based on points if so needed
if (dirty) {
compute();
}
// Make sure the parameters are available
if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c)) {
throw new UndefinedLineException(
"Line parameters not properly set");
}
}
//---------//
// compute //
//---------//
/**
* Compute the line equation, based on the cumulated number of points
*/
private void compute ()
{
if (n < 2) {
throw new UndefinedLineException(
"Not enough defining points : " + n);
}
// Make a choice between horizontal vs vertical
double hDen = (n * sx2) - (sx * sx);
double vDen = (n * sy2) - (sy * sy);
logger.debug("hDen={} vDen={}", hDen, vDen);
if (abs(hDen) >= abs(vDen)) {
// Use a rather horizontal orientation, y = mx +p
isRatherVertical = false;
a = ((n * sxy) - (sx * sy)) / hDen;
b = -1d;
c = ((sy * sx2) - (sx * sxy)) / hDen;
} else {
// Use a rather vertical orientation, x = my +p
isRatherVertical = true;
a = -1d;
b = ((n * sxy) - (sx * sy)) / vDen;
c = ((sx * sy2) - (sy * sxy)) / vDen;
}
normalize();
dirty = false;
}
//-----------//
// normalize //
//-----------//
/**
* Compute the distance normalizing factor
*/
private void normalize ()
{
double norm = hypot(a, b);
a /= norm;
b /= norm;
c /= norm;
}
}