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   import java.util.Iterator;
8   import java.util.List;
9   
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  
13  import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
14  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
15  import com.github.celldynamics.quimp.geom.SegmentedShapeRoi;
16  import com.github.celldynamics.quimp.utils.QuimPArrayUtils;
17  
18  import ij.IJ;
19  import ij.ImagePlus;
20  import ij.gui.PolygonRoi;
21  import ij.gui.Roi;
22  
23  /**
24   * Represent collection of SnakeHandlers.
25   * 
26   * @author rtyson
27   * @author p.baniukiewicz
28   */
29  public class Nest implements IQuimpSerialize {
30  
31    private static final Logger LOGGER = LoggerFactory.getLogger(Nest.class.getName());
32  
33    /**
34     * List of SnakeHandlers.
35     * 
36     * <p>SnakeHandlers get subsequent IDs therefore index in this array matches SnakeHandler's ID.
37     * This relation can be broken when any cell is deleted using
38     * {@link #removeHandler(SnakeHandler)} method that simply removes it from array shifting other
39     * objects down.
40     * 
41     * <p>This array should be accessed by {@link #getHandler(int)} when one needs access handler on
42     * certain index but does not care about its ID or {@link #getHandlerofId(int)} when ID is
43     * crucial.
44     */
45    private ArrayList<SnakeHandler> sHs;
46    /**
47     * Number of stored snakes in nest.
48     */
49    private int NSNAKES;
50    /**
51     * Number of live stored snakes in nest.
52     */
53    private int ALIVE;
54    /**
55     * Next free ID.
56     */
57    private int nextID;
58  
59    /**
60     * Default constructor.
61     */
62    public Nest() {
63      NSNAKES = 0;
64      ALIVE = 0;
65      nextID = 0;
66      sHs = new ArrayList<SnakeHandler>();
67    }
68  
69    /**
70     * Convert array of SegmentedShapeRoi to SnakeHandlers.
71     * 
72     * <p>Conversion within one SnakeHandler is stopped when there is defective Snake.
73     * 
74     * @param roiArray First level stands for objects (SnakeHandlers, second for Snakes within one
75     *        chain
76     */
77    public void addHandlers(ArrayList<ArrayList<SegmentedShapeRoi>> roiArray) {
78      LOGGER.trace("Adding " + roiArray.size() + " SnakeHandlers");
79      for (List<SegmentedShapeRoi> lsS : roiArray) { // over chains (same cell different frames)
80        try {
81          sHs.add(new SnakeHandler(lsS, nextID));
82          nextID++;
83          NSNAKES++;
84          ALIVE++;
85        } catch (Exception e) {
86          LOGGER.error("A snake on frame " + lsS.get(0).getFrame() + " failed to initilise "
87                  + e.getMessage());
88        }
89      }
90    }
91  
92    /**
93     * Add rois to Nest - convert them to Snake and store in SnakeHandler.
94     * 
95     * @param roiArray roiArray
96     * @param startFrame start frame, rois from roiArray are added to subsequent frames.
97     */
98    public void addHandlers(Roi[] roiArray, int startFrame) {
99      int i = 0;
100     for (; i < roiArray.length; i++) {
101       try {
102         sHs.add(new SnakeHandler(roiArray[i], startFrame, nextID));
103         nextID++;
104         NSNAKES++;
105         ALIVE++;
106       } catch (Exception e) {
107         BOA_.log("A snake failed to initilise: " + e.getMessage());
108       }
109     }
110     BOA_.log("Added " + roiArray.length + " cells at frame " + startFrame);
111     BOA_.log("Cells being tracked: " + NSNAKES);
112   }
113 
114   /**
115    * Add roi to Nest - convert them to Snake and store in SnakeHandler.
116    * 
117    * @param r ROI object that contain image object to be segmented
118    * @param startFrame Current frame
119    * @return SnakeHandler object that is also stored in Nest
120    */
121   public SnakeHandler addHandler(final Roi r, int startFrame) {
122     SnakeHandler sh;
123     try {
124       sh = new SnakeHandler(r, startFrame, nextID);
125       sHs.add(sh);
126       nextID++;
127       NSNAKES++;
128       ALIVE++;
129       BOA_.log("Added one cell, begining frame " + startFrame);
130     } catch (Exception e) {
131       BOA_.log("Added cell failed to initilise");
132       LOGGER.debug(e.getMessage(), e);
133       return null;
134     }
135     BOA_.log("Cells being tracked: " + NSNAKES);
136     return sh;
137   }
138 
139   /**
140    * Gets SnakeHandler.
141    * 
142    * @param s Index of SnakeHandler to get.
143    * @return SnakeHandler stored on index s. It may refer to SnakeHandler ID but may break when
144    *         any cell has been deleted.
145    * @see #getHandlerofId(int)
146    */
147   public SnakeHandler getHandler(int s) {
148     return sHs.get(s);
149   }
150 
151   /**
152    * Return list of handlers stored in Nest.
153    * 
154    * @return unmodifiable list of handlers.
155    */
156   public List<SnakeHandler> getHandlers() {
157     return Collections.unmodifiableList(sHs);
158   }
159 
160   /**
161    * Get SnakeHandler of given id.
162    * 
163    * @param id ID of SnakeHandler to find in Nest.
164    * @return SnakeHandler with demanded ID. Throw exception if not found.
165    * @see #getHandler(int)
166    */
167   public SnakeHandler getHandlerofId(int id) {
168     int ret = -1;
169     for (int i = 0; i < sHs.size(); i++) {
170       if (sHs.get(i) != null && sHs.get(i).getID() == id) {
171         ret = i;
172         break;
173       }
174     }
175     if (ret < 0) {
176       throw new IllegalArgumentException("SnakeHandler of index " + id + " not found in nest");
177     } else {
178       return getHandler(ret);
179     }
180   }
181 
182   /**
183    * Write all Snakes to file.
184    * 
185    * <p>File names are deducted in called functions.
186    * 
187    * @return true if write operation has been successful
188    * @throws IOException when the file exists but is a directory rather than a regular file, does
189    *         not exist but cannot be created, or cannot be opened for any other reason
190    */
191   public boolean writeSnakes() throws IOException {
192     Iterator<SnakeHandler> shitr = sHs.iterator();
193     ArrayList<SnakeHandler> toRemove = new ArrayList<>(); // will keep handler to remove
194     SnakeHandler sh;
195     while (shitr.hasNext()) {
196       sh = (SnakeHandler) shitr.next(); // get SnakeHandler from Nest
197       sh.findLastFrame(); // find its last frame (frame with valid contour)
198       if (sh.getStartFrame() > sh.getEndFrame()) {
199         IJ.error("Snake " + sh.getID() + " not written as its empty. Deleting it.");
200         toRemove.add(sh);
201         continue;
202       }
203       if (!sh.writeSnakes()) {
204         return false;
205       }
206     }
207     // removing from list (after iterator based loop)
208     for (int i = 0; i < toRemove.size(); i++) {
209       removeHandler(toRemove.get(i));
210     }
211     return true;
212   }
213 
214   /**
215    * Remove SnakeHandler from Nest.
216    * 
217    * @param sh handler to remove.
218    */
219   public void kill(final SnakeHandler sh) {
220     sh.kill();
221     ALIVE--;
222   }
223 
224   /**
225    * Make all Snakes live in nest.
226    */
227   public void reviveNest() {
228     Iterator<SnakeHandler> shitr = sHs.iterator();
229     while (shitr.hasNext()) {
230       SnakeHandler/../../../com/github/celldynamics/quimp/SnakeHandler.html#SnakeHandler">SnakeHandler sh = (SnakeHandler) shitr.next();
231       sh.revive();
232     }
233     ALIVE = NSNAKES;
234   }
235 
236   /**
237    * Check if Nest is empty.
238    * 
239    * @return true if Nest is empty
240    */
241   public boolean isVacant() {
242     if (NSNAKES == 0) {
243       return true;
244     }
245     return false;
246   }
247 
248   /**
249    * Check if there is live Snake in Nest.
250    * 
251    * @return true if all snakes in Nest are dead
252    */
253   public boolean allDead() {
254     if (ALIVE == 0 || NSNAKES == 0) {
255       return true;
256     }
257     return false;
258   }
259 
260   /**
261    * Return true is all snake handlers are froze by user.
262    * 
263    * @return true if all frozen or no snake handlers.
264    */
265   public boolean allFrozen() {
266     boolean ret = true;
267     for (SnakeHandler s : sHs) {
268       ret = ret && s.isSnakeHandlerFrozen();
269     }
270     return ret;
271   }
272 
273   /**
274    * Write <i>stQP</i> file using current Snakes
275    * 
276    * <p><b>Warning</b>
277    * 
278    * <p>It can set current slice in ImagePlus (modifies the object state).
279    * 
280    * @param oi instance of current ImagePlus (required by CellStat that extends
281    *        ij.measure.Measurements
282    * @param saveStats if true stQP file is saved in disk, false stats are evaluated only and
283    *        returned
284    * @return CellStat objects with calculated statistics for every cell.
285    */
286   public List<CellStatsEval> analyse(final ImagePlus oi, boolean saveStats) {
287     OutlineHandler outputH;
288     SnakeHandler sh;
289     ArrayList<CellStatsEval> ret = new ArrayList<>();
290     Iterator<SnakeHandler> shitr = sHs.iterator();
291     while (shitr.hasNext()) {
292       sh = (SnakeHandler) shitr.next();
293 
294       File statsFile;
295       outputH = new OutlineHandler(sh);
296       if (saveStats == true) { // compatibility with old (#263), reread snakes from snQP
297         statsFile = new File(BOA_.qState.boap.deductStatsFileName(sh.getID()));
298       } else { // new approach store all in QCONF
299         statsFile = null;
300       }
301       CellStatsEvalllStatsEval.html#CellStatsEval">CellStatsEval tmp = new CellStatsEval(outputH, oi, statsFile,
302               BOA_.qState.boap.getImageScale(), BOA_.qState.boap.getImageFrameInterval());
303       ret.add(tmp);
304     }
305     return ret;
306   }
307 
308   /**
309    * Reset live snakes to ROI's.
310    */
311   public void resetNest() {
312     reviveNest();
313     Iterator<SnakeHandler> shitr = sHs.iterator();
314     ArrayList<SnakeHandler> toRemove = new ArrayList<>(); // will keep handler to remove
315     while (shitr.hasNext()) {
316       SnakeHandler/../../../com/github/celldynamics/quimp/SnakeHandler.html#SnakeHandler">SnakeHandler sh = (SnakeHandler) shitr.next();
317       try {
318         sh.reset();
319       } catch (Exception e) {
320         LOGGER.error("Could not reset snake " + e.getMessage(), e);
321         BOA_.log("Could not reset snake " + sh.getID());
322         BOA_.log("Removing snake " + sh.getID());
323         // collect handler to remove. It will be removed later to avoid list modification in
324         // iterator (#186)
325         toRemove.add(sh);
326       }
327     }
328     // removing from list (after iterator based loop)
329     for (int i = 0; i < toRemove.size(); i++) {
330       removeHandler(toRemove.get(i));
331     }
332   }
333 
334   /**
335    * Remove {@link SnakeHandler} from Nest.
336    * 
337    * @param sh SnakeHandler to remove.
338    */
339   public void removeHandler(final SnakeHandler sh) {
340     if (sh.isLive()) {
341       ALIVE--;
342     }
343     sHs.remove(sh);
344     NSNAKES--;
345   }
346 
347   /**
348    * Remove all handlers from Nest. Make Nest empty.
349    */
350   public void cleanNest() {
351     sHs.clear();
352     NSNAKES = 0;
353     ALIVE = 0;
354     nextID = 0;
355   }
356 
357   /**
358    * Get NEst size.
359    * 
360    * @return Get number of SnakeHandlers (snakes) in nest
361    */
362   public int size() {
363     return NSNAKES;
364   }
365 
366   /**
367    * Prepare for segmentation from frame f.
368    * 
369    * @param f current frame under segmentation
370    */
371   void resetForFrame(int f) {
372     reviveNest();
373     Iterator<SnakeHandler> shitr = sHs.iterator();
374     ArrayList<SnakeHandler> toRemove = new ArrayList<>(); // will keep handler to remove
375     while (shitr.hasNext()) {
376       SnakeHandler/../../../com/github/celldynamics/quimp/SnakeHandler.html#SnakeHandler">SnakeHandler sh = (SnakeHandler) shitr.next();
377       if (sh.isSnakeHandlerFrozen()) {
378         continue; // do not update live snake for frozen, it is updated in runBoa
379       }
380       try {
381         if (f <= sh.getStartFrame()) {
382           // BOA_.log("Reset snake " + sH.getID() + " as Roi");
383           sh.reset();
384         } else {
385           // BOA_.log("Reset snake " + sH.getID() + " as prev snake");
386           sh.resetForFrame(f);
387         }
388       } catch (Exception e) {
389         LOGGER.debug("Could not reset snake " + e.getMessage(), e);
390         BOA_.log("Could not reset snake " + sh.getID());
391         BOA_.log("Removing snake " + sh.getID());
392         // collect handler to remove. It will be removed later to avoid list modification in
393         // iterator (#186)
394         toRemove.add(sh);
395       }
396     }
397     // removing from list (after iterator based loop)
398     for (int i = 0; i < toRemove.size(); i++) {
399       removeHandler(toRemove.get(i));
400     }
401   }
402 
403   /**
404    * Get list of snakes (its IDs) that are on frame frame.
405    * 
406    * @param frame Frame find snakes in
407    * @return List of Snake id on frame
408    */
409   public List<Integer> getSnakesforFrame(int frame) {
410     ArrayList<Integer> ret = new ArrayList<Integer>();
411     Iterator<SnakeHandler> shiter = sHs.iterator();
412     while (shiter.hasNext()) { // over whole nest
413       SnakeHandler sh = shiter.next(); // for every SnakeHandler
414       if (sh.getStartFrame() > frame || sh.getEndFrame() < frame) {
415         continue; // no snake in frame
416       }
417       if (sh.isStoredAt(frame)) { // if limits are ok check if this particular snake exist
418         // it is not deleted by user on this particular frame after successful creating as
419         // series of Snakes
420         Snake s = sh.getStoredSnake(frame);
421         ret.add(s.getSnakeID()); // if yes get its id
422       }
423     }
424     return ret;
425   }
426 
427   /**
428    * Count the snakes that exist at, or after, frame.
429    * 
430    * @param frame frame
431    * @return number of snakes
432    */
433   int nbSnakesAt(int frame) {
434     int n = 0;
435     for (int i = 0; i < NSNAKES; i++) {
436       if (sHs.get(i).getStartFrame() >= frame) {
437         n++;
438       }
439     }
440     return n;
441   }
442 
443   /**
444    * Store OutlineHandler as finalSnake.
445    * 
446    * <p>Use {@link com.github.celldynamics.quimp.SnakeHandler#copyFromFinalToSeg()} to populate
447    * snake over segSnakes.
448    * 
449    * <p>Conversion to Snakes is through Rois
450    * 
451    * @param oh OutlineHandler to convert from.
452    */
453   public void addOutlinehandler(final OutlineHandler oh) {
454     SnakeHandler sh = addHandler(oh.indexGetOutline(0).asFloatRoi(), oh.getStartFrame());
455     if (sh == null) {
456       LOGGER.error("Outline handler could not be added");
457       return;
458     }
459     Outline o;
460     for (int i = oh.getStartFrame(); i <= oh.getEndFrame(); i++) {
461       o = oh.getStoredOutline(i);
462       sh.storeRoi((PolygonRoi) o.asFloatRoi(), i);
463     }
464     sh.copyFromFinalToSeg();
465   }
466 
467   /**
468    * Find Snake that distance of its centroid to given point is smaller than given value.
469    * 
470    * @param offScreenX x coordinate
471    * @param offScreenY y coordinate
472    * @param frame frame
473    * @param dist maximal distance
474    * @return SnakeHandler that meets requirements or null.
475    */
476   public SnakeHandler findClosestTo(int offScreenX, int offScreenY, int frame, double dist) {
477     SnakeHandler snakeH;
478     Snake snake;
479     ExtendedVector2d snakeV;
480     ExtendedVector2dExtendedVector2d.html#ExtendedVector2d">ExtendedVector2d mdV = new ExtendedVector2d(offScreenX, offScreenY);
481     List<Double> distance = new ArrayList<Double>();
482 
483     for (int i = 0; i < size(); i++) { // calc all distances
484       snakeH = getHandler(i);
485       if (snakeH.isStoredAt(frame)) {
486         snake = snakeH.getStoredSnake(frame);
487         snakeV = snake.getCentroid();
488         distance.add(ExtendedVector2d.lengthP2P(mdV, snakeV));
489       }
490     }
491     int minIndex = QuimPArrayUtils.minListIndex(distance);
492     double minDistance = distance.get(minIndex);
493     if (minDistance < dist) {
494       return getHandler(minIndex);
495     } else {
496       return null;
497     }
498   }
499 
500   /*
501    * (non-Javadoc)
502    * 
503    * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#beforeSerialize()
504    */
505   @Override
506   public void beforeSerialize() {
507     Iterator<SnakeHandler> shitr = sHs.iterator();
508     ArrayList<SnakeHandler> toRemove = new ArrayList<>(); // will keep handler to remove
509     SnakeHandler sh;
510     // sanity operation - delete defective snakes
511     while (shitr.hasNext()) {
512       sh = (SnakeHandler) shitr.next(); // get SnakeHandler from Nest
513       sh.findLastFrame(); // find its last frame (frame with valid contour)
514       if (sh.getStartFrame() > sh.getEndFrame()) {
515         IJ.error("Snake " + sh.getID() + " not written as its empty. Deleting it.");
516         toRemove.add(sh);
517         continue;
518       }
519       sh.beforeSerialize();
520     }
521     // removing from list (after iterator based loop)
522     for (int i = 0; i < toRemove.size(); i++) {
523       removeHandler(toRemove.get(i));
524     }
525   }
526 
527   /*
528    * (non-Javadoc)
529    * 
530    * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#afterSerialize()
531    */
532   @Override
533   public void afterSerialize() throws Exception {
534     Iterator<SnakeHandler> shitr = sHs.iterator();
535     SnakeHandler sh;
536     while (shitr.hasNext()) {
537       sh = (SnakeHandler) shitr.next(); // get SnakeHandler from Nest
538       sh.afterSerialize();
539     }
540 
541   }
542 
543   /*
544    * (non-Javadoc)
545    * 
546    * @see java.lang.Object#toString()
547    */
548   @Override
549   public String toString() {
550     return "Nest [sHs=" + sHs + ", NSNAKES=" + NSNAKES + ", ALIVE=" + ALIVE + ", nextID=" + nextID
551             + "]";
552   }
553 }