View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.util.ArrayList;
6   import java.util.Collections;
7   
8   import org.slf4j.Logger;
9   import org.slf4j.LoggerFactory;
10  
11  import com.github.celldynamics.quimp.filesystem.FileExtensions;
12  import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
13  import com.github.celldynamics.quimp.plugin.ParamList;
14  import com.github.celldynamics.quimp.plugin.binaryseg.BinarySegmentation_;
15  import com.github.celldynamics.quimp.plugin.engine.PluginFactory;
16  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
17  
18  import ij.ImagePlus;
19  import ij.io.FileInfo;
20  
21  /*
22   * //!>
23   * @startuml doc-files/BOAState_UML.png
24   * BOAState *-- "1" BOAp
25   * BOAState *-- "1" SegParam
26   * BOAState <|.. IQuimpSerialize
27   * BOAState : +boap
28   * BOAState : +SegParam
29   * BOAState : -segParamSnapshots
30   * BOAState : +SnakePluginList
31   * BOAState : +Nest
32   * BOAState : ... 
33   * BOAState : +beforeSerialize()
34   * BOAState : +afterSerialize()
35   * BOAState : +store()
36   * BOAState : +storeOnlyEdited()
37   * SegParam : +nodeList
38   * SegParam : +imageForce
39   * SegParam : +equals()
40   * SegParam : +hashCode()
41   * SegParam : +setDefaults()
42   * SegParam : ...()
43   * SegParam : ...
44   * IQuimpSerialize : +beforeSerialize()
45   * IQuimpSerialize : +afterSerialize()
46   * BOAp : -imageScale
47   * BOAp : -scaleAdjusted
48   * BOAp : +frame
49   * BOAp : ~zoom
50   * BOAp : ...
51   * BOAp : ...()
52   * @enduml
53   * //!<
54   */
55  /**
56   * Hold current BOA state that can be serialized.
57   * 
58   * <p>This class is composed from two inner classes:
59   * <ul>
60   * <li>BOAp - holds internal state of BOA plugin, maintained mainly for compatibility reasons
61   * <li>SegParam - holds segmentation parameters, exposed to UI
62   * </ul>
63   * 
64   * <p>Moreover there are several fields related to new features of QuimP like storing internal state
65   * for every frame separately or SnakePlugins.<br>
66   * 
67   * <img src="doc-files/BOAState_UML.png"/><br>
68   * 
69   * @author p.baniukiewicz
70   * @see Serializer
71   */
72  public class BOAState implements IQuimpSerialize {
73  
74    private static final Logger LOGGER = LoggerFactory.getLogger(BOAState.class.getName());
75    /**
76     * Reference to segmentation parameters. Holds current parameters.
77     * 
78     * <p>On every change of BOA state it is stored as copy in segParamSnapshots for current frame.
79     * This is why that field is transient
80     * 
81     * @see com.github.celldynamics.quimp.BOA_#run(String)
82     * @see <a href=
83     *      "http://www.trac-wsbc.linkpc.net:8080/trac/QuimP/wiki/ConfigurationHandling">ConfigurationHandling</a>
84     */
85    public transient SegParam segParam;
86    /**
87     * Reference to old BOAp class, keeps internal state of BOA.
88     */
89    public BOAp boap;
90    /**
91     * Instance of binary segmentation plugin that converts BW masks into snakes.
92     * 
93     * <p>This is regular plugin but handled separately from SnakePlugins and it is not provided as
94     * external jar
95     */
96    public transient BinarySegmentation_ binarySegmentationPlugin;
97    /**
98     * Configuration of BinarySegmentation plugin if it was used. Used during saving boa state.
99     */
100   ParamList binarySegmentationParam;
101   /**
102    * Keep snapshots of SegParam objects for every frame separately.
103    */
104   private ArrayList<SegParam> segParamSnapshots;
105   /**
106    * Keep snapshots of SnakePluginList objects for every frame separately.
107    * 
108    * <p>Plugin configurations are stored as well (but without plugin references)
109    */
110   public ArrayList<SnakePluginList> snakePluginListSnapshots;
111   /**
112    * List of plugins selected in plugin stack and information if they are active or not. This field
113    * is not serializable because snakePluginListSnapshots keeps configurations for
114    * every frame.
115    * 
116    * <p>Holds current parameters as the main object not referenced in BOAp On every change of BOA
117    * state it is stored as copy in snakePluginListSnapshots for current frame. This is why that
118    * field is transient
119    * 
120    * @see com.github.celldynamics.quimp.SnakePluginList
121    * @see com.github.celldynamics.quimp.BOA_#run(String)
122    * @see com.github.celldynamics.quimp.BOAState#store(int)
123    */
124   public transient SnakePluginList snakePluginList;
125   /**
126    * Reference to Nest, which is serializable as well. This is main object not referenced in other
127    * parts of QuimP
128    */
129   public Nest nest;
130   /**
131    * Store information whether for current frame button <b>Edit</b> was used. Do not indicate that
132    * any of Snakes was edited.
133    */
134   public ArrayList<Boolean> isFrameEdited;
135 
136   /**
137    * Hold user parameters for segmentation algorithm.
138    * 
139    * <p>Most of those parameters are available from BOA user menu. This class supports cloning and
140    * comparing.
141    * 
142    * @author p.baniukiewicz
143    * @see com.github.celldynamics.quimp.BOAState
144    */
145   class SegParam {
146     /**
147      * Number of nodes on ROI edge.
148      * 
149      * <p>Cell segmentation parameter. Check user manual or our publications for details.
150      */
151     private double nodeRes;
152     /**
153      * Distance to blow up chain.
154      * 
155      * <p>Check user manual or our publications for details.
156      */
157     public int blowup;
158     /**
159      * Critical velocity.
160      * 
161      * <p>Cell segmentation parameter. Check user manual or our publications for details.
162      */
163     public double vel_crit;
164     /**
165      * Central force.
166      * 
167      * <p>Cell segmentation parameter. Check user manual or our publications for details.
168      */
169     public double f_central;
170     /**
171      * Image force.
172      * 
173      * <p>Cell segmentation parameter. Check user manual or our publications for details.
174      */
175     public double f_image;
176     /**
177      * Max iterations per contraction.
178      * 
179      * <p>Cell segmentation parameter. Check user manual or our publications for details.
180      */
181     public int max_iterations;
182     /**
183      * Sample tan.
184      * 
185      * <p>Cell segmentation parameter. Check user manual or our publications for details.
186      */
187     public int sample_tan;
188     /**
189      * Sample norm.
190      * 
191      * <p>Cell segmentation parameter. Check user manual or our publications for details.
192      */
193     public int sample_norm;
194     /**
195      * Contraction force.
196      * 
197      * <p>Cell segmentation parameter. Check user manual or our publications for details.
198      */
199     public double f_contract;
200     /**
201      * Final shrink.
202      * 
203      * <p>Cell segmentation parameter. Check user manual or our publications for details.
204      */
205     public double finalShrink;
206     // Switch Params
207     /**
208      * Next contraction begins with prev chain.
209      */
210     public boolean use_previous_snake;
211     /**
212      * Decide whether to show paths on screen.
213      * 
214      * <p>Cell segmentation parameter. Check user manual or our publications for details.
215      */
216     public boolean showPaths;
217     /**
218      * Whether to act as an expanding snake.
219      * 
220      * <p>Visualisation option parameter. Check user manual or our publications for details.
221      * 
222      * @deprecated Not sure what it actually does, will be disabled in GUI but keep here for
223      *             compatibility
224      */
225     public boolean expandSnake;
226     /**
227      * Min distance between nodes.
228      * 
229      * <p>Cell segmentation parameter.
230      */
231     private double min_dist;
232     /**
233      * Max distance between nodes.
234      * 
235      * <p>Cell segmentation parameter.
236      */
237     private double max_dist;
238     /**
239      * This is sign for forces that eventually contracts or expands the snake.
240      * 
241      * <p>true value is for contraction (standard behaviour).
242      * 
243      * @see #reverseForces()
244      */
245     public boolean contractingDirection = true;
246 
247     /**
248      * Copy constructor.
249      * 
250      * @param src object to copy
251      */
252     public SegParam(final SegParam src) {
253       this.nodeRes = src.nodeRes;
254       this.blowup = src.blowup;
255       this.vel_crit = src.vel_crit;
256       this.f_central = src.f_central;
257       this.f_image = src.f_image;
258       this.max_iterations = src.max_iterations;
259       this.sample_tan = src.sample_tan;
260       this.sample_norm = src.sample_norm;
261       this.f_contract = src.f_contract;
262       this.finalShrink = src.finalShrink;
263       this.use_previous_snake = src.use_previous_snake;
264       this.showPaths = src.showPaths;
265       this.expandSnake = src.expandSnake;
266       this.min_dist = src.min_dist;
267       this.max_dist = src.max_dist;
268       this.contractingDirection = src.contractingDirection;
269     }
270 
271     /**
272      * Sets default values of parameters.
273      */
274     public SegParam() {
275       setDefaults();
276       // defaults for GUI settings
277       showPaths = false;
278       use_previous_snake = true; // next contraction begins with last chain
279       expandSnake = false; // set true to act as an expanding snake
280 
281     }
282 
283     /*
284      * (non-Javadoc)
285      * 
286      * @see java.lang.Object#hashCode()
287      */
288     @Override
289     public int hashCode() {
290       final int prime = 31;
291       int result = 1;
292       result = prime * result + blowup;
293       result = prime * result + (expandSnake ? 1231 : 1237);
294       long temp;
295       temp = Double.doubleToLongBits(f_central);
296       result = prime * result + (int) (temp ^ (temp >>> 32));
297       temp = Double.doubleToLongBits(f_contract);
298       result = prime * result + (int) (temp ^ (temp >>> 32));
299       temp = Double.doubleToLongBits(max_dist);
300       result = prime * result + (int) (temp ^ (temp >>> 32));
301       temp = Double.doubleToLongBits(min_dist);
302       result = prime * result + (int) (temp ^ (temp >>> 32));
303       temp = Double.doubleToLongBits(f_image);
304       result = prime * result + (int) (temp ^ (temp >>> 32));
305       temp = Double.doubleToLongBits(finalShrink);
306       result = prime * result + (int) (temp ^ (temp >>> 32));
307       result = prime * result + max_iterations;
308       temp = Double.doubleToLongBits(nodeRes);
309       result = prime * result + (int) (temp ^ (temp >>> 32));
310       result = prime * result + sample_norm;
311       result = prime * result + sample_tan;
312       result = prime * result + (showPaths ? 1231 : 1237);
313       result = prime * result + (use_previous_snake ? 1231 : 1237);
314       temp = Double.doubleToLongBits(vel_crit);
315       result = prime * result + (int) (temp ^ (temp >>> 32));
316       result = prime * result + (contractingDirection ? 1231 : 1237);
317       return result;
318     }
319 
320     /*
321      * (non-Javadoc)
322      * 
323      * @see java.lang.Object#equals(java.lang.Object)
324      */
325     @Override
326     public boolean equals(Object obj) {
327       if (this == obj) {
328         return true;
329       }
330       if (obj == null) {
331         return false;
332       }
333       if (!(obj instanceof SegParam)) {
334         return false;
335       }
336       SegParam other = (SegParam) obj;
337       if (blowup != other.blowup) {
338         return false;
339       }
340       if (expandSnake != other.expandSnake) {
341         return false;
342       }
343       if (Double.doubleToLongBits(f_central) != Double.doubleToLongBits(other.f_central)) {
344         return false;
345       }
346       if (Double.doubleToLongBits(min_dist) != Double.doubleToLongBits(other.min_dist)) {
347         return false;
348       }
349       if (Double.doubleToLongBits(max_dist) != Double.doubleToLongBits(other.max_dist)) {
350         return false;
351       }
352       if (Double.doubleToLongBits(f_contract) != Double.doubleToLongBits(other.f_contract)) {
353         return false;
354       }
355       if (Double.doubleToLongBits(f_image) != Double.doubleToLongBits(other.f_image)) {
356         return false;
357       }
358       if (Double.doubleToLongBits(finalShrink) != Double.doubleToLongBits(other.finalShrink)) {
359         return false;
360       }
361       if (max_iterations != other.max_iterations) {
362         return false;
363       }
364       if (Double.doubleToLongBits(nodeRes) != Double.doubleToLongBits(other.nodeRes)) {
365         return false;
366       }
367       if (sample_norm != other.sample_norm) {
368         return false;
369       }
370       if (sample_tan != other.sample_tan) {
371         return false;
372       }
373       if (showPaths != other.showPaths) {
374         return false;
375       }
376       if (use_previous_snake != other.use_previous_snake) {
377         return false;
378       }
379       if (Double.doubleToLongBits(vel_crit) != Double.doubleToLongBits(other.vel_crit)) {
380         return false;
381       }
382       if (contractingDirection != other.contractingDirection) {
383         return false;
384       }
385       return true;
386     }
387 
388     /**
389      * Return nodeRes.
390      * 
391      * @return nodeRes field
392      */
393     public double getNodeRes() {
394       return nodeRes;
395     }
396 
397     /**
398      * Set nodeRes field and calculate <tt>min_dist</tt> and <tt>max_dist</tt>.
399      * 
400      * @param d resolution
401      */
402     public void setNodeRes(double d) {
403       nodeRes = d;
404       if (nodeRes < 1) {
405         min_dist = 1; // min distance between nodes
406         max_dist = 2.3; // max distance between nodes
407         return;
408       }
409       min_dist = nodeRes; // min distance between nodes
410       max_dist = nodeRes * 1.9; // max distance between nodes
411     }
412 
413     /**
414      * Set default parameters for contour matching algorithm.
415      * 
416      * <p>These parameters are external - available for user to set in GUI.
417      */
418     public void setDefaults() {
419       setNodeRes(6.0);
420       blowup = 20; // distance to blow up chain
421       vel_crit = 0.005;
422       f_central = 0.04;
423       f_image = 0.2; // image force
424       max_iterations = 4000; // max iterations per contraction
425       sample_tan = 4;
426       sample_norm = 12;
427       f_contract = 0.04;
428       finalShrink = 3d;
429       contractingDirection = true;
430     }
431 
432     /**
433      * Gets the max dist.
434      *
435      * @return the max dist
436      */
437     public double getMax_dist() {
438       return max_dist;
439     }
440 
441     /**
442      * Gets the min dist.
443      *
444      * @return the min dist
445      */
446     public double getMin_dist() {
447       return min_dist;
448     }
449 
450     /*
451      * (non-Javadoc)
452      * 
453      * @see java.lang.Object#toString()
454      */
455     @Override
456     public String toString() {
457       return "SegParam [nodeRes=" + nodeRes + ", blowup=" + blowup + ", velCrit=" + vel_crit
458               + ", centralForce=" + f_central + ", imageForce=" + f_image + ", maxIterations="
459               + max_iterations + ", sampleTan=" + sample_tan + ", sampleNorm=" + sample_norm
460               + ", contractForce=" + f_contract + ", finalShrink=" + finalShrink
461               + ", use_previous_snake=" + use_previous_snake + ", showPaths=" + showPaths
462               + ", expandSnake=" + expandSnake + ", min_dist=" + min_dist + ", max_dist=" + max_dist
463               + " contractingDirection=" + contractingDirection + "]";
464     }
465 
466     /**
467      * Reverse forces and then changes direction of snake contracting.
468      * 
469      * <p>Related to {@link #contractingDirection}.
470      */
471     public void reverseForces() {
472       if (contractingDirection == true) { // default contracting
473         finalShrink = Math.abs(finalShrink);
474         f_image = Math.abs(f_image);
475         f_central = Math.abs(f_central);
476         // vel_crit = Math.abs(vel_crit);
477         blowup = 20;// Math.abs(blowup);
478       } else { // expand - reversed forces
479         finalShrink = -1.0 * Math.abs(finalShrink); // if user set - in spinner, we want it overrid
480         f_image = -1.0 * Math.abs(f_image);
481         f_central = -1.0 * Math.abs(f_central);
482         // vel_crit = -1.0 * Math.abs(vel_crit);
483         blowup = -1;// Math.abs(blowup);
484       }
485     }
486 
487   } // end of SegParam
488 
489   /**
490    * Holds parameters defining snake and controlling contour matching algorithm.
491    * 
492    * <p>BOAp is static class contains internal as well as external parameters used to define snake
493    * and to control contour matching algorithm. There are also several basic get/set methods for
494    * accessing selected parameters, setting default
495    * {@link com.github.celldynamics.quimp.BOAState.SegParam#setDefaults() values} and
496    * writing/reading
497    * these (external) parameters to/from disk. File format used for storing data in files is
498    * defined at {@link QParams} class.
499    * 
500    * <p>External parameters are those related to algorithm options whereas internal are those
501    * related
502    * to internal settings of algorithm, GUI and whole plugin
503    * 
504    * <p>This class is shared among different QuimP components.
505    * 
506    * @author rtyson
507    * @see com.github.celldynamics.quimp.QParams
508    */
509   public class BOAp {
510     /**
511      * handle to original file obtained from IJ (usually image opened).
512      * 
513      * <p>This filed is serialised for further verification of QCONF and image matching but not
514      * restored on load to not overwrite currently opened image name.
515      */
516     private File orgFile;
517     /**
518      * Corename for output, initially contains path and name without extension from orgFile and
519      * without cell number.
520      * 
521      * <p>Can be changed by user on save Change of this field causes change of the
522      * <tt>fileName</tt>
523      */
524     private File outputFileCore;
525     /**
526      * <tt>outputFileCore</tt> but without path and extension.
527      */
528     private String fileName;
529     /**
530      * read in parameter file.
531      */
532     transient QParams readQp;
533     // internal parameters
534     /**
535      * Maximum number of nodes (% of starting nodes).
536      */
537     int NMAX; // name related to QCONF file do not change
538 
539     /**
540      * The delta t.
541      */
542     double delta_t;
543 
544     /**
545      * The sensitivity.
546      */
547     double sensitivity;
548 
549     /**
550      * The f friction.
551      */
552     double f_friction;
553     /**
554      * Number of frames in stack.
555      */
556     private int FRAMES; // name related to QCONF file do not change
557     private int WIDTH; // name related to QCONF file do not change
558     private int HEIGHT; // name related to QCONF file do not change
559     /**
560      * Cut loops in chain every X frames.
561      */
562     int cut_every;
563     /**
564      * output old QuimP format.
565      */
566     boolean oldFormat;
567     /**
568      * save snake data.
569      */
570     boolean saveSnake;
571 
572     /**
573      * distance between centroids at which contact is tested for.
574      */
575     double proximity;
576     /**
577      * Proximity of nodes to freeze when blowing up.
578      */
579     double proxFreeze;
580 
581     /**
582      * The saved one.
583      */
584     boolean savedOne;
585     /**
586      * Use json pretty format.
587      */
588     boolean savePretty = true;
589 
590     /**
591      * Current frame, CustomStackWindow.updateSliceSelector().
592      */
593     public int frame;
594     /**
595      * Snake selected in zoom selector, negative value if 100% view.
596      */
597     public int snakeToZoom = -1;
598 
599     /**
600      * The single image.
601      */
602     boolean singleImage;
603 
604     /**
605      * The params exist.
606      */
607     String paramsExist; // on startup check if defaults are needed to set
608 
609     /**
610      * The zoom.
611      */
612     boolean zoom;
613 
614     /**
615      * The do delete.
616      */
617     boolean doDelete;
618     /**
619      * true if we are in freeze mode."
620      */
621     transient boolean doFreeze;
622 
623     /**
624      * The do delete seg.
625      */
626     boolean doDeleteSeg;
627     /**
628      * is select a cell for editing active.
629      */
630     boolean editMode;
631     /**
632      * currently editing cell iD. -1 if not editing
633      */
634     int editingID;
635 
636     /**
637      * The use sub pixel.
638      */
639     boolean useSubPixel = true;
640     /**
641      * use to test how many times a method is called.
642      */
643     int callCount;
644 
645     private double imageScale; // scale of image read from ip
646     private boolean scaleAdjusted = false; // true when adjusted in constructor
647 
648     /**
649      * Get scale of the image.
650      * 
651      * @return the imageScale
652      */
653     public double getImageScale() {
654       return imageScale;
655     }
656 
657     /**
658      * Set scale of teh image.
659      * 
660      * @param imageScale the imageScale to set
661      */
662     public void setImageScale(double imageScale) {
663       if (imageScale == 0) {
664         this.imageScale = 1;
665         this.scaleAdjusted = true;
666       } else {
667         this.imageScale = imageScale;
668       }
669     }
670 
671     /**
672      * Get scaleAdjusted.
673      * 
674      * @return the scaleAdjusted
675      */
676     public boolean isScaleAdjusted() {
677       return scaleAdjusted;
678     }
679 
680     private double imageFrameInterval;
681     private boolean fIAdjusted = false;
682 
683     /**
684      * Get imageFrameInterval.
685      * 
686      * @return the imageFrameInterval
687      */
688     public double getImageFrameInterval() {
689       return imageFrameInterval;
690     }
691 
692     /**
693      * Set imageFrameInterval.
694      * 
695      * @param imageFrameInterval the imageFrameInterval to set
696      */
697     public void setImageFrameInterval(double imageFrameInterval) {
698       if (imageFrameInterval == 0) {
699         this.imageFrameInterval = 1;
700         this.fIAdjusted = true;
701       } else {
702         this.imageFrameInterval = imageFrameInterval;
703       }
704     }
705 
706     /**
707      * Get fIAdjusted.
708      * 
709      * @return the fIAdjusted
710      */
711     public boolean isfIAdjusted() {
712       return fIAdjusted;
713     }
714 
715     /**
716      * Default constructor.
717      */
718     public BOAp() {
719       savedOne = false;
720       // nestSize = 0;
721       // internal parameters
722       NMAX = 250; // maximum number of nodes (% of starting nodes)
723       delta_t = 1.;
724       sensitivity = 0.5;
725       cut_every = 8; // cut loops in chain every X interations
726       oldFormat = false; // output old QuimP format?
727       saveSnake = true; // save snake data
728       proximity = 150; // distance between centroids at which contact is tested for
729       proxFreeze = 1; // proximity of nodes to freeze when blowing up
730       f_friction = 0.6;
731       doDelete = false;
732       doDeleteSeg = false;
733       zoom = false;
734       editMode = false;
735       editingID = -1;
736       callCount = 0;
737       frame = 1;
738       savePretty = true;
739     }
740 
741     /**
742      * Plot or not snakes after processing by plugins.
743      * 
744      * <p>If true both snakes, after segmentation and after filtering are plotted.
745      */
746     boolean isProcessedSnakePlotted = true;
747 
748     /**
749      * Define if first node of Snake (head) is plotted or not.
750      */
751     boolean isHeadPlotted = false;
752 
753     /**
754      * If cell is zoomed, all parameter changes influence only this cell. (UI)
755      */
756     boolean isZoomFreeze = false;
757 
758     /**
759      * When any plugin fails this field defines how QuimP should behave.
760      * 
761      * <p>When it is true QuimP breaks process of segmentation and do not store filtered snake in
762      * SnakeHandler.
763      * TODO Implement this feature
764      * 
765      * @see <a href="http://www.trac-wsbc.linkpc.net:8080/trac/QuimP/ticket/81">ticket#81</a>
766      */
767     boolean stopOnPluginError = true;
768 
769     /**
770      * Initialize internal parameters of BOA plugin from ImagePlus.
771      * 
772      * <p>Most of these parameters are related to state machine of BOA. There are also parameters
773      * related to internal state of Active Contour algorithm. Defaults for parameters available
774      * for user are set in {@link com.github.celldynamics.quimp.BOAState.SegParam#setDefaults()}
775      * 
776      * @param ip Reference to segmented image passed from IJ
777      */
778     public void setup(final ImagePlus ip) {
779       FileInfo fileinfo = ip.getOriginalFileInfo();
780       if (fileinfo == null) {
781         orgFile = new File(File.separator, ip.getTitle());
782         setOutputFileCore(File.separator + ip.getTitle());
783       } else {
784         orgFile = new File(fileinfo.directory, fileinfo.fileName);
785         setOutputFileCore(fileinfo.directory + orgFile.getName());
786       }
787 
788       FRAMES = ip.getStackSize(); // get number of frames
789 
790       WIDTH = ip.getWidth();
791       HEIGHT = ip.getHeight();
792       paramsExist = "YES";
793 
794     }
795 
796     /**
797      * Get FRAMES.
798      * 
799      * @return the fRAMES
800      */
801     public int getFrames() {
802       return FRAMES;
803     }
804 
805     /**
806      * Get WIDTH.
807      * 
808      * @return the wIDTH
809      */
810     public int getWidth() {
811       return WIDTH;
812     }
813 
814     /**
815      * Get HEIGHT.
816      * 
817      * @return the hEIGHT
818      */
819     public int getHeight() {
820       return HEIGHT;
821     }
822 
823     /**
824      * Get orgFile - image opened in BOA.
825      * 
826      * @return the orgFile
827      */
828     public File getOrgFile() {
829       return orgFile;
830     }
831 
832     /**
833      * Set orgFile.
834      * 
835      * @param orgFile the orgFile to set
836      */
837     public void setOrgFile(File orgFile) {
838       this.orgFile = orgFile;
839     }
840 
841     /**
842      * Get outputFileCore.
843      * 
844      * @return the outputFileCore
845      * @see #outputFileCore
846      */
847     public File getOutputFileCore() {
848       return outputFileCore;
849     }
850 
851     /**
852      * Set outputFileCore.
853      * 
854      * <p>From compatibility reasons outputFileCore is File type.
855      * 
856      * @param outputFileCore the outputFileCore to set. should not contain extension
857      */
858     private void setOutputFileCore(File outputFileCore) {
859       this.outputFileCore = outputFileCore;
860       fileName = outputFileCore.getName();
861     }
862 
863     /**
864      * Set setOutputFileCore.
865      * 
866      * @param outputFileCore the outputFileCore to set
867      */
868     public void setOutputFileCore(String outputFileCore) {
869       setOutputFileCore(new File(QuimpToolsCollection.removeExtension(outputFileCore)));
870     }
871 
872     /**
873      * Get fileName.
874      * 
875      * @return the fileName
876      */
877     public String getFileName() {
878       return fileName;
879     }
880 
881     /**
882      * Generate Snake file name basing on ID.
883      * 
884      * <p>Mainly to have this in one place. Use outputFileCore that is set by user choice of output
885      * 
886      * @param id of Snake
887      * @return Full path to file with extension
888      */
889     public String deductSnakeFileName(int id) {
890       LOGGER.trace(getOutputFileCore().getAbsoluteFile().toString());
891       return getOutputFileCore().getAbsoluteFile() + "_" + id + FileExtensions.snakeFileExt;
892     }
893 
894     /**
895      * Generate stats file name basing on ID.
896      * 
897      * <p>Mainly to have this in one place. Use outputFileCore that is set by user choice of output
898      * 
899      * @param id of Snake
900      * @return Full path to file with extension
901      */
902     public String deductStatsFileName(int id) {
903       return getOutputFileCore().getAbsoluteFile() + "_" + id + FileExtensions.statsFileExt;
904     }
905 
906     /**
907      * Generate main param file (old) name basing on ID.
908      * 
909      * <p>Mainly to have this in one place. Use outputFileCore that is set by user choice of output
910      * 
911      * @param id of Snake
912      * @return Full path to file with extension
913      */
914     public String deductParamFileName(int id) {
915       return getOutputFileCore().getAbsoluteFile() + "_" + id + FileExtensions.configFileExt;
916     }
917 
918     /**
919      * Generate main filter config file name.
920      * 
921      * <p>Mainly to have this in one place. Use outputFileCore that is set by user choice of output
922      * 
923      * @return Full path to file with extension
924      */
925     public String deductFilterFileName() {
926       return getOutputFileCore().getAbsoluteFile() + FileExtensions.pluginFileExt;
927     }
928 
929     /**
930      * Generate main param file (new) name.
931      * 
932      * <p>Mainly to have this in one place. Use outputFileCore that is set by user choice of output
933      * 
934      * @return Full path to file with extension
935      */
936     public String deductNewParamFileName() {
937       return getOutputFileCore().getAbsoluteFile() + FileExtensions.newConfigFileExt;
938     }
939 
940   }
941 
942   /**
943    * Default constructor. Should be used with care as some image properties are not initialised
944    * here.
945    * 
946    * @see BOAState#BOAState(ImagePlus)
947    */
948   public BOAState() {
949     boap = new BOAp(); // build BOAp
950     segParam = new SegParam(); // and SegParam
951     snakePluginList = new SnakePluginList();
952     binarySegmentationParam = new ParamList(); // save empty list even if plugin not used
953     nest = new Nest();
954   }
955 
956   /**
957    * Construct BOAState object for given stack size. Initializes other internal fields.
958    * 
959    * @param ip current image object, can be \c null. In latter case only subclasses are
960    *        initialized
961    */
962   public BOAState(final ImagePlus ip) {
963     this(ip, null, null);
964 
965   }
966 
967   /**
968    * Construct full base object filling snapshots with default but valid objects.
969    * 
970    * @param ip current image object, can be \c null. In latter case only subclasses are
971    *        initialized
972    * @param pf PluginFactory used for creating plugins
973    * @param vu ViewUpdater reference
974    */
975   public BOAState(final ImagePlus ip, final PluginFactory pf, final ViewUpdater vu) {
976     this();
977     snakePluginList = new SnakePluginList(BOA_.NUM_SNAKE_PLUGINS, pf, vu);
978     if (ip == null) {
979       return;
980     }
981 
982     initializeSnapshots(ip, pf, vu);
983     // set scale read from image, set also scaleAdjusted if scale from image is wrong
984     boap.setImageScale(ip.getCalibration().pixelWidth);
985     // set interval read from image, set also fIAdjusted if scale from image is wrong
986     boap.setImageFrameInterval(ip.getCalibration().frameInterval);
987     boap.setup(ip);
988   }
989 
990   private void initializeSnapshots(final ImagePlus ip, final PluginFactory pf,
991           final ViewUpdater vu) {
992     int numofframes = ip.getStackSize();
993     // fill snaphots with default values
994     segParamSnapshots = new ArrayList<SegParam>(Collections.nCopies(numofframes, new SegParam()));
995     snakePluginListSnapshots = new ArrayList<SnakePluginList>(
996             Collections.nCopies(numofframes, new SnakePluginList(BOA_.NUM_SNAKE_PLUGINS, pf, vu)));
997     isFrameEdited = new ArrayList<Boolean>(Collections.nCopies(numofframes, false));
998     LOGGER.debug("Initialize storage of size: " + numofframes + " size of segParams: "
999             + segParamSnapshots.size());
1000   }
1001 
1002   /**
1003    * Make snapshot of current objects state.
1004    * 
1005    * @param frame actual frame numbered from 1
1006    * @see com.github.celldynamics.quimp.SnakePluginList
1007    */
1008   public void store(int frame) {
1009     LOGGER.debug(
1010             "Data stored at frame:" + frame + " size of segParams is " + segParamSnapshots.size());
1011     segParamSnapshots.set(frame - 1, new SegParam(segParam));
1012     // download Plugin config as well
1013     snakePluginListSnapshots.set(frame - 1, snakePluginList.getDeepCopy());
1014   }
1015 
1016   /**
1017    * Restore from snapshots data to current one.
1018    * 
1019    * <p>Technically makes reference links between snapshots and fields keeping hot data.
1020    * 
1021    * @param frame current frame
1022    * @see com.github.celldynamics.quimp.SnakePluginList
1023    */
1024   public void restore(int frame) {
1025     LOGGER.trace("Data restored from frame:" + frame);
1026     SegParam tmp = segParamSnapshots.get(frame - 1);
1027     if (tmp != null) {
1028       segParam = tmp;
1029     }
1030     snakePluginList = snakePluginListSnapshots.get(frame - 1);
1031     snakePluginList.uploadPluginsConfig(); // same plugin across frames is represented by the same
1032     // instance, every time frame is restored plugin configuration must be updated to current
1033   }
1034 
1035   /**
1036    * Copy {@link SegParam} data from given snapshot to current frame.
1037    * 
1038    * <p>Exchange data between {@link #segParamSnapshots} and {@link #segParam} fields making
1039    * copy of object but not reference like {@link #restore(int)}.
1040    * 
1041    * @param sourceFrame valid index of frame to copy parameters from. Numbered from 1
1042    * @see BOAState#copyPluginListFromSnapshot(int)
1043    */
1044   public void copySegParamFromSnapshot(int sourceFrame) {
1045     SegParam tmp = segParamSnapshots.get(sourceFrame - 1);
1046     if (tmp != null) {
1047       segParam = new SegParam(tmp);
1048     }
1049   }
1050 
1051   /**
1052    * Copy {@link SnakePluginList} data from given snapshot to current frame.
1053    * 
1054    * <p>Exchange data between {@link #snakePluginListSnapshots} and {@link #snakePluginList} fields
1055    * making copy of object but not reference like {@link #restore(int)}.
1056    * 
1057    * @param sourceFrame valid index of frame to copy parameters from. Numbered from 1
1058    * @see #copySegParamFromSnapshot(int)
1059    */
1060   public void copyPluginListFromSnapshot(int sourceFrame) {
1061     snakePluginList.clear(); // clear current
1062     // snakePluginListSnapshots keeps only params and name, whereas deepCopy requires real
1063     // instance to download config from it.
1064     SnakePluginList previous = snakePluginListSnapshots.get(sourceFrame - 1);
1065     // So create instances on copy first (config will be uploaded here)
1066     previous.afterSerialize();
1067     // and make deep copy downloading config back
1068     snakePluginList = previous.getDeepCopy();
1069     snakePluginList.afterSerialize(); // instance all plugins
1070     // qState.store(qState.boap.frame); // copy to current snapshot
1071     // recalculatePlugins(); // update screen
1072 
1073   }
1074 
1075   /**
1076    * Store information whether frame was edited only.
1077    * 
1078    * <p>Can be called when global state does not change, e.g. user clicked Edit button so
1079    * parameters and plugins have not been modified.
1080    * 
1081    * @param frame current frame numbered from 1
1082    */
1083   public void storeOnlyEdited(int frame) {
1084     isFrameEdited.set(frame - 1, true);
1085   }
1086 
1087   /**
1088    * Reset BOAState class.
1089    * 
1090    * <p>This method does:
1091    * <ol>
1092    * <li>Closes all windows from plugins
1093    * <li>Cleans all snapshots
1094    * <li>Set default parameters
1095    * </ol>
1096    * 
1097    * @param ip current image object, can be null. In latter case only subclasses are
1098    *        initialized
1099    * @param pf PluginFactory used for creating plugins
1100    * @param vu ViewUpdater reference
1101    */
1102   public void reset(final ImagePlus ip, final PluginFactory pf, final ViewUpdater vu) {
1103     if (snakePluginList != null) {
1104       snakePluginList.clear();
1105     }
1106     if (snakePluginListSnapshots != null) {
1107       for (SnakePluginList sp : snakePluginListSnapshots) {
1108         if (sp != null) {
1109           sp.clear();
1110         }
1111       }
1112     }
1113     // boap = new BOAp(); // must be disabled becaue boap keeps some data related to loaded
1114     // image that never changes
1115     segParam = new SegParam(); // and SegParam
1116     initializeSnapshots(ip, pf, vu);
1117   }
1118 
1119   /**
1120    * Should be called before serialization.
1121    * 
1122    * <p>Creates ArrayLists from Shape.
1123    */
1124   @Override
1125   public void beforeSerialize() {
1126     nest.beforeSerialize(); // prepare snakes
1127     if (binarySegmentationPlugin != null) {
1128       binarySegmentationParam = binarySegmentationPlugin.getPluginConfig();
1129     } else {
1130       binarySegmentationParam = new ParamList();
1131     }
1132 
1133     // snakePluginListSnapshots and segParamSnapshots do not need beforeSerialize()
1134   }
1135 
1136   /*
1137    * (non-Javadoc)
1138    * 
1139    * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#afterSerialize()
1140    */
1141   @Override
1142   public void afterSerialize() throws Exception {
1143     LOGGER.trace("After serialize called");
1144     nest.afterSerialize(); // rebuild Shape<T extends PointsList<T>> from ArrayList used for storing
1145     snakePluginList.afterSerialize(); // assumes that snakePluginList contains valid refs to Facory
1146 
1147     // recreate file objects. without these lines the File objects after serialization are
1148     // created but they do not keep information about root, e.g. getAbsolutePath() returns
1149     // program_path/stored_in_json_path
1150     boap.outputFileCore = new File(boap.outputFileCore.toString());
1151     boap.orgFile = new File(boap.orgFile.toString());
1152     // restore local segParam to be first from segParamSnapshots
1153     if (segParamSnapshots.size() > 0) {
1154       if (segParamSnapshots.get(0) != null) {
1155         segParam = new SegParam(segParamSnapshots.get(0));
1156         // otherwise segParam will be default owng to SegParam constructor
1157       }
1158     }
1159   }
1160 
1161   /**
1162    * Write set of snake parameters to disk.
1163    * 
1164    * <p>writeParams method creates <i>paQP</i> master file, referencing other associated files and
1165    * <i>csv</i> file with statistics.
1166    * 
1167    * <p>Compatibility layer with old QuimP
1168    * 
1169    * @param sid ID of cell. If many cells segmented in one time, QuimP produces separate parameter
1170    *        file for every of them
1171    * @param startF Start frame (typically beginning of stack)
1172    * @param endF End frame (typically end of stack)
1173    * @see com.github.celldynamics.quimp.QParams
1174    * @see <a href=
1175    *      "http://www.trac-wsbc.linkpc.net:8080/trac/QuimP/ticket/176#comment:3">ticket/176</a>
1176    */
1177   public void writeParams(int sid, int startF, int endF) {
1178     try {
1179       if (boap.saveSnake) {
1180         File paramFile = new File(boap.deductParamFileName(sid));
1181         QParamsuimp/QParams.html#QParams">QParams qp = new QParams(paramFile);
1182         qp.setSegImageFile(boap.orgFile);
1183         qp.setSnakeQP(new File(boap.deductSnakeFileName(sid)));
1184         qp.setStatsQP(new File(boap.deductStatsFileName(sid)));
1185         qp.setImageScale(BOA_.qState.boap.imageScale);
1186         qp.setFrameInterval(BOA_.qState.boap.imageFrameInterval);
1187         qp.setStartFrame(startF);
1188         qp.setEndFrame(endF);
1189         qp.nmax = boap.NMAX;
1190         qp.setBlowup(segParam.blowup);
1191         qp.maxIterations = segParam.max_iterations;
1192         qp.sampleTan = segParam.sample_tan;
1193         qp.sampleNorm = segParam.sample_norm;
1194         qp.deltaT = boap.delta_t;
1195         qp.setNodeRes(segParam.nodeRes);
1196         qp.velCrit = segParam.vel_crit;
1197         qp.centralForce = segParam.f_central;
1198         qp.contractForce = segParam.f_contract;
1199         qp.imageForce = segParam.f_image;
1200         qp.frictionForce = boap.f_friction;
1201         qp.finalShrink = segParam.finalShrink;
1202         qp.sensitivity = boap.sensitivity;
1203 
1204         qp.writeParams();
1205       }
1206     } catch (IOException e) {
1207       LOGGER.debug(e.getMessage(), e);
1208       LOGGER.error("Could not write parameters to file", e.getMessage());
1209     }
1210   }
1211 
1212   /**
1213    * Read set of snake parameters from disk.
1214    * 
1215    * <p>readParams method reads <i>paQP</i> master file, referencing other associated files.
1216    * 
1217    * @param paramFile paQP configuration file
1218    * 
1219    * @return Status of operation, true when file has been loaded successfully, false when file has
1220    *         not been opened correctly or {@link com.github.celldynamics.quimp.QParams#readParams()}
1221    *         returned false
1222    * @see com.github.celldynamics.quimp.QParams
1223    * @see <a href=
1224    *      "http://www.trac-wsbc.linkpc.net:8080/trac/QuimP/ticket/176#comment:3">ticket/176</a>
1225    */
1226   public boolean readParams(File paramFile) {
1227     boap.readQp = new QParams(paramFile);
1228 
1229     try {
1230       boap.readQp.readParams();
1231     } catch (QuimpException e) {
1232       BOA_.log("Failed to read parameter file " + e.getMessage());
1233       return false;
1234     }
1235     loadParams(boap.readQp);
1236     BOA_.log("Successfully read parameters");
1237     return true;
1238   }
1239 
1240   /**
1241    * Build internal boa state from QParams object.
1242    * 
1243    * <p><b>Warning</b>
1244    * 
1245    * <p>frame interval and image scale are not loaded by this function due to compatibility with BOA
1246    * workflow - user set scale on beginning and then use this method to load paQP files.
1247    * 
1248    * @param readQp QParams object
1249    */
1250   public void loadParams(QParams readQp) {
1251 
1252     boap.NMAX = readQp.nmax;
1253     segParam.blowup = readQp.getBlowup();
1254     segParam.max_iterations = readQp.maxIterations;
1255     segParam.sample_tan = readQp.sampleTan;
1256     segParam.sample_norm = readQp.sampleNorm;
1257     boap.delta_t = readQp.deltaT;
1258     segParam.nodeRes = readQp.getNodeRes();
1259     segParam.vel_crit = readQp.velCrit;
1260     segParam.f_central = readQp.centralForce;
1261     segParam.f_contract = readQp.contractForce;
1262     segParam.f_image = readQp.imageForce;
1263 
1264     if (readQp.paramFormat == QParams.QUIMP_11) {
1265       segParam.finalShrink = readQp.finalShrink;
1266     }
1267     boap.readQp = readQp;
1268     // copy loaded data to snapshots
1269     for (int f = readQp.getStartFrame(); f <= readQp.getEndFrame(); f++) {
1270       store(f);
1271     }
1272   }
1273 }