CellStatsEval.java

package com.github.celldynamics.quimp;

import java.awt.Polygon;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

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

import ij.IJ;
import ij.ImagePlus;
import ij.gui.PolygonRoi;
import ij.gui.Roi;
import ij.measure.Measurements;
import ij.process.ImageProcessor;
import ij.process.ImageStatistics;

/**
 * Calculate statistics for whole stack (all cells).
 * 
 * <p>Stats are written on disk after calling constructor. Additionally there is separate list
 * maintained with the same data. They can be collected calling {@link #getStatH()}. This is due to
 * compatibility with old QuimP.
 * 
 * @author tyson
 * @author p.baniukiewicz
 */
public class CellStatsEval implements Measurements {
  /**
   * Hold all stats for cell. The same data are written to disk as csv file.
   */
  private CellStats statH;

  /**
   * The output H.
   */
  OutlineHandler outputH;

  /**
   * The outfile.
   */
  File outfile;

  /**
   * The i plus.
   */
  ImagePlus iplus;

  /**
   * The i proc.
   */
  ImageProcessor iproc;

  /**
   * The is.
   */
  ImageStatistics is;

  /**
   * The scale used for evaluation of {@link FrameStatistics}.
   * 
   * @see #record()
   */
  double scale;

  /**
   * The frame interval used for evaluation of {@link FrameStatistics}.
   * 
   * @see #record()
   */
  double frameInterval;

  /**
   * Create and run the object.
   * 
   * <p>After creating the object, file with stats is written and stats are available by calling
   * {@link #getStatH()} method.
   * 
   * @param oh OutlineHandler
   * @param ip image associated with OutlineHandler
   * @param f file name to write stats, if null file is not created
   * @param s image scale
   * @param fi frame interval
   */
  public CellStatsEval(OutlineHandler oh, ImagePlus ip, File f, double s, double fi) {
    IJ.showStatus("BOA-Calculating Cell stats");
    outputH = oh;
    outfile = f;
    iplus = ip;
    iproc = ip.getProcessor();
    scale = s;
    frameInterval = fi;

    FrameStatistics[] stats = record();
    iplus.setSlice(1);
    iplus.killRoi();
    if (f == null) { // do not write stQP file
      buildData(stats);
    } else {
      try {
        buildData(stats);
        FrameStatistics.write(stats, outfile, s, fi);
      } catch (IOException e) {
        IJ.error("could not open out file");
      }
    }
  }

  /**
   * Only create the object. Stats file is not created but results are available by calling
   * {@link #getStatH()} method.
   * 
   * @param oh OutlineHandler
   * @param ip image associated with OutlineHandler
   * @param s image scale
   * @param fi frame interval
   */
  public CellStatsEval(OutlineHandler oh, ImagePlus ip, double s, double fi) {
    this(oh, ip, null, s, fi);
  }

  /**
   * Calculate stats.
   * 
   * <p><b>Warning</b>
   * 
   * <p>Number of calculated stats must be reflected in {@link #buildData(FrameStatistics[])}.
   * 
   * <p>Scales stored in processed image are used. Even if {@link BOAState.BOAp} stores scale of
   * image, it is used scale from tiff (they are the same as user scale is copied to image in
   * initialisation stage).
   * 
   * @return Array with stats for every frame for one cell. Array is for compatibility reasons. New
   *         format uses List of objects.
   * @see #getStatH()
   */
  private FrameStatistics[] record() {
    // ImageStack orgStack = orgIpl.getStack();
    FrameStatistics[] stats = new FrameStatistics[outputH.getSize()];

    double distance = 0;
    Outline o;
    PolygonRoi roi;
    int store;

    for (int f = outputH.getStartFrame(); f <= outputH.getEndFrame(); f++) {
      IJ.showProgress(f, outputH.getEndFrame());
      store = f - outputH.getStartFrame();

      o = outputH.getStoredOutline(f);
      iplus.setSlice(f); // also updates the processor
      stats[store] = new FrameStatistics();

      Polygon opoly = o.asPolygon();
      roi = new PolygonRoi(opoly, Roi.POLYGON);

      iplus.setRoi(roi);
      is = iplus.getStatistics(AREA + CENTROID + ELLIPSE + SHAPE_DESCRIPTORS); // this does scale to
      // image

      // all theses already to scale
      stats[store].frame = f;
      stats[store].area = is.area;
      stats[store].centroid.setX(is.xCentroid);
      stats[store].centroid.setY(is.yCentroid);

      stats[store].elongation = is.major / is.minor; // include both axis plus elongation
      stats[store].perimiter = roi.getLength(); // o.getLength();
      stats[store].circularity =
              4 * Math.PI * (stats[store].area / (stats[store].perimiter * stats[store].perimiter));
      stats[store].displacement =
              ExtendedVector2d.lengthP2P(stats[0].centroid, stats[store].centroid);

      if (store != 0) {
        stats[store].speed =
                ExtendedVector2d.lengthP2P(stats[store - 1].centroid, stats[store].centroid);
        distance += ExtendedVector2d.lengthP2P(stats[store - 1].centroid, stats[store].centroid);
        stats[store].dist = distance;
      } else {
        stats[store].dist = 0;
        stats[store].speed = 0;
      }

      if (distance != 0) {
        stats[store].persistance = stats[store].displacement / distance;
      } else {
        stats[store].persistance = 0;
      }

    }

    // convert centroid to pixels
    for (int f = outputH.getStartFrame(); f <= outputH.getEndFrame(); f++) {
      store = f - outputH.getStartFrame();
      stats[store].centroidToPixels(scale);
    }

    return stats;
  }

  /**
   * Complementary to write method. Create the same data as write but in form of arrays. For
   * compatible reasons. New format uses this representation.
   * 
   * @param s Frame statistics calculated by
   *        {@link com.github.celldynamics.quimp.CellStatsEval#record()}
   */
  private void buildData(FrameStatistics[] s) {
    statH = new CellStats(new ArrayList<FrameStatistics>(Arrays.asList(s)));
  }

  /**
   * Return statistics in new format for one cell along all frames it appears in.
   * 
   * <p>This is roughly the same as FrameStatistics[] computed by this class. New format is included
   * in QCONF file through {@link StatsCollection}
   * 
   * @return the statH
   */
  public CellStats getStatH() {
    return statH;
  }

}