/*- ******************************************************************************* * 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; /** * A hyperbolic region of interest with the start point as the focus. In the rotated frame, * it can be represented as (x+l/e)^2 / a^2 - y^2 / b^2 = 1, where l = b^2/a and e = sqrt(1 + b^2/a^2) */ public class HyperbolicROI extends OrientableROIBase implements IParametricROI, Serializable { private double l; // semi-latus rectum private double e; // eccentricity /** * No argument constructor need for serialization */ public HyperbolicROI() { this(1, Math.sqrt(2), 0, 0); } /** * Create a hyperbolic ROI * @param semi semi-latus rectum (half length of chord parallel to directrix, passing through focus) * @param eccentricity measure of non-circularity (>1) * @param ptx centre point x value * @param pty centre point y value */ public HyperbolicROI(double semi, double eccentricity, double ptx, double pty) { this(semi, eccentricity, 0, ptx, pty); } /** * Create a hyperbolic ROI * @param semi semi-latus rectum (half length of chord parallel to directrix, passing through focus) * @param eccentricity measure of non-circularity (>1) * @param angle major axis angle * @param ptx centre point x value * @param pty centre point y value */ public HyperbolicROI(double semi, double eccentricity, double angle, double ptx, double pty) { spt = new double[] { ptx, pty }; l = semi; e = eccentricity; ang = angle; checkAngle(); } @Override public void downsample(double subFactor) { super.downsample(subFactor); l /= subFactor; } @Override public HyperbolicROI copy() { HyperbolicROI c = new HyperbolicROI(l, e, ang, spt[0], spt[1]); c.name = name; c.plot = plot; return c; } /** * @return Returns semi-latus rectum */ public double getSemilatusRectum() { return l; } /** * Set semi-latus rectum * @param semi */ public void setSemilatusRectum(double semi) { l = semi; setDirty(); } /** * @return eccentricity */ public double getEccentricity() { return e; } /** * Set eccentricity * @param eccentricity */ public void setEccentricity(double eccentricity) { e = eccentricity; setDirty(); } /** * Get point on hyperbola at given angle * @param angle in radians * @return point */ @Override public double[] getPoint(double angle) { double[] pt = getRelativePoint(angle); pt[0] += spt[0]; pt[1] += spt[1]; return pt; } /** * Get point on hyperbola at given angle relative to focus * @param angle in radians * @return point */ public double[] getRelativePoint(double angle) { double cb = Math.cos(angle); double denom = 1 - e * cb; // NB when this is negative, the point is on other nappe of double cone if (denom == 0) { double[] pt = transformToOriginal(1, 0); if (pt[0] != 0) pt[0] *= Double.POSITIVE_INFINITY; if (pt[1] != 0) pt[1] *= Double.POSITIVE_INFINITY; return pt; // } else if (denom < 0) { // return new double[] {Double.NaN, Double.NaN}; } double sb = Math.sin(angle); double r = l / denom; return transformToOriginal(r * cb, r * sb); } /** * @return angle of asymptote (which acts as a minimum for angle) */ public double getAsymptoteAngle() { return Math.acos(1 / e); } /** * Get point on hyperbolic at given angle * @param angle in degrees * @return point */ public double[] getPointDegrees(double angle) { return getPoint(Math.toRadians(angle)); } /** * Get distance from focus to point on hyperbolic at given angle * @param angle in radians * @return distance */ public double getDistance(double angle) { double[] p = getRelativePoint(angle); return Math.hypot(p[0], p[1]); } /** * <emph>Important:</emph> this returns null as a hyperbolic is unbounded */ @Override public RectangularROI getBounds() { return null; } /** * Determine if point is on or inside hyperbolic * @param x * @param y * @return true if hyperbolic contains point */ @Override public boolean containsPoint(double x, double y) { return false; } @Override public boolean isNearOutline(double x, double y, double distance) { x -= spt[0]; y -= spt[1]; double[] pt = transformToRotated(x, y); double a = Math.atan2(pt[1], pt[0]); double[] pr = getRelativePoint(a); return Math.hypot(pt[0] - pr[0], pt[1] - pr[1]) <= distance; } /** * Calculate values for angle at which hyperbola will intersect vertical line of given x * @param x * @return possible angles */ @Override public double[] getVerticalIntersectionParameters(double x) { double[] pt = transformXToOriginal(l); x -= spt[0]; pt[0] += x*e; x /= Math.hypot(pt[0], pt[1]); if (x < -1 || x > 1) { return null; } double t = Math.atan2(pt[1], pt[0]); if (x == -1 || x == 1) { // touching case return sanifyAngles(Math.acos(x) - t); } x = Math.acos(x); return sanifyAngles(x - t, 2 * Math.PI - x - t); } /** * Calculate values for angle at which hyperbola will intersect horizontal line of given y * @param y * @return possible angles */ @Override public double[] getHorizontalIntersectionParameters(double y) { double[] pt = transformXToOriginal(l); y -= spt[1]; pt[1] += y*e; y /= Math.hypot(pt[0], pt[1]); if (y < -1 || y > 1) { return null; } double t = Math.atan2(pt[1], pt[0]); if (y == -1 || y == 1) { // touching case return sanifyAngles(Math.acos(y) - t); } y = Math.asin(y); return sanifyAngles(y - t, Math.PI - y - t); } /** * @param d * @return start angle of positive branch at distance from focus */ @Override public double getStartParameter(double d) { return Math.acos((1 - l / d) / e); } /** * @param d * @return end angle of positive branch at distance from focus */ @Override public double getEndParameter(double d) { return Math.PI * 2 - getStartParameter(d); } @Override public String toString() { return super.toString() + String.format("point=%s, semi-latus=%g, eccentricity=%g, angle=%g", Arrays.toString(spt), l, e, getAngleDegrees()); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); long temp; temp = Double.doubleToLongBits(e); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(l); 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; HyperbolicROI other = (HyperbolicROI) obj; if (Double.doubleToLongBits(e) != Double.doubleToLongBits(other.e)) return false; if (Double.doubleToLongBits(l) != Double.doubleToLongBits(other.l)) return false; return true; } }