/*-
*******************************************************************************
* Copyright (c) 2011, 2014 Diamond Light Source Ltd.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Peter Chang - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.dawnsci.analysis.dataset.roi;
import java.io.Serializable;
import java.util.Arrays;
import org.eclipse.dawnsci.analysis.api.roi.IParametricROI;
/**
* Class for linear regions of interest
*/
public class LinearROI extends OrientableROIBase implements IParametricROI, Serializable {
private double len; // length
private boolean crossHair; // enable secondary linear ROI that bisects at 90 degrees
private transient double[] ept;
/**
* Default new line
*/
public LinearROI() {
this(1.);
}
/**
* @param len
*/
public LinearROI(double len) {
this(len, 0.0);
}
/**
* @param len
* @param ang in radians
*/
public LinearROI(double len, double ang) {
this.spt = new double[] {0, 0};
this.len = len;
this.ang = ang;
checkAngle();
crossHair = false;
}
/**
* New line from origin to point
* @param pt
*/
public LinearROI(double[] pt) {
this(new double[] {0, 0}, pt);
}
/**
* New line given by points
* @param spt
* @param ept
*/
public LinearROI(double[] spt, double[] ept) {
this.spt = spt.clone();
this.ept = ept.clone();
double x = ept[0] - spt[0];
double y = ept[1] - spt[1];
len = Math.hypot(x, y);
ang = Math.atan2(y, x);
checkAngle();
crossHair = false;
}
@Override
protected void setDirty() {
super.setDirty();
ept = null;
}
/**
* Set start point whilst keeping end point
* @param pt
*/
public void setPointKeepEndPoint(int[] pt) {
setPointKeepEndPoint(ROIUtils.convertToDoubleArray(pt));
}
/**
* Set start point whilst keeping end point
* @param pt
*/
public void setPointKeepEndPoint(double[] pt) {
double[] ept = getEndPoint();
spt[0] = pt[0];
spt[1] = pt[1];
double x = ept[0] - pt[0];
double y = ept[1] - pt[1];
len = Math.hypot(x, y);
ang = Math.atan2(y, x);
bounds = null;
checkAngle();
}
/**
* @param f
* @return point from normalized length along line
*/
@Override
public double[] getPoint(double f) {
return new double[] { spt[0] + f*len*cang,
spt[1] + f*len*sang };
}
/**
* @return end point
*/
public double[] getEndPoint() {
if (ept == null)
ept = getPoint(1);
return ept;
}
/**
* @param f
* @return point from normalized length along line
*/
public int[] getIntPoint(double f) {
double[] pt = getPoint(f);
return new int[] { (int) pt[0], (int) pt[1] };
}
/**
* @return end point
*/
public int[] getIntEndPoint() {
double[] pt = getPoint(1);
return new int[] { (int) pt[0], (int) pt[1] };
}
/**
* Change line to have specified end point
* @param eptx
* @param epty
*/
public void setEndPoint(double eptx, double epty) {
if (ept == null) {
ept = new double[2];
}
ept[0] = eptx;
ept[1] = epty;
double x = eptx - spt[0];
double y = epty - spt[1];
len = Math.hypot(x, y);
ang = Math.atan2(y, x);
bounds = null;
checkAngle();
}
/**
* Change line to have specified end point
* @param ept
*/
public void setEndPoint(double[] ept) {
setEndPoint(ept[0], ept[1]);
}
/**
* @param ept
*/
public void setEndPoint(int[] ept) {
setEndPoint(ept[0], ept[1]);
}
/**
* @return mid point
*/
public double[] getMidPoint() {
return getPoint(0.5);
}
/**
* Change line to have specified mid point
* @param mpt
*/
public void setMidPoint(double[] mpt) {
spt[0] = mpt[0] - 0.5*len*cang;
spt[1] = mpt[1] - 0.5*len*sang;
setDirty();
}
/**
* For Jython
* @param angle The angle in degrees to set
*/
public void setAngledegrees(double angle) {
setAngleDegrees(angle);
}
/**
* Change line to have specified length
* @param len
*/
public void setLength(double len) {
this.len = len;
setDirty();
}
/**
* @return length
*/
public double getLength() {
return len;
}
/**
* @param pt
* @return angle as measured from midpoint to given point
*/
public double getAngleRelativeToMidPoint(int[] pt) {
return getAngleRelativeToMidPoint(ROIUtils.convertToDoubleArray(pt));
}
/**
* @param pt
* @return angle as measured from midpoint to given point
*/
public double getAngleRelativeToMidPoint(double[] pt) {
double[] mpt = getMidPoint();
mpt[0] = pt[0] - mpt[0];
mpt[1] = pt[1] - mpt[1];
return Math.atan2(mpt[1], mpt[0]);
}
@Override
public LinearROI copy() {
LinearROI roi = new LinearROI();
roi.setName(name);
roi.setPoint(spt.clone());
roi.setPlot(plot);
roi.setAngle(ang);
roi.setLength(len);
roi.setCrossHair(crossHair);
return roi;
}
/**
* Subtract an offset to starting point
* @param pt
*/
public void subPoint(int[] pt) {
spt[0] -= pt[0];
spt[1] -= pt[1];
setDirty();
}
/**
* Subtract an offset to starting point
* @param pt
*/
public void subPoint(double[] pt) {
spt[0] -= pt[0];
spt[1] -= pt[1];
setDirty();
}
/**
* Move line along its length
* @param distance
*/
public void translateAlongLength(double distance) {
spt[0] += distance*cang;
spt[1] += distance*sang;
setDirty();
}
/**
* @param f
* @return point given by normalised length on perpendicular bisector
*/
public int[] getPerpendicularBisectorIntPoint(double f) {
double[] mpt = getMidPoint();
double offset = (f-0.5)*len;
double bangle = ang + Math.PI * 0.5; // bisector angle
return new int[] { (int) (mpt[0] + offset*Math.cos(bangle)),
(int) (mpt[1] + offset*Math.sin(bangle)) };
}
/**
* @param f
* @return point given by normalised length on perpendicular bisector
*/
public double[] getPerpendicularBisectorPoint(double f) {
double[] mpt = getMidPoint();
double offset = (f-0.5)*len;
double bangle = ang + Math.PI * 0.5; // bisector angle
return new double[] { mpt[0] + offset*Math.cos(bangle),
mpt[1] + offset*Math.sin(bangle) };
}
/**
* @param crossHair The crossHair to set.
*/
public void setCrossHair(boolean crossHair) {
this.crossHair = crossHair;
}
/**
* @return Returns the crossHair.
*/
public boolean isCrossHair() {
return crossHair;
}
@Override
public RectangularROI getBounds() {
if (bounds == null) {
bounds = new RectangularROI(1, 0);
double[] ept = getEndPoint();
double pax = Math.min(spt[0], ept[0]);
double pbx = Math.max(spt[0], ept[0]);
double pay = Math.min(spt[1], ept[1]);
double pby = Math.max(spt[1], ept[1]);
bounds.setPoint(pax, pay);
bounds.setLengths(pbx - pax, pby - pay);
}
return bounds;
}
@Override
public boolean containsPoint(double x, double y) {
return isNearOutline(x, y, Math.max(Math.ulp(x), Math.ulp(y)));
}
@Override
public boolean isNearOutline(double x, double y, double distance) {
if (!super.isNearOutline(x, y, distance))
return false;
return ROIUtils.isNearSegment(cang, sang, len, x - spt[0], y - spt[1], distance);
}
@Override
public String toString() {
return super.toString() + String.format("point=%s, length=%g, angle=%g", Arrays.toString(spt), len, getAngleDegrees());
}
@Override
public double[] getVerticalIntersectionParameters(double x) {
if (Math.abs(cang) <= Math.ulp(1d)) {
return x == spt[0] ? new double[] { Double.NaN } : null;
}
x -= spt[0];
double f = x / (len * cang);
if (f < 0 || f > 1)
return null;
return new double[] { f };
}
@Override
public double[] getHorizontalIntersectionParameters(double y) {
if (sang == 0) {
return y == spt[1] ? new double[] { Double.NaN } : null;
}
y -= spt[1];
double f = y / (len * sang);
if (f < 0 || f > 1)
return null;
return new double[] { f };
}
/**
* @return 0
*/
@Override
public double getStartParameter(double d) {
return 0;
}
/**
* @return 1
*/
@Override
public double getEndParameter(double d) {
return 1;
}
@Override
public double[] findHorizontalIntersections(double y) {
double[] xi = null;
if (sang == 0) {
if (y == spt[1]) {
xi = new double[] {spt[0], getEndPoint()[0]};
Arrays.sort(xi);
}
} else {
y -= spt[1];
double f = y / sang;
if (f >= 0 && f <= len) {
xi = new double[] {spt[0] + f*cang};
}
}
return xi;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (crossHair ? 1231 : 1237);
result = prime * result + Arrays.hashCode(ept);
long temp;
temp = Double.doubleToLongBits(len);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
LinearROI other = (LinearROI) obj;
if (crossHair != other.crossHair)
return false;
if (Double.doubleToLongBits(len) != Double.doubleToLongBits(other.len))
return false;
return true;
}
}