/*- ******************************************************************************* * 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 parabolic region of interest with the start point as the focus. In the rotated frame, * it can be represented as x-p = 4 a y^2 where p = 2 a */ public class ParabolicROI extends OrientableROIBase implements IParametricROI, Serializable { private double tp; // twice focal parameter (or latus rectum) /** * No argument constructor need for serialization */ public ParabolicROI() { this(2, 0, 0, 0); } /** * Create a parabolic ROI * @param focal length * @param ptx centre point x value * @param pty centre point y value */ public ParabolicROI(double focal, double ptx, double pty) { this(focal, 0, ptx, pty); } /** * Create a parabolic ROI * @param focal length * @param angle major axis angle * @param ptx centre point x value * @param pty centre point y value */ public ParabolicROI(double focal, double angle, double ptx, double pty) { spt = new double[] { ptx, pty }; setFocalParameter(focal); ang = angle; checkAngle(); } @Override public void downsample(double subFactor) { super.downsample(subFactor); tp /= subFactor; } @Override public ParabolicROI copy() { ParabolicROI c = new ParabolicROI(0.5*tp, ang, spt[0], spt[1]); c.name = name; c.plot = plot; return c; } /** * @return Returns focal parameter */ public double getFocalParameter() { return 0.5*tp; } /** * Set focal parameter * @param focal */ public void setFocalParameter(double focal) { tp = 2*focal; setDirty(); } /** * Get point on parabola 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 parabola at given angle relative to focus * @param angle in radians * @return point */ public double[] getRelativePoint(double angle) { double cb = Math.cos(angle); if (cb == 1) { 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; } double sb = Math.sin(angle); double r = tp / (1 - cb); return transformToOriginal(r * cb, r * sb); } /** * Get point on parabola 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 parabola 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 parabola is unbounded */ @Override public RectangularROI getBounds() { return null; } /** * Determine if point is on or inside ellipse * @param x * @param y * @return true if ellipse 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); return Math.abs(pt[0] * pt[0] - tp * pt[1]) <= distance; } /** * Calculate values for angle at which parabola will intersect vertical line of given x * @param x * @return possible angles */ @Override public double[] getVerticalIntersectionParameters(double x) { double[] pt = transformXToOriginal(tp); x -= spt[0]; pt[0] += x; 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 parabola will intersect horizontal line of given y * @param y * @return possible angles */ @Override public double[] getHorizontalIntersectionParameters(double y) { double[] pt = transformXToOriginal(tp); y -= spt[1]; pt[1] += y; 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 at distance from focus */ @Override public double getStartParameter(double d) { return Math.acos(1 - tp/d); } /** * @param d * @return end angle 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, focal=%g, angle=%g", Arrays.toString(spt), getFocalParameter(), getAngleDegrees()); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); long temp; temp = Double.doubleToLongBits(tp); 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; ParabolicROI other = (ParabolicROI) obj; if (Double.doubleToLongBits(tp) != Double.doubleToLongBits(other.tp)) return false; return true; } }