View Javadoc
1   package com.github.celldynamics.quimp.plugin.protanalysis;
2   
3   import java.awt.Color;
4   import java.awt.Point;
5   import java.awt.Polygon;
6   import java.util.ArrayList;
7   import java.util.Arrays;
8   import java.util.Collections;
9   import java.util.Comparator;
10  import java.util.Iterator;
11  import java.util.List;
12  
13  import org.apache.commons.lang3.tuple.Pair;
14  import org.slf4j.Logger;
15  import org.slf4j.LoggerFactory;
16  
17  import com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisOptions.OutlinesToImage;
18  import com.github.celldynamics.quimp.plugin.qanalysis.STmap;
19  import com.github.celldynamics.quimp.utils.graphics.GraphicsElements;
20  
21  import ij.ImagePlus;
22  import ij.ImageStack;
23  import ij.gui.Overlay;
24  import ij.gui.PointRoi;
25  import ij.gui.PolygonRoi;
26  import ij.plugin.ZProjector;
27  import ij.process.FloatProcessor;
28  import ij.process.ImageProcessor;
29  
30  /**
31   * Support various methods of visualising protrusion data.
32   * 
33   * <p>In general all plots are added to image used to construct this object as overlay layer.
34   * 
35   * <p>This super class contains methods for creating static plots in [x,y] domain from coordinates
36   * in [outline,frame] system (native for maps generated by Qanalysis).
37   * 
38   * <p><b>Warning</b>
39   * 
40   * <p>It is assumed that plotted maps have frames on x-axis and indexes on y-axis
41   * 
42   * @author p.baniukiewicz
43   *
44   */
45  public abstract class TrackVisualisation {
46  
47    /**
48     * The Constant LOGGER.
49     */
50    static final Logger LOGGER = LoggerFactory.getLogger(TrackVisualisation.class.getName());
51    /**
52     * Radius of circles plotted.
53     */
54    public double circleRadius = 7.;
55    /**
56     * Color for maxima points.
57     */
58    public static Color MAXIMA_COLOR = Color.MAGENTA;
59  
60    /**
61     * Definition of colors used to plot tracks.
62     * 
63     * <p>These are:
64     * <ol>
65     * <li>index 0 - backtracked position of point
66     * <li>index 1 - forwardtracked position of point.
67     * <li>index 2 - other
68     * </ol>
69     */
70    public static Color[] color = { Color.YELLOW, Color.GREEN, Color.WHITE };
71  
72    /**
73     * The original image.
74     */
75    protected ImagePlus originalImage; // reference of image to be plotted on
76  
77    /**
78     * The overlay.
79     */
80    protected Overlay overlay;
81  
82    /**
83     * Create correct object.
84     * 
85     * <p>If input image contains any overlay data, they will be extended by new plots.
86     * 
87     * @param originalImage Image to be plotted on.
88     */
89    public TrackVisualisation(ImagePlus originalImage) {
90      this.originalImage = originalImage;
91      LOGGER.trace("Num of slices: " + originalImage.getStackSize());
92      overlay = originalImage.getOverlay(); // check for existing overlay
93      if (overlay == null) {
94        overlay = new Overlay();
95      }
96  
97    }
98  
99    /**
100    * Construct object from raw ImageProcessor.
101    * 
102    * @param name Name of the image
103    * @param imp ImageProcessor
104    */
105   public TrackVisualisation(String name, ImageProcessor imp) {
106     this(new ImagePlus(name, imp));
107   }
108 
109   /**
110    * Plot filled circle on overlay.
111    * 
112    * @param x center
113    * @param y center
114    * @param color color
115    * @param radius radius
116    */
117   public void plotCircle(double x, double y, Color color, double radius) {
118     // create ROI
119     PolygonRoi or = GraphicsElements.getCircle(x, y, color, radius);
120     overlay.add(or); // add to collection of overlays
121   }
122 
123   /**
124    * getOriginalImage.
125    * 
126    * @return the originalImage
127    */
128   public ImagePlus getOriginalImage() {
129     return originalImage;
130   }
131 
132   /**
133    * Helper method.
134    * 
135    * <p>Allows to convert enum to index of array of Colors.
136    * 
137    * @param track track
138    * @return Color from color array
139    */
140   protected Color getColor(Track track) {
141     Color c;
142     Track.TrackType type = track.type;
143     switch (type) {
144       case FORWARD:
145         c = color[1];
146         break;
147       case BACKWARD:
148         c = color[0];
149         break;
150       case OTHER:
151         c = color[2];
152         break;
153       default:
154         throw new IllegalArgumentException("Color not supported");
155     }
156     return c;
157   }
158 
159   /**
160    * Flatten stack according to given type.
161    * 
162    * <p>Output has the same resolution (x,y,t) as input. For stacks, slices are duplicated. Refer to
163    * ij.plugin.ZProjector.setMethod(int)
164    * 
165    * @param method How to flatten - ZProjector methods.
166    * @param preserveStack - if <b>true</b> size of output stack is preserved (slices are
167    *        duplicated to form stack with the same number of slices as original one). Otherwise
168    *        only one slice is built
169    */
170   public void flatten(int method, boolean preserveStack) {
171     ImageStack is = originalImage.getStack();
172     is = is.convertToFloat(); // for better averaging
173 
174     ZProjector zp = new ZProjector(new ImagePlus(originalImage.getTitle(), is));
175     zp.setStartSlice(1);
176     zp.setStopSlice(originalImage.getStackSize());
177     zp.setMethod(method);
178     zp.doProjection();
179     ImagePlus ret = zp.getProjection();
180     // recreate stack if needed
181     if (originalImage.getStackSize() > 1 && preserveStack) {
182       ImageStack imS = new ImageStack(ret.getWidth(), ret.getHeight());
183       for (int s = 0; s < originalImage.getStackSize(); s++) {
184         imS.addSlice(ret.getProcessor().convertToByte(true));
185       }
186       originalImage = new ImagePlus(originalImage.getTitle(), imS);
187     } else { // return only one slice (due to input format or preserveStack flag status)
188       originalImage =
189               new ImagePlus(originalImage.getTitle(), ret.getProcessor().convertToByte(true));
190     }
191 
192   }
193 
194   /**
195    * Clear overlay.
196    */
197   public void clear() {
198     if (overlay != null) {
199       overlay.clear();
200     }
201   }
202 
203   /**
204    * Subclass for plotting on single image in coord space [outline,frame].
205    * 
206    * @author p.baniukiewicz
207    *
208    */
209   static class Map extends TrackVisualisation {
210 
211     /**
212      * Denote if map is rotated.
213      * 
214      * <p>QuimP maps returned by
215      * {@link STmap#map2ColorImagePlus(String, String, double[][], double, double)} are rotated in
216      * relation to raw map returned by {@link STmap#getMotMap()}. If this filed is false it means
217      * raw maps, use true for ImagePlus map.
218      * 
219      * <p>Internally all tracks refer to map that has time on x axis (rotated==false).
220      * 
221      * @see STmap#map2ImagePlus(String, ImageProcessor)
222      */
223     private boolean rotated = false;
224     // scales if maps are scaled by map2ColorImagePlus
225     private double ts = 1.0;
226     private double os = 1.0;
227 
228     /**
229      * Instantiates a new map from ImagePlus.
230      *
231      * <p>Note that x-coords must be time and y - outline which is reverse to QuimP format.
232      * 
233      * @param originalImage the original image
234      */
235     public Map(ImagePlus originalImage) {
236       super(originalImage);
237     }
238 
239     /**
240      * Instantiates a new map from ImagePlus.
241      *
242      * <p>Should be used with output from
243      * {@link STmap#map2ColorImagePlus(String, String, double[][], double, double)} after setting
244      * correct scales. Typically ts = 400/frames and os = 1
245      * 
246      * @param originalImage the original image
247      * @param rotated true if map is rotated so time is y and outline is x. Typically map is output
248      *        from {@link STmap#map2ColorImagePlus(String, String, double[][], double, double)}
249      * @param ts time scale if map is scaled as abobe
250      * @param os outline scale
251      */
252     public Map(ImagePlus originalImage, boolean rotated, double ts, double os) {
253       super(originalImage);
254       this.rotated = rotated;
255       this.ts = ts;
256       this.os = os;
257     }
258 
259     /**
260      * Instantiates a new map.
261      *
262      * @param name the name
263      * @param imp the imp
264      */
265     public Map(String name, ImageProcessor imp) {
266       super(name, imp);
267     }
268 
269     /**
270      * Create object from raw data like e.g. motility map.
271      * 
272      * @param name Name of the image
273      * @param data 2D data.
274      */
275     public Map(String name, float[][] data) {
276       super(name, new FloatProcessor(data));
277     }
278 
279     /**
280      * Plot unrelated points on image (static).
281      * 
282      * @param points list of points to plot in coordinates (index,frame)
283      * @param color color of point
284      * @param radius radius of point
285      */
286     public void addCirclesToImage(Polygon points, Color color, double radius) {
287       Polygon polsc = scale(points); // time scale
288       int[] indexes = polsc.ypoints;
289       int[] frames = polsc.xpoints;
290       for (int n = 0; n < points.npoints; n++) {
291         // decode frame,outline to screen coordinates
292         if (frames[n] < 0 || indexes[n] < 0) {
293           continue;
294         }
295         plotCircle(frames[n], indexes[n], color, radius);
296       }
297       originalImage.setOverlay(overlay); // add to image
298     }
299 
300     /**
301      * Plot maxima found by {@link MaximaFinder} on current image.
302      * 
303      * @param maxF properly initialized {@link MaximaFinder} object.
304      */
305     public void addMaximaToImage(MaximaFinder maxF) {
306       Polygon max = maxF.getMaxima();
307 
308       Polygon polsc = scale(max); // time scale
309       PointRoi pr = GraphicsElements.getPoint(polsc, TrackVisualisation.MAXIMA_COLOR);
310       overlay.add(pr);
311       originalImage.setOverlay(overlay);
312     }
313 
314     private int[] scale(int[] in, int len, double sc) {
315       int[] ret = new int[len];
316       for (int i = 0; i < len; i++) {
317         ret[i] = (int) Math.round(in[i] * sc);
318       }
319       return ret;
320     }
321 
322     private Polygon scale(Polygon in) {
323       int[] xp;
324       int[] yp;
325       if (rotated) {
326         yp = scale(in.xpoints, in.npoints, ts);
327         xp = scale(in.ypoints, in.npoints, os);
328       } else {
329         xp = scale(in.xpoints, in.npoints, ts);
330         yp = scale(in.ypoints, in.npoints, os);
331       }
332       return new Polygon(xp, yp, xp.length);
333     }
334 
335     /**
336      * Add lines defined as polygons to image.
337      * 
338      * @param trackCollection initialised TrackCollection object
339      * 
340      */
341     public void addTrackingLinesToImage(TrackCollection trackCollection) {
342       Iterator<Pair<Track, Track>> it = trackCollection.iterator();
343       while (it.hasNext()) {
344         Pair<Track, Track> pair = it.next();
345 
346         Polygon pairLeft = scale(pair.getLeft().asPolygon());
347         Polygon pairRight = scale(pair.getRight().asPolygon());
348 
349         PolygonRoi pr = GraphicsElements.getLine(pairLeft, getColor(pair.getLeft())); // b
350         // pr.fitSpline();
351         overlay.add(pr);
352         pr = GraphicsElements.getLine(pairRight, getColor(pair.getRight())); // fw
353         // pr.fitSpline();
354         overlay.add(pr);
355       }
356       originalImage.setOverlay(overlay);
357     }
358 
359   }
360 
361   /**
362    * Class for plotting on [x,y] image.
363    * 
364    * @author p.baniukiewicz
365    *
366    */
367   static class Image extends TrackVisualisation {
368 
369     /**
370      * Instantiates a new image.
371      *
372      * @param originalImage the original image
373      */
374     public Image(ImagePlus originalImage) {
375       super(originalImage);
376     }
377 
378     /**
379      * Instantiates a new image.
380      *
381      * @param name the name
382      * @param imp the imp
383      */
384     public Image(String name, ImageProcessor imp) {
385       super(name, imp);
386     }
387 
388     /**
389      * Plot unrelated points on image (static).
390      * 
391      * @param mapCell source of coordinate maps
392      * @param points list of points to plot in coordinates (index,frame)
393      * @param color color of point
394      * @param radius radius of point
395      */
396     public void addCirclesToImage(STmap mapCell, Polygon points, Color color, double radius) {
397       double[][] x = mapCell.getxMap();
398       double[][] y = mapCell.getyMap();
399       int[] indexes = points.ypoints;
400       int[] frames = points.xpoints;
401       for (int n = 0; n < points.npoints; n++) {
402         // decode frame,outline to screen coordinates
403         if (frames[n] < 0 || indexes[n] < 0) {
404           continue;
405         }
406         double xcoord = x[frames[n]][indexes[n]]; // screen coordinate of
407         double ycoord = y[frames[n]][indexes[n]]; // (frame,index) point
408         plotCircle(xcoord, ycoord, color, radius);
409       }
410       originalImage.setOverlay(overlay); // add to image
411     }
412 
413     /**
414      * Plot static elements on image if they are not null.
415      * 
416      * @param mapCell STmap
417      * @param trackCollection initialised TrackCollection object
418      * @param mf maxima according to Prot_Analysis.MaximaFinder
419      */
420     public void addElementsToImage(STmap mapCell, TrackCollection trackCollection,
421             MaximaFinder mf) {
422       if (mf != null) {
423         Polygon max = mf.getMaxima();
424         addCirclesToImage(mapCell, max, TrackVisualisation.MAXIMA_COLOR, circleRadius);
425       }
426       if (trackCollection != null) {
427         addTrackingLinesToImage(mapCell, trackCollection);
428       }
429     }
430 
431     /**
432      * Plot tracking lines before and after maxima points (static).
433      * 
434      * @param mapCell map related to given cell.
435      * @param trackCollection
436      * 
437      */
438     public void addTrackingLinesToImage(STmap mapCell, TrackCollection trackCollection) {
439       double[][] x = mapCell.getxMap(); // temporary x and y coordinates for given cell
440       double[][] y = mapCell.getyMap();
441       // these are raw coordinates of tracking lines extracted from List<PolygonRoi> pL
442       ArrayList<float[]> xcoorda = new ArrayList<>();
443       ArrayList<float[]> ycoorda = new ArrayList<>();
444       int al = 0;
445       // iterate over tracks
446       Iterator<Track> it = trackCollection.iteratorTrack();
447       while (it.hasNext()) {
448         Track track = it.next();
449         Polygon pr = track.asPolygon();
450         // create store for tracking line coordinates
451         xcoorda.add(new float[pr.npoints]);
452         ycoorda.add(new float[pr.npoints]);
453         // counter of invalid vertexes. According to TrackMap#trackForward last points can
454         // be -1 when user provided longer time span than available. (last in term of time)
455         int invalidVertex = 0;
456         // decode frame,outline to x,y
457         for (int f = 0; f < pr.npoints; f++) {
458           // -1 stands for points that are outside of range - assured by TrackMap.class
459           if (pr.ypoints[f] < 0 || pr.xpoints[f] < 0) {
460             invalidVertex++; // count bad points
461             continue;
462           }
463           xcoorda.get(al)[f] = (float) x[pr.xpoints[f]][pr.ypoints[f]];
464           ycoorda.get(al)[f] = (float) y[pr.xpoints[f]][pr.ypoints[f]];
465         }
466         PolygonRoi polyRoi = GraphicsElements.getLine(xcoorda.get(al), ycoorda.get(al),
467                 pr.npoints - invalidVertex, getColor(track));
468         overlay.add(polyRoi);
469         al++;
470       }
471       originalImage.setOverlay(overlay); // add to image
472     }
473   }
474 
475   /**
476    * Subclass for plotting on stacks in coord space [x,y,f].
477    * 
478    * @author p.baniukiewicz
479    *
480    */
481   static class Stack extends TrackVisualisation {
482 
483     /**
484      * Instantiates a new stack.
485      *
486      * @param originalImage the original image
487      */
488     public Stack(ImagePlus originalImage) {
489       super(originalImage);
490     }
491 
492     /**
493      * Constructor.
494      * 
495      * @param name name
496      * @param imp base ImageProcessor
497      */
498     public Stack(String name, ImageProcessor imp) {
499       super(name, imp);
500     }
501 
502     /**
503      * Plot unrelated points on image (stack).
504      * 
505      * @param mapCell source of coordinate maps
506      * @param points list of points to plot in coordinates (index,frame)
507      * @param color color of point
508      * @param radius radius of point
509      */
510     public void addCirclesToImage(STmap mapCell, Polygon points, Color color, double radius) {
511       double[][] x = mapCell.getxMap();
512       double[][] y = mapCell.getyMap();
513       int[] indexes = points.ypoints;
514       int[] frames = points.xpoints;
515 
516       // LOGGER.trace("Frames:" + Arrays.toString(frames));
517       // LOGGER.trace("Indexe:" + Arrays.toString(indexes));
518       for (int n = 0; n < points.npoints; n++) {
519         // decode frame,outline to screen coordinates
520         if (frames[n] < 0 || indexes[n] < 0) {
521           continue;
522         }
523         double xcoord = x[frames[n]][indexes[n]]; // screen coordinate of
524         double ycoord = y[frames[n]][indexes[n]]; // (frame,index) point
525         plotCircle(xcoord, ycoord, frames[n] + 1, color, radius);
526       }
527       originalImage.setOverlay(overlay); // add to image
528     }
529 
530     /**
531      * Plot unrelated points on image (stack). Input compatible with
532      * {@link TrackMapAnalyser#getIntersectionParents(List, int)}.
533      * 
534      * @param mapCell source of coordinate maps
535      * @param points list of points to plot in coordinates (index,frame)
536      * @param color color of point
537      * @param radius radius of point
538      */
539     public void addCirclesToImage(STmap mapCell, List<Pair<Point, Point>> points, Color color,
540             double radius) {
541       int[] x = new int[points.size()];
542       int[] y = new int[points.size()];
543       int l = 0;
544       for (Pair<Point, Point> p : points) {
545         x[l] = p.getRight().x;
546         y[l] = p.getRight().y;
547         l++;
548       }
549       Polygon poly = new Polygon(x, y, points.size());
550       addCirclesToImage(mapCell, poly, color, radius);
551     }
552 
553     /**
554      * Plot maxima for each frame.
555      * 
556      * <p>Work like {@link #addTrackingLinesToImage(STmap, TrackCollection)} but does not remember
557      * lines but only points.
558      * 
559      * @param mapCell map related to given cell.
560      * @param trackCollection initialised TrackCollection object TODO This method uses old
561      *        approach assuming that back and forward tracks are repeating.
562      */
563     public void addTrackingMaximaToImage(STmap mapCell, TrackCollection trackCollection) {
564       double[][] x = mapCell.getxMap(); // temporary x and y coordinates for given cell
565       double[][] y = mapCell.getyMap();
566       // these are raw coordinates of tracking lines extracted from List<PolygonRoi> pL
567       ArrayList<float[]> xcoorda = new ArrayList<>();
568       ArrayList<float[]> ycoorda = new ArrayList<>();
569       int al = 0;
570       // iterate over tracks
571       Iterator<Track> it = trackCollection.iteratorTrack();
572       while (it.hasNext()) {
573         Track track = it.next();
574         Polygon polyR = track.asPolygon();
575         // we need to sort tracking line points according to frames where they appear in
576         // first convert poygon to list of Point2i object
577         List<Point> plR =
578                 TrackMapAnalyser.polygon2Point2i(new ArrayList<Polygon>(Arrays.asList(polyR)));
579         // then sort this list according y-coordinate (frame)
580         Collections.sort(plR, new ListPoint2iComparator());
581         // convert to polygon again but now it is sorted along frames
582         Polygon plRsorted = TrackMapAnalyser.point2i2Polygon(plR);
583         // create store for tracking line coordinates
584         xcoorda.add(new float[plRsorted.npoints]);
585         ycoorda.add(new float[plRsorted.npoints]);
586         // counter of invalid vertexes. According to TrackMap#trackForward last points can
587         // be -1 when user provided longer time span than available. (last in term of time)
588         int invalidVertex = 0;
589         // decode frame,outline to x,y
590         for (int f = 0; f < plRsorted.npoints; f++) {
591           // -1 stands for points that are outside of range - assured by TrackMap.class
592           if (plRsorted.ypoints[f] < 0 || plRsorted.xpoints[f] < 0) {
593             invalidVertex++; // count bad points
594             continue;
595           }
596           xcoorda.get(al)[f] = (float) x[plRsorted.xpoints[f]][plRsorted.ypoints[f]];
597           ycoorda.get(al)[f] = (float) y[plRsorted.xpoints[f]][plRsorted.ypoints[f]];
598         }
599         // now xcoorda,yccora keep coordinates of aL track, it is time to plot
600         // iterate over points in sorted polygon (one track line) even indexes stand for
601         // backward tracking, odd for forward tracking lines Some last points can be skipped
602         // here (sorting does not influence this because last points means last in term of
603         // time)
604         for (int f = 0; f < plRsorted.npoints - invalidVertex; f++) {
605           // x/ycoorda keep all points of tracking lines but PolygonRoi constructor allow
606           // to define how many first of them we take. This allows us to add points
607           // together with frames - in result the line grows as frames rise. After
608           // sorting, first points are those on lower frames
609           // set colors (remember about backward/forward order)
610           PolygonRoi polyRoi = GraphicsElements.getCircle(xcoorda.get(al)[f], ycoorda.get(al)[f],
611                   color[al % 2], circleRadius);
612           // set where we want plot f+1 points from x/ycoorda
613           polyRoi.setPosition((int) plRsorted.xpoints[f] + 1);
614           overlay.add(polyRoi);
615         }
616         al++;
617       }
618       originalImage.setOverlay(overlay); // add to image
619 
620     }
621 
622     /**
623      * Plot tracking lines before and after maxima points (in term of frames).
624      * 
625      * <p>First backward tracking lines are plotted then forward in two different colors. For given
626      * maximum first is plotted backward tracking frame by frame, then forward tracking.
627      * Backward tracking is visible as long as forward tracking is plotted. Then both disappear.
628      * 
629      * @param mapCell map related to given cell.
630      * @param trackCollection initialised TrackCollection object TODO This method uses old
631      *        approach assuming that back and forward tracks are repeating.
632      */
633     public void addTrackingLinesToImage(STmap mapCell, TrackCollection trackCollection) {
634       double[][] x = mapCell.getxMap(); // temporary x and y coordinates for given cell
635       double[][] y = mapCell.getyMap();
636       // these are raw coordinates of tracking lines extracted from List<PolygonRoi> pL
637       ArrayList<float[]> xcoorda = new ArrayList<>();
638       ArrayList<float[]> ycoorda = new ArrayList<>();
639       int al = 0;
640       // iterate over tracks
641       Iterator<Track> it = trackCollection.iteratorTrack();
642       while (it.hasNext()) {
643         Track track = it.next();
644         Polygon polyR = track.asPolygon();
645         // we need to sort tracking line points according to frames where they appear in
646         // first convert poygon to list of Point2i object
647         List<Point> plR =
648                 TrackMapAnalyser.polygon2Point2i(new ArrayList<Polygon>(Arrays.asList(polyR)));
649         // then sort this list according y-coordinate (frame)
650         Collections.sort(plR, new ListPoint2iComparator());
651         // convert to polygon again but now it is sorted along frames
652         Polygon plRsorted = TrackMapAnalyser.point2i2Polygon(plR);
653         // create store for tracking line coordinates
654         xcoorda.add(new float[plRsorted.npoints]);
655         ycoorda.add(new float[plRsorted.npoints]);
656         // counter of invalid vertexes. According to TrackMap#trackForward last points can
657         // be -1 when user provided longer time span than available. (last in term of time)
658         int invalidVertex = 0;
659         // decode frame,outline to x,y
660         for (int f = 0; f < plRsorted.npoints; f++) {
661           // -1 stands for points that are outside of range - assured by TrackMap.class
662           if (plRsorted.ypoints[f] < 0 || plRsorted.xpoints[f] < 0) {
663             invalidVertex++; // count bad points
664             continue;
665           }
666           xcoorda.get(al)[f] = (float) x[plRsorted.xpoints[f]][plRsorted.ypoints[f]];
667           ycoorda.get(al)[f] = (float) y[plRsorted.xpoints[f]][plRsorted.ypoints[f]];
668         }
669         // now xcoorda,yccora keep coordinates of aL track, it is time to plot
670         // iterate over points in sorted polygon (one track line) even indexes stand for
671         // backward tracking, odd for forward tracking lines Some last points can be skipped
672         // here (sorting does not influence this because last points means last in term of
673         // time)
674         for (int f = 0; f < plRsorted.npoints - invalidVertex; f++) {
675           // x/ycoorda keep all points of tracking lines but PolygonRoi constructor allow
676           // to define how many first of them we take. This allows us to add points
677           // together with frames - in result the line grows as frames rise. After
678           // sorting, first points are those on lower frames
679           // set colors (remember about backward/forward order)
680           PolygonRoi polyRoi =
681                   GraphicsElements.getLine(xcoorda.get(al), ycoorda.get(al), f + 1, color[al % 2]);
682           // set where we want plot f+1 points from x/ycoorda
683           polyRoi.setPosition((int) plRsorted.xpoints[f] + 1);
684           overlay.add(polyRoi);
685           // If there is maximum on x frame and we plotted backward line from x-n to x, we
686           // wont to keep it during plotting forward tracking from x to x+z frames. So
687           // this whole line is plotted on every x-x+z frame
688           if (al % 2 == 1) {
689             PolygonRoi polyRoi1 = GraphicsElements.getLine(xcoorda.get(al - 1), ycoorda.get(al - 1),
690                     xcoorda.get(al - 1).length, color[al % 2 - 1]);
691             polyRoi1.setPosition((int) plRsorted.xpoints[f] + 1);
692             overlay.add(polyRoi1);
693           }
694         }
695         al++;
696       }
697       originalImage.setOverlay(overlay); // add to image
698     }
699 
700     /**
701      * Plot maxima found by {@link MaximaFinder} on current image.
702      * 
703      * @param mapCell map related to given cell.
704      * @param mf properly initialized {@link MaximaFinder} object.
705      */
706     public void addMaximaToImage(STmap mapCell, MaximaFinder mf) {
707       Polygon max = mf.getMaxima();
708       addCirclesToImage(mapCell, max, TrackVisualisation.MAXIMA_COLOR, 7);
709     }
710 
711     /**
712      * Plot filled circle on overlay on given frame.
713      * 
714      * @param x center
715      * @param y center
716      * @param frame frame
717      * @param color color
718      * @param radius radius
719      */
720     public void plotCircle(double x, double y, int frame, Color color, double radius) {
721       // create ROI
722       PolygonRoi or = GraphicsElements.getCircle(x, y, color, radius);
723       // set z-position of ROI!!!
724       or.setPosition(frame);
725       overlay.add(or); // add to collection of overlays
726     }
727 
728     /**
729      * Plot outline around cell on image.
730      * 
731      * <p>Uses {@link OutlinesToImage} options from {@link ProtAnalysisOptions} through filed
732      * {@link ProtAnalysisOptions#selOutlineColoring}
733      * 
734      * @param mapCell map related to given cell.
735      * @param config configuration object defining colors, type of plot, etc.
736      * @see ProtAnalysisOptions#selOutlineColoring
737      * @see OutlinesToImage
738      */
739     public void addOutlinesToImage(STmap mapCell, ProtAnalysisOptions config) {
740       double[][] mm = mapCell.getMotMap();
741       double[][] cm = mapCell.getConvMap();
742 
743       switch (config.selOutlineColoring.plotType) {
744         case MOTILITY:
745           plotOutline(mapCell.getxMap(), mapCell.getyMap(),
746                   new Color[] { config.selOutlineColoring.motColor,
747                       config.selOutlineColoring.defColor },
748                   new double[] { config.selOutlineColoring.motThreshold }, mapCell.getMotMap());
749           break;
750         case CONVEXITY:
751           plotOutline(mapCell.getxMap(), mapCell.getyMap(),
752                   new Color[] { config.selOutlineColoring.convColor,
753                       config.selOutlineColoring.defColor },
754                   new double[] { config.selOutlineColoring.convThreshold }, mapCell.getConvMap());
755           break;
756         case CONVANDEXP: {
757           // prepare fake map
758           double[][] tmpMap = new double[mm.length][];
759           for (int f = 0; f < tmpMap.length; f++) {
760             tmpMap[f] = new double[mm[f].length];
761             for (int r = 0; r < mm[f].length; r++) {
762               tmpMap[f][r] = ((mm[f][r] > 0 && cm[f][r] > 0)) ? 1.0 : -1.0;
763             }
764           }
765           plotOutline(
766                   mapCell.getxMap(), mapCell.getyMap(), new Color[] {
767                       config.selOutlineColoring.convColor, config.selOutlineColoring.defColor },
768                   new double[] { 0 }, tmpMap);
769         }
770           break;
771         case CONCANDRETR: {
772           // prepare fake map
773           double[][] tmpMap = new double[mm.length][];
774           for (int f = 0; f < tmpMap.length; f++) {
775             tmpMap[f] = new double[mm[f].length];
776             for (int r = 0; r < mm[f].length; r++) {
777               tmpMap[f][r] = (mm[f][r] < 0 && cm[f][r] < 0) ? 1.0 : -1.0;
778             }
779           }
780           plotOutline(
781                   mapCell.getxMap(), mapCell.getyMap(), new Color[] {
782                       config.selOutlineColoring.motColor, config.selOutlineColoring.defColor },
783                   new double[] { 0 }, tmpMap);
784         }
785           break;
786         case BOTH: {
787           double[][] tmpMap = new double[mm.length][];
788           for (int f = 0; f < tmpMap.length; f++) {
789             tmpMap[f] = new double[mm[f].length];
790             for (int r = 0; r < mm[f].length; r++) {
791               tmpMap[f][r] = (mm[f][r] > 0 && cm[f][r] > 0) ? 1.0 : -1.0;
792             }
793           }
794           double[][] tmpMap1 = new double[mm.length][];
795           for (int f = 0; f < tmpMap.length; f++) {
796             tmpMap1[f] = new double[mm[f].length];
797             for (int r = 0; r < mm[f].length; r++) {
798               tmpMap1[f][r] = (mm[f][r] < 0 && cm[f][r] < 0) ? 1.0 : -1.0;
799             }
800           }
801 
802           plotOutline(mapCell.getxMap(), mapCell.getyMap(),
803                   new Color[] { config.selOutlineColoring.motColor,
804                       config.selOutlineColoring.convColor, config.selOutlineColoring.defColor },
805                   new double[] { 0, 0 }, tmpMap, tmpMap1);
806 
807         }
808           break;
809         case UNIFORM: {
810           // plot any map with impossible threshold (def color)
811           plotOutline(mapCell.getxMap(), mapCell.getyMap(),
812                   new Color[] { config.selOutlineColoring.motColor,
813                       config.selOutlineColoring.defColor },
814                   new double[] { Double.MAX_VALUE }, mapCell.getMotMap());
815         }
816           break;
817         default:
818           throw new IllegalArgumentException("Plot Type not supported");
819       }
820 
821       originalImage.setOverlay(overlay);
822     }
823 
824     /**
825      * Helper method for plotting outlines.
826      * 
827      * <p>Plot outline according to given maps and thresholds with specified colour. Points that
828      * does not meet criterion are plotted in default colour.
829      * 
830      * @param x x-coordinates map
831      * @param y y-coordinates map
832      * @param color array of colours used for plotting maps given in <tt>map</tt>. The array
833      *        must contain default colour on its last position. Its size is usually greater by 1
834      *        than number of maps.
835      * @param threshold array of threshold that applies to maps. Must be size of maps.
836      * @param map list of maps to be plotted with colour <tt>color</tt> if they meet criterion
837      *        <tt>threshold</tt>
838      */
839     private void plotOutline(double[][] x, double[][] y, Color[] color, double[] threshold,
840             double[][]... map) {
841       Polygon[] lines = new Polygon[map.length + 1]; // last line is default one
842       for (int i = 0; i < lines.length; i++) {
843         lines[i] = new Polygon();
844       }
845       // plot map lines (those points that are consecutive for given map and its criterion)
846       // and default lines (those consecutive points that do no belong to any map)
847       for (int f = 0; f < x.length; f++) { // over frames
848         for (int r = 0; r < x[0].length; r++) { // over indexes of outline
849           int l = 0;
850           boolean added = false; // indicate whether given point is plotted as map
851           for (Object item : map) { // over maps
852             double[][] tmpmap = (double[][]) item;
853             // check threshold. If point is accepted, any started default line is
854             // finished here and new map line is started.
855             if (tmpmap[f][r] >= threshold[l]) {
856               // store this point as l-th line
857               lines[l].addPoint((int) Math.round(x[f][r]), (int) Math.round(y[f][r]));
858               added = true; // mark that this point is plotted already within any map
859               // plot default no-map points if any stored (stored in else part)
860               if (lines[lines.length - 1].npoints > 0) {
861                 PolygonRoi polyR = GraphicsElements.getLine(lines[lines.length - 1],
862                         color[color.length - 1], f + 1);
863                 overlay.add(polyR);
864                 lines[lines.length - 1] = new Polygon();
865               }
866             } else { // if not meet criterion plot all lines and initialise them again
867               if (lines[l].npoints > 0) {
868                 PolygonRoi polyR = GraphicsElements.getLine(lines[l], color[l], f + 1);
869                 overlay.add(polyR);
870                 lines[l] = new Polygon();
871               }
872               // and and add this point to default if not plotted yet
873               if (!added) {
874                 lines[lines.length - 1].addPoint((int) Math.round(x[f][r]),
875                         (int) Math.round(y[f][r]));
876               }
877             }
878             l++;
879           }
880         }
881         // plot if else-if above does not fire (if fire all lines are initialized again)
882         PolygonRoi polyR;
883         for (int i = 0; i < lines.length; i++) {
884           if (lines[i].npoints > 0) {
885             polyR = GraphicsElements.getLine(lines[i], color[i], f + 1);
886             overlay.add(polyR);
887             lines[i] = new Polygon();
888           }
889         }
890       }
891     }
892 
893     /**
894      * Compare Point2i objects along frames (x-coordinate).
895      * 
896      * @author p.baniukiewicz
897      *
898      */
899     class ListPoint2iComparator implements Comparator<Point> {
900 
901       /*
902        * (non-Javadoc)
903        * 
904        * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
905        */
906       @Override
907       public int compare(Point o1, Point o2) {
908         if (o1.x < o2.x) {
909           return -1;
910         }
911         if (o1.x > o2.x) {
912           return 1;
913         } else {
914           return 0;
915         }
916       }
917 
918     }
919   }
920 }