FrameStatistics.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 com.github.celldynamics.quimp.geom.ExtendedVector2d;
import com.github.celldynamics.quimp.plugin.ana.ChannelStat;
import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
import ij.IJ;
import ij.measure.ResultsTable;
/**
* Hold statistic evaluated for one frame and one outline, both geometric and fluorescence.
*
* @author p.baniukiewicz
* @see CellStatsEval
* @see ChannelStat
*/
public class FrameStatistics {
/**
* Frame number.
*/
public int frame;
/**
* Area of outline.
*/
public double area;
/**
* Centroid of outline.
*/
public ExtendedVector2d centroid;
/**
* Elongation of outline. An ellipse is fitted to the cell outline and the major/minor axis used
* to compute the elongation of the cell’s shape. Elongation = major axis∕minor axis. Note that a
* value of 1 does not necessarily represent a perfectly circular cell, only a circular fitted
* ellipse.
*/
public double elongation;
/**
* Circularity of outline. A measure of circularity defined by the following equation:
* 4*PI*Area/(Perimeter*Perimeter). A value of 1 reveals the cell’s outline to be perfectly
* circular.
*/
public double circularity;
/**
* Perimeter of outline. Length of the cell perimeter (segmented outline).
*/
public double perimiter;
/**
* Displacement of outline. Distance the cell centroid has moved from its position in the first
* recorded frame.
*/
public double displacement;
/**
* Cumulated distance that cell moved from the first recorded frame. (sum of distances between i-1
* and i frame)
*/
public double dist;
/**
* Persistence of outline. Persistence in direction, calculated as Displacement∕Dist.Travelled
* (chemotaxis index). A value of 1 reveals that a cell has moved in a straight line. Decreasing
* values denote a cell moving increasingly erratically.
*/
public double persistance;
/**
* Speed at which the centroid moved between the current and previous frame.
*/
public double speed;
/**
* Unknown.
*
* @deprecated Not used
*/
public double persistanceToSource;
/**
* Dispersion of outline.
*
* @deprecated Not used
*/
public double dispersion;
/**
* Extension of outline.
*
* @deprecated Not used
*/
public double extension;
/**
* Fluorescence stats added by ANA module.
*/
public ChannelStat[] channels;
/**
* Default constructor, create empty container.
*/
public FrameStatistics() {
centroid = new ExtendedVector2d();
channels = new ChannelStat[3];
channels[0] = new ChannelStat();
channels[1] = new ChannelStat();
channels[2] = new ChannelStat();
}
/**
* Rescale scalable parameters (area, perimeter,etc) to SI units.
*
* @param scale scale
* @param frameInterval frame interval
*/
public void toScale(double scale, double frameInterval) {
area = QuimpToolsCollection.areaToScale(area, scale);
perimiter = QuimpToolsCollection.distanceToScale(perimiter, scale);
displacement = QuimpToolsCollection.distanceToScale(displacement, scale);
dist = QuimpToolsCollection.distanceToScale(dist, scale);
speed = QuimpToolsCollection.speedToScale(speed, scale, frameInterval); // over 1 frame
}
/**
* Re-scale centroid to pixels (from SI).
*
* @param scale scale
*/
void centroidToPixels(double scale) {
centroid.setXY(centroid.getX() / scale, centroid.getY() / scale);
}
/**
* Clear channel statistics.
*
* @see ChannelStat
*/
public void clearFluo() {
this.channels[0] = new ChannelStat();
this.channels[1] = new ChannelStat();
this.channels[2] = new ChannelStat();
}
/**
* Write stat file in old format (stQP.csv).
*
* @param s statistics to write
* @param outfile file name
* @param scale image scale
* @param frameInterval data frame interval
* @throws IOException on file error
*/
public static void write(FrameStatistics[] s, File outfile, double scale, double frameInterval)
throws IOException {
PrintWriter pw = new PrintWriter(new FileWriter(outfile), true); // auto flush
IJ.log("Writing to file: " + outfile.getName());
pw.print("#p2\n#QuimP output - " + outfile.getAbsolutePath() + "\n");
pw.print("# Centroids are given in pixels. Distance & speed & area measurements are scaled"
+ " to micro meters\n");
pw.print("# Scale: " + scale + " micro meter per pixel | Frame interval: " + frameInterval
+ " sec\n");
pw.print("# Frame,X-Centroid,Y-Centroid,Displacement,Dist. Traveled,"
+ "Directionality,Speed,Perimeter,Elongation,Circularity,Area");
for (int i = 0; i < s.length; i++) {
pw.print("\n" + s[i].frame + "," + IJ.d2s(s[i].centroid.getX(), 2) + ","
+ IJ.d2s(s[i].centroid.getY(), 2) + "," + IJ.d2s(s[i].displacement) + ","
+ IJ.d2s(s[i].dist) + "," + IJ.d2s(s[i].persistance) + "," + IJ.d2s(s[i].speed) + ","
+ IJ.d2s(s[i].perimiter) + "," + IJ.d2s(s[i].elongation) + ","
+ IJ.d2s(s[i].circularity, 3) + "," + IJ.d2s(s[i].area));
}
pw.print("\n#\n# Fluorescence measurements");
writeFluo(s, pw, 0);
writeFluo(s, pw, 1);
writeFluo(s, pw, 2);
pw.close();
}
private static void writeFluo(FrameStatistics[] s, PrintWriter pw, int c) {
pw.print("\n#\n# Channel " + (c + 1)
+ ";Frame, Total Fluo.,Mean Fluo.,Cortex Width, Cyto. Area,Total Cyto. Fluo.,"
+ " Mean Cyto. Fluo.,"
+ "Cortex Area,Total Cortex Fluo., Mean Cortex Fluo., %age Cortex Fluo.");
for (int i = 0; i < s.length; i++) {
pw.print("\n" + s[i].frame + "," + IJ.d2s(s[i].channels[c].totalFluor) + ","
+ IJ.d2s(s[i].channels[c].meanFluor) + "," + IJ.d2s(s[i].channels[c].cortexWidth));
pw.print("," + IJ.d2s(s[i].channels[c].innerArea) + ","
+ IJ.d2s(s[i].channels[c].totalInnerFluor) + ","
+ IJ.d2s(s[i].channels[c].meanInnerFluor));
pw.print("," + IJ.d2s(s[i].channels[c].cortexArea) + ","
+ IJ.d2s(s[i].channels[c].totalCorFluo) + "," + IJ.d2s(s[i].channels[c].meanCorFluo)
+ "," + IJ.d2s(s[i].channels[c].percCortexFluo));
}
}
/**
* Load statistics from old file format.
*
* @param infile file to read
* @return Array of statistics for all object in frame
* @throws IOException on file error
*/
public static FrameStatistics[] read(File infile) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(infile));
String thisLine;
int i = 0;
// count the number of frames in .scv file
while ((thisLine = br.readLine()) != null) {
if (thisLine.startsWith("# Fluorescence measurements")) {
break;
}
if (thisLine.startsWith("#")) {
continue;
}
// System.out.println(thisLine);
i++;
}
br.close();
FrameStatistics[] stats = new FrameStatistics[i];
i = 0;
String[] split;
br = new BufferedReader(new FileReader(infile)); // re-open and read
while ((thisLine = br.readLine()) != null) {
if (thisLine.startsWith("# Channel")) { // reached fluo stats
break;
}
if (thisLine.startsWith("#")) {
continue;
}
// System.out.println(thisLine);
split = thisLine.split(",");
stats[i] = new FrameStatistics();
stats[i].frame = (int) QuimpToolsCollection.s2d(split[0]);
stats[i].centroid.setXY(QuimpToolsCollection.s2d(split[1]),
QuimpToolsCollection.s2d(split[2]));
stats[i].displacement = QuimpToolsCollection.s2d(split[3]);
stats[i].dist = QuimpToolsCollection.s2d(split[4]);
stats[i].persistance = QuimpToolsCollection.s2d(split[5]);
stats[i].speed = QuimpToolsCollection.s2d(split[6]);
stats[i].perimiter = QuimpToolsCollection.s2d(split[7]);
stats[i].elongation = QuimpToolsCollection.s2d(split[8]);
stats[i].circularity = QuimpToolsCollection.s2d(split[9]);
stats[i].area = QuimpToolsCollection.s2d(split[10]);
i++;
}
readChannel(0, stats, br);
readChannel(1, stats, br);
readChannel(2, stats, br);
br.close();
return stats;
}
private static void readChannel(int c, FrameStatistics[] stats, BufferedReader br)
throws IOException {
String thisLine;
String[] split;
int i = 0;
while ((thisLine = br.readLine()) != null) {
if (thisLine.startsWith("# Channel")) {
break;
}
if (thisLine.startsWith("#")) {
continue;
}
split = thisLine.split(",");
// split[0] == frame
stats[i].channels[c].totalFluor = QuimpToolsCollection.s2d(split[1]);
stats[i].channels[c].meanFluor = QuimpToolsCollection.s2d(split[2]);
stats[i].channels[c].cortexWidth = QuimpToolsCollection.s2d(split[3]);
stats[i].channels[c].innerArea = QuimpToolsCollection.s2d(split[4]);
stats[i].channels[c].totalInnerFluor = QuimpToolsCollection.s2d(split[5]);
stats[i].channels[c].meanInnerFluor = QuimpToolsCollection.s2d(split[6]);
stats[i].channels[c].cortexArea = QuimpToolsCollection.s2d(split[7]);
stats[i].channels[c].totalCorFluo = QuimpToolsCollection.s2d(split[8]);
stats[i].channels[c].meanCorFluo = QuimpToolsCollection.s2d(split[9]);
stats[i].channels[c].percCortexFluo = QuimpToolsCollection.s2d(split[10]);
i++;
}
}
/**
* Add channel statistic to given ResultsTable.
*
* @param rt IJ result table
* @param channelno channel number for fluoro stats
* @see #addStatToResultTable(ResultsTable)
*/
public void addFluoToResultTable(ResultsTable rt, int channelno) {
// Those fields must be related to writeFluo
ChannelStat cs = channels[channelno]; // reference to channel
rt.incrementCounter();
rt.addValue("frame", frame);
rt.addValue("TotalFluo", cs.totalFluor);
rt.addValue("MeanFluo", cs.meanFluor);
rt.addValue("Cortex Width", cs.cortexWidth);
rt.addValue("Cyto. Area", cs.innerArea);
rt.addValue("Total Cyto. Fluo.", cs.totalInnerFluor);
rt.addValue("Mean Cyto. Fluo.h", cs.meanInnerFluor);
rt.addValue("Cortex Area", cs.cortexArea);
rt.addValue("Total Cortex Fluo.", cs.totalCorFluo);
rt.addValue("Mean Cortex Fluo.", cs.meanCorFluo);
rt.addValue("%age Cortex Fluo.", cs.percCortexFluo);
}
/**
* Add statistics for this frame to resutl table.
*
* @param rt IJ result table
* @see #addFluoToResultTable(ResultsTable, int)
*/
public void addStatToResultTable(ResultsTable rt) {
rt.incrementCounter();
rt.addValue("Frame", frame);
rt.addValue("Area", area);
rt.addValue("Centroid_x", centroid.getX());
rt.addValue("Centroid_y", centroid.getY());
rt.addValue("Circularity", circularity);
rt.addValue("Displacement", displacement);
rt.addValue("Distance", dist);
rt.addValue("Elongation", elongation);
rt.addValue("Perimeter", perimiter);
rt.addValue("Persistance", persistance);
rt.addValue("Speed", speed);
}
}