View Javadoc
1   package com.github.celldynamics.quimp.plugin.protanalysis;
2   
3   import java.awt.Point;
4   import java.awt.Polygon;
5   import java.util.ArrayList;
6   import java.util.Arrays;
7   import java.util.Collections;
8   import java.util.HashMap;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.ListIterator;
12  
13  import org.apache.commons.lang3.tuple.ImmutablePair;
14  import org.apache.commons.lang3.tuple.Pair;
15  import org.slf4j.Logger;
16  import org.slf4j.LoggerFactory;
17  
18  import com.github.celldynamics.quimp.geom.MapTracker;
19  import com.github.celldynamics.quimp.plugin.qanalysis.STmap;
20  import com.github.celldynamics.quimp.utils.QuimPArrayUtils;
21  
22  /**
23   * Track point using tracking map generated by
24   * {@link com.github.celldynamics.quimp.geom.MapTracker}.
25   * 
26   * <p>All methods in this class are safe either for empty tracks returned by MapTracker or
27   * trackMaxima.
28   * Tracking point coordinates may contain invalid values (negative).
29   * 
30   * @author p.baniukiewicz
31   *
32   */
33  public class TrackMapAnalyser {
34  
35    /**
36     * The Constant LOGGER.
37     */
38    static final Logger LOGGER = LoggerFactory.getLogger(TrackMapAnalyser.class.getName());
39    /**
40     * Allow detection common points in backward and forward tracks generated for the same starting
41     * point.
42     * 
43     * @see com.github.celldynamics.quimp.geom.MapTracker#includeFirst
44     */
45    public static final int WITH_SELFCROSSING = 2;
46    /**
47     * Disallow detection common points in backward and forward tracks generated for the same
48     * starting point.
49     * 
50     * @see com.github.celldynamics.quimp.geom.MapTracker#includeFirst
51     */
52    public static final int WITHOUT_SELFCROSSING = 4;
53    /**
54     * Maximum point (source of tracks) is included in tracks (if <tt>true</tt>).
55     * 
56     * <p>It should be changed carefully as many other procedures can assume that first point is
57     * included in Tracks.
58     */
59    private static boolean INCLUDE_INITIAL = true;
60  
61    /**
62     * Hold result of Map generation and analysis.
63     */
64    private TrackCollection trackCollection;
65  
66    /**
67     * getTrackCollection.
68     * 
69     * @return the trackCollection
70     */
71    public TrackCollection getTrackCollection() {
72      return trackCollection;
73    }
74  
75    /**
76     * Instantiates a new track map analyser. Assumes that Maximum point (source of tracks) is
77     * included in tracks.
78     * 
79     * @see #INCLUDE_INITIAL
80     */
81    public TrackMapAnalyser() {
82      trackCollection = new TrackCollection(INCLUDE_INITIAL);
83    }
84  
85    /**
86     * Track maxima across motility map as long as they fulfil criterion of amplitude.
87     * 
88     * <p>Return (as internal field {@link TrackCollection}) list of points tracked from every maximum
89     * point as long as they meet criterion. Maximum point can be included in this list depending on
90     * setting of {@link com.github.celldynamics.quimp.geom.MapTracker#includeFirst} flag. First
91     * points
92     * in tracks are initial points. Forward track is sorted within increasing frames from starting
93     * point, backward according to decreasing frames.
94     * 
95     * @param mapCell holds all maps generated and saved by QuimP
96     * @param drop the value (in x/100) while velocity remains above of the peak speed. E.g for
97     *        drop=1 all tracked points are considered (along positive motility), drop=0.5 stands
98     *        for points that are above 0.5*peakval, where peakval is the value of found maximum.
99     *        Drop < 0 disables this feature.
100    * @param maximaFinder properly initialized object that holds maxima of motility map. All maxima
101    *        are tracked.
102    * 
103    */
104   public void trackMaxima(final STmap mapCell, double drop, final MaximaFinder maximaFinder) {
105     int numFrames = mapCell.getMotMap().length;
106     // int[] indexes = new int[numFrames];
107     Polygon maxi = maximaFinder.getMaxima(); // restore computed maxima
108     double[] maxValues = maximaFinder.getMaxValues(); // max values in order of maxi
109     // build tracking map
110     MapTrackerp/geom/MapTracker.html#MapTracker">MapTracker trackMap = new MapTracker(mapCell.getOriginMap(), mapCell.getCoordMap());
111     trackMap.includeFirst = INCLUDE_INITIAL; // include also initial point
112     ArrayList<Point> trackForward = null;
113     ArrayList<Point> trackBackward = null;
114     // end indexes of accepted elements after checking criterion
115     int nb = 0;
116     int nf = 0;
117     // iterate through all maxima - take only indexes (x)
118     for (int i = 0; i < maxi.npoints; i++) {
119       int index = maxi.ypoints[i]; // considered index
120       int frame = maxi.xpoints[i]; // considered frame
121       LOGGER.trace("Max = [" + frame + "," + index + "]");
122       // trace forward every index until end of time
123       trackForward = (ArrayList<Point>) trackMap.trackForwardValid(frame, index, numFrames - frame);
124       // trace backward every index until end of time
125       trackBackward = (ArrayList<Point>) trackMap.trackBackwardValid(frame, index, frame);
126       Collections.reverse(trackBackward);
127       // check where is drop off - index that has velocity below drop
128       double dropValue;
129       dropValue = maxValues[i] - maxValues[i] * drop;
130       for (nb = 0; nb < trackBackward.size() && trackBackward.get(nb).y >= 0; nb++) {
131         double val = (mapCell.getMotMap()[trackBackward.get(nb).x][trackBackward.get(nb).y]);
132         if (drop >= 0 && val < dropValue) { // test only for positive drops (negative turns off)
133           break;
134         }
135       }
136       LOGGER.trace("tBackward: " + trackBackward);
137       LOGGER.trace("Accepted:" + nb);
138 
139       for (nf = 0; nf < trackForward.size() && trackForward.get(nf).y >= 0; nf++) {
140         double val = (mapCell.getMotMap()[trackForward.get(nf).x][trackForward.get(nf).y]);
141         if (drop >= 0 && val < dropValue) {
142           break;
143         }
144       }
145       LOGGER.trace("tForward: " + trackForward);
146       LOGGER.trace("Accepted:" + nf);
147       // store tracking lines
148       // Nb and Nf are pointer AFTER last valid point
149       trackCollection.addPair(trackBackward.subList(0, nb), trackForward.subList(0, nf));
150     }
151   }
152 
153   /**
154    * getCommonPoints.
155    * 
156    * @return All common points among tracks without self crossings (forward-backward for the same
157    *         starting point)
158    */
159   public Polygon getCommonPoints() {
160     ArrayList<Point> tmpRet = new ArrayList<>();
161     List<Pair<Track, Track>> tracks = trackCollection.getBf();
162     for (int i = 0; i < tracks.size() - 1; i++) {
163       for (int j = i + 1; j < tracks.size(); j++) {
164         Track b1 = tracks.get(i).getLeft();
165         Track b2 = tracks.get(j).getLeft();
166         Track f1 = tracks.get(i).getRight();
167         Track f2 = tracks.get(j).getRight();
168         // check b1-b2, b1-f2, b2-f1, f1-f2
169         // b1-b2
170         {
171           Trackmics/quimp/plugin/protanalysis/Track.html#Track">Track copy = new Track(b1);
172           copy.retainAll(b2);
173           tmpRet.addAll(copy);
174         }
175         // b1-f2
176         {
177           Trackmics/quimp/plugin/protanalysis/Track.html#Track">Track copy = new Track(b1);
178           copy.retainAll(f2);
179           tmpRet.addAll(copy);
180         }
181         // b2-f1
182         {
183           Trackmics/quimp/plugin/protanalysis/Track.html#Track">Track copy = new Track(b2);
184           copy.retainAll(f1);
185           tmpRet.addAll(copy);
186         }
187         // f1-f2
188         {
189           Trackmics/quimp/plugin/protanalysis/Track.html#Track">Track copy = new Track(f1);
190           copy.retainAll(f2);
191           tmpRet.addAll(copy);
192         }
193       }
194     }
195     LOGGER.debug("Common points found:" + tmpRet.size());
196     return point2i2Polygon(QuimPArrayUtils.removeDuplicates(tmpRet));
197   }
198 
199   /**
200    * Find common points among polygons.
201    * 
202    * <p>Check whether there are common points among polygons stored in List.
203    * 
204    * <p><b>Warning</b>
205    * 
206    * <p>Polygon of size 0 may contain x,y, arrays of size 4, only number of points is 0
207    * 
208    * @param tracks List of polygons.
209    * @return Polygon of size 0 when no intersection or polygons whose vertices are common for
210    *         polygons in <tt>tracks</tt>. If there are vertexes shared among more than two
211    *         polygons, they appear only once in returned polygon.
212    */
213   public Polygon getIntersectionPoints(List<Polygon> tracks) {
214     List<Polygon> tmpRet = new ArrayList<>();
215     for (int i = 0; i < tracks.size() - 1; i++) {
216       for (int j = i + 1; j < tracks.size(); j++) {
217         Polygon retPol = getIntersectionPoints(tracks.get(i), tracks.get(j));
218         if (retPol.npoints != 0) {
219           tmpRet.add(retPol); // add retained elements (common with p2)
220         }
221       }
222     }
223     // remove repeating vertexes
224     List<Point> retP2i = QuimPArrayUtils.removeDuplicates(polygon2Point2i(tmpRet));
225     // convert from list of polygons to one polygon
226     return point2i2Polygon(retP2i);
227   }
228 
229   /**
230    * Check if p1 and p2 have common vertexes.
231    * 
232    * @param p1 Polygon
233    * @param p2 Polygon
234    * @return Polygon whose vertexes are those common for p1 and p2.
235    */
236   public Polygon getIntersectionPoints(Polygon p1, Polygon p2) {
237     Polygon ret = new Polygon();
238     List<Point> tmpRet = new ArrayList<>();
239     List<Point> p1p = polygon2Point2i(Arrays.asList(p1)); // polygon as list of points
240     List<Point> p2p = polygon2Point2i(Arrays.asList(p2)); // polygon as list of points
241     // check if p1 and p2 have common elements
242     p1p.retainAll(p2p);
243     tmpRet.addAll(p1p); // add retained elements (common with p2)
244 
245     ret = point2i2Polygon(tmpRet);
246     return ret;
247   }
248 
249   /**
250    * Find common points among polygons.
251    * 
252    * <p>This method provides also parents of every common point. Parents are given as indexes of
253    * polygons in input list that have common vertex.
254    * 
255    * @param tracks List of polygons.
256    * @param mode WITHOUT_SELFCROSSING | WITH_SELFCROSSING
257    * @return List of common points together with their parents List(Pair(Parents,Point)). If there
258    *         is no common points the list is empty
259    */
260   public List<Pair<Point, Point>> getIntersectionParents(List<Polygon> tracks, int mode) {
261     ArrayList<Pair<Point, Point>> retTmp = new ArrayList<>();
262     List<Pair<Point, Point>> ret;
263     for (int i = 0; i < tracks.size() - 1; i++) {
264       for (int j = i + 1; j < tracks.size(); j++) {
265         Polygon retPol = getIntersectionPoints(tracks.get(i), tracks.get(j));
266         for (int n = 0; n < retPol.npoints; n++) {
267           Pair<Point, Point> pairTmp = new ImmutablePair<Point, Point>(new Point(i, j),
268                   new Point(retPol.xpoints[n], retPol.ypoints[n]));
269           retTmp.add(pairTmp);
270         }
271       }
272     }
273     ret = retTmp;
274     if ((mode & WITHOUT_SELFCROSSING) == WITHOUT_SELFCROSSING) {
275       ret = removeSelfCrossings(ret);
276     }
277     return ret;
278   }
279 
280   /**
281    * Removes the self repeatings.
282    *
283    * @param intersections the intersections
284    * @param tracks the tracks
285    * @return the list
286    */
287   public List<Pair<Point, Point>> removeSelfRepeatings(List<Pair<Point, Point>> intersections,
288           List<Polygon> tracks) {
289     HashMap<Integer, List<Pair<Point, Point>>> map = new HashMap<>();
290     List<Pair<Point, Point>> ret = new ArrayList<>();
291     // collect all intersections into separate maps according to parent (left only considered)
292     for (Pair<Point, Point> p : intersections) {
293       Integer parentleft = p.getLeft().x;
294       if (map.get(parentleft) == null) {
295         map.put(parentleft, new ArrayList<>()); // if no create
296       }
297       map.get(parentleft).add(p); // add crossection point to this key
298     }
299     // now, there are intersection points under keys which are their left parent.
300     // go through every set and check which point is first along this parent
301     Iterator<Integer> it = map.keySet().iterator();
302     int minInd = Integer.MAX_VALUE;
303     while (it.hasNext()) {
304       Integer key = it.next();
305       List<Pair<Point, Point>> values = map.get(key);
306       Pair<Point, Point> minPoint = null; // will never be added to ret as it will be
307       // Initialised or exception will be thrown
308       for (Pair<Point, Point> p : values) { // iterate over intersections for given parent
309         // get indexes of back and for tracks
310         // This is strictly related to trackMaxima return order
311         int back;
312         int forw;
313         if (p.getLeft().x % 2 == 0) { // if index is even it is back and forward is next one
314           back = p.getLeft().x;
315           forw = back + 1;
316         } else { // if index is uneven this is forward and back is previous
317           forw = p.getLeft().x;
318           back = forw - 1;
319         }
320         int ind = enumeratePoint(tracks.get(back), tracks.get(forw), p.getRight());
321         if (ind < 0) {
322           throw new IllegalArgumentException("Point does not exist in track");
323         }
324         if (ind < minInd) {
325           minInd = ind;
326           minPoint = p;
327         }
328       }
329       ret.add(minPoint);
330     }
331     return ret;
332 
333   }
334 
335   /**
336    * Remove self crossings that happen between backward and forward tracks for the same initial
337    * point.
338    * 
339    * {@link #trackMaxima} returns alternating tracks tracks, therefore every pair i,i+1 is related
340    * to the same starting points, for even i. If the flag
341    * com.github.celldynamics.quimp.geom.TrackMap.includeFirst is set, those two tracks share one
342    * point
343    * that is also starting point.
344    * 
345    * <p>This method remove those Pairs that come from parent (even,uneven).
346    * 
347    * @param input input data
348    * @return input list without common points between tracks that belong to the same starting
349    *         point.
350    * @see #trackMaxima(STmap, double, MaximaFinder)
351    */
352   private List<Pair<Point, Point>> removeSelfCrossings(List<Pair<Point, Point>> input) {
353     ArrayList<Pair<Point, Point>> ret = new ArrayList<>(input);
354     ListIterator<Pair<Point, Point>> it = ret.listIterator();
355     while (it.hasNext()) {
356       Pair<Point, Point> element = it.next();
357       // remove because first parent is even and second is next track. <even,uneven> are
358       // <backward,forward> according to trackMaxima.
359       if (element.getLeft().x % 2 == 0 && element.getLeft().x + 1 == element.getLeft().y) {
360         it.remove();
361       }
362     }
363     return ret;
364   }
365 
366   /**
367    * Convert list of Polygons to list of Points.
368    * 
369    * <p>The difference is that for polygons points are kept in 1d arrays, whereas for Point2i they
370    * are as separate points that allows object comparison.
371    * 
372    * @param list List of polygons to convert
373    * @return List of points constructed from all polygons.
374    */
375   public static List<Point> polygon2Point2i(List<Polygon> list) {
376     List<Point> ret = new ArrayList<>();
377     for (Polygon pl : list) { // every polygon
378       for (int i = 0; i < pl.npoints; i++) {
379         ret.add(new Point(pl.xpoints[i], pl.ypoints[i]));
380       }
381     }
382     return ret;
383   }
384 
385   /**
386    * Convert list of Points to list of Polygons.
387    * 
388    * <p>The difference is that for polygons points are kept in 1d arrays, whereas for Point2i they
389    * are as separate points that allows object comparison.
390    * 
391    * @param list List of points to convert
392    * @return Polygon constructed from all points. This is 1-element list.
393    */
394   public static Polygon point2i2Polygon(List<Point> list) {
395     int[] x = new int[list.size()];
396     int[] y = new int[list.size()];
397     int l = 0;
398     for (Point p : list) { // every point
399       x[l] = p.x;
400       y[l] = p.y;
401       l++;
402     }
403     return new Polygon(x, y, list.size());
404   }
405 
406   /**
407    * Get index of point in the whole track line composed from backward+forward tracks.
408    * 
409    * <p>Assumes that order og points in tracks is correct, from first to last. (assured by
410    * {@link #trackMaxima(STmap, double, MaximaFinder)}.
411    * 
412    * <p>Use {@link #INCLUDE_INITIAL} to check whether initial point is included in tracks. If it is
413    * it means that it appears twice (for backward and forward tracks respectively). then it is
414    * counted only one. For <tt>false</tt> state all points are counted.
415    * 
416    * @param backwardMap backwardMap
417    * @param forwardMap forwardMap
418    * @param point point to check
419    * @return Total index of point or -1 if not found in these track maps.
420    */
421   static int enumeratePoint(Polygon backwardMap, Polygon forwardMap, Point point) {
422     int i = 0;
423     int delta = 0;
424     // if maximum is included in tracks it appear there twice, for backward and forward
425     // track
426     if (INCLUDE_INITIAL && forwardMap.npoints > 0 && backwardMap.npoints > 0) {
427       delta = 1;
428     }
429     // do no count last point (maximum) if it is there. It will be counted for forward track
430     for (i = 0; i < backwardMap.npoints - delta; i++) {
431       if (backwardMap.xpoints[i] == point.x && backwardMap.ypoints[i] == point.y) {
432         return i;
433       }
434     }
435     for (; i < forwardMap.npoints + backwardMap.npoints - delta; i++) {
436       if (forwardMap.xpoints[i - backwardMap.npoints + delta] == point.x
437               && forwardMap.ypoints[i - backwardMap.npoints + delta] == point.y) {
438         return i;
439       }
440     }
441     return -1;
442   }
443 
444 }