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.security.InvalidParameterException;
10  
11  import org.slf4j.Logger;
12  import org.slf4j.LoggerFactory;
13  
14  import com.github.celldynamics.quimp.filesystem.FileExtensions;
15  import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
16  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
17  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
18  
19  import ij.IJ;
20  
21  /**
22   * Collection of outlines for subsequent frames (<it>f1</it> and <it>f2</it>) for one cell.
23   * 
24   * @author tyson
25   * @author p.baniukiewicz
26   */
27  public class OutlineHandler extends ShapeHandler<Outline> implements IQuimpSerialize {
28  
29    /**
30     * The Constant LOGGER.
31     */
32    static final Logger LOGGER = LoggerFactory.getLogger(OutlineHandler.class.getName());
33    /**
34     * Array of given cell outlines found for frames (<tt>startFrame</tt> and <tt>endFrame</tt>).
35     */
36    private Outline[] outlines;
37    private transient QParams qp;
38    // all transient fields are rebuild in afterSerialzie findStatLimits()
39    private transient int size;
40  
41    /**
42     * The max coor.
43     */
44    public transient ExtendedVector2d maxCoor;
45  
46    /**
47     * The min coor.
48     */
49    public transient ExtendedVector2d minCoor;
50  
51    /**
52     * The mig limits.
53     */
54    // min and max limits
55    public transient double[] migLimits;
56  
57    /**
58     * The flu lims.
59     */
60    public transient double[][] fluLims;
61  
62    /**
63     * The curv limits.
64     */
65    public transient double[] curvLimits;
66    /**
67     * longest outline in outlines.
68     */
69    public transient double maxLength = 0;
70  
71    /**
72     * The read success.
73     */
74    public transient boolean readSuccess;
75  
76    /**
77     * Instantiates a new outline handler.
78     *
79     * @param params the params
80     */
81    public OutlineHandler(QParams params) {
82      qp = params;
83      startFrame = qp.getStartFrame();
84      endFrame = qp.getEndFrame();
85  
86      // System.out.println("start frame: " + startFrame + ", endframe: " +
87      // endFrame);
88  
89      if (!readOutlines(qp.getSnakeQP())) { // initialize also arrays by findStatsLimits()
90        IJ.error("Failed to read in snakQP (OutlineHandler:36)");
91        readSuccess = false;
92        size = 0;
93      } else {
94        size = outlines.length;
95        readSuccess = true;
96      }
97    }
98  
99    /**
100    * Copy constructor.
101    * 
102    * @param src to copy from
103    */
104   public OutlineHandler/OutlineHandler.html#OutlineHandler">OutlineHandler(final OutlineHandler src) {
105     super(src);
106     this.outlines = new Outline[src.outlines.length];
107     for (int o = 0; o < this.outlines.length; o++) {
108       this.outlines[o] = new Outline(src.outlines[o]);
109     }
110     size = src.size;
111     /*
112      * // this is calculated by findStatLimits() maxCoor = src.maxCoor; minCoor = src.minCoor;
113      * migLimits = new double[src.migLimits.length]; System.arraycopy(src.migLimits, 0,
114      * migLimits, 0, src.migLimits.length); fluLims = new double[src.fluLims.length][]; for (int
115      * i = 0; i < src.fluLims.length; i++) { fluLims[i] = new double[src.fluLims[i].length];
116      * System.arraycopy(src.fluLims[i], 0, fluLims[i], 0, src.fluLims[i].length); } curvLimits =
117      * new double[src.curvLimits.length]; System.arraycopy(src.curvLimits, 0, curvLimits, 0,
118      * src.curvLimits.length);
119      */
120     for (Outline o : outlines) {
121       if (o.getLength() > maxLength) {
122         maxLength = o.getLength();
123       }
124     }
125     findStatLimits(); // fill maxCoor, minCoor, migLimits, fluLims, curvLimits
126   }
127 
128   /**
129    * Conversion constructor.
130    * 
131    * <p>Converts SnakeHandler to OutlineHandler. Converted are only Snakes and their range
132    * 
133    * @param snake source SnakeHandler
134    */
135   public OutlineHandler(final SnakeHandler snake) {
136     this(snake.startFrame, snake.endFrame); // create array and set ranges
137     for (int f = startFrame; f <= endFrame; f++) { // copy all snakes
138       Snake s = snake.getStoredSnake(f); // get original
139       if (s != null) {
140         setOutline(f, new Outline(s)); // convert to Outline
141       }
142     }
143     findStatLimits();
144   }
145 
146   /**
147    * Instantiates a new outline handler.
148    *
149    * @param s start frame
150    * @param e end frame
151    */
152   public OutlineHandler(int s, int e) {
153     size = e - s + 1;
154     outlines = new Outline[size];
155     startFrame = s;
156     endFrame = e;
157   }
158 
159   /**
160    * Default constructor. Initialises one frame.
161    */
162   public OutlineHandler() {
163     this(0, 0);
164   }
165 
166   /**
167    * Gets the start frame.
168    *
169    * @return the start frame
170    */
171   @Override
172   public int getStartFrame() {
173     return startFrame;
174   }
175 
176   /**
177    * Gets the end frame.
178    *
179    * @return the end frame
180    */
181   @Override
182   public int getEndFrame() {
183     return endFrame;
184   }
185 
186   /**
187    * Return Outline stored for frame f.
188    *
189    * @param f the frame
190    * @return the outline or null
191    */
192   public Outline getStoredOutline(int f) {
193     if (f - startFrame < 0 || f - startFrame > outlines.length) {
194       LOGGER.info("Tried to access negative frame store: frame: " + f);
195       return null;
196     }
197     return outlines[f - startFrame];
198   }
199 
200   /**
201    * Checks if is outline at.
202    *
203    * @param f the f
204    * @return true, if is outline at
205    */
206   public boolean isOutlineAt(int f) {
207     if (f - startFrame < 0) {
208       return false;
209     } else if (f - startFrame >= outlines.length) {
210       return false;
211     } else if (outlines[f - startFrame] == null) {
212       return false;
213     } else {
214       return true;
215     }
216   }
217 
218   /**
219    * Index get outline.
220    *
221    * @param i the i
222    * @return the outline
223    */
224   public Outline indexGetOutline(int i) {
225     return outlines[i];
226   }
227 
228   /**
229    * Sets the outline.
230    *
231    * @param f the f
232    * @param o the o
233    */
234   public void setOutline(int f, Outline o) {
235     outlines[f - startFrame] = o;
236     double length = o.getLength();
237     if (length > maxLength) {
238       maxLength = length;
239     }
240 
241   }
242 
243   private boolean readOutlines(final File f) {
244     if (!f.exists()) {
245       IJ.error("Cannot locate snake file (" + FileExtensions.snakeFileExt + ")\n'"
246               + f.getAbsolutePath() + "'");
247       return false;
248     }
249     if (qp == null) {
250       throw new InvalidParameterException(
251               "QParams is null. This object has not been created (loaded) from QParams data");
252     }
253 
254     String thisLine;
255 
256     maxLength = 0;
257     // maxFlu = 0.;
258     int nn;
259     int index;
260     double length;
261     Vert head;
262     Vert n;
263     Vert prevn;
264     size = 0;
265 
266     try {
267       // first count the outlines
268       BufferedReader br = new BufferedReader(new FileReader(f));
269       while ((thisLine = br.readLine()) != null) {
270         if (thisLine.startsWith("#")) {
271           continue;
272         }
273         nn = (int) QuimpToolsCollection.s2d(thisLine);
274         for (int i = 0; i < nn; i++) {
275           // System.out.println(br.readLine() + ", " + );
276           br.readLine();
277           // br.readLine();
278         }
279         size++;
280       }
281       br.close();
282       // IJ.write("num outlines " + size);
283       outlines = new Outline[size];
284 
285       int s = 0;
286       // read outlines into memory
287       br = new BufferedReader(new FileReader(f));
288 
289       while ((thisLine = br.readLine()) != null) { // while loop begins here
290         // System.out.println(thisLine);
291         if (thisLine.startsWith("#")) {
292           continue; // skip comments
293         }
294 
295         index = 0;
296         head = new Vert(index); // dummy head node
297         head.setHead(true);
298         prevn = head;
299         index++;
300 
301         nn = (int) QuimpToolsCollection.s2d(thisLine);
302 
303         for (int i = 0; i < nn; i++) {
304           thisLine = br.readLine();
305           String[] split = thisLine.split("\t");
306           n = new Vert(index);
307 
308           n.coord = QuimpToolsCollection.s2d(split[0]);
309           n.setX(QuimpToolsCollection.s2d(split[1]));
310           n.setY(QuimpToolsCollection.s2d(split[2]));
311 
312           n.fCoord = QuimpToolsCollection.s2d(split[3]); // Origon
313           n.gCoord = QuimpToolsCollection.s2d(split[4]); // G-Origon
314           n.distance = QuimpToolsCollection.s2d(split[5]); // speed
315 
316           // store flu measurements
317           n.fluores[0].intensity = QuimpToolsCollection.s2d(split[6]);
318 
319           if (qp.paramFormat == QParams.QUIMP_11) {
320             // has other channels and x and y
321             n.fluores[0].x = QuimpToolsCollection.s2d(split[7]);
322             n.fluores[0].y = QuimpToolsCollection.s2d(split[8]);
323 
324             n.fluores[1].intensity = QuimpToolsCollection.s2d(split[9]);
325             n.fluores[1].x = QuimpToolsCollection.s2d(split[10]);
326             n.fluores[1].y = QuimpToolsCollection.s2d(split[11]);
327 
328             n.fluores[2].intensity = QuimpToolsCollection.s2d(split[12]);
329             n.fluores[2].x = QuimpToolsCollection.s2d(split[13]);
330             n.fluores[2].y = QuimpToolsCollection.s2d(split[14]);
331           }
332 
333           n.unfreeze();
334           index++;
335           prevn.setNext(n);
336           n.setPrev(prevn);
337           prevn = n;
338 
339         }
340         // link tail to head
341         prevn.setNext(head);
342         head.setPrev(prevn);
343 
344         // head is dummy element that will be removed. To deal with random selection of new head we
345         // remember next element to it (which is first element from snQP)
346         Vert newHead = head.getNext(); // this will be new head
347 
348         Outlineimp/Outline.html#Outline">Outline tmp = new Outline(head, nn + 1); // dont forget the head node
349         // WARN potential incompatibility with old code. see
350         // 3784b9f1afb1dd317bd4740e17f02627fa89bc41 for original Outline and OutlineHandler
351         tmp.removeVert(head); // new head is randomly selected
352         tmp.setHead(newHead); // be sure to set head to first node on snQP list.
353 
354         outlines[s] = tmp;
355         outlines[s].updateNormals(true);
356         outlines[s].makeAntiClockwise();
357         outlines[s].coordReset(); // there is no cord data in snQP file this set it as Position.
358         length = outlines[s].getLength();
359         if (length > maxLength) {
360           maxLength = length;
361         }
362         s++;
363         LOGGER.trace("Outline: " + s + " head =[" + outlines[s - 1].getHead().getX() + ","
364                 + outlines[s - 1].getHead().getY() + "]");
365       } // end while
366       br.close();
367 
368       if (qp.paramFormat == QParams.OLD_QUIMP) {
369         qp.setStartFrame(1);
370         qp.setEndFrame(size);
371         this.endFrame = size;
372         this.startFrame = 1;
373         qp.writeParams(); // replace the old format parameter file
374       }
375       this.findStatLimits();
376 
377       return true;
378     } catch (IOException e) {
379       LOGGER.debug(e.getMessage(), e);
380       LOGGER.error("Could not read outlines", e.getMessage());
381       return false;
382     } catch (NullPointerException e1) {
383       LOGGER.debug(e1.getMessage(), e1);
384       LOGGER.error("Damaged snQP file", e1.getMessage());
385       return false;
386     }
387   }
388 
389   /**
390    * Evaluate <tt>maxCoor</tt>, <tt>minCoor</tt>, <tt>migLimits</tt>, <tt>fluLims</tt>,
391    * <tt>curvLimits</tt>.
392    * 
393    * <p>Initialise arrays as well
394    */
395   private void findStatLimits() {
396     maxCoor = new ExtendedVector2d();
397     minCoor = new ExtendedVector2d();
398     fluLims = new double[3][2];
399     migLimits = new double[2];
400     // convLimits = new double[2];
401     curvLimits = new double[2]; // not filled until Q_Analsis run. smoothed curvature
402 
403     // cycle through all frames and find the min and max for all data
404     // store min and max coor\migration\flu for plotting
405     Outline outline;
406     Vert n;
407     for (int i = 0; i < outlines.length; i++) {
408       outline = outlines[i];
409       if (outline == null) {
410         continue;
411       }
412       n = outline.getHead();
413       if (i == 0) {
414         minCoor.setXY(n.getX(), n.getY());
415         maxCoor.setXY(n.getX(), n.getY());
416         migLimits[0] = n.distance;
417         migLimits[1] = n.distance;
418         // convLimits[0] = n.convexity;
419         // convLimits[1] = n.convexity;
420         for (int j = 0; j < n.fluores.length; j++) {
421           fluLims[j][0] = n.fluores[j].intensity;
422           fluLims[j][1] = n.fluores[j].intensity;
423         }
424       }
425 
426       do {
427         if (n.getX() > maxCoor.getX()) {
428           maxCoor.setX(n.getX());
429         }
430         if (n.getY() > maxCoor.getY()) {
431           maxCoor.setY(n.getY());
432         }
433         if (n.getX() < minCoor.getX()) {
434           minCoor.setX(n.getX());
435         }
436         if (n.getY() < minCoor.getY()) {
437           minCoor.setY(n.getY());
438         }
439 
440         if (n.distance < migLimits[0]) {
441           migLimits[0] = n.distance;
442         }
443         if (n.distance > migLimits[1]) {
444           migLimits[1] = n.distance;
445         }
446 
447         // if(n.convexity < convLimits[0]) convLimits[0] = n.convexity;
448         // if(n.convexity > convLimits[1]) convLimits[1] = n.convexity;
449 
450         for (int j = 0; j < n.fluores.length; j++) {
451           if (n.fluores[j].intensity < fluLims[j][0]) {
452             fluLims[j][0] = n.fluores[j].intensity;
453           }
454           if (n.fluores[j].intensity > fluLims[j][1]) {
455             fluLims[j][1] = n.fluores[j].intensity;
456           }
457         }
458 
459         n = n.getNext();
460       } while (!n.isHead());
461       // see com.github.celldynamics.quimp.plugin.qanalysis.STmap.calcCurvature()
462       Vert v;
463       for (int f = getStartFrame(); f <= getEndFrame(); f++) {
464         Outline o = getStoredOutline(f);
465         if (o == null) {
466           continue;
467         }
468         v = o.getHead();
469 
470         // find min and max of sum curvature
471         v = o.getHead();
472         if (f == getStartFrame()) {
473           curvLimits[1] = v.curvatureSum;
474           curvLimits[0] = v.curvatureSum;
475         }
476         do {
477           if (v.curvatureSum > curvLimits[1]) {
478             curvLimits[1] = v.curvatureSum;
479           }
480           if (v.curvatureSum < curvLimits[0]) {
481             curvLimits[0] = v.curvatureSum;
482           }
483           v = v.getNext();
484         } while (!v.isHead());
485       }
486     }
487 
488     // Set limits to equal positive and negative
489     migLimits = QuimpToolsCollection.setLimitsEqual(migLimits);
490     curvLimits = QuimpToolsCollection.setLimitsEqual(curvLimits);
491   }
492 
493   /**
494    * Gets the size.
495    *
496    * @return the size
497    */
498   public int getSize() {
499     return size;
500   }
501 
502   /**
503    * Copy Outline into internal outlines array on correct position.
504    * 
505    * @param o Outline to copy.
506    * @param frame Frame where copy Outline to.
507    */
508   public void save(Outline o, int frame) {
509     outlines[frame - startFrame] = new Outline(o);
510   }
511 
512   /**
513    * Write <b>this</b> outline to disk.
514    * 
515    * @param outFile file to save
516    * @param isEccmRun was ECMM run?
517    */
518   public void writeOutlines(File outFile, boolean isEccmRun) {
519     LOGGER.debug("Write outline at: " + outFile);
520     try {
521       PrintWriter pw = new PrintWriter(new FileWriter(outFile), true); // auto flush
522       pw.write("#QuimP11 node data");
523       if (isEccmRun) {
524         pw.print("-ECMM");
525       }
526       pw.write("\n#Node Position\tX-coord\tY-coord\tOrigin\tG-Origin\tSpeed");
527       pw.write("\tFluor_Ch1\tCh1_x\tCh1_y\tFluor_Ch2\tCh2_x\tCh2_y\tFluor_CH3\tCH3_x\tCh3_y\n#");
528 
529       Outline o;
530       for (int i = startFrame; i <= endFrame; i++) {
531         o = getStoredOutline(i);
532         pw.write("\n#Frame " + i);
533         write(pw, o.getNumPoints(), o.getHead());
534       }
535       pw.close();
536     } catch (Exception e) {
537       IJ.log("could not open out file " + outFile.getAbsolutePath());
538       return;
539     }
540   }
541 
542   private static void write(PrintWriter pw, int verts, Vert v) {
543     pw.print("\n" + verts);
544     //!>
545     do {
546       pw.print("\n" + IJ.d2s(v.coord, 6) + "\t" // Perimeter coord
547               + IJ.d2s(v.getX(), 2) + "\t" // X coord
548               + IJ.d2s(v.getY(), 2) + "\t" // Y coord
549               + IJ.d2s(v.fCoord, 6) + "\t" // Origin
550               + IJ.d2s(v.gCoord, 6) + "\t" // G-Origin
551               + IJ.d2s(v.distance, 6) + "\t" // Speed
552               + IJ.d2s(v.fluores[0].intensity, 6) + "\t" // Fluor_Ch1
553               + IJ.d2s(v.fluores[0].x, 0) + "\t" // Ch1_x
554               + IJ.d2s(v.fluores[0].y, 0) + "\t" // Ch1_y
555               + IJ.d2s(v.fluores[1].intensity, 6) + "\t" // Fluor_Ch2
556               + IJ.d2s(v.fluores[1].x, 0) + "\t" // Ch2_x
557               + IJ.d2s(v.fluores[1].y, 0) + "\t" // Ch2_y
558               + IJ.d2s(v.fluores[2].intensity, 6) + "\t" // Fluor_CH3
559               + IJ.d2s(v.fluores[2].x, 0) + "\t" // CH3_x
560               + IJ.d2s(v.fluores[2].y, 0)); // CH3_y
561       //!<
562       v = v.getNext();
563     } while (!v.isHead());
564   }
565 
566   /**
567    * Prepare all Outline stored in this OutlineHandler for loading.
568    */
569   @Override
570   public void beforeSerialize() {
571     for (Outline o : outlines) {
572       if (o != null) {
573         o.beforeSerialize(); // convert outlines to array
574       }
575     }
576   }
577 
578   /**
579    * Call afterSerialzie() for other objects and restore transient fields where possible.
580    */
581   @Override
582   public void afterSerialize() throws Exception {
583     for (Outline o : outlines) {
584       if (o != null) {
585         o.afterSerialize(); // convert array to outlines
586       }
587     }
588     // restore other fields
589     size = outlines.length;
590     for (Outline o : outlines) {
591       if (o.getLength() > maxLength) {
592         maxLength = o.getLength();
593       }
594     }
595     findStatLimits(); // fill maxCoor, minCoor, migLimits, fluLims, curvLimits
596 
597   }
598 }