Vert.java

package com.github.celldynamics.quimp;

import java.util.Arrays;

import com.github.celldynamics.quimp.geom.ExtendedVector2d;

import ij.IJ;

/**
 * Represents a vertex in the outline. Contains several methods that operate on vertexes and
 * vectors. Properties defined for {@link Vert} object are updated by different QuimP modules (ECMM,
 * QA)
 * 
 * @author rtyson
 * @author p.baniukiewicz
 */
public class Vert extends PointsList<Vert> {
  /**
   * Charge on the vertex.
   */
  public double charge;
  /**
   * distance vert migrated (actually converted to speed by Tool.speedToScale.
   */
  public double distance;
  /**
   * fluorescence channels 1-3.
   */
  public final FluoMeasurement[] fluores = new FluoMeasurement[3];
  /**
   * curvature local to a node. Updated by {@link Vert#setCurvatureLocal()} called during
   * creation and serialization.
   */
  public double curvatureLocal;
  /**
   * Smoothed curvature. Updated during map generation (Q Analysis) by
   * {@link com.github.celldynamics.quimp.plugin.qanalysis.STmap#calcCurvature()} or
   * {@link com.github.celldynamics.quimp.plugin.qanalysis.STmap#averageCurvature(Outline)}
   */
  @SuppressWarnings("javadoc")
  public double curvatureSmoothed;
  /**
   * summed curvature over x microns this is the value recorded into maps. Updated during map
   * generation (Q Analysis) by
   * {@link com.github.celldynamics.quimp.plugin.qanalysis.STmap#calcCurvature()}
   */
  @SuppressWarnings("javadoc")
  public double curvatureSum;
  /**
   * coord relative to head node on current frame. Set during ECMM
   */
  public double coord;
  /**
   * coord relative to coord on previous frame. Set by
   * {@link com.github.celldynamics.quimp.plugin.ecmm.Mapping#migrate()} and during changing
   * resolution
   * in ECMM
   */
  @SuppressWarnings("javadoc")
  public double fCoord;
  /**
   * landing relative to previous frame. Set by
   * {@link com.github.celldynamics.quimp.Vert#setLandingCoord(ExtendedVector2d, Vert)} called on
   * solving ECMM equations
   */
  public double fLandCoord;
  /**
   * global coord relative to head node on frame 1. Set by
   * {@link com.github.celldynamics.quimp.plugin.ecmm.Mapping#migrate()} and during changing
   * resolution
   * in ECMM
   */
  @SuppressWarnings("javadoc")
  public double gCoord;
  /**
   * landing coord relative to head node on frame 1. Set by
   * {@link com.github.celldynamics.quimp.Vert#setLandingCoord(ExtendedVector2d, Vert)} called on
   * solving ECMM equations
   */
  public double gLandCoord;
  /**
   * tarLandingCoord.
   */
  public double tarLandingCoord;
  /**
   * Color of Vert.
   */
  public QColor color;
  /**
   * Vert represents an intersect point and is temporary. Mark start end of sectors.
   */
  private transient boolean intPoint;
  /**
   * The vert has been snapped to an edge.
   */
  public transient boolean snapped;
  /**
   * intsectID.
   */
  public int intsectID;
  /**
   * Internal state of the vert. Possible values:
   * <ul>
   * <li>0 - undetermined
   * <li>1 - forms valid sector
   * <li>2 - LOOSE sector
   * <li>3 - forms inverted sector
   * <li>4 - inverted and loose
   * </ul>
   */
  public int intState;

  /**
   * Default constructor, creates Vert element with ID=1.
   */
  public Vert() {
    super();
    vertInitializer();
  }

  /**
   * Create Vert element with given ID.
   * 
   * @param t ID of Vert
   */
  public Vert(int t) {
    super(t);
    vertInitializer();
  }

  /**
   * Create Vert from {x,y} coordinates.
   * 
   * @param xx x-axis coordinate
   * @param yy y-axis coordinate
   * @param t id of Vert
   */
  public Vert(double xx, double yy, int t) {
    super(xx, yy, t);
    vertInitializer();
  }

  /**
   * Copy constructor. Copy properties of Vert. Previous or next points are not copied
   * 
   * @param src Source Vert
   */
  public Vert(final Vert src) {
    super(src);
    charge = src.charge;
    distance = src.distance;
    for (int i = 0; i < 3; i++) {
      fluores[i] = new FluoMeasurement(src.fluores[i]);
    }
    curvatureLocal = src.curvatureLocal;
    curvatureSmoothed = src.curvatureSmoothed;
    curvatureSum = src.curvatureSum;
    coord = src.coord;
    fCoord = src.fCoord;
    fLandCoord = src.fLandCoord;
    gCoord = src.gCoord;
    gLandCoord = src.gLandCoord;
    tarLandingCoord = src.tarLandingCoord;
    color = new QColor(src.color);
    intPoint = src.intPoint;
    snapped = src.snapped;
    intsectID = src.intsectID;
    intState = src.intState;
  }

  /**
   * Conversion constructor.
   * 
   * @param src Node to convert to Vert
   */
  public Vert(final Node src) {
    super(src);
    vertInitializer(); // default params that are not passed from Vert
  }

  /**
   * Initializers for Vert object.
   */
  private void vertInitializer() {
    intPoint = false;
    intState = 0;
    fCoord = -1;
    gCoord = -1;
    color = new QColor(1, 0, 0);
    for (int i = 0; i < 3; i++) {
      fluores[i] = new FluoMeasurement(-2, -2, -2);
    }
  }

  /**
   * (non-Javadoc)
   * 
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    long temp;
    temp = Double.doubleToLongBits(charge);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + ((color == null) ? 0 : color.hashCode());
    temp = Double.doubleToLongBits(coord);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(curvatureLocal);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(curvatureSmoothed);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(curvatureSum);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(distance);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(fCoord);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(fLandCoord);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + Arrays.hashCode(fluores);
    temp = Double.doubleToLongBits(gCoord);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(gLandCoord);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + (intPoint ? 1231 : 1237);
    result = prime * result + intState;
    result = prime * result + intsectID;
    result = prime * result + (snapped ? 1231 : 1237);
    temp = Double.doubleToLongBits(tarLandingCoord);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    return result;
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (!super.equals(obj)) {
      return false;
    }
    if (!(obj instanceof Vert)) {
      return false;
    }
    Vert other = (Vert) obj;
    if (Double.doubleToLongBits(charge) != Double.doubleToLongBits(other.charge)) {
      return false;
    }
    if (color == null) {
      if (other.color != null) {
        return false;
      }
    } else if (!color.equals(other.color)) {
      return false;
    }
    if (Double.doubleToLongBits(coord) != Double.doubleToLongBits(other.coord)) {
      return false;
    }
    if (Double.doubleToLongBits(curvatureLocal) != Double.doubleToLongBits(other.curvatureLocal)) {
      return false;
    }
    if (Double.doubleToLongBits(curvatureSmoothed) != Double
            .doubleToLongBits(other.curvatureSmoothed)) {
      return false;
    }
    if (Double.doubleToLongBits(curvatureSum) != Double.doubleToLongBits(other.curvatureSum)) {
      return false;
    }
    if (Double.doubleToLongBits(distance) != Double.doubleToLongBits(other.distance)) {
      return false;
    }
    if (Double.doubleToLongBits(fCoord) != Double.doubleToLongBits(other.fCoord)) {
      return false;
    }
    if (Double.doubleToLongBits(fLandCoord) != Double.doubleToLongBits(other.fLandCoord)) {
      return false;
    }
    if (!Arrays.equals(fluores, other.fluores)) {
      return false;
    }
    if (Double.doubleToLongBits(gCoord) != Double.doubleToLongBits(other.gCoord)) {
      return false;
    }
    if (Double.doubleToLongBits(gLandCoord) != Double.doubleToLongBits(other.gLandCoord)) {
      return false;
    }
    if (intPoint != other.intPoint) {
      return false;
    }
    if (intState != other.intState) {
      return false;
    }
    if (intsectID != other.intsectID) {
      return false;
    }
    if (snapped != other.snapped) {
      return false;
    }
    if (Double.doubleToLongBits(tarLandingCoord) != Double
            .doubleToLongBits(other.tarLandingCoord)) {
      return false;
    }
    return true;
  }

  /**
   * Old part of code.
   * 
   * @param s s
   * @deprecated This is old part of code
   */
  public void print(String s) {
    System.out.print(s + "vert: " + tracknumber + ", x:" + getX() + ", y:" + getY() + ", coord: "
            + coord + ", fCoord: " + fCoord + ", gCoord: " + gCoord);
    if (intPoint) {
      System.out.print(", isIntPoint (" + intsectID + ")");
    }
    if (head) {
      System.out.print(", Head");
    }
    System.out.println("");
  }

  /**
   * isIntPoint.
   * 
   * @return intPoint
   */
  public boolean isIntPoint() {
    return intPoint;
  }

  /**
   * setIntPoint.
   * 
   * @param t t
   * @param i i
   */
  public void setIntPoint(boolean t, int i) {
    intPoint = t;
    intsectID = i;
    tracknumber = -1;
  }

  /**
   * setLandingCoord.
   * 
   * @param p p
   * @param edge edge
   */
  public void setLandingCoord(ExtendedVector2d p, Vert edge) {
    Vert edge2 = edge.getNext(); // 'edge' is the 1st vert of an edge

    while (edge.isIntPoint()) {
      edge = edge.getPrev();
    }
    while (edge2.isIntPoint()) {
      edge2 = edge2.getNext();
    }

    // relative position of landing
    double d1 = ExtendedVector2d.lengthP2P(edge.point, edge2.point);
    double d2 = ExtendedVector2d.lengthP2P(edge.point, p);
    double prop = d2 / d1;

    gLandCoord = calcLanding(edge.gCoord, edge2.gCoord, prop);
    fLandCoord = calcLanding(edge.coord, edge2.coord, prop);

    if (gLandCoord >= 1 || gLandCoord < 0 || fLandCoord >= 1 || fLandCoord < 0) {
      System.out.println("Vert253:setLandingCoord-Error in landing coord\n\t" + "gLandCoord= "
              + gLandCoord + ", fLandCoord = " + fLandCoord);

    }

    /*
     * double d1 = Vec2d.lengthP2P(edge.point, edge2.point); double d2 =
     * Vec2d.lengthP2P(edge.point, p); double prop = d2 / d1;
     * 
     * double edge2coord = edge2.coord; // if wrapping around if (edge2.coord < edge.coord) {
     * edge2coord += 1; }
     * 
     * double coordDist = edge2coord - edge.coord; landingCoor = edge.coord + (prop *
     * coordDist);
     * 
     * if (landingCoor >= 1) { //if wrapped around and passed 1 landingCoor -= 1; }
     * 
     * if (landingCoor < 0 || landingCoor > 1 || Double.isNaN(landingCoor)) {
     * IJ.write("ERROR 1: NAN or < landing coord ("+IJ.d2s(landingCoor)+ ")< 0; e1=" +
     * edge.coord + ", e2=" + edge2.coord + ", prop" + prop); }
     */
  }

  private double calcLanding(double coordA, double coordB, double prop) {
    double landing;

    if (coordB < coordA) {
      coordB += 1;
    }

    double coordDist = coordB - coordA;
    landing = coordA + (prop * coordDist);

    if (landing >= 1) { // if wrapped around and passed 1
      landing -= 1;
    }

    if (landing < 0 || landing >= 1 || Double.isNaN(landing)) {
      System.out.println(
              "Vert295:calcLanding ERROR in landing coord:\n\t" + "landing:" + IJ.d2s(landing)
                      + ", coordA=" + coordA + ", coorB=" + coordB + ", proportion=" + prop);
    }
    return landing;
  }

  /**
   * Calculate and set local field {@link #curvatureLocal}.
   */
  public void setCurvatureLocal() {
    curvatureLocal = getCurvatureLocal();
  }

  /**
   * setFluoresChannel.
   * 
   * @param m Fluoro measurements
   * @param channel channel to set
   */
  public void setFluoresChannel(FluoMeasurement m, int channel) {
    fluores[channel].intensity = m.intensity;
    fluores[channel].x = m.x;
    fluores[channel].y = m.y;
  }

  /**
   * setFluoresChannel.
   * 
   * @param x coord
   * @param y coord
   * @param i fluoro intensity
   * @param channel channel
   */
  public void setFluoresChannel(int x, int y, int i, int channel) {
    fluores[channel].intensity = i;
    fluores[channel].x = x;
    fluores[channel].y = y;
  }

  /**
   * setFluores.
   * 
   * @param m all channels measurements
   */
  public void setFluores(FluoMeasurement[] m) {

    for (int i = 0; i < 3; i++) {
      fluores[i].intensity = m[i].intensity;
      fluores[i].x = m[i].x;
      fluores[i].y = m[i].y;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "Vert [charge=" + charge + ", distance=" + distance + ", fluores="
            + Arrays.toString(fluores) + ", curvatureLocal=" + curvatureLocal
            + ", curvatureSmoothed=" + curvatureSmoothed + ", curvatureSum=" + curvatureSum
            + ", coord=" + coord + ", fCoord=" + fCoord + ", fLandCoord=" + fLandCoord + ", gCoord="
            + gCoord + ", gLandCoord=" + gLandCoord + ", tarLandingCoord=" + tarLandingCoord
            + ", color=" + color + ", intsectID=" + intsectID + ", intState=" + intState
            + ", toString()=" + super.toString() + "]";
  }

  /**
   * Dis coord 2 coord.
   *
   * @param a the a
   * @param b the b
   * @return the double
   */
  static double disCoord2Coord(double a, double b) {
    if (a < b) {
      return b - a;
    }
    if (b < a) {
      return (1 - a) + b;
    } else {
      return 0;
    }
  }

  /**
   * Adds the coords.
   *
   * @param a the a
   * @param b the b
   * @return the double
   */
  static double addCoords(double a, double b) {
    double r = a + b;
    if (r >= 1) {
      r = r - 1;
    }
    return r;
  }

}