View Javadoc
1   package com.github.celldynamics.quimp.plugin.ana;
2   
3   import java.awt.AWTEvent;
4   import java.awt.Checkbox;
5   import java.awt.Choice;
6   import java.awt.Color;
7   import java.awt.Polygon;
8   import java.io.File;
9   import java.io.IOException;
10  import java.util.ArrayList;
11  import java.util.Arrays;
12  
13  import org.slf4j.Logger;
14  import org.slf4j.LoggerFactory;
15  
16  import com.github.celldynamics.quimp.CellStats;
17  import com.github.celldynamics.quimp.FrameStatistics;
18  import com.github.celldynamics.quimp.Outline;
19  import com.github.celldynamics.quimp.OutlineHandler;
20  import com.github.celldynamics.quimp.QParams;
21  import com.github.celldynamics.quimp.QParamsQconf;
22  import com.github.celldynamics.quimp.QuimP;
23  import com.github.celldynamics.quimp.QuimpException;
24  import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
25  import com.github.celldynamics.quimp.Vert;
26  import com.github.celldynamics.quimp.filesystem.ANAParamCollection;
27  import com.github.celldynamics.quimp.filesystem.DataContainer;
28  import com.github.celldynamics.quimp.filesystem.OutlinesCollection;
29  import com.github.celldynamics.quimp.filesystem.converter.FormatConverter;
30  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
31  import com.github.celldynamics.quimp.plugin.AbstractPluginQconf;
32  import com.github.celldynamics.quimp.plugin.QuimpPluginException;
33  import com.github.celldynamics.quimp.plugin.ecmm.ECMM_Mapping;
34  import com.github.celldynamics.quimp.plugin.ecmm.ECMp;
35  import com.github.celldynamics.quimp.plugin.ecmm.ODEsolver;
36  import com.github.celldynamics.quimp.utils.QuimPArrayUtils;
37  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
38  
39  import ij.IJ;
40  import ij.ImagePlus;
41  import ij.Prefs;
42  import ij.WindowManager;
43  import ij.gui.DialogListener;
44  import ij.gui.GenericDialog;
45  import ij.gui.Overlay;
46  import ij.gui.PointRoi;
47  import ij.gui.PolygonRoi;
48  import ij.gui.Roi;
49  import ij.gui.YesNoCancelDialog;
50  import ij.measure.Measurements;
51  import ij.measure.ResultsTable;
52  import ij.plugin.Converter;
53  import ij.plugin.filter.Analyzer;
54  import ij.process.ImageProcessor;
55  import ij.process.ImageStatistics;
56  
57  /**
58   * Main ANA class implementing IJ PlugInFilter.
59   * 
60   * @author tyson
61   */
62  public class ANA_ extends AbstractPluginQconf implements DialogListener {
63  
64    private static String thisPluginName = "ANA";
65  
66    /**
67     * The Constant LOGGER.
68     */
69    static final Logger LOGGER = LoggerFactory.getLogger(ANA_.class.getName());
70  
71    private OutlineHandler oh; // set by runFrom*
72    private OutlineHandler outputH;
73    private OutlineHandler ecmH;
74    private OutlinesCollection outputOutlineHandlers; // output for new data file
75    private Outline frameOneClone;
76    private ECMM_Mapping ecmMapping;
77    private Overlay overlay;
78    // outlines can be plotted separately. They are generated by Ana() and stored here
79    private ArrayList<Roi> storedOuterROI; // outer outline for each frame for all cells
80    private ArrayList<Roi> storedInnerROI; // inner outline for each frame for all cells
81  
82    private ImagePlus setupImage; // image fluoro
83    private ImageProcessor orgIpr; // passed by setup
84  
85    /**
86     * ANA extends statistics generated by BOA by fluorescence related data.
87     * 
88     * <p>This is object that holds stats read from stQP file.
89     */
90    private FrameStatistics[] fluoStats;
91    private ANAp anap;
92    private static final int m =
93            Measurements.AREA + Measurements.INTEGRATED_DENSITY + Measurements.MEAN;
94  
95    /**
96     * Default constructor called always.
97     */
98    public ANA_() {
99      super(new AnaOptions(), thisPluginName);
100     storedOuterROI = new ArrayList<>();
101     storedInnerROI = new ArrayList<>();
102     anap = new ANAp();
103     ECMp.plot = false;
104     ecmMapping = new ECMM_Mapping(1);
105   }
106 
107   /*
108    * (non-Javadoc)
109    * 
110    * @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#run(java.lang.String)
111    */
112   @Override
113   public void run(String arg) {
114     // overcome problem, IJ UI somehow break this so show before or IJ.run below
115     // publishMacroString("ANA");
116     setupImage = WindowManager.getCurrentImage();
117     if (setupImage == null) {
118       IJ.error("Image required to take fluoresence measurments.");
119       return;
120     }
121     if (setupImage.getOriginalFileInfo() == null
122             || setupImage.getOriginalFileInfo().directory.matches("")) {
123       IJ.log("Error: Fluorescence file needs to be saved to disk");
124       IJ.error("Please save your fluorescence image to file.");
125       return;
126     }
127     Prefs.interpolateScaledImages = false; // switch off interpolation of zoomed images
128     // IJ.run("Appearance...", " menu=0"); // switch off interpolation of zoomed images
129     overlay = new Overlay();
130     setupImage.setOverlay(overlay);
131     orgIpr = setupImage.getProcessor();
132     super.run(arg);
133   }
134 
135   /*
136    * (non-Javadoc)
137    * 
138    * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#executer()
139    */
140   @Override
141   protected void executer() throws QuimpException {
142     super.executer(); // will run runFrom*
143     // post-processing
144     if (qconfLoader.getQp() == null) {
145       return; // cancelled
146     }
147     // and then do the rest
148     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
149     // post-plotting
150     overlay = new Overlay();
151     setupImage.setOverlay(overlay);
152     for (int f = 1; f < setupImage.getStackSize(); f++) {
153       setupImage.setSlice(f);
154       for (OutlineHandler ohTmp : outputOutlineHandlers.oHs) {
155         if (f >= ohTmp.getStartFrame() && f <= ohTmp.getEndFrame()) {
156           Outline o = ohTmp.getStoredOutline(f);
157           if (o == null) { // should not happen
158             continue;
159           }
160           drawSamplePointsFloat(o, f);
161           setupImage.draw();
162         }
163       }
164     }
165     // plotting outlines on separate image
166     if (opts.plotOutlines) {
167       ImagePlus orgIplclone = setupImage.duplicate();
168       orgIplclone.show();
169       new Converter().run("RGB Color");
170       Overlay overlay = new Overlay();
171       orgIplclone.setOverlay(overlay);
172       for (Roi r : storedOuterROI) {
173         overlay.add(r);
174       }
175       for (Roi r : storedInnerROI) {
176         overlay.add(r);
177       }
178       orgIplclone.draw();
179     }
180 
181     // edd results to IJtable named Results - to allow Summarise
182     if (opts.fluoResultTable || opts.fluoResultTableAppend) {
183       if (qconfLoader.isFileLoaded() == QParams.NEW_QUIMP) {
184         ResultsTable rt;
185         if (opts.fluoResultTableAppend) { // get current table
186           rt = Analyzer.getResultsTable();
187         } else { // or create new
188           rt = new ResultsTable();
189           Analyzer.setResultsTable(rt);
190         }
191         // iterate over cells - all cells for this experiment are cumulated in one table
192         for (CellStats cs : qconfLoader.getStats().getStatCollection()) {
193           cs.addFluosToResultTable(rt, opts.channel);
194         }
195         rt.show("Results");
196       } else {
197         LOGGER.warn(
198                 "Results can be shown in IJ table only if ANA is started with QCONF file format");
199       }
200     }
201     ecmMapping = null;
202   }
203 
204   /*
205    * (non-Javadoc)
206    * 
207    * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#validate()
208    */
209   @Override
210   protected void validate() throws QuimpException {
211     qconfLoader.getBOA();
212     qconfLoader.getEcmm(); // verify whether ecmm has been run (throws if not)
213     qconfLoader.getStats(); // verify whether file contains stats
214   }
215 
216   /*
217    * (non-Javadoc)
218    * 
219    * @see com.github.celldynamics.quimp.plugin.IQuimpPlugin#about()
220    */
221   @Override
222   public String about() {
223     return "ANA plugin.\n" + "Authors: Piotr Baniukiewicz\n"
224             + "mail: p.baniukiewicz@warwick.ac.uk\n" + "Richard Tyson";
225   }
226 
227   /*
228    * (non-Javadoc)
229    * 
230    * @see com.github.celldynamics.quimp.plugin.AbstractPluginFilterQconf#runFromQconf()
231    */
232   @Override
233   protected void runFromQconf() throws QuimpException {
234     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
235     LOGGER.debug("Processing from new file format");
236     if (apiCall == false && errorSink == MessageSinkTypes.GUI && qconfLoader.isANAPresent()) {
237       YesNoCancelDialog ync;
238       ync = new YesNoCancelDialog(IJ.getInstance(), "Overwrite",
239               "You are about to override previous ANA results. Is it ok?");
240       if (!ync.yesPressed()) { // if no or cancel
241         throw new QuimpPluginException("Cancelled - no changes made in input file",
242                 MessageSinkTypes.MESSAGE, true);
243       }
244     }
245 
246     QParamsQconf/../../../../../com/github/celldynamics/quimp/QParamsQconf.html#QParamsQconf">QParamsQconf qp = (QParamsQconf) qconfLoader.getQp();
247     ANAParamCollection anaStates;
248     OutlinesCollection ecmmState = qp.getLoadedDataContainer().ECMMState;
249     outputOutlineHandlers = new OutlinesCollection(ecmmState.oHs.size());
250     if (qp.getLoadedDataContainer().getANAState() == null) {
251       // create ANA slots for all outlines
252       anaStates = new ANAParamCollection(ecmmState.oHs.size()); // store ANA options for every cell
253     } else {
254       anaStates = qp.getLoadedDataContainer().getANAState(); // update old
255     }
256     try {
257       // sanity check for stats - they can be empty if QCONF results from conversion from paQP
258       // without`
259       // stQP present
260       if (qconfLoader.getStats().sHs.isEmpty()) {
261         throw new QuimpPluginException("Stats not found in QCONF file.");
262       }
263       for (int i = 0; i < ecmmState.oHs.size(); i++) { // go over all outlines
264         // For compatibility, all methods have the same syntax (assumes that there is only one
265         // handler)
266         qp.setActiveHandler(i); // set current handler number.
267         oh = ecmmState.oHs.get(i); // restore handler from ecmm
268         anap = anaStates.aS.get(i); // get i-th ana parameters
269         anap.setup(qconfLoader.getQp());
270 
271         // get stats stored in QCONF, they are extended by ANA (ChannelStat field)
272         fluoStats = qconfLoader.getStats().sHs.get(i).framestat.toArray(new FrameStatistics[0]);
273 
274         investigateChannels(oh.indexGetOutline(0));// find first empty channel, change anap
275         if (anap.noData && oh.getSize() == 1) {
276           // only one frame, so no ECMM. set outline res to 2
277           System.out.println("Only one frame. set marker res to 2");
278           oh.indexGetOutline(0).setResolution(anap.oneFrameRes); // should be 2!!!
279         }
280         setImageScale();
281         setupImage.setSlice(qconfLoader.getQp().getStartFrame());
282         // openadialog only if called from IJ, apiCall==false for all IJ calls, so check also sink
283         // to find if run from macros
284         if (apiCall == false && errorSink != MessageSinkTypes.IJERROR && !anaDialog()) {
285           IJ.log("ANA cancelled");
286           return;
287         } else { // macro, do part of anaDialog
288           frameOneClone = (Outline) oh.indexGetOutline(0).clone(); // FIXME Change to copy construc
289           anap.setCortextWidthScale(opts.userScale); // set scale from macro instead from UI
290           if (opts.clearFlu && !anap.cleared) {
291             resetFluo();
292           }
293         }
294         anap.fluTiffs[opts.channel] = new File(setupImage.getOriginalFileInfo().directory,
295                 setupImage.getOriginalFileInfo().fileName);
296         outputH = new OutlineHandler(oh); // copy input to output (ana will add fields to it)
297         runPlugin(); // fills outputH and ChannelStat in FrameStatistics
298         // save fluoro always statFile if old format selected
299         if (QuimP.newFileFormat.get() == false || QuimP.newFileFormat.get() == true) {
300           FrameStatistics.write(fluoStats, anap.statFile, anap.scale, anap.frameInterval);
301         }
302         CellStats statH = qconfLoader.getStats().sHs.get(i); // store fluoro in QCONF
303         statH.framestat = new ArrayList<FrameStatistics>(Arrays.asList(fluoStats)); // store stats
304         outputOutlineHandlers.oHs.add(i, new OutlineHandler(outputH)); // store actual result cont
305       }
306 
307       DataContainer dc = qp.getLoadedDataContainer();
308       dc.ECMMState = outputOutlineHandlers; // assign ECMM container to global output
309       dc.ANAState = anaStates;
310       qp.writeParams(); // save global container
311     } catch (IOException e) {
312       throw new QuimpPluginException(e);
313     }
314     // generate additional OLD files (stQP is generated in loop already), disabled #263, enabled 228
315     if (QuimP.newFileFormat.get() == false) {
316       FormatConverterystem/converter/FormatConverter.html#FormatConverter">FormatConverter foramtConv = new FormatConverter(qconfLoader);
317       foramtConv.doConversion();
318     }
319     IJ.log("The new data file " + qconfLoader.getQp().getParamFile().toString()
320             + " has been updated by results of ECMM analysis.");
321   }
322 
323   /*
324    * (non-Javadoc)
325    * 
326    * @see com.github.celldynamics.quimp.plugin.AbstractPluginFilterQconf#runFromPaqp()
327    */
328   @Override
329   protected void runFromPaqp() throws QuimpException {
330     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
331     outputOutlineHandlers = new OutlinesCollection(1);
332     oh = new OutlineHandler(qconfLoader.getQp());
333     try {
334       anap.setup(qconfLoader.getQp());
335       fluoStats = FrameStatistics.read(anap.statFile);
336       investigateChannels(oh.indexGetOutline(0));// find first empty channel
337 
338       if (anap.noData && oh.getSize() == 1) {
339         // only one frame, so no ECMM. set outline res to 2
340         System.out.println("Only one frame. set marker res to 2");
341         oh.indexGetOutline(0).setResolution(anap.oneFrameRes); // should be 2!!!
342       }
343 
344       setImageScale();
345       setupImage.setSlice(qconfLoader.getQp().getStartFrame());
346       if (!oh.readSuccess) {
347         throw new QuimpException("Could not read OutlineHandler");
348       }
349       // openadialog only if called from IJ
350       if (apiCall == false && errorSink != MessageSinkTypes.IJERROR && !anaDialog()) {
351         IJ.log("ANA cancelled");
352         return;
353       } else { // macro, do part of anaDialog
354         frameOneClone = (Outline) oh.indexGetOutline(0).clone(); // FIXME Change to copy construc
355         anap.setCortextWidthScale(opts.userScale);
356         if (opts.clearFlu && !anap.cleared) {
357           resetFluo();
358         }
359       }
360       System.out.println("CHannel: " + (opts.channel + 1));
361       // qp.cortexWidth = ANAp.cortexWidthScale;
362       anap.fluTiffs[opts.channel] = new File(setupImage.getOriginalFileInfo().directory,
363               setupImage.getOriginalFileInfo().fileName);
364 
365       outputH = new OutlineHandler(oh.getStartFrame(), oh.getEndFrame());
366       runPlugin(); // fills outputH and ChannelStat in FrameStatistics
367 
368       anap.inFile.delete();
369       anap.statFile.delete();
370       outputH.writeOutlines(anap.outFile, qconfLoader.getQp().isEcmmHasRun());
371       FrameStatistics.write(fluoStats, anap.statFile, anap.scale, anap.frameInterval);
372 
373       // ----Write temp files-------
374       // File tempFile = new File(ANAp.outFile.getAbsolutePath() +
375       // ".tempANA.txt");
376       // outputH.writeOutlines(tempFile);
377       // File tempStats = new File(ANAp.statFile.getAbsolutePath() +
378       // ".tempStats.csv");
379       // FluoStats.write(fluoStats, tempStats);
380       // IJ.log("ECMM:137, saving to a temp file instead");
381       // --------------------------
382 
383       qconfLoader.getQp().cortexWidth = anap.getCortexWidthScale();
384       qconfLoader.getQp().fluTiffs = anap.fluTiffs;
385       qconfLoader.getQp().writeParams();
386     } catch (IOException e) {
387       throw new QuimpPluginException(e);
388     }
389     outputOutlineHandlers.oHs.add(0, new OutlineHandler(outputH)); // for plotting purposes
390 
391   }
392 
393   /*
394    * (non-Javadoc)
395    * 
396    * @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#showUi(boolean)
397    */
398   @Override
399   public void showUi(boolean val) throws Exception {
400     // load on GUI show as well
401     executer();
402     if (qconfLoader != null && qconfLoader.getQp() != null) {
403       options.paramFile = qconfLoader.getQp().getParamFile().getAbsolutePath();
404     }
405   }
406 
407   /**
408    * Show dialog.
409    * 
410    * @return true if OK pressed
411    */
412   private boolean anaDialog() {
413     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
414     GenericDialog pd = new GenericDialog("ANA Dialog", IJ.getInstance());
415     // initialise scale UI from QCONF
416     pd.addNumericField("Cortex width (\u00B5m)", anap.getCortexWidthScale(), 2);
417 
418     String[] channelC = { "1", "2", "3" };
419     pd.addChoice("Save in channel", channelC, channelC[opts.channel]);
420     pd.addCheckbox("Normalise to interior", opts.normalise);
421     pd.addCheckbox("Sample at Ch" + (anap.useLocFromCh + 1) + " locations", opts.sampleAtSame);
422     pd.addCheckbox("Clear stored measurements", false);
423     pd.addCheckbox("New image with outlines? ", opts.plotOutlines);
424     pd.addCheckbox("Copy results to IJ Table?", opts.fluoResultTable);
425     pd.addCheckbox("Append results to IJ Table?", opts.fluoResultTableAppend);
426     pd.addDialogListener(this);
427 
428     frameOneClone = (Outline) oh.indexGetOutline(0).clone(); // FIXME Change to copy constructor
429     drawOutlineAsOverlay(frameOneClone, Color.RED);
430     shrink(frameOneClone);
431     this.markFrozenNodesNormal(frameOneClone);
432     setupImage.draw();
433     drawOutlineAsOverlay(frameOneClone, Color.RED);
434     pd.showDialog();
435 
436     return pd.wasOKed();
437 
438   }
439 
440   /*
441    * (non-Javadoc)
442    * 
443    * @see ij.gui.DialogListener#dialogItemChanged(ij.gui.GenericDialog, java.awt.AWTEvent)
444    */
445   @Override
446   public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
447     // fills options from UI
448     if (gd.wasOKed()) {
449       return true;
450     }
451     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
452     // add and append results can not be both active
453     {
454       Checkbox cb4 = (Checkbox) gd.getCheckboxes().elementAt(4); // move results to table
455       Checkbox cb5 = (Checkbox) gd.getCheckboxes().elementAt(5); // append results to table
456       if (e.getSource() == cb4) {
457         if (cb4.getState()) {
458           cb5.setState(false);
459         }
460       }
461       if (e.getSource() == cb5) {
462         if (cb5.getState()) {
463           cb4.setState(false);
464         }
465       }
466     }
467 
468     Checkbox cb = (Checkbox) gd.getCheckboxes().elementAt(2); // clear measurements
469     opts.clearFlu = cb.getState();
470     Choice iob = (Choice) gd.getChoices().elementAt(0);
471     if (opts.clearFlu && !anap.cleared) { // reset if clear measurments checked
472       System.out.println("reset fluo");
473       resetFluo();
474       cb.setLabel("Measurments Cleared");
475       IJ.log("All fluorescence measurements have been cleared");
476       iob.select(0);
477       return true;
478     }
479 
480     opts.channel = gd.getNextChoiceIndex();
481     opts.normalise = gd.getNextBoolean();
482     opts.sampleAtSame = gd.getNextBoolean();
483     opts.plotOutlines = ((Checkbox) gd.getCheckboxes().elementAt(3)).getState();
484     // under multiple AAN run if there are many cells, remember only
485     opts.fluoResultTable = ((Checkbox) gd.getCheckboxes().elementAt(4)).getState();
486     opts.fluoResultTableAppend = ((Checkbox) gd.getCheckboxes().elementAt(5)).getState();
487     // copy scale to macro options and configuration
488     opts.userScale = gd.getNextNumber();
489     anap.setCortextWidthScale(opts.userScale);
490     if (anap.cleared) { // can't deselect
491       cb.setState(true);
492     }
493 
494     frameOneClone = (Outline) oh.indexGetOutline(0).clone(); // FIXME Change to copy constructor
495     overlay.clear();
496     drawOutlineAsOverlay(frameOneClone, Color.RED);
497     shrink(frameOneClone);
498     this.markFrozenNodesNormal(frameOneClone);
499     setupImage.draw();
500     drawOutlineAsOverlay(frameOneClone, Color.RED);
501     return true;// gd.invalidNumber();
502   }
503 
504   /**
505    * Reset fluo.
506    */
507   void resetFluo() {
508     // reset all fluo back to -2 and st res to 2 if only one frame
509     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
510     Outline o;
511     for (int i = 0; i < oh.getSize(); i++) {
512       o = oh.indexGetOutline(i);
513       o.clearFluores();
514       fluoStats[i].clearFluo();
515     }
516 
517     if (oh.getSize() == 1) {
518       // only one frame, so no ECMM. set outline res to 2
519       System.out.println("Only one frame. set marker res to 2");
520       oh.indexGetOutline(0).setResolution(anap.oneFrameRes);
521     }
522 
523     // clear frame stats
524     anap.noData = true;
525     opts.channel = 0;
526     anap.useLocFromCh = -1;
527     anap.presentData[1] = 0;
528     anap.presentData[2] = 0;
529     anap.presentData[0] = 0;
530     anap.fluTiffs[0] = new File("/");
531     anap.fluTiffs[1] = new File("/");
532     anap.fluTiffs[2] = new File("/");
533 
534     opts.channel = 0;
535     anap.cleared = true;
536   }
537 
538   /**
539    * Sets the image scale.
540    */
541   void setImageScale() {
542     setupImage.getCalibration().frameInterval = anap.frameInterval;
543     setupImage.getCalibration().pixelHeight = anap.scale;
544     setupImage.getCalibration().pixelWidth = anap.scale;
545   }
546 
547   /**
548    * Main method for fluorescence measurements analysis. Adds also new stats to FrameStatistics.
549    * 
550    * @see #runFromQconf()
551    * @see #runFromPaqp()
552    */
553   private void runPlugin() {
554     Roi outerRoi;
555     Roi innerRoi;
556     Outline o1;
557     Outline s1;
558     Outline s2;
559     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
560     IJ.showStatus("Running ANA (" + oh.getSize() + " frames)");
561     for (int f = oh.getStartFrame(); f <= oh.getEndFrame(); f++) { // change i to frames
562       IJ.log("Frame " + f);
563       IJ.showProgress(f, oh.getEndFrame());
564 
565       setupImage.setSlice(f);
566       o1 = oh.getStoredOutline(f);
567 
568       s1 = new Outline(o1);
569       s2 = new Outline(o1);
570       shrink(s2);
571 
572       // HACK for Du's embryoImage
573       // shrink(s1);
574       // s1.scale(14, 0.2);
575       // ***
576 
577       // prepare overlay for current frame for plotting inner and outer outline
578       overlay = new Overlay();
579       setupImage.setOverlay(overlay);
580       outerRoi = o1.asFloatRoi(); // convert outlines to ROI
581       innerRoi = s2.asFloatRoi();
582       outerRoi.setPosition(f); // set for frame f
583       outerRoi.setStrokeColor(Color.BLUE);
584       innerRoi.setPosition(f);
585       innerRoi.setStrokeColor(Color.RED);
586 
587       // store in object, will be plotted depending on user choice.
588       storedInnerROI.add(innerRoi);
589       storedOuterROI.add(outerRoi);
590       overlay.add(outerRoi); // this is for real time preview during computations
591       overlay.add(innerRoi);
592 
593       Polygon polyS2 = s2.asPolygon();
594       setFluoStats(s1.asPolygon(), polyS2, f); // compute FrameStatistics for frame f
595 
596       // compute Vert.fluores field in Outline (FluoMeasurement[] fluores)
597       // use sample points already there
598       if (opts.sampleAtSame && anap.useLocFromCh != -1) {
599         useGivenSamplepoints(o1);
600       } else {
601 
602         ecmH = new OutlineHandler(1, 2);
603         ecmH.setOutline(1, s1);
604         ecmH.setOutline(2, s2);
605 
606         ecmH = ecmMapping.runByANA(ecmH, orgIpr, anap.getCortexWidthPixel());
607 
608         // copy flur data to o1 and save
609         // some nodes may fail to migrate properly so need to check
610         // tracknumbers match
611         Vert v = o1.getHead();
612         Vert v2 = ecmH.getStoredOutline(2).getHead();
613 
614         while (v2.getTrackNum() != v.getTrackNum()) { // check id's match
615           v = v.getNext();
616           if (v.isHead()) {
617             IJ.error("ANA fail");
618             break;
619             // return;
620           }
621         }
622 
623         int vertStart;
624         do {
625           v.setFluoresChannel(v2.fluores[0], opts.channel);
626           v2 = v2.getNext();
627           if (v2.isHead()) {
628             break;
629           }
630           vertStart = v.getTrackNum();
631           // find next vert in o1 that matches v2
632           do {
633             v = v.getNext();
634             v.setFluoresChannel((int) Math.round(v.getX()), (int) Math.round(v.getY()), -1,
635                     opts.channel); // map fail if -1. fix by interpolation
636             if (vertStart == v.getTrackNum()) {
637               System.out.println("ANA fail");
638               return;
639             }
640           } while (v2.getTrackNum() != v.getTrackNum());
641         } while (!v2.isHead());
642 
643         interpolateFailures(o1);
644       }
645 
646       if (opts.normalise) {
647         normalise2Interior(o1, f);
648       }
649       outputH.save(o1, f);
650     }
651   }
652 
653   private void shrink(Outline o) {
654     // shrink outline
655     o.scaleOutline(anap.getCortexWidthPixel(), -anap.stepRes, anap.angleTh, anap.freezeTh);
656 
657     o.unfreezeAll();
658   }
659 
660   private void markFrozenNodesNormal(Outline o) {
661     float[] x;
662     float[] y;
663     ExtendedVector2d norm;
664     PolygonRoi pr;
665     Vert v = o.getHead();
666     do {
667       if (v.isFrozen()) {
668         overlay.setStrokeColor(Color.RED);
669         norm = new ExtendedVector2d(v.getX(), v.getY());
670         norm.addVec(v.getNormal());
671         // norm.addVec(new Vect2d(1,1));
672 
673         x = new float[2];
674         y = new float[2];
675 
676         x[0] = (float) v.getX();
677         x[1] = (float) norm.getX();
678         y[0] = (float) v.getY();
679         y[1] = (float) norm.getY();
680         pr = new PolygonRoi(x, y, 2, Roi.POLYGON);
681         overlay.add(pr);
682       }
683 
684       v = v.getNext();
685     } while (!v.isHead());
686   }
687 
688   /**
689    * Compute statistics.
690    * 
691    * <p>Update {@link com.github.celldynamics.quimp.plugin.ana.ChannelStat} in
692    * {@link com.github.celldynamics.quimp.FrameStatistics}
693    * 
694    * @param outerPoly outerPoly
695    * @param innerPoly innerPoly
696    * @param f frame
697    */
698   private void setFluoStats(Polygon outerPoly, Polygon innerPoly, int f) {
699     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
700     int store = f - anap.startFrame; // frame to index
701     // System.out.println("store: " + store);
702     fluoStats[store].frame = f;
703 
704     orgIpr.setRoi(outerPoly);
705     // this does NOT scale to image
706     ImageStatistics is = ImageStatistics.getStatistics(orgIpr, m, null);
707     double outerAreaRaw = is.area;
708     fluoStats[store].channels[opts.channel].totalFluor = is.mean * outerAreaRaw;
709     fluoStats[store].channels[opts.channel].meanFluor = is.mean;
710 
711     orgIpr.setRoi(innerPoly);
712     is = ImageStatistics.getStatistics(orgIpr, m, null);
713 
714     fluoStats[store].channels[opts.channel].innerArea =
715             QuimpToolsCollection.areaToScale(is.area, anap.scale);
716     fluoStats[store].channels[opts.channel].totalInnerFluor = is.mean * is.area;
717     fluoStats[store].channels[opts.channel].meanInnerFluor = is.mean;
718 
719     fluoStats[store].channels[opts.channel].cortexArea =
720             fluoStats[store].area - fluoStats[store].channels[opts.channel].innerArea; // scaled
721     fluoStats[store].channels[opts.channel].totalCorFluo =
722             fluoStats[store].channels[opts.channel].totalFluor
723                     - fluoStats[store].channels[opts.channel].totalInnerFluor;
724     fluoStats[store].channels[opts.channel].meanCorFluo =
725             fluoStats[store].channels[opts.channel].totalCorFluo / (outerAreaRaw - is.area);
726 
727     fluoStats[store].channels[opts.channel].percCortexFluo =
728             (fluoStats[store].channels[opts.channel].totalCorFluo
729                     / fluoStats[store].channels[opts.channel].totalFluor) * 100;
730     fluoStats[store].channels[opts.channel].cortexWidth = anap.getCortexWidthScale();
731   }
732 
733   private void normalise2Interior(Outline o, int f) {
734     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
735     // interior mean fluorescence is used to normalse membrane measurments
736     int store = f - anap.startFrame; // frame to index
737     Vert v = o.getHead();
738     do {
739       v.fluores[opts.channel].intensity = v.fluores[opts.channel].intensity
740               / fluoStats[store].channels[opts.channel].meanInnerFluor;
741       v = v.getNext();
742     } while (!v.isHead());
743 
744   }
745 
746   private void drawOutlineAsOverlay(Outline o, Color c) {
747     Roi r = o.asFloatRoi();
748     if (r.subPixelResolution()) {
749       System.out.println("is sub pixel");
750     } else {
751       System.out.println("is not sub pixel");
752     }
753     overlay.setStrokeColor(c);
754     overlay.add(r);
755     setupImage.updateAndDraw();
756   }
757 
758   private void investigateChannels(Outline o) {
759     // flu maps
760     int firstEmptyCh = -1;
761     int firstFullCh = -1;
762     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
763 
764     anap.presentData = new int[3];
765     anap.noData = true;
766 
767     Vert v = o.getHead();
768     for (int i = 0; i < 3; i++) {
769       if (v.fluores[i].intensity == -2) { // no data
770         anap.presentData[i] = 0;
771         if (firstEmptyCh == -1) {
772           firstEmptyCh = i;
773         }
774       } else {
775         anap.presentData[i] = 1;
776         IJ.log("Data exists in channel " + (i + 1));
777         anap.noData = false;
778         if (firstFullCh == -1) {
779           firstFullCh = i;
780         }
781         // anap.setCortextWidthScale(fluoStats[0].channels[i].cortexWidth);
782       }
783     }
784 
785     if (QuimPArrayUtils.sumArray(anap.presentData) == 3) {
786       firstEmptyCh = 0;
787     }
788 
789     if (anap.noData) {
790       opts.channel = 0;
791       IJ.log("No previous sample points available.");
792       anap.useLocFromCh = -1;
793     } else {
794       opts.channel = firstEmptyCh;
795       IJ.log("Sample points from channel " + (firstFullCh + 1) + " available.");
796       anap.useLocFromCh = firstFullCh;
797     }
798 
799     v = o.getHead();
800     for (int i = 0; i < 3; i++) {
801       if (v.fluores[i].intensity != -2) {
802         anap.setCortextWidthScale(fluoStats[0].channels[i].cortexWidth);
803       }
804     }
805   }
806 
807   private void interpolateFailures(Outline o) {
808     Vert v = o.getHead();
809     Vert last;
810     Vert nex;
811     double disLtoN; // distance last to nex
812     double disLtoV; // distance last to V
813     double ratio;
814     double intensityDiff;
815     boolean fail;
816     int firstID;
817     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
818     do {
819       fail = false;
820       if (v.fluores[opts.channel].intensity == -1) {
821         IJ.log("\tInterpolated failed node intensity (position: " + v.coord + ")");
822         // failed to map - interpolate with last/next successful
823 
824         last = v.getPrev();
825         firstID = last.getTrackNum();
826         while (last.fluores[opts.channel].intensity == -1) {
827           last = last.getPrev();
828           if (last.getTrackNum() == firstID) {
829             IJ.log("Could not interpolate as all nodes failed");
830             v.fluores[opts.channel].intensity = 0;
831             fail = true;
832           }
833         }
834 
835         nex = v.getNext();
836         firstID = nex.getTrackNum();
837         while (nex.fluores[opts.channel].intensity == -1) {
838           nex = nex.getNext();
839           if (nex.getTrackNum() == firstID) {
840             IJ.log("Could not interpolate as all nodes failed");
841             v.fluores[opts.channel].intensity = 0;
842             fail = true;
843           }
844         }
845 
846         if (fail) {
847           v = v.getNext();
848           continue;
849         }
850 
851         disLtoN = ExtendedVector2d.lengthP2P(last.getPoint(), nex.getPoint());
852         disLtoV = ExtendedVector2d.lengthP2P(last.getPoint(), v.getPoint());
853         ratio = disLtoV / disLtoN;
854         if (ratio > 1) {
855           ratio = 1;
856         }
857         if (ratio < 0) {
858           ratio = 0;
859         }
860         intensityDiff = (nex.fluores[opts.channel].intensity - last.fluores[opts.channel].intensity)
861                 * ratio;
862         v.fluores[opts.channel].intensity = last.fluores[opts.channel].intensity + intensityDiff;
863         if (v.fluores[opts.channel].intensity < 0 || v.fluores[opts.channel].intensity > 255) {
864           IJ.log("Error. Interpolated intensity out of range. Set to zero.");
865           v.fluores[opts.channel].intensity = 0;
866         }
867       }
868 
869       v = v.getNext();
870     } while (!v.isHead());
871   }
872 
873   private void drawSamplePointsFloat(Outline o, int frame) {
874     float x;
875     float y;
876     PointRoi pr;
877     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
878     Vert v = o.getHead();
879     do {
880       x = (float) v.fluores[opts.channel].x;
881       y = (float) v.fluores[opts.channel].y;
882       pr = new PointRoi(x + 0.5, y + 0.5);
883       pr.setPosition(frame);
884       overlay.add(pr);
885       v = v.getNext();
886     } while (!v.isHead());
887   }
888 
889   /**
890    * Add fluorescence data to outline.
891    * 
892    * @param o1 outline to complete o1.fluores[channel] data
893    */
894   private void useGivenSamplepoints(Outline o1) {
895     int x;
896     int y;
897     AnaOptions/../../../../../com/github/celldynamics/quimp/plugin/ana/AnaOptions.html#AnaOptions">AnaOptions opts = (AnaOptions) options;
898     Vert v = o1.getHead();
899     do {
900       x = (int) v.fluores[anap.useLocFromCh].x;
901       y = (int) v.fluores[anap.useLocFromCh].y;
902       // use the same sampling as for ECMM solving
903       v.fluores[opts.channel].intensity = ODEsolver.sampleFluo(orgIpr, x, y);
904       v.fluores[opts.channel].x = x;
905       v.fluores[opts.channel].y = y;
906       v = v.getNext();
907     } while (!v.isHead());
908 
909   }
910 }