View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.FileReader;
6   import java.io.FileWriter;
7   import java.io.IOException;
8   import java.io.PrintWriter;
9   import java.util.Arrays;
10  import java.util.List;
11  
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  
15  import com.github.celldynamics.quimp.BOA_.CustomStackWindow;
16  import com.github.celldynamics.quimp.filesystem.FileExtensions;
17  import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
18  import com.github.celldynamics.quimp.geom.SegmentedShapeRoi;
19  import com.github.celldynamics.quimp.plugin.utils.QuimpDataConverter;
20  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
21  
22  import ij.IJ;
23  import ij.gui.PolygonRoi;
24  import ij.gui.Roi;
25  
26  /**
27   * Store all the snakes computed for one cell across frames and it is responsible for writing them
28   * to file.
29   * 
30   * <p>For any further processing outside QuimP <tt>finalSnakes</tt> should be used.
31   * 
32   * @author rtyson
33   * @author p.baniukiewicz
34   *
35   */
36  public class SnakeHandler extends ShapeHandler<Snake> implements IQuimpSerialize {
37  
38    /**
39     * The Constant LOGGER.
40     */
41    static final Logger LOGGER = LoggerFactory.getLogger(SnakeHandler.class.getName());
42    /**
43     * initial ROI, not stored but rebuilt from snake on load.
44     */
45    private transient Roi roi;
46    /**
47     * initial snake being currently processed.
48     * 
49     * <p>Live snake is preserved for whole {@link SnakeHandler} regardless image frame.
50     */
51    private Snake liveSnake;
52    /**
53     * Series of snakes, result of cell segm. and plugin processing.
54     * 
55     * <p>These are the same as stored in <i>snQP</i> file for each frame and can be plugin processed.
56     */
57    private Snake[] finalSnakes;
58    /**
59     * series of snakes, result of cell segmentation only. Before plugin application.
60     */
61    private Snake[] segSnakes;
62    /**
63     * ID of Snakes stored in this SnakeHandler.
64     */
65    private int ID;
66    /**
67     * If true this snakeHandler (and related to it continuous series of snakes) is not modified
68     * segmentation is called.
69     * 
70     * @see BOA_#runBoa(int, int)
71     * @see #freezeHandler()
72     * @see #unfreezeHandler()
73     */
74    private boolean snakeHandlerFrozen = false;
75  
76    /**
77     * Instantiates a new snake handler. Do not initialise anything.
78     */
79    public SnakeHandler() {
80    }
81  
82    /**
83     * Constructor of SnakeHandler. Stores ROI with object for segmentation.
84     * 
85     * @param r ROI with selected object
86     * @param frame Current frame for which the ROI is taken
87     * @param id Unique Snake ID controlled by Nest object
88     * @throws BoaException on problem with Snake creation
89     */
90    public SnakeHandler(final Roi r, int frame, int id) throws BoaException {
91      this();
92      startFrame = frame;
93      endFrame = BOA_.qState.boap.getFrames();
94      roi = r;
95      // snakes array keeps snakes across frames from current to end. Current
96      // is that one for which cell has been added
97      finalSnakes = new Snake[BOA_.qState.boap.getFrames() - startFrame + 1]; // stored snakes
98      segSnakes = new Snake[BOA_.qState.boap.getFrames() - startFrame + 1]; // stored snakes
99      ID = id;
100     liveSnake = new Snake(r, ID, false);
101     backupLiveSnake(frame);
102   }
103 
104   /**
105    * Copy constructor. Create SnakeHandler from list of already prepared outlines.
106    * 
107    * <p>For every frame it copies provided snake to all three arrays: finalSnakes, segSnakes,
108    * liveSnake and sets first and last frame using data from SegmentedShapeRoi object
109    * 
110    * @param snakes List of outlines that will be propagated from first frame. First frame is wrote
111    *        down in first element of this list
112    * @param id Unique Snake ID controlled by Nest object
113    * @throws BoaException on problem with Snake creation
114    * @see com.github.celldynamics.quimp.geom.SegmentedShapeRoi
115    */
116   public SnakeHandler(List<SegmentedShapeRoi> snakes, int id) throws BoaException {
117     this();
118     startFrame = snakes.get(0).getFrame(); // get first frame from outline
119     finalSnakes = new Snake[BOA_.qState.boap.getFrames() - startFrame + 1]; // stored snakes
120     segSnakes = new Snake[BOA_.qState.boap.getFrames() - startFrame + 1]; // stored snakes
121     ID = id;
122     roi = snakes.get(0); // set initial roi to first snake
123     for (SegmentedShapeRoi ss : snakes) {
124       liveSnake = new Snake(ss.getOutlineasPoints(), ID); // tmp for next two methods
125       backupLiveSnake(ss.getFrame()); // fill segSnakes for frame
126       storeLiveSnake(ss.getFrame()); // fill finalSnakes for frame
127     }
128     endFrame = snakes.get(snakes.size() - 1).getFrame();
129     liveSnake = new Snake(snakes.get(0).getOutlineasPoints(), ID); // set live again for frame
130     // SegmentedShapeRoi contains number of frame that it came from. The are sorted as frames so
131     // last originates from last frame
132     endFrame = snakes.get(snakes.size() - 1).getFrame();
133     LOGGER.debug("Added" + this.toString()); // try toString
134   }
135 
136   /**
137    * Make copy of liveSnake into final snakes array.
138    * 
139    * @param frame Frame for which liveSnake will be copied to
140    */
141   public void storeLiveSnake(int frame) {
142     finalSnakes[frame - startFrame] = null; // delete at current frame
143     finalSnakes[frame - startFrame] = new Snake(liveSnake, ID);
144   }
145 
146   /**
147    * Stores liveSnake (currently processed) in segSnakes array.
148    * 
149    * <p>For one SnakeHandler there is only one liveSnake which is processed "in place" by
150    * segmentation methods. It is virtually moved from frame to frame and copied to final snakes
151    * after segmentation on current frame and processing by plugins. It must be backed up for every
152    * frame to make possible restoring original snakes when active plugin has been deselected.
153    * 
154    * @param frame current frame
155    */
156   public void backupLiveSnake(int frame) {
157     LOGGER.trace("Stored live snake in frame " + frame + " ID " + ID);
158     segSnakes[frame - startFrame] = null; // delete at current frame
159     segSnakes[frame - startFrame] = new Snake(liveSnake, ID);
160   }
161 
162   /**
163    * Makes copy of snake and store it as final snake.
164    * 
165    * @param snake Snake to store
166    * @param frame Frame for which liveSnake will be copied to
167    */
168   public void storeThisSnake(final Snake snake, int frame) {
169     finalSnakes[frame - startFrame] = null; // delete at current frame
170     finalSnakes[frame - startFrame] = new Snake(snake, ID);
171   }
172 
173   /**
174    * Makes copy of snake and store it as segmented snake.
175    * 
176    * @param snake Snake to store
177    * @param frame Frame for which liveSnake will be copied to
178    */
179   public void backupThisSnake(final Snake snake, int frame) {
180     segSnakes[frame - startFrame] = null; // delete at current frame
181     segSnakes[frame - startFrame] = new Snake(snake, ID);
182   }
183 
184   /**
185    * Copy all segSnakes to finalSnakes.
186    */
187   public void copyFromSegToFinal() {
188     for (int i = 0; i < segSnakes.length; i++) {
189       if (segSnakes[i] == null) {
190         finalSnakes[i] = null;
191       } else {
192         finalSnakes[i] = new Snake(segSnakes[i]);
193       }
194     }
195   }
196 
197   /**
198    * Copy all finalSnakes to segSnakes.
199    */
200   public void copyFromFinalToSeg() {
201     for (int i = 0; i < finalSnakes.length; i++) {
202       if (finalSnakes[i] == null) {
203         segSnakes[i] = null;
204       } else {
205         segSnakes[i] = new Snake(finalSnakes[i]);
206       }
207     }
208   }
209 
210   /**
211    * Copy final snake from frame to liveSnake.
212    * 
213    * @param frame frame to copy from (counted from 1)
214    */
215   public void copyFromFinalToLive(int frame) {
216     if (finalSnakes[frame - startFrame] == null) {
217       return;
218     }
219     liveSnake = new Snake(finalSnakes[frame - startFrame]);
220 
221   }
222 
223   /**
224    * Write Snakes from this handler to *.snPQ file. Display also user interface
225    * 
226    * @return true if save has been successful or false if user cancelled it
227    * @throws IOException when the file exists but is a directory rather than a regular file, does
228    *         not exist but cannot be created, or cannot be opened for any other reason
229    */
230   public boolean writeSnakes() throws IOException {
231     String snakeOutFile = BOA_.qState.boap.deductSnakeFileName(ID);
232     LOGGER.debug("Write " + FileExtensions.snakeFileExt + " at: " + snakeOutFile);
233     PrintWriter pw = new PrintWriter(new FileWriter(snakeOutFile), true); // auto flush
234     pw.write("#QuimP11 Node data");
235     pw.write("\n#Node Position\tX-coord\tY-coord\tOrigin\tG-Origin\tSpeed");
236     pw.write("\tFluor_Ch1\tCh1_x\tCh1_y\tFluor_Ch2\tCh2_x\tCh2_y\tFluor_CH3\tCH3_x\tCh3_y\n#");
237 
238     Snake s;
239     for (int i = startFrame; i <= endFrame; i++) {
240       s = getStoredSnake(i);
241       s.setPositions(); // calculate position field
242       pw.write("\n#Frame " + i);
243       write(pw, i + 1, s.getNumPoints(), s.getHead());
244     }
245     pw.close();
246     BOA_.qState.writeParams(ID, startFrame, endFrame);
247 
248     if (BOA_.qState.boap.oldFormat) {
249       writeOldFormats();
250     }
251     return true;
252   }
253 
254   /**
255    * Write one Node to disk (one line in snPQ file).
256    * 
257    * @param pw print writer
258    * @param frame frame number
259    * @param nodes number of nodes
260    * @param n node to write
261    */
262   private void write(final PrintWriter pw, int frame, int nodes, Node n) {
263     pw.print("\n" + nodes);
264 
265     do {
266       // fluo values (x,y, itensity)
267       pw.print("\n" + IJ.d2s(n.position, 6) + "\t" + IJ.d2s(n.getX(), 2) + "\t"
268               + IJ.d2s(n.getY(), 2) + "\t0\t0\t0" + "\t-2\t-2\t-2\t-2\t-2\t-2\t-2\t-2\t-2");
269       n = n.getNext();
270     } while (!n.isHead());
271 
272   }
273 
274   /**
275    * Format before QuimP11.
276    * 
277    * @throws IOException on file problem
278    */
279   private void writeOldFormats() throws IOException {
280     // create file to outpurt old format
281     File old = new File(BOA_.qState.boap.getOutputFileCore().getParent(),
282             BOA_.qState.boap.getFileName() + ".dat");
283     PrintWriter pw = new PrintWriter(new FileWriter(old), true); // auto flush
284 
285     for (int i = 0; i < finalSnakes.length; i++) {
286       if (finalSnakes[i] == null) {
287         break;
288       }
289       if (i != 0) {
290         pw.print("\n");
291       } // no new line at top
292       pw.print(finalSnakes[i].getNumPoints());
293 
294       Node n = finalSnakes[i].getHead();
295       do {
296         pw.print("\n" + IJ.d2s(n.getX(), 6));
297         pw.print("\n" + IJ.d2s(n.getY(), 6));
298         n = n.getNext();
299       } while (!n.isHead());
300     }
301     pw.close();
302 
303     old = new File(BOA_.qState.boap.getOutputFileCore().getParent(),
304             BOA_.qState.boap.getFileName() + ".dat_tn");
305     pw = new PrintWriter(new FileWriter(old), true); // auto flush
306 
307     for (int i = 0; i < finalSnakes.length; i++) {
308       if (finalSnakes[i] == null) {
309         break;
310       }
311       if (i != 0) {
312         pw.print("\n");
313       } // no new line at top
314       pw.print(finalSnakes[i].getNumPoints());
315 
316       Node n = finalSnakes[i].getHead();
317       do {
318         pw.print("\n" + IJ.d2s(n.getX(), 6));
319         pw.print("\n" + IJ.d2s(n.getY(), 6));
320         pw.print("\n" + n.getTrackNum());
321         n = n.getNext();
322       } while (!n.isHead());
323     }
324     pw.close();
325 
326     old = new File(BOA_.qState.boap.getOutputFileCore().getParent(),
327             BOA_.qState.boap.getFileName() + ".dat1");
328     pw = new PrintWriter(new FileWriter(old), true); // auto flush
329 
330     pw.print(IJ.d2s(BOA_.qState.boap.NMAX, 6) + "\n");
331     pw.print(IJ.d2s(BOA_.qState.boap.delta_t, 6) + "\n");
332     pw.print(IJ.d2s(BOA_.qState.segParam.max_iterations, 6) + "\n");
333     pw.print(IJ.d2s(BOA_.qState.segParam.getMin_dist(), 6) + "\n");
334     pw.print(IJ.d2s(BOA_.qState.segParam.getMax_dist(), 6) + "\n");
335     pw.print(IJ.d2s(BOA_.qState.segParam.blowup, 6) + "\n");
336     pw.print(IJ.d2s(BOA_.qState.segParam.sample_tan, 6) + "\n");
337     pw.print(IJ.d2s(BOA_.qState.segParam.sample_norm, 6) + "\n");
338     pw.print(IJ.d2s(BOA_.qState.segParam.vel_crit, 6) + "\n");
339     pw.print(IJ.d2s(BOA_.qState.segParam.f_central, 6) + "\n");
340     pw.print(IJ.d2s(BOA_.qState.segParam.f_contract, 6) + "\n");
341     pw.print(IJ.d2s(BOA_.qState.boap.f_friction, 6) + "\n");
342     pw.print(IJ.d2s(BOA_.qState.segParam.f_image, 6) + "\n");
343     pw.print(IJ.d2s(1.0, 6) + "\n");
344     pw.print(IJ.d2s(BOA_.qState.boap.sensitivity, 6) + "\n");
345     pw.print(IJ.d2s(BOA_.qState.boap.cut_every, 6) + "\n");
346     pw.print("100");
347 
348     pw.close();
349   }
350 
351   /**
352    * Gets the live snake.
353    *
354    * @return the live snake
355    */
356   public Snake getLiveSnake() {
357     return liveSnake;
358   }
359 
360   /**
361    * Gets the backup snake.
362    *
363    * @param f the f
364    * @return the backup snake
365    */
366   public Snake getBackupSnake(int f) {
367     LOGGER.trace("Asked for backup snake at frame " + f + " ID " + ID);
368     if (f - startFrame < 0) {
369       LOGGER.info("Tried to access negative frame store: frame: " + f + ", snakeID: " + ID);
370       return null;
371     }
372     return segSnakes[f - startFrame];
373   }
374 
375   /**
376    * Return final Snake (after plugins) stored for frame f.
377    * 
378    * @param f frame
379    * @return Snake at frame f or null
380    */
381   public Snake getStoredSnake(int f) {
382     if (f - startFrame < 0) {
383       LOGGER.info("Tried to access negative frame store: frame: " + f + ", snakeID: " + ID);
384       return null;
385     }
386     return finalSnakes[f - startFrame];
387   }
388 
389   /**
390    * Validate whether there is any Snake at frame f.
391    * 
392    * @param f frame to validate
393    * @return true if finalSnakes array contains valid Snake at frame f
394    */
395   boolean isStoredAt(int f) {
396     if (f - startFrame < 0) {
397       return false;
398     } else if (f - startFrame >= finalSnakes.length) {
399       return false;
400     } else if (finalSnakes[f - startFrame] == null) {
401       return false;
402     } else {
403       return true;
404     }
405 
406   }
407 
408   /**
409    * Read Snake from file.
410    *
411    * <p>May not be compatible wit old version due to changes in Snake constructor.
412    *
413    * @param inFile file to read
414    * @return value of 1
415    * @throws Exception on problem
416    * @see <a href="link">com.github.celldynamics.quimp.OutlineHandler.readOutlines(File)</a>
417    */
418   @Deprecated
419   public int snakeReader(final File inFile) throws Exception {
420     String thisLine;
421     int nn;
422     int index;
423     double x;
424     double y;
425     Node head;
426     Node n;
427     Node prevn;
428     int s = 0;
429     BufferedReader br = null;
430 
431     try {
432       br = new BufferedReader(new FileReader(inFile));
433 
434       while ((thisLine = br.readLine()) != null) {
435         index = 0;
436         head = new Node(index); // dummy head node
437         head.setHead(true);
438         prevn = head;
439         index++;
440 
441         nn = (int) QuimpToolsCollection.s2d(thisLine);
442 
443         for (int i = 0; i < nn; i++) {
444           x = QuimpToolsCollection.s2d(br.readLine());
445           y = QuimpToolsCollection.s2d(br.readLine());
446 
447           n = new Node(index);
448           n.setX(x);
449           n.setY(y);
450           index++;
451 
452           prevn.setNext(n);
453           n.setPrev(prevn);
454 
455           prevn = n;
456 
457         }
458         // link tail to head
459         prevn.setNext(head);
460         head.setPrev(prevn);
461 
462         finalSnakes[s] = new Snake(head, nn + 1, ID); // dont forget the head
463         // due to compatibility with code above. old versions made copies of list WARN potential
464         // uncompatibility with old code. old constructor made copy of this list and deleted first
465         // dummy node. Now it just covers this list
466         finalSnakes[s].removeNode(head);
467         s++;
468       } // end while
469     } catch (IOException e) {
470       System.err.println("Error: " + e);
471     } finally {
472       if (br != null) {
473         try {
474           br.close();
475         } catch (Exception e) {
476           e.printStackTrace();
477         }
478       }
479     }
480 
481     return 1;
482   }
483 
484   /**
485    * Revive.
486    */
487   public void revive() {
488     liveSnake.alive = true;
489   }
490 
491   /**
492    * Kill.
493    */
494   public void kill() {
495     liveSnake.alive = false;
496   }
497 
498   /**
499    * Reset snakes in handler. Recreate them using stored ROI.
500    *
501    * @throws BoaException on snake creation problem
502    */
503   public void reset() throws BoaException {
504     liveSnake = new Snake(roi, ID, false);
505   }
506 
507   /**
508    * Gets the id of handler.
509    *
510    * @return the id
511    */
512   public int getID() {
513     return ID;
514   }
515 
516   /**
517    * Checks if is live snake is live.
518    *
519    * @return true, if is live
520    */
521   public boolean isLive() {
522     return liveSnake.alive;
523   }
524 
525   /**
526    * Delete snake stored at at frame.
527    *
528    * @param frame the frame to delete snake from
529    */
530   void deleteStoreAt(int frame) {
531     if (frame - startFrame < 0) {
532       BOA_.log("Tried to delete negative frame store\n\tframe:" + frame + "\n\tsnakeID:" + ID);
533     } else {
534       finalSnakes[frame - startFrame] = null;
535       segSnakes[frame - startFrame] = null;
536     }
537   }
538 
539   /**
540    * Delete snakes from frame to end.
541    *
542    * @param frame the start frame to delete from
543    */
544   void deleteStoreFrom(int frame) {
545     for (int i = frame; i <= BOA_.qState.boap.getFrames(); i++) {
546       deleteStoreAt(i);
547     }
548     endFrame = frame;
549   }
550 
551   /**
552    * Prepare current frame for segmentation.
553    * 
554    * <p>Create liveSnake using final snake stored in previous frame or use original ROI for
555    * creating new Snake
556    * 
557    * @param f Current segmented frame
558    */
559   void resetForFrame(int f) {
560     try {
561       if (BOA_.qState.segParam.use_previous_snake) {
562         // set to last segmentation ready for blowup
563         liveSnake = new Snake((PolygonRoi) this.getStoredSnake(f - 1).asFloatRoi(), ID);
564       } else {
565         liveSnake = new Snake(roi, ID, false);
566       }
567     } catch (Exception e) {
568       BOA_.log("Could not reset live snake form frame" + f);
569       LOGGER.debug(e.getMessage(), e);
570     }
571   }
572 
573   /**
574    * Store ROI as snake in finalSnakes.
575    * 
576    * @param r roi to create Snake from
577    * @param frame frame
578    */
579   void storeRoi(final PolygonRoi r, int frame) {
580     try {
581       Snakeimp/Snake.html#Snake">Snake snake = new Snake(r, ID);
582       snake.calcCentroid();
583       this.deleteStoreAt(frame);
584       storeThisSnake(snake, frame);
585       backupThisSnake(snake, frame);
586       // BOA_.log("Storing ROI snake " + ID + " frame " + f);
587     } catch (Exception e) {
588       BOA_.log("Could not store ROI");
589       LOGGER.debug(e.getMessage(), e);
590     }
591   }
592 
593   /**
594    * Find the first missing contour at series of frames and set end frame to the previous one.
595    */
596   void findLastFrame() {
597     for (int i = startFrame; i <= BOA_.qState.boap.getFrames(); i++) {
598       if (!isStoredAt(i)) {
599         endFrame = i - 1;
600         return;
601       }
602     }
603     endFrame = BOA_.qState.boap.getFrames();
604   }
605 
606   /**
607    * Return true if this handler is frozen.
608    * 
609    * <p>Frozen handler is excluded from frame segmentation.
610    * 
611    * @return status of this handler.
612    * @see #freezeHandler()
613    * @see #unfreezeHandler()
614    */
615   public boolean isSnakeHandlerFrozen() {
616     return snakeHandlerFrozen;
617   }
618 
619   /**
620    * Prevent this handler from segmentation.
621    * 
622    * @see #unfreezeHandler()
623    * @see #isSnakeHandlerFrozen()
624    * @see CustomStackWindow#itemStateChanged(java.awt.event.ItemEvent) (zoom action)
625    */
626   public void freezeHandler() {
627     snakeHandlerFrozen = true;
628   }
629 
630   /**
631    * Unlock handler.
632    * 
633    * @see #freezeHandler()
634    * @see #isSnakeHandlerFrozen()
635    * @see CustomStackWindow#itemStateChanged(java.awt.event.ItemEvent) (zoom action)
636    */
637   public void unfreezeHandler() {
638     snakeHandlerFrozen = false;
639   }
640 
641   /*
642    * (non-Javadoc)
643    * 
644    * @see java.lang.Object#toString()
645    */
646   @Override
647   public String toString() {
648     return "SnakeHandler [liveSnake=" + liveSnake + ", finalSnakes=" + Arrays.toString(finalSnakes)
649             + ", ID=" + ID + ", startFrame=" + startFrame + ", endFrame=" + endFrame + "]";
650   }
651 
652   /**
653    * Prepare all Snake stored in this SnakeHandler for saving.
654    */
655   @Override
656   public void beforeSerialize() {
657     if (liveSnake != null) {
658       liveSnake.beforeSerialize(); // convert liveSnake to array
659     }
660     for (Snake s : finalSnakes) {
661       if (s != null) {
662         s.beforeSerialize(); // convert finalSnakes to array
663       }
664     }
665     for (Snake s : segSnakes) {
666       if (s != null) {
667         s.beforeSerialize(); // convert segSnakes to array
668       }
669     }
670     findLastFrame(); // set correct first-last frame field
671   }
672 
673   /**
674    * Prepare all Snake stored in this SnakeHandler for loading.
675    */
676   @Override
677   public void afterSerialize() throws Exception {
678     if (liveSnake != null) {
679       liveSnake.afterSerialize();
680     }
681     for (Snake s : finalSnakes) {
682       if (s != null) {
683         s.afterSerialize();
684       }
685     }
686     for (Snake s : segSnakes) {
687       if (s != null) {
688         s.afterSerialize();
689       }
690     }
691     // restore roi as first snake from segmented snakes
692     if (segSnakes.length > 0) {
693       int i = 0;
694       while (i < segSnakes.length && segSnakes[i++] == null) {
695         ; // find first not null snake
696       }
697       QuimpDataConverter/utils/QuimpDataConverter.html#QuimpDataConverter">QuimpDataConverter dc = new QuimpDataConverter(segSnakes[--i]);
698       // rebuild roi from snake
699       roi = new PolygonRoi(dc.getFloatX(), dc.getFloatY(), Roi.FREEROI);
700     }
701   }
702 }