View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.awt.Polygon;
4   import java.io.File;
5   import java.io.IOException;
6   import java.util.ArrayList;
7   import java.util.Arrays;
8   
9   import com.github.celldynamics.quimp.filesystem.StatsCollection;
10  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
11  
12  import ij.IJ;
13  import ij.ImagePlus;
14  import ij.gui.PolygonRoi;
15  import ij.gui.Roi;
16  import ij.measure.Measurements;
17  import ij.process.ImageProcessor;
18  import ij.process.ImageStatistics;
19  
20  /**
21   * Calculate statistics for whole stack (all cells).
22   * 
23   * <p>Stats are written on disk after calling constructor. Additionally there is separate list
24   * maintained with the same data. They can be collected calling {@link #getStatH()}. This is due to
25   * compatibility with old QuimP.
26   * 
27   * @author tyson
28   * @author p.baniukiewicz
29   */
30  public class CellStatsEval implements Measurements {
31    /**
32     * Hold all stats for cell. The same data are written to disk as csv file.
33     */
34    private CellStats statH;
35  
36    /**
37     * The output H.
38     */
39    OutlineHandler outputH;
40  
41    /**
42     * The outfile.
43     */
44    File outfile;
45  
46    /**
47     * The i plus.
48     */
49    ImagePlus iplus;
50  
51    /**
52     * The i proc.
53     */
54    ImageProcessor iproc;
55  
56    /**
57     * The is.
58     */
59    ImageStatistics is;
60  
61    /**
62     * The scale used for evaluation of {@link FrameStatistics}.
63     * 
64     * @see #record()
65     */
66    double scale;
67  
68    /**
69     * The frame interval used for evaluation of {@link FrameStatistics}.
70     * 
71     * @see #record()
72     */
73    double frameInterval;
74  
75    /**
76     * Create and run the object.
77     * 
78     * <p>After creating the object, file with stats is written and stats are available by calling
79     * {@link #getStatH()} method.
80     * 
81     * @param oh OutlineHandler
82     * @param ip image associated with OutlineHandler
83     * @param f file name to write stats, if null file is not created
84     * @param s image scale
85     * @param fi frame interval
86     */
87    public CellStatsEval(OutlineHandler oh, ImagePlus ip, File f, double s, double fi) {
88      IJ.showStatus("BOA-Calculating Cell stats");
89      outputH = oh;
90      outfile = f;
91      iplus = ip;
92      iproc = ip.getProcessor();
93      scale = s;
94      frameInterval = fi;
95  
96      FrameStatistics[] stats = record();
97      iplus.setSlice(1);
98      iplus.killRoi();
99      if (f == null) { // do not write stQP file
100       buildData(stats);
101     } else {
102       try {
103         buildData(stats);
104         FrameStatistics.write(stats, outfile, s, fi);
105       } catch (IOException e) {
106         IJ.error("could not open out file");
107       }
108     }
109   }
110 
111   /**
112    * Only create the object. Stats file is not created but results are available by calling
113    * {@link #getStatH()} method.
114    * 
115    * @param oh OutlineHandler
116    * @param ip image associated with OutlineHandler
117    * @param s image scale
118    * @param fi frame interval
119    */
120   public CellStatsEval(OutlineHandler oh, ImagePlus ip, double s, double fi) {
121     this(oh, ip, null, s, fi);
122   }
123 
124   /**
125    * Calculate stats.
126    * 
127    * <p><b>Warning</b>
128    * 
129    * <p>Number of calculated stats must be reflected in {@link #buildData(FrameStatistics[])}.
130    * 
131    * <p>Scales stored in processed image are used. Even if {@link BOAState.BOAp} stores scale of
132    * image, it is used scale from tiff (they are the same as user scale is copied to image in
133    * initialisation stage).
134    * 
135    * @return Array with stats for every frame for one cell. Array is for compatibility reasons. New
136    *         format uses List of objects.
137    * @see #getStatH()
138    */
139   private FrameStatistics[] record() {
140     // ImageStack orgStack = orgIpl.getStack();
141     FrameStatisticstistics.html#FrameStatistics">FrameStatistics[] stats = new FrameStatistics[outputH.getSize()];
142 
143     double distance = 0;
144     Outline o;
145     PolygonRoi roi;
146     int store;
147 
148     for (int f = outputH.getStartFrame(); f <= outputH.getEndFrame(); f++) {
149       IJ.showProgress(f, outputH.getEndFrame());
150       store = f - outputH.getStartFrame();
151 
152       o = outputH.getStoredOutline(f);
153       iplus.setSlice(f); // also updates the processor
154       stats[store] = new FrameStatistics();
155 
156       Polygon opoly = o.asPolygon();
157       roi = new PolygonRoi(opoly, Roi.POLYGON);
158 
159       iplus.setRoi(roi);
160       is = iplus.getStatistics(AREA + CENTROID + ELLIPSE + SHAPE_DESCRIPTORS); // this does scale to
161       // image
162 
163       // all theses already to scale
164       stats[store].frame = f;
165       stats[store].area = is.area;
166       stats[store].centroid.setX(is.xCentroid);
167       stats[store].centroid.setY(is.yCentroid);
168 
169       stats[store].elongation = is.major / is.minor; // include both axis plus elongation
170       stats[store].perimiter = roi.getLength(); // o.getLength();
171       stats[store].circularity =
172               4 * Math.PI * (stats[store].area / (stats[store].perimiter * stats[store].perimiter));
173       stats[store].displacement =
174               ExtendedVector2d.lengthP2P(stats[0].centroid, stats[store].centroid);
175 
176       if (store != 0) {
177         stats[store].speed =
178                 ExtendedVector2d.lengthP2P(stats[store - 1].centroid, stats[store].centroid);
179         distance += ExtendedVector2d.lengthP2P(stats[store - 1].centroid, stats[store].centroid);
180         stats[store].dist = distance;
181       } else {
182         stats[store].dist = 0;
183         stats[store].speed = 0;
184       }
185 
186       if (distance != 0) {
187         stats[store].persistance = stats[store].displacement / distance;
188       } else {
189         stats[store].persistance = 0;
190       }
191 
192     }
193 
194     // convert centroid to pixels
195     for (int f = outputH.getStartFrame(); f <= outputH.getEndFrame(); f++) {
196       store = f - outputH.getStartFrame();
197       stats[store].centroidToPixels(scale);
198     }
199 
200     return stats;
201   }
202 
203   /**
204    * Complementary to write method. Create the same data as write but in form of arrays. For
205    * compatible reasons. New format uses this representation.
206    * 
207    * @param s Frame statistics calculated by
208    *        {@link com.github.celldynamics.quimp.CellStatsEval#record()}
209    */
210   private void buildData(FrameStatistics[] s) {
211     statH = new CellStats(new ArrayList<FrameStatistics>(Arrays.asList(s)));
212   }
213 
214   /**
215    * Return statistics in new format for one cell along all frames it appears in.
216    * 
217    * <p>This is roughly the same as FrameStatistics[] computed by this class. New format is included
218    * in QCONF file through {@link StatsCollection}
219    * 
220    * @return the statH
221    */
222   public CellStats getStatH() {
223     return statH;
224   }
225 
226 }