OutlineHandler.java

package com.github.celldynamics.quimp;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.InvalidParameterException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.celldynamics.quimp.filesystem.FileExtensions;
import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
import com.github.celldynamics.quimp.geom.ExtendedVector2d;
import com.github.celldynamics.quimp.utils.QuimpToolsCollection;

import ij.IJ;

/**
 * Collection of outlines for subsequent frames (<it>f1</it> and <it>f2</it>) for one cell.
 * 
 * @author tyson
 * @author p.baniukiewicz
 */
public class OutlineHandler extends ShapeHandler<Outline> implements IQuimpSerialize {

  /**
   * The Constant LOGGER.
   */
  static final Logger LOGGER = LoggerFactory.getLogger(OutlineHandler.class.getName());
  /**
   * Array of given cell outlines found for frames (<tt>startFrame</tt> and <tt>endFrame</tt>).
   */
  private Outline[] outlines;
  private transient QParams qp;
  // all transient fields are rebuild in afterSerialzie findStatLimits()
  private transient int size;

  /**
   * The max coor.
   */
  public transient ExtendedVector2d maxCoor;

  /**
   * The min coor.
   */
  public transient ExtendedVector2d minCoor;

  /**
   * The mig limits.
   */
  // min and max limits
  public transient double[] migLimits;

  /**
   * The flu lims.
   */
  public transient double[][] fluLims;

  /**
   * The curv limits.
   */
  public transient double[] curvLimits;
  /**
   * longest outline in outlines.
   */
  public transient double maxLength = 0;

  /**
   * The read success.
   */
  public transient boolean readSuccess;

  /**
   * Instantiates a new outline handler.
   *
   * @param params the params
   */
  public OutlineHandler(QParams params) {
    qp = params;
    startFrame = qp.getStartFrame();
    endFrame = qp.getEndFrame();

    // System.out.println("start frame: " + startFrame + ", endframe: " +
    // endFrame);

    if (!readOutlines(qp.getSnakeQP())) { // initialize also arrays by findStatsLimits()
      IJ.error("Failed to read in snakQP (OutlineHandler:36)");
      readSuccess = false;
      size = 0;
    } else {
      size = outlines.length;
      readSuccess = true;
    }
  }

  /**
   * Copy constructor.
   * 
   * @param src to copy from
   */
  public OutlineHandler(final OutlineHandler src) {
    super(src);
    this.outlines = new Outline[src.outlines.length];
    for (int o = 0; o < this.outlines.length; o++) {
      this.outlines[o] = new Outline(src.outlines[o]);
    }
    size = src.size;
    /*
     * // this is calculated by findStatLimits() maxCoor = src.maxCoor; minCoor = src.minCoor;
     * migLimits = new double[src.migLimits.length]; System.arraycopy(src.migLimits, 0,
     * migLimits, 0, src.migLimits.length); fluLims = new double[src.fluLims.length][]; for (int
     * i = 0; i < src.fluLims.length; i++) { fluLims[i] = new double[src.fluLims[i].length];
     * System.arraycopy(src.fluLims[i], 0, fluLims[i], 0, src.fluLims[i].length); } curvLimits =
     * new double[src.curvLimits.length]; System.arraycopy(src.curvLimits, 0, curvLimits, 0,
     * src.curvLimits.length);
     */
    for (Outline o : outlines) {
      if (o.getLength() > maxLength) {
        maxLength = o.getLength();
      }
    }
    findStatLimits(); // fill maxCoor, minCoor, migLimits, fluLims, curvLimits
  }

  /**
   * Conversion constructor.
   * 
   * <p>Converts SnakeHandler to OutlineHandler. Converted are only Snakes and their range
   * 
   * @param snake source SnakeHandler
   */
  public OutlineHandler(final SnakeHandler snake) {
    this(snake.startFrame, snake.endFrame); // create array and set ranges
    for (int f = startFrame; f <= endFrame; f++) { // copy all snakes
      Snake s = snake.getStoredSnake(f); // get original
      if (s != null) {
        setOutline(f, new Outline(s)); // convert to Outline
      }
    }
    findStatLimits();
  }

  /**
   * Instantiates a new outline handler.
   *
   * @param s start frame
   * @param e end frame
   */
  public OutlineHandler(int s, int e) {
    size = e - s + 1;
    outlines = new Outline[size];
    startFrame = s;
    endFrame = e;
  }

  /**
   * Default constructor. Initialises one frame.
   */
  public OutlineHandler() {
    this(0, 0);
  }

  /**
   * Gets the start frame.
   *
   * @return the start frame
   */
  @Override
  public int getStartFrame() {
    return startFrame;
  }

  /**
   * Gets the end frame.
   *
   * @return the end frame
   */
  @Override
  public int getEndFrame() {
    return endFrame;
  }

  /**
   * Return Outline stored for frame f.
   *
   * @param f the frame
   * @return the outline or null
   */
  public Outline getStoredOutline(int f) {
    if (f - startFrame < 0 || f - startFrame > outlines.length) {
      LOGGER.info("Tried to access negative frame store: frame: " + f);
      return null;
    }
    return outlines[f - startFrame];
  }

  /**
   * Checks if is outline at.
   *
   * @param f the f
   * @return true, if is outline at
   */
  public boolean isOutlineAt(int f) {
    if (f - startFrame < 0) {
      return false;
    } else if (f - startFrame >= outlines.length) {
      return false;
    } else if (outlines[f - startFrame] == null) {
      return false;
    } else {
      return true;
    }
  }

  /**
   * Index get outline.
   *
   * @param i the i
   * @return the outline
   */
  public Outline indexGetOutline(int i) {
    return outlines[i];
  }

  /**
   * Sets the outline.
   *
   * @param f the f
   * @param o the o
   */
  public void setOutline(int f, Outline o) {
    outlines[f - startFrame] = o;
    double length = o.getLength();
    if (length > maxLength) {
      maxLength = length;
    }

  }

  private boolean readOutlines(final File f) {
    if (!f.exists()) {
      IJ.error("Cannot locate snake file (" + FileExtensions.snakeFileExt + ")\n'"
              + f.getAbsolutePath() + "'");
      return false;
    }
    if (qp == null) {
      throw new InvalidParameterException(
              "QParams is null. This object has not been created (loaded) from QParams data");
    }

    String thisLine;

    maxLength = 0;
    // maxFlu = 0.;
    int nn;
    int index;
    double length;
    Vert head;
    Vert n;
    Vert prevn;
    size = 0;

    try {
      // first count the outlines
      BufferedReader br = new BufferedReader(new FileReader(f));
      while ((thisLine = br.readLine()) != null) {
        if (thisLine.startsWith("#")) {
          continue;
        }
        nn = (int) QuimpToolsCollection.s2d(thisLine);
        for (int i = 0; i < nn; i++) {
          // System.out.println(br.readLine() + ", " + );
          br.readLine();
          // br.readLine();
        }
        size++;
      }
      br.close();
      // IJ.write("num outlines " + size);
      outlines = new Outline[size];

      int s = 0;
      // read outlines into memory
      br = new BufferedReader(new FileReader(f));

      while ((thisLine = br.readLine()) != null) { // while loop begins here
        // System.out.println(thisLine);
        if (thisLine.startsWith("#")) {
          continue; // skip comments
        }

        index = 0;
        head = new Vert(index); // dummy head node
        head.setHead(true);
        prevn = head;
        index++;

        nn = (int) QuimpToolsCollection.s2d(thisLine);

        for (int i = 0; i < nn; i++) {
          thisLine = br.readLine();
          String[] split = thisLine.split("\t");
          n = new Vert(index);

          n.coord = QuimpToolsCollection.s2d(split[0]);
          n.setX(QuimpToolsCollection.s2d(split[1]));
          n.setY(QuimpToolsCollection.s2d(split[2]));

          n.fCoord = QuimpToolsCollection.s2d(split[3]); // Origon
          n.gCoord = QuimpToolsCollection.s2d(split[4]); // G-Origon
          n.distance = QuimpToolsCollection.s2d(split[5]); // speed

          // store flu measurements
          n.fluores[0].intensity = QuimpToolsCollection.s2d(split[6]);

          if (qp.paramFormat == QParams.QUIMP_11) {
            // has other channels and x and y
            n.fluores[0].x = QuimpToolsCollection.s2d(split[7]);
            n.fluores[0].y = QuimpToolsCollection.s2d(split[8]);

            n.fluores[1].intensity = QuimpToolsCollection.s2d(split[9]);
            n.fluores[1].x = QuimpToolsCollection.s2d(split[10]);
            n.fluores[1].y = QuimpToolsCollection.s2d(split[11]);

            n.fluores[2].intensity = QuimpToolsCollection.s2d(split[12]);
            n.fluores[2].x = QuimpToolsCollection.s2d(split[13]);
            n.fluores[2].y = QuimpToolsCollection.s2d(split[14]);
          }

          n.unfreeze();
          index++;
          prevn.setNext(n);
          n.setPrev(prevn);
          prevn = n;

        }
        // link tail to head
        prevn.setNext(head);
        head.setPrev(prevn);

        // head is dummy element that will be removed. To deal with random selection of new head we
        // remember next element to it (which is first element from snQP)
        Vert newHead = head.getNext(); // this will be new head

        Outline tmp = new Outline(head, nn + 1); // dont forget the head node
        // WARN potential incompatibility with old code. see
        // 3784b9f1afb1dd317bd4740e17f02627fa89bc41 for original Outline and OutlineHandler
        tmp.removeVert(head); // new head is randomly selected
        tmp.setHead(newHead); // be sure to set head to first node on snQP list.

        outlines[s] = tmp;
        outlines[s].updateNormals(true);
        outlines[s].makeAntiClockwise();
        outlines[s].coordReset(); // there is no cord data in snQP file this set it as Position.
        length = outlines[s].getLength();
        if (length > maxLength) {
          maxLength = length;
        }
        s++;
        LOGGER.trace("Outline: " + s + " head =[" + outlines[s - 1].getHead().getX() + ","
                + outlines[s - 1].getHead().getY() + "]");
      } // end while
      br.close();

      if (qp.paramFormat == QParams.OLD_QUIMP) {
        qp.setStartFrame(1);
        qp.setEndFrame(size);
        this.endFrame = size;
        this.startFrame = 1;
        qp.writeParams(); // replace the old format parameter file
      }
      this.findStatLimits();

      return true;
    } catch (IOException e) {
      LOGGER.debug(e.getMessage(), e);
      LOGGER.error("Could not read outlines", e.getMessage());
      return false;
    } catch (NullPointerException e1) {
      LOGGER.debug(e1.getMessage(), e1);
      LOGGER.error("Damaged snQP file", e1.getMessage());
      return false;
    }
  }

  /**
   * Evaluate <tt>maxCoor</tt>, <tt>minCoor</tt>, <tt>migLimits</tt>, <tt>fluLims</tt>,
   * <tt>curvLimits</tt>.
   * 
   * <p>Initialise arrays as well
   */
  private void findStatLimits() {
    maxCoor = new ExtendedVector2d();
    minCoor = new ExtendedVector2d();
    fluLims = new double[3][2];
    migLimits = new double[2];
    // convLimits = new double[2];
    curvLimits = new double[2]; // not filled until Q_Analsis run. smoothed curvature

    // cycle through all frames and find the min and max for all data
    // store min and max coor\migration\flu for plotting
    Outline outline;
    Vert n;
    for (int i = 0; i < outlines.length; i++) {
      outline = outlines[i];
      if (outline == null) {
        continue;
      }
      n = outline.getHead();
      if (i == 0) {
        minCoor.setXY(n.getX(), n.getY());
        maxCoor.setXY(n.getX(), n.getY());
        migLimits[0] = n.distance;
        migLimits[1] = n.distance;
        // convLimits[0] = n.convexity;
        // convLimits[1] = n.convexity;
        for (int j = 0; j < n.fluores.length; j++) {
          fluLims[j][0] = n.fluores[j].intensity;
          fluLims[j][1] = n.fluores[j].intensity;
        }
      }

      do {
        if (n.getX() > maxCoor.getX()) {
          maxCoor.setX(n.getX());
        }
        if (n.getY() > maxCoor.getY()) {
          maxCoor.setY(n.getY());
        }
        if (n.getX() < minCoor.getX()) {
          minCoor.setX(n.getX());
        }
        if (n.getY() < minCoor.getY()) {
          minCoor.setY(n.getY());
        }

        if (n.distance < migLimits[0]) {
          migLimits[0] = n.distance;
        }
        if (n.distance > migLimits[1]) {
          migLimits[1] = n.distance;
        }

        // if(n.convexity < convLimits[0]) convLimits[0] = n.convexity;
        // if(n.convexity > convLimits[1]) convLimits[1] = n.convexity;

        for (int j = 0; j < n.fluores.length; j++) {
          if (n.fluores[j].intensity < fluLims[j][0]) {
            fluLims[j][0] = n.fluores[j].intensity;
          }
          if (n.fluores[j].intensity > fluLims[j][1]) {
            fluLims[j][1] = n.fluores[j].intensity;
          }
        }

        n = n.getNext();
      } while (!n.isHead());
      // see com.github.celldynamics.quimp.plugin.qanalysis.STmap.calcCurvature()
      Vert v;
      for (int f = getStartFrame(); f <= getEndFrame(); f++) {
        Outline o = getStoredOutline(f);
        if (o == null) {
          continue;
        }
        v = o.getHead();

        // find min and max of sum curvature
        v = o.getHead();
        if (f == getStartFrame()) {
          curvLimits[1] = v.curvatureSum;
          curvLimits[0] = v.curvatureSum;
        }
        do {
          if (v.curvatureSum > curvLimits[1]) {
            curvLimits[1] = v.curvatureSum;
          }
          if (v.curvatureSum < curvLimits[0]) {
            curvLimits[0] = v.curvatureSum;
          }
          v = v.getNext();
        } while (!v.isHead());
      }
    }

    // Set limits to equal positive and negative
    migLimits = QuimpToolsCollection.setLimitsEqual(migLimits);
    curvLimits = QuimpToolsCollection.setLimitsEqual(curvLimits);
  }

  /**
   * Gets the size.
   *
   * @return the size
   */
  public int getSize() {
    return size;
  }

  /**
   * Copy Outline into internal outlines array on correct position.
   * 
   * @param o Outline to copy.
   * @param frame Frame where copy Outline to.
   */
  public void save(Outline o, int frame) {
    outlines[frame - startFrame] = new Outline(o);
  }

  /**
   * Write <b>this</b> outline to disk.
   * 
   * @param outFile file to save
   * @param isEccmRun was ECMM run?
   */
  public void writeOutlines(File outFile, boolean isEccmRun) {
    LOGGER.debug("Write outline at: " + outFile);
    try {
      PrintWriter pw = new PrintWriter(new FileWriter(outFile), true); // auto flush
      pw.write("#QuimP11 node data");
      if (isEccmRun) {
        pw.print("-ECMM");
      }
      pw.write("\n#Node Position\tX-coord\tY-coord\tOrigin\tG-Origin\tSpeed");
      pw.write("\tFluor_Ch1\tCh1_x\tCh1_y\tFluor_Ch2\tCh2_x\tCh2_y\tFluor_CH3\tCH3_x\tCh3_y\n#");

      Outline o;
      for (int i = startFrame; i <= endFrame; i++) {
        o = getStoredOutline(i);
        pw.write("\n#Frame " + i);
        write(pw, o.getNumPoints(), o.getHead());
      }
      pw.close();
    } catch (Exception e) {
      IJ.log("could not open out file " + outFile.getAbsolutePath());
      return;
    }
  }

  private static void write(PrintWriter pw, int verts, Vert v) {
    pw.print("\n" + verts);
    //!>
    do {
      pw.print("\n" + IJ.d2s(v.coord, 6) + "\t" // Perimeter coord
              + IJ.d2s(v.getX(), 2) + "\t" // X coord
              + IJ.d2s(v.getY(), 2) + "\t" // Y coord
              + IJ.d2s(v.fCoord, 6) + "\t" // Origin
              + IJ.d2s(v.gCoord, 6) + "\t" // G-Origin
              + IJ.d2s(v.distance, 6) + "\t" // Speed
              + IJ.d2s(v.fluores[0].intensity, 6) + "\t" // Fluor_Ch1
              + IJ.d2s(v.fluores[0].x, 0) + "\t" // Ch1_x
              + IJ.d2s(v.fluores[0].y, 0) + "\t" // Ch1_y
              + IJ.d2s(v.fluores[1].intensity, 6) + "\t" // Fluor_Ch2
              + IJ.d2s(v.fluores[1].x, 0) + "\t" // Ch2_x
              + IJ.d2s(v.fluores[1].y, 0) + "\t" // Ch2_y
              + IJ.d2s(v.fluores[2].intensity, 6) + "\t" // Fluor_CH3
              + IJ.d2s(v.fluores[2].x, 0) + "\t" // CH3_x
              + IJ.d2s(v.fluores[2].y, 0)); // CH3_y
      //!<
      v = v.getNext();
    } while (!v.isHead());
  }

  /**
   * Prepare all Outline stored in this OutlineHandler for loading.
   */
  @Override
  public void beforeSerialize() {
    for (Outline o : outlines) {
      if (o != null) {
        o.beforeSerialize(); // convert outlines to array
      }
    }
  }

  /**
   * Call afterSerialzie() for other objects and restore transient fields where possible.
   */
  @Override
  public void afterSerialize() throws Exception {
    for (Outline o : outlines) {
      if (o != null) {
        o.afterSerialize(); // convert array to outlines
      }
    }
    // restore other fields
    size = outlines.length;
    for (Outline o : outlines) {
      if (o.getLength() > maxLength) {
        maxLength = o.getLength();
      }
    }
    findStatLimits(); // fill maxCoor, minCoor, migLimits, fluLims, curvLimits

  }
}