/*-
*******************************************************************************
* 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 java.util.Set;
import java.util.TreeSet;
import org.eclipse.dawnsci.analysis.api.roi.IROI;
import org.eclipse.dawnsci.analysis.api.roi.IRectangularROI;
/**
* Class for rectangular region of interest
*/
public class RectangularROI extends OrientableROIBase implements IRectangularROI, Serializable {
protected double[] len; // width and height. These should NOT be negative
private boolean clippingCompensation; // compensate for clipping
/**
* @param clippingCompensation The clippingCompensation to set.
*/
public void setClippingCompensation(boolean clippingCompensation) {
this.clippingCompensation = clippingCompensation;
}
/**
* @return Returns the clippingCompensation.
*/
public boolean isClippingCompensation() {
return clippingCompensation;
}
/**
* Default has undefined start point and lengths
*/
public RectangularROI() {
this(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0);
}
/**
* Square constructor
*
* @param width A Non-Negative value for the width
* @param angle
* @throws IllegalArgumentException if the width is negative (Not allowed to have negative width or height)
*/
public RectangularROI(double width, double angle) throws IllegalArgumentException {
this(0, 0, width, width, angle);
}
/**
* @param width A Non-Negative value for the width
* @param height A Non-Negative value for the height
* @param angle
* @throws IllegalArgumentException if the width or height is negative (Not allowed to have negative width or height)
*/
public RectangularROI(double width, double height, double angle) throws IllegalArgumentException {
this(0, 0, width, height, angle);
}
/**
* @param ptx
* @param pty
* @param width A Non-Negative value for the width
* @param height A Non-Negative value for the height
* @param angle
* @throws IllegalArgumentException if the width or height is negative (Not allowed to have negative width or height)
*/
public RectangularROI(double ptx, double pty, double width, double height, double angle) throws IllegalArgumentException {
this(ptx, pty, width, height, angle, false);
}
/**
* @param ptx
* @param pty
* @param width A Non-Negative value for the width
* @param height A Non-Negative value for the height
* @param angle
* @param clip
* @throws IllegalArgumentException if the width or height is negative (Not allowed to have negative width or height)
*/
public RectangularROI(double ptx, double pty, double width, double height, double angle, boolean clip) throws IllegalArgumentException {
if (width < 0) {
throw new IllegalArgumentException("RectangularROI cannot have negative width");
} else if (height < 0) {
throw new IllegalArgumentException("RectangularROI cannot have negative height");
}
spt = new double[] { ptx, pty };
len = new double[] { width, height };
ang = angle;
checkAngle();
clippingCompensation = clip;
}
/**
* @param spt start point
* @param ept end point
*/
public RectangularROI(double[] spt, double[] ept) {
this.spt = spt;
len = new double[2];
double lx = ept[0] - spt[0];
double ly = ept[1] - spt[1];
if (lx > 0) {
if (ly > 0) {
len[0] = lx;
len[1] = ly;
ang = 0;
} else {
len[0] = lx;
len[1] = -ly;
ang = Math.PI * 1.5;
}
} else {
if (ly > 0) {
len[0] = -lx;
len[1] = ly;
ang = Math.PI * 0.5;
} else {
len[0] = -lx;
len[1] = -ly;
ang = Math.PI;
}
}
checkAngle();
}
/**
* @param len (width, height) Non-Negative numbers for the width and height
* @throws IllegalArgumentException if the width or height is negative (Not allowed to have negative width or height)
*/
public void setLengths(double len[]) throws IllegalArgumentException {
if (len[0] < 0) {
throw new IllegalArgumentException("RectangularROI cannot have negative width");
} else if (len[1] < 0) {
throw new IllegalArgumentException("RectangularROI cannot have negative height");
}
this.len[0] = len[0];
this.len[1] = len[1];
setDirty();
}
/**
* @param major (width) A Non-Negative value for the width
* @param minor (height) A Non-Negative value for the height
* @throws IllegalArgumentException if the width or height is negative (Not allowed to have negative width or height)
*/
public void setLengths(double major, double minor) throws IllegalArgumentException {
if (major < 0) {
throw new IllegalArgumentException("RectangularROI cannot have negative width");
} else if (minor < 0) {
throw new IllegalArgumentException("RectangularROI cannot have negative height");
}
len[0] = major;
len[1] = minor;
setDirty();
}
/**
* @param x (width)
* @param y (height)
* @throws IllegalArgumentException if the width or height is negative as a result (Not allowed to have negative width or height)
*/
public void addToLengths(double x, double y) throws IllegalArgumentException {
if (len[0] + x < 0) {
throw new IllegalArgumentException("RectangularROI cannot have negative width");
} else if (len[1] + y < 0) {
throw new IllegalArgumentException("RectangularROI cannot have negative height");
}
len[0] += x;
len[1] += y;
setDirty();
}
/**
* Return point from normalized coordinates within rectangle
*
* @param fx
* @param fy
* @return point
*/
public double[] getPoint(double fx, double fy) {
return new double[] { spt[0] + fx * len[0] * cang - fy * len[1] * sang,
spt[1] + fx * len[0] * sang + fy * len[1] * cang };
}
private double[] getRelativePoint(double fx, double fy) {
return new double[] { fx * len[0] * cang - fy * len[1] * sang,
fx * len[0] * sang + fy * len[1] * cang };
}
/**
* Return point from normalized coordinates within rectangle
*
* @param fx
* @param fy
* @return point
*/
public int[] getIntPoint(double fx, double fy) {
return new int[] { (int) (spt[0] + fx * len[0] * cang - fy * len[1] * sang),
(int) (spt[1] + fx * len[0] * sang + fy * len[1] * cang) };
}
@Override
public double[] getEndPoint() {
return getPoint(1., 1.);
}
/**
* @return mid point
*/
public double[] getMidPoint() {
return getPoint(0.5, 0.5);
}
/**
* Change rectangle to have specified mid point
* @param mpt
*/
public void setMidPoint(double[] mpt) {
spt[0] = mpt[0] - 0.5*len[0]*cang + 0.5*len[1]*sang;
spt[1] = mpt[1] - 0.5*len[0]*sang - 0.5*len[1]*cang;
setDirty();
}
/**
* @param index (0 for width, 1 for height)
* @return length
*/
@Override
public double getLength(int index) {
return len[index];
}
@Override
public int getIntLength(int index) {
return (int)len[index];
}
/**
* @return reference to the lengths
*/
public double[] getLengths() {
return len;
}
/**
* @return integer lengths
*/
@Override
public int[] getIntLengths() {
return new int[] { (int) len[0], (int) len[1] };
}
@Override
public double getAngle() {
return ang;
}
/**
* For Jython
* @param angle The angle in degrees to set
*/
public void setAngledegrees(double angle) {
setAngleDegrees(angle);
}
@Override
public RectangularROI copy() {
RectangularROI c = new RectangularROI(spt[0], spt[1], len[0], len[1], ang, clippingCompensation);
c.name = name;
c.plot = plot;
return c;
}
/**
* @param pt
* @return angle as measured from midpoint to given point
*/
public double getAngleRelativeToMidPoint(int[] pt) {
return getAngleRelativeToPoint(0.5, 0.5, ROIUtils.convertToDoubleArray(pt));
}
/**
* @param pt
* @return angle as measured from midpoint to given point
*/
public double getAngleRelativeToMidPoint(double[] pt) {
return getAngleRelativeToPoint(0.5, 0.5, pt);
}
/**
* @param fx
* @param fy
* @param pt
* @return angle as measured from normalized coordinates within rectangle to given point
*/
public double getAngleRelativeToPoint(double fx, double fy, int[] pt) {
return getAngleRelativeToPoint(fx, fy, ROIUtils.convertToDoubleArray(pt));
}
/**
* @param fx
* @param fy
* @param pt
* @return angle as measured from normalized coordinates within rectangle to given point
*/
public double getAngleRelativeToPoint(double fx, double fy, double[] pt) {
double[] fpt = getPoint(fx, fy);
fpt[0] = pt[0] - fpt[0];
fpt[1] = pt[1] - fpt[1];
return Math.atan2(fpt[1], fpt[0]);
}
/**
* Start a rotated rectangle with predefined starting point and given end point and determine new starting point
*
* @param pt
*/
public void setEndPointKeepLengths(double[] pt) {
double[] ps = null;
// work in rotated coords
ps = transformToRotated(pt[0], pt[1]);
spt = transformToOriginal(ps[0] - len[0], ps[1] - len[1]);
setDirty();
}
/**
* @param pt
*/
public void setEndPoint(int[] pt) {
setEndPoint(ROIUtils.convertToDoubleArray(pt));
}
/**
* Start a rotated rectangle with predefined starting point and given end point and determine new starting point and
* lengths
*
* @param pt
*/
public void setEndPoint(double[] pt) {
double[] ps = null;
double[] pe = null;
// work in rotated coords
ps = transformToRotated(spt[0], spt[1]);
pe = transformToRotated(pt[0], pt[1]);
// check and correct bounding box
if (ps[0] > pe[0]) {
double t = ps[0];
ps[0] = pe[0];
pe[0] = t;
}
if (ps[1] > pe[1]) {
double t = ps[1];
ps[1] = pe[1];
pe[1] = t;
}
len[0] = pe[0] - ps[0];
len[1] = pe[1] - ps[1];
spt = transformToOriginal(ps[0], ps[1]);
setDirty();
}
/**
* Start a rotated rectangle with predefined starting point and given end point and determine new starting point and
* lengths
*
* @param pt
* @param moveX
* @param moveY
*/
public void setEndPoint(int[] pt, boolean moveX, boolean moveY) {
setEndPoint(ROIUtils.convertToDoubleArray(pt), moveX, moveY);
}
/**
* Start a rotated rectangle with predefined starting point and given end point and determine new starting point and
* lengths
*
* @param pt
* @param moveX
* @param moveY
*/
public void setEndPoint(double[] pt, boolean moveX, boolean moveY) {
double[] ps = null;
double[] pe = null;
// work in rotated coords
ps = transformToRotated(spt[0], spt[1]);
pe = transformToRotated(pt[0], pt[1]);
if (moveX) {
len[0] = pe[0] - ps[0];
if (len[0] < 0) { // don't allow negative lengths
len[0] = 0;
}
}
if (moveY) {
len[1] = pe[1] - ps[1];
if (len[1] < 0) { // don't allow negative lengths
len[1] = 0;
}
}
setDirty();
}
/**
* Add an offset to angle
*
* @param angle
*/
public void addAngle(double angle) {
ang += angle;
checkAngle();
setDirty();
}
/**
* Subtract an offset from starting point
*
* @param pt
*/
public void subPoint(int[] pt) {
spt[0] -= pt[0];
spt[1] -= pt[1];
setDirty();
}
/**
* Translate by normalized coordinates (in rotated frame)
* @param fx
* @param fy
*/
public void translate(double fx, double fy) {
double[] ps = getRelativePoint(fx, fy);
spt[0] += ps[0];
spt[1] += ps[1];
setDirty();
}
/**
* Set start point whilst keeping end point
*
* @param dpt
* change in start point
* @param moveX
* @param moveY
*/
public void setPointKeepEndPoint(int[] dpt, boolean moveX, boolean moveY) {
setEndPoint(ROIUtils.convertToDoubleArray(dpt), moveX, moveY);
}
/**
* Set start point whilst keeping end point
*
* @param dpt
* change in start point
* @param moveX
* @param moveY
*/
public void setPointKeepEndPoint(double[] dpt, boolean moveX, boolean moveY) {
double[] ps = null;
double[] pe = null;
// work in rotated coords
pe = transformToRotated(dpt[0], dpt[1]);
if (moveX) {
double dx = pe[0] >= len[0] ? len[0] : pe[0]; // don't allow negative lengths
ps = transformToOriginal(dx, 0);
len[0] -= dx;
spt[0] += ps[0];
spt[1] += ps[1];
}
if (moveY) {
double dy = pe[1] >= len[1] ? len[1] : pe[1]; // don't allow negative lengths
ps = transformToOriginal(0, dy);
len[1] -= dy;
spt[0] += ps[0];
spt[1] += ps[1];
}
setDirty();
}
/**
* Adjust ROI whilst keeping a diagonal point in place
*
* @param cpt
* @param ept
* @param pt
* @param first
*/
public void adjustKeepDiagonalPoint(int[] cpt, double[] ept, int[] pt, boolean first) {
adjustKeepDiagonalPoint(ROIUtils.convertToDoubleArray(cpt), ept, ROIUtils.convertToDoubleArray(pt), first);
}
/**
* Adjust ROI whilst keeping a diagonal point in place
*
* @param cpt
* @param ept
* @param pt
* @param first
*/
public void adjustKeepDiagonalPoint(double[] cpt, double[] ept, double[] pt, boolean first) {
double[] ps = null;
double[] pe = null;
// work in rotated coords
ps = transformToRotated(spt[0], spt[1]);
pe = transformToRotated(pt[0] - cpt[0] + ept[0], pt[1] - cpt[1] + ept[1]);
if (first) { // move end x, start y
len[0] = pe[0] - ps[0];
if (len[0] < 0)
len[0] = 0;
pe = transformToRotated(pt[0] - cpt[0], pt[1] - cpt[1]);
double dy = pe[1] >= len[1] ? len[1] : pe[1]; // don't allow negative lengths
ps = transformToOriginal(0, dy);
len[1] -= dy;
spt[0] += ps[0];
spt[1] += ps[1];
} else { // move end y, start x
len[1] = pe[1] - ps[1];
if (len[1] < 0)
len[1] = 0;
pe = transformToRotated(pt[0] - cpt[0], pt[1] - cpt[1]);
double dx = pe[0] >= len[0] ? len[0] : pe[0]; // don't allow negative lengths
ps = transformToOriginal(dx, 0);
len[0] -= dx;
spt[0] += ps[0];
spt[1] += ps[1];
}
setDirty();
}
@Override
public RectangularROI getBounds() {
if (bounds == null) {
if (ang == 0) {
bounds = copy();
} else {
bounds = new RectangularROI();
double ac = Math.abs(cang);
double as = Math.abs(sang);
bounds.setPoint(spt.clone());
bounds.setLengths(ac * len[0] + as * len[1], as * len[0] + ac * len[1]);
if (sang >= 0) {
if (cang >= 0) {
bounds.addPoint(-sang * len[1], 0);
} else {
bounds.addPoint(-bounds.getLength(0), cang * len[1]);
}
} else {
if (cang < 0) {
bounds.addPoint(cang * len[0], -bounds.getLength(1));
} else {
bounds.addPoint(0, sang * len[0]);
}
}
}
}
return bounds;
}
@Override
public boolean containsPoint(double x, double y) {
x -= spt[0];
y -= spt[1];
if (ang == 0) {
if (x < 0 || x > len[0])
return false;
return y >= 0 && y <= len[1];
}
// work in rotated coords
if (bounds == null) {
getBounds();
}
double[] pr = transformToRotated(x, y);
if (pr[0] < 0 || pr[0] > len[0])
return false;
return pr[1] >= 0 && pr[1] <= len[1];
}
@Override
public boolean isNearOutline(double x, double y, double distance) {
if (!super.isNearOutline(x, y, distance))
return false;
double[] pta = spt;
double[] ptb = getPoint(1, 0);
if (ROIUtils.isNearSegment(ptb[0] - pta[0], ptb[1] - pta[1], x - pta[0], y - pta[1], distance))
return true;
pta = ptb;
ptb = getPoint(1, 1);
if (ROIUtils.isNearSegment(ptb[0] - pta[0], ptb[1] - pta[1], x - pta[0], y - pta[1], distance))
return true;
pta = ptb;
ptb = getPoint(0, 1);
if (ROIUtils.isNearSegment(ptb[0] - pta[0], ptb[1] - pta[1], x - pta[0], y - pta[1], distance))
return true;
pta = ptb;
ptb = spt;
return ROIUtils.isNearSegment(ptb[0] - pta[0], ptb[1] - pta[1], x - pta[0], y - pta[1], distance);
}
@Override
public void downsample(double subFactor) {
spt[0] /= subFactor;
spt[1] /= subFactor;
len[0] /= subFactor;
len[1] /= subFactor;
setDirty();
}
@Override
public String toString() {
return super.toString() + String.format("X Start=%s, Y Start=%s, Width=%s, Height=%s, Angle=%g",
String.valueOf(spt[0]), String.valueOf(spt[1]),
String.valueOf(len[0]), String.valueOf(len[1]), getAngleDegrees());
}
@Override
public double[] findHorizontalIntersections(final double y) {
if (!intersectHorizontal(y))
return null;
double[] xi, pta, ptb;
Set<Double> values = new TreeSet<Double>();
pta = spt;
ptb = getPoint(1, 0);
xi = ROIUtils.findYIntersection(pta, ptb, y);
if (xi != null) {
if (xi.length == 2)
return xi;
values.add(xi[0]);
}
pta = ptb;
ptb = getPoint(1, 1);
xi = ROIUtils.findYIntersection(pta, ptb, y);
if (xi != null) {
if (xi.length == 2)
return xi;
values.add(xi[0]);
}
pta = ptb;
ptb = getPoint(0, 1);
xi = ROIUtils.findYIntersection(pta, ptb, y);
if (xi != null) {
if (xi.length == 2)
return xi;
values.add(xi[0]);
}
pta = ptb;
ptb = spt;
xi = ROIUtils.findYIntersection(pta, ptb, y);
if (xi != null) {
if (xi.length == 2)
return xi;
values.add(xi[0]);
}
xi = new double[values.size()];
int i = 0;
for (Double d : values) {
xi[i++] = d;
}
return xi;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (clippingCompensation ? 1231 : 1237);
result = prime * result + Arrays.hashCode(len);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
RectangularROI other = (RectangularROI) obj;
if (clippingCompensation != other.clippingCompensation)
return false;
if (!Arrays.equals(len, other.len))
return false;
return true;
}
@Override
public IRectangularROI bounds(IROI roi) {
IRectangularROI bnd = this.getBounds();
double[] s1 = bnd.getPoint();
double[] e1 = bnd.getEndPoint();
bnd = roi.getBounds();
double[] s2 = bnd.getPoint();
double[] e2 = bnd.getEndPoint();
return new RectangularROI(new double[]{Math.min(s1[0], s2[0]), Math.min(s1[1], s2[1])},
new double[]{Math.max(e1[0], e2[0]), Math.max(e1[1], e2[1])});
}
}