ProtStat.java

package com.github.celldynamics.quimp.plugin.protanalysis;

import java.awt.Polygon;
import java.awt.geom.Point2D;
import java.io.PrintWriter;
import java.util.Iterator;

import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.celldynamics.quimp.CellStats;
import com.github.celldynamics.quimp.FrameStatistics;
import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
import com.github.celldynamics.quimp.plugin.protanalysis.Track.TrackType;
import com.github.celldynamics.quimp.plugin.qanalysis.STmap;
import com.github.celldynamics.quimp.utils.QuimPArrayUtils;

import ij.measure.ResultsTable;

/*
 * !>
 * @startuml doc-files/ProtStat_1_UML.png 
 * title Class dependency and most important methods. IQuimpSerialize <|.. ProtStat
 * ProtStat *-- "1" CellStatistics
 * ProtStat *-- "1" ProtrusionStatistics
 * class ProtStat {
 * +cellStatistics : CellStatistics
 * +protStatistics : ProtrusionStatistics
 * +void writeCell()
 * +void writeProtrusion()
 * }
 * class CellStatistics {
 * -void writeCellHeader()
 * -void writeCellRecord()
 * }
 * class ProtrusionStatistics {
 * -void writeProtHeader()
 * -void writeProtRecord()
 * }
 * @enduml
 * !<
 */
/**
 * Compute statistics for one cell.
 * 
 * <br>
 * <img src="doc-files/ProtStat_1_UML.png"/><br>
 * 
 * @author p.baniukiewicz
 *
 */
public class ProtStat implements IQuimpSerialize {

  /**
   * The Constant LOGGER.
   */
  static final Logger LOGGER = LoggerFactory.getLogger(ProtStat.class.getName());
  /**
   * Frame window used for computation. Should be uneven.
   */
  private final int framewindow = 3;

  private CellStats cs;
  private TrackCollection tc;
  private MaximaFinder mf;
  private STmap maps;
  /**
   * Number of frames.
   */
  private int frames;
  /**
   * Map resolution.
   */
  private int mapRes;

  /**
   * Total number of protrusions.
   */
  private int protCount;

  /**
   * Hold cell shape based statistics.
   */
  public CellStatistics cellStatistics;
  /**
   * Hold protrusion statistics.
   */
  public ProtrusionStatistics protStatistics;

  /**
   * Construct the object using various data containers.
   * 
   * @param mf MaximaFinder
   * @param tc TrackCollection
   * @param cs CellStats
   * @param maps STmap
   */
  public ProtStat(MaximaFinder mf, TrackCollection tc, CellStats cs, STmap maps) {
    this.mf = mf;
    this.tc = tc;
    this.cs = cs;
    this.maps = maps;
    frames = maps.getT();
    mapRes = maps.getRes();
    protCount = mf.getMaximaNumber();

    cellStatistics = new CellStatistics();
    protStatistics = new ProtrusionStatistics();

  }

  /**
   * Collection of methods for compute cell shape based statistics.
   * 
   * @author p.baniukiewicz
   *
   */
  class CellStatistics {

    /**
     * The displacement.
     */
    // parameter vs. frames
    double[] displacement;

    /**
     * The distance.
     */
    double[] distance;

    /**
     * The area.
     */
    double[] area;

    /**
     * The circularity.
     */
    double[] circularity;
    /**
     * Mean of motility.
     */
    double[] motMean;
    /**
     * Variance of motility.
     */
    double[] motVar;
    /**
     * Mean of convexity.
     */
    double[] conMean;
    /**
     * Variance of convexity.
     */
    double[] conVar;
    /**
     * Number of protrusions in every frame.
     */
    double[] protcount;

    /**
     * Compute all cell statistics.
     */
    public CellStatistics() {
      getFromCellStats(); // copy some stats from CellStats
      // calculate basic stats for motility and convexity
      motMean = QuimPArrayUtils.getMeanR(maps.getMotMap());
      conMean = QuimPArrayUtils.getMeanR(maps.getConvMap());
      motVar = QuimPArrayUtils.getVarR(maps.getMotMap());
      conVar = QuimPArrayUtils.getVarR(maps.getConvMap());
      protcount = countProtrusions();
    }

    /**
     * Extract already calculated stats from CellStats.
     * 
     * <p>Fill internal fields of this class.
     */
    private void getFromCellStats() {
      displacement = new double[frames];
      distance = new double[frames];
      area = new double[frames];
      circularity = new double[frames];
      int l = 0;
      for (FrameStatistics frameStat : cs.framestat) {
        displacement[l] = frameStat.displacement;
        distance[l] = frameStat.dist;
        area[l] = frameStat.area;
        circularity[l] = frameStat.circularity;
        l++;
      }
    }

    /**
     * Count protrusions for every frame.
     * 
     * @return Number of protrusions found for every frame. TODO use framewindow
     */
    private double[] countProtrusions() {
      Polygon maxima = mf.getMaxima();
      int[] tmpframes = maxima.xpoints; // frame numbers from maxima
      double[] hist = new double[frames]; // bin count
      for (int f = 0; f < tmpframes.length; f++) {
        hist[tmpframes[f]]++;
      }
      return hist;
    }

    /**
     * Add header to common file before next cell.
     * 
     * @param bf writer
     * @param cellno Number of cell.
     */
    private void writeCellHeader(PrintWriter bf, int cellno) {
      //!>
      String ret = "#Cell:" + cellno;
      LOGGER.trace(ret);
      String h = "#Frame," + "Displacement," + "Distance," + "Area," + "Circularity," + "meanMot,"
              + "varMot," + "meanConv," + "varConv," + "protCount";
      //!<
      LOGGER.trace(h);
      bf.print(ret + '\n');
      bf.print(h + '\n');
      bf.flush();
    }

    /**
     * Write one line for given frame for current cell.
     * 
     * @param bf writer
     * @param frameno frame
     */
    private void writeCellRecord(PrintWriter bf, int frameno) {
      String ret = Integer.toString(frameno + 1) + ',';
      ret = ret.concat(Double.toString(displacement[frameno])) + ',';
      ret = ret.concat(Double.toString(distance[frameno])) + ',';
      ret = ret.concat(Double.toString(area[frameno])) + ',';
      ret = ret.concat(Double.toString(circularity[frameno])) + ',';
      ret = ret.concat(Double.toString(motMean[frameno])) + ',';
      ret = ret.concat(Double.toString(motVar[frameno])) + ',';
      ret = ret.concat(Double.toString(conMean[frameno])) + ',';
      ret = ret.concat(Double.toString(conVar[frameno])) + ',';
      ret = ret.concat(Double.toString(protcount[frameno]));

      LOGGER.trace(ret);
      bf.print(ret + '\n');
      bf.flush();
    }

    /**
     * Add cell statistic to given ResultsTable.
     * 
     * @param rt table
     * @see #writeCellRecord(PrintWriter, int)
     * @see #writeCellHeader(PrintWriter, int)
     */
    public void addCellToCellTable(ResultsTable rt) {
      // Those fields must be related to writeCellHeader and writeCellRecord
      for (int i = 0; i < displacement.length; i++) {
        rt.incrementCounter();
        rt.addValue("frame", i + 1);
        rt.addValue("displacement", displacement[i]);
        rt.addValue("distance", distance[i]);
        rt.addValue("area", area[i]);
        rt.addValue("circularity", circularity[i]);
        rt.addValue("motMean", motMean[i]);
        rt.addValue("motVar", motVar[i]);
        rt.addValue("conMean", conMean[i]);
        rt.addValue("conVar", conVar[i]);
        rt.addValue("protcount", protcount[i]);
      }
    }

  }

  /**
   * Collection of methods for compute protrusion statistics.
   * 
   * @author p.baniukiewicz
   *
   */
  class ProtrusionStatistics {
    /**
     * Indicate position of the tip on cell outline. Not normalised.
     */
    int[] tipPositionIndex;
    /**
     * Screen coordinate of the tip.
     */
    Point2D.Double[] tipCoordinate;
    /**
     * Tip frame.
     */
    int[] tipFrame;
    /**
     * First frame where tip appeared, within given criterion of motility drop.
     */
    int[] tipFirstFrame;
    /**
     * Last frame where tip appeared, within given criterion of motility drop.
     */
    int[] tipLastFrame;

    /**
     * Compute protrusion stats.
     */
    public ProtrusionStatistics() {
      getFromTrackStats();
    }

    /**
     * Extract statistic data from {@link TrackCollection}.
     */
    private void getFromTrackStats() {
      if (tc.isInitialPointIncluded() == false) {
        throw new IllegalArgumentException(
                "This method assumes that initial point must be included in Track");
      }
      tipPositionIndex = new int[protCount];
      tipCoordinate = new Point2D.Double[protCount];
      tipFrame = new int[protCount];
      tipFirstFrame = new int[protCount];
      tipLastFrame = new int[protCount];
      Iterator<Pair<Track, Track>> it = tc.iterator(); // over backward,forward pairs
      int l = 0;
      while (it.hasNext()) {
        Pair<Track, Track> p = it.next();
        Track t1 = p.getLeft();
        Track t2 = p.getRight();
        tipPositionIndex[l] = t1.getOutline(0); // first point is maximum
        tipCoordinate[l] = t1.getXY(0, maps.getxMap(), maps.getyMap());
        tipFrame[l] = t1.getFrame(0);
        if (t1.type == TrackType.BACKWARD && t2.type == TrackType.FORWARD) {
          tipFirstFrame[l] = t1.getFrame(t1.size() - 1);
          tipLastFrame[l] = t2.getFrame(t2.size() - 1);
        } else if (t1.type == TrackType.FORWARD && t2.type == TrackType.BACKWARD) {
          tipFirstFrame[l] = t2.getFrame(t2.size() - 1);
          tipLastFrame[l] = t1.getFrame(t1.size() - 1);
        }
        l++;
      }
    }

    /**
     * Add header to common file before next cell.
     * 
     * @param bf writer
     * @param cellno Number of cell.
     */
    private void writeProtHeader(PrintWriter bf, int cellno) {
      //!<
      String ret = "#Cell:" + cellno;
      LOGGER.trace(ret);
      String h = "#Id," + "Position," + "x-xoordinate," + "y-coordinate," + "Frame," + "FirstFrame,"
              + "LastFrame";
      /**/
      LOGGER.trace(h);
      bf.print(ret + '\n');
      bf.print(h + '\n');
      bf.flush();
    }

    /**
     * Write one line for given frame for current cell.
     * 
     * @param bf writer
     * @param id protrusion id
     */
    private void writeProtRecord(PrintWriter bf, int id) {
      String ret = Integer.toString(id + 1) + ',';
      ret = ret.concat(Integer.toString(tipPositionIndex[id])) + ',';
      ret = ret.concat(Double.toString(tipCoordinate[id].x)) + ',';
      ret = ret.concat(Double.toString(tipCoordinate[id].y)) + ',';
      ret = ret.concat(Integer.toString(tipFrame[id])) + ',';
      ret = ret.concat(Integer.toString(tipFirstFrame[id])) + ',';
      ret = ret.concat(Integer.toString(tipLastFrame[id]));

      LOGGER.trace(ret);
      bf.print(ret + '\n');
      bf.flush();
    }

  }

  /**
   * Add stats for this cell to output.
   * 
   * @param bf place to write
   * @param cellno Cell number
   */
  public void writeCell(PrintWriter bf, int cellno) {
    LOGGER.debug("Writing cell stats at:" + bf.toString());
    cellStatistics.writeCellHeader(bf, cellno);
    for (int f = 0; f < frames; f++) {
      cellStatistics.writeCellRecord(bf, f);
    }
  }

  /**
   * Write stats for protrusions for this cell.
   * 
   * @param bf place to write
   * @param cellno Cell number.
   */
  public void writeProtrusion(PrintWriter bf, int cellno) {
    LOGGER.debug("Writing prot stats at:" + bf.toString());
    protStatistics.writeProtHeader(bf, cellno);
    for (int t = 0; t < tc.getBf().size(); t++) {
      protStatistics.writeProtRecord(bf, t);
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#beforeSerialize()
   */
  @Override
  public void beforeSerialize() {
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#afterSerialize()
   */
  @Override
  public void afterSerialize() throws Exception {
  }
}