View Javadoc
1   package com.github.celldynamics.quimp.plugin.protanalysis;
2   
3   import java.io.FileNotFoundException;
4   import java.io.PrintWriter;
5   import java.nio.file.Paths;
6   import java.util.ArrayList;
7   import java.util.HashSet;
8   
9   import org.slf4j.Logger;
10  import org.slf4j.LoggerFactory;
11  
12  import com.github.celldynamics.quimp.Outline;
13  import com.github.celldynamics.quimp.QParamsQconf;
14  import com.github.celldynamics.quimp.QuimpException;
15  import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
16  import com.github.celldynamics.quimp.filesystem.FileExtensions;
17  import com.github.celldynamics.quimp.plugin.AbstractPluginQconf;
18  import com.github.celldynamics.quimp.plugin.QuimpPluginException;
19  import com.github.celldynamics.quimp.plugin.qanalysis.STmap;
20  
21  import ij.ImagePlus;
22  
23  // TODO Update UML below
24  /*
25   * !>
26   * @startuml doc-files/Prot_Analysis_1_UML.png 
27   * salt
28   * {
29   *  {^"Visual tracking"
30   *    {+
31   *    Text field with help
32   *    ...
33   *    // Select points with   // 
34   *    // CTRL key//
35   *    }
36   *    **Selected:**             4
37   *    {
38   *    [to ROI] | [from ROI]
39   *    }
40   *    [Clear all points]
41   *    { (X) Static | () Dynamic}
42   *    [X] Show tracked point
43   *    [X] Smooth tracks
44   *    ^Outline color  ^
45   *    [ ] Open in new image
46   *    [Track           ]
47   *    [Clear Overlay   ] 
48   *  }
49   *  {^"Maps"
50   *  ^Select cell     ^
51   *    { [Mot ] | [Convex] | [Fluo] }
52   *  }
53   *  {^"Tables and plots"
54   *    [X] Plot selected
55   *    ^Select cell     ^
56   *    { (X) Ch1 | ( ) Ch2 | ( ) Ch3}
57   *    ...
58   *    {
59   *    [X] X-Centr | [ ] Y-Centr
60   *    [ ] Displ | [ ] Distance 
61   *    [ ] Direct | [ ] Speed
62   *    [ ] Perim | [ ] Elong
63   *    [ ] Circ | [ ] Area
64   *    ==== | ===
65   *    [ ] Total fl | [ ] Mean fl
66   *    [ ] Cortex wd | [ ] Cyto ar
67   *    [ ] Total ctf | [ ] Mean ctf
68   *    [ ] Cortex ar | [ ] Total ctf
69   *    [ ] Mean ctf |     
70   *   }
71   *  ===
72   *  [Generate           ]
73   *  }
74   *  {^"Ploar plots"
75   *  [Click point        ]
76   *  [Get from ROI       ]
77   *  Selected point:     127,45
78   *  ^Select relative to^
79   *  [Show plots         ]
80   *  }
81   * }
82   * @enduml
83   * 
84   * @startuml doc-files/Prot_Analysis_2_UML.png
85   * 
86   * usecase UC0 as "**Load QCONF**
87   * --
88   * Open QCONF file
89   * ..UC0.."
90   * 
91   * usecase UC1 as "**Select points on contour**
92   * --
93   * Use can click and select multiple
94   * points in cell contour.
95   * ==
96   * This works within all frames
97   * ..UC1..
98   * "
99   * 
100  * usecase UC2 as "**Transfer points to ROI**
101  * -- 
102  * Selected points can be
103  * copied to ROI manager
104  * ..UC2..
105  * "
106  * 
107  * usecase UC3 as "**Transfer points from ROI**
108  * --
109  * Copy points from ROI
110  * manager and show them
111  * in contour.
112  * ==
113  * * Delete old points
114  * * Deal with different
115  * frames
116  * ..UC3..
117  * "
118  * 
119  * usecase UC4 as "**Show selected points**
120  * --
121  * Show points for each frame
122  * as user slide slider
123  * ..UC4..
124  * "
125  * 
126  * usecase UC5 as "**Clear points**
127  * --
128  * Remove all points
129  * ..UC5.."
130  * 
131  * usecase UC6 as "**Track points**
132  * --
133  * Perform tracking for
134  * selected points
135  * ==
136  * Regards static or dynamic
137  * ..UC6..
138  * "
139  * 
140  * usecase UC6b as "**Plot intensity**
141  * --
142  * Plot intensity change
143  * over tracking point
144  * ==
145  * This hsould be default
146  * for each tracking
147  * ..UC6b..
148  * "
149  * 
150  * usecase UC6a as "**Save tracks**
151  * --
152  * Save tracks after
153  * tracking to csv
154  * file
155  * ==
156  * ..UC6a..
157  * "
158  * 
159  * usecase UC7 as "**Display tracking**
160  * --
161  * Show results on screen
162  * ==
163  * * Depending on settings show
164  * in original window or separate
165  * * Show dynamic or static
166  * * Color outline
167  * * Smooth if option selected
168  * ..UC7..
169  * "
170  * 
171  * usecase UC8 as "**Color outline**
172  * --
173  * Show outline in selected
174  * color
175  * ==
176  * Colorscale scaled to range
177  * ..UC8..
178  * "
179  * 
180  * usecase UC9 as "**Clear overlay**
181  * --
182  * Clear tracking
183  * ==
184  * * Clear original window
185  * * Remove points
186  * ..UC9.."
187  * 
188  * usecase UC10 as "**Plot maps**
189  * --
190  * Show selected maps
191  * ==
192  * Together with **UC10a**
193  * ROI allows to select
194  * maxim on the map and
195  * track them
196  * ..UC10.."
197  * 
198  * usecase UC10a as "**Plot raw maps**
199  * --
200  * Show selected maps as unscaled
201  * ==
202  * Together with transferring
203  * ROI allows to select
204  * maxim on the map and
205  * track them
206  * ..UC10a.."
207  * 
208  * usecase UC11 as "**Plot 2d**
209  * --
210  * Plot selected metrics as
211  * 2D plot in function of
212  * frames
213  * ==
214  * * Can open many plots at
215  * once
216  * * Should allow to select
217  * cell and channel
218  * ..UC11.."
219  * 
220  * usecase UC12 as "**Copy to table**
221  * --
222  * Copy selected metrics to
223  * IJ table.
224  * ==
225  * * Should allow to select
226  * cell and channel
227  * ..UC12..
228  * "
229  * 
230  * usecase UC13 as "**Polar plots**
231  * --
232  * Generate polar plots
233  * ==
234  * * Save or show (depending on IJ
235  * features in showing vector files)
236  * * Show in log if saved
237  * ..UC13..
238  * "
239  * 
240  * usecase UC14 as "**Select origin point**
241  * --
242  * Allow to select origin
243  * point for polar plots
244  * ==
245  * * click on screen
246  * * Relative to screen
247  * * Relative to cell
248  * ..UC14..
249  * "
250  * 
251  * usecase UC15 as "**Predefined trackings**
252  * --
253  * Allow to track points
254  * from predefined settings
255  * ==
256  * Like:
257  * * Max from motility map
258  * ..UC15..
259  * "
260  * 
261  * usecase UC16 as "**Smooth tracks**
262  * --
263  * Apply smoothing to tracks
264  * ==
265  * * If option selected
266  * ..UC16..
267  * "
268  * 
269  * note bottom of (UC12) : Decide how to deal\nwith many tables
270  * 
271  * note right of (UC8)
272  * Decide if standalone
273  * Now dependend from UC7
274  * Tracking must be done first and 
275  * tracking map shown but this can
276  * be a standalone option as well
277  * end note
278  * 
279  * User -> (UC0)
280  * User -> (UC1)
281  * (UC1) ..> (UC4) : <<include>>
282  * User -> (UC12)
283  * User -> (UC5)
284  * User -> (UC3)
285  * (UC3) ..> (UC5) : <<extend>>
286  * User -> (UC2)
287  * User -> (UC6)
288  * (UC6) ..> (UC7) : <<include>>
289  * (UC6) ..> (UC6a) : <<include>>
290  * (UC6) ..> (UC6b) : <<include>>
291  * (UC6) ..> (UC15) : <<include>>
292  * (UC7) ..> (UC8) : <<include>>
293  * (UC7) ..> (UC16) : <<include>>
294  * User -> (UC9)
295  * (UC9) ..> (UC5) : <<extend>>
296  * User --> (UC10)
297  * (UC10) ..> (UC10a) : <<include>>
298  * User --> (UC11)
299  * User --> (UC13)
300  * (UC13) ..> (UC14) : <<include>>
301  * @enduml
302  * !<
303  */
304 /**
305  * Main class for Protrusion Analysis module.
306  * 
307  * <p>Contain business logic for protrusion analysis. The UI is built by
308  * {@link com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisUi}. The communication
309  * between
310  * these modules is through
311  * {@link com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisOptions}
312  * 
313  * <br>
314  * <img src="doc-files/Prot_Analysis_1_UML.png"/><br>
315  * 
316  * <br>
317  * <img src="doc-files/Prot_Analysis_2_UML.png"/><br>
318  * 
319  * @author p.baniukiewicz
320  */
321 public class Prot_Analysis extends AbstractPluginQconf {
322 
323   static final Logger LOGGER = LoggerFactory.getLogger(Prot_Analysis.class.getName());
324 
325   private static String thisPluginName = "Protrusion Analysis";
326 
327   ImagePlus image = null;
328   // points selected by user for current frame, cleared on each slice shift. In image coordinates
329   PointHashSet selected = new PointHashSet();
330   // updated on each slice, outlines for current frame
331   ArrayList<Outline> outlines = new ArrayList<>();
332   /**
333    * Instance of module UI.
334    * 
335    * <p>Initialised by this constructor.
336    */
337   ProtAnalysisUi frameGui;
338 
339   /**
340    * Current frame, 0-based.
341    */
342   int currentFrame = 0;
343 
344   /**
345    * Default constructor.
346    * 
347    */
348   public Prot_Analysis() {
349     // here we do not load file! so can not create ui
350     super(new ProtAnalysisOptions(), thisPluginName);
351     selected = new PointHashSet();
352     outlines = new ArrayList<>();
353   }
354 
355   /**
356    * Constructor that allows to provide own configuration parameters.
357    * 
358    * <p>Immediately executes all computations.
359    * 
360    * @param paramString parameter string.
361    * @throws QuimpPluginException on error
362    */
363   public Prot_Analysis(String paramString) throws QuimpPluginException {
364     // 1. Load File
365     // 2. Run runFormQconf (but we overwritten it here to be empty so nothing happens yet)
366     super(paramString, new ProtAnalysisOptions(), thisPluginName);
367     selected = new PointHashSet();
368     outlines = new ArrayList<>();
369     createUIInstance();
370   }
371 
372   /**
373    * Create UI instance.
374    *
375    * <p>Require loaded file.
376    */
377   private void createUIInstance() {
378     ImagePlus image = getImage(); // obain image from loaded file
379     LOGGER.trace("Attached image " + image.toString());
380     frameGui = new ProtAnalysisUi(this, image); // build UI (we need image)
381   }
382 
383   /**
384    * get current sink.
385    * 
386    * @return sink type
387    */
388   MessageSinkTypes getSink() {
389     return errorSink;
390   }
391 
392   /*
393    * (non-Javadoc)
394    * 
395    * @see com.github.celldynamics.quimp.plugin.PluginTemplate#validate()
396    */
397   @Override
398   protected void validate() throws QuimpException {
399     super.validate();
400     qconfLoader.getEcmm();
401     qconfLoader.getQ();
402     qconfLoader.getStats();
403   }
404 
405   /**
406    * Write cell statistic and protrusion statistics to files.
407    * 
408    * <p>Currently not used might be useful. Example of use:
409    * 
410    * <pre>
411    * <code>
412    * // write stats, and add to table
413    * writeStats(h, mapCell, mf, trackCollection).cellStatistics.addCellToCellTable(rt);
414    * </code>
415    * </pre>
416    * 
417    * @param h Cell number
418    * @param mapCell cell map
419    * @param mf maxima finder object
420    * @param trackCollection track collection object
421    * @return ProtStat instance of object that keeps cell statistics. Can be used to form e.g.
422    *         table with results.
423    * 
424    * @throws FileNotFoundException if stats can not be written
425    */
426   private ProtStat writeStats(int h, STmap mapCell, MaximaFinder mf,
427           TrackCollection trackCollection) throws FileNotFoundException {
428     QParamsQconf/../../../../../com/github/celldynamics/quimp/QParamsQconf.html#QParamsQconf">QParamsQconf qp = (QParamsQconf) qconfLoader.getQp();
429     // Maps are correlated in order with Outlines in DataContainer.
430     // write data
431     PrintWriter cellStatFile = new PrintWriter(
432             Paths.get(qp.getPath(), qp.getFileName() + "_" + h + FileExtensions.cellStatSuffix)
433                     .toFile());
434     PrintWriter protStatFile = new PrintWriter(
435             Paths.get(qp.getPath(), qp.getFileName() + "_" + h + FileExtensions.protStatSuffix)
436                     .toFile());
437     new ProtStat(mf, trackCollection, qp.getLoadedDataContainer().getStats().sHs.get(h), mapCell)
438             .writeProtrusion(protStatFile, h);
439 
440     ProtStatimp/plugin/protanalysis/ProtStat.html#ProtStat">ProtStat cellStat = new ProtStat(mf, trackCollection,
441             qp.getLoadedDataContainer().getStats().sHs.get(h), mapCell);
442 
443     cellStat.writeCell(cellStatFile, h);
444     protStatFile.close();
445     cellStatFile.close();
446     return cellStat;
447   }
448 
449   /*
450    * (non-Javadoc)
451    * 
452    * @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#showUi(boolean)
453    */
454   @Override
455   public void showUi(boolean val) throws Exception {
456     // we need to load file here because UI require image from QCONF
457     // execute this only if run with default constructor and empty run("") method (plugin from menu)
458     // if (options.paramFile == null || options.paramFile.isEmpty()) {
459     // loadFile(options.paramFile);
460     // if (qconfLoader != null && qconfLoader.getQp() != null) {
461     // options.paramFile = qconfLoader.getQp().getParamFile().getAbsolutePath();
462     // }
463     // createUIInstance();
464     // }
465     if (frameGui != null) {
466       frameGui.showUI(true);
467       // gui.setVisible(true);
468     } else {
469       LOGGER.error("You need image (and QCONF) to see UI");
470     }
471   }
472 
473   /*
474    * (non-Javadoc)
475    * 
476    * @see com.github.celldynamics.quimp.plugin.IQuimpPlugin#about()
477    */
478   @Override
479   public String about() {
480     return "Protrusion Analysis Plugin.\n" + "Author: Piotr Baniukiewicz\n"
481             + "mail: p.baniukiewicz@warwick.ac.uk";
482   }
483 
484   /*
485    * (non-Javadoc)
486    * 
487    * @see ij.plugin.PlugIn#run(java.lang.String)
488    */
489   @Override
490   public void run(String arg) {
491     super.run(arg);
492   }
493 
494   /*
495    * (non-Javadoc)
496    * 
497    * @see com.github.celldynamics.quimp.plugin.AbstractOptionsParser#parseArgumentString(java.lang.
498    * String)
499    */
500   @Override
501   protected boolean parseArgumentString(String arg) throws QuimpPluginException {
502     // override only to get always true at output, hack that will cause execute executer in run()
503     // method, and then LoadFile and runFromQconf. We need this because we do not support parameters
504     // and want to see UI every time but we need loaded file to build UI.
505     // With this approach we have the same path for IJ plugin run and IJ macro run (only file):
506 
507     // run like IJ macro - scenario 1
508     // Prot_Analysis pa = new Prot_Analysis();
509     // pa.run("");
510 
511     // IJ from script - scenario 2
512     // Prot_Analysis pa = new Prot_Analysis();
513     // pa.run("{paramFile:src/test/Resources-static/ProtAnalysisTest/fluoreszenz-test.QCONF}");
514     // Without this hack scenario 1 will try to open UI (showUI) without loaded file.
515     super.parseArgumentString(arg);
516     return true;
517   }
518 
519   @Override
520   protected void runFromPaqp() throws QuimpException {
521     throw new QuimpException("This plugin does not support paQP files.");
522 
523   }
524 
525   /*
526    * (non-Javadoc)
527    * 
528    * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#runFromQconf()
529    */
530   @Override
531   protected void runFromQconf() throws QuimpException {
532     // we do not support run from macro so just show UI here
533     createUIInstance();
534     try {
535       showUi(true);
536     } catch (Exception e) {
537       throw new QuimpException(e);
538     }
539   }
540 
541   /**
542    * Get image associated with loaded QCONF.
543    * 
544    * @return image or null if image could not be loaded
545    */
546   ImagePlus getImage() {
547     if (image == null) {
548       if (getQconfLoader() != null) {
549         image = getQconfLoader().getImage();
550       } else {
551         throw new RuntimeException("Can not obtain image");
552       }
553     }
554     return image;
555   }
556 
557   /**
558    * Get gui.
559    * 
560    * @return Main window class.
561    */
562   ProtAnalysisUi getGui() {
563     return frameGui;
564   }
565 
566   /**
567    * Keep list of selected points.
568    * 
569    * <p>Reason of this class is that {@link ProtAnalysisUi} and {@link CustomCanvas} operate on
570    * 2D
571    * images without knowledge about frame, which is needed. They also use java.awt.Point as main
572    * class. Therefore the point selected in the image by user in {@link CustomCanvas} contains only
573    * x,y and cell number (all stored in {@link PointCoords}). Frame number is appended with
574    * {@link PointHashSet#add(PointCoords)}, called in this context (frame is stored in
575    * Prot_Analysis.currentFrame)
576    * 
577    * <p>Field {@link Prot_Analysis#currentFrame} is updated by {@link ProtAnalysisUi} whereas
578    * point operations happen in {@link CustomCanvas}. {@link Prot_Analysis} integrates all
579    * informations.
580    * 
581    * @author p.baniukiewicz
582    *
583    */
584   @SuppressWarnings("serial")
585   class PointHashSet extends HashSet<PointCoords> {
586 
587     /**
588      * Add information about current frame to point.
589      * 
590      * @param e 2D point from current frame. Field {@link PointCoords#frame} will be overwritten by
591      *        current frame.
592      * @return true if point exists in set.
593      */
594     @Override
595     public boolean add(PointCoords e) {
596       e.frame = currentFrame;
597       LOGGER.debug("Added point: " + e);
598       return super.add(e);
599     }
600 
601     /**
602      * Add {@link PointCoords} with frame number.
603      * 
604      * <p>In contrary to {@link #add(PointCoords)} this method does not override frame number in
605      * specified {@link PointCoords}.
606      * 
607      * @param e 2D point from current frame.
608      * @return true if point exists in set.
609      */
610     boolean addRaw(PointCoords e) {
611       LOGGER.debug("Added raw point: " + e);
612       return super.add(e);
613     }
614 
615     /**
616      * Remove point from set.
617      * 
618      * @param e point to remove
619      * @return true if exist
620      */
621     public boolean remove(PointCoords e) {
622       e.frame = currentFrame;
623       return super.remove(e);
624     }
625 
626   }
627 
628 }