View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.util.List;
4   
5   import org.scijava.vecmath.Tuple2d;
6   import org.slf4j.Logger;
7   import org.slf4j.LoggerFactory;
8   
9   import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
10  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
11  
12  import ij.IJ;
13  import ij.gui.Roi;
14  import ij.process.FloatPolygon;
15  
16  /**
17   * Represent Outline object used as Snake representation after ECMM mapping.
18   * 
19   * <p>Outline can have the same Shape as Snake but distribution of Vert may be different than
20   * distribution of Node in Snake. Outline is produced after ECMM and used in further analysis.
21   * 
22   * @author rtyson
23   * @author p.baniukiewicz
24   */
25  public class Outline extends Shape<Vert> implements Cloneable, IQuimpSerialize {
26  
27    /**
28     * The Constant LOGGER.
29     */
30    static final Logger LOGGER = LoggerFactory.getLogger(Outline.class.getName());
31  
32    /**
33     * Default constructor. Create empty outline.
34     */
35    public Outline() {
36      super();
37    }
38  
39    /**
40     * Create a Outline from existing linked list
41     * 
42     * <p>Behaviour of this method was changed. Now it does not make copy of Vert. In old approach
43     * there was dummy node deleted in this constructor.
44     * 
45     * <pre>
46     * <code>
47     * index = 0;
48     * head = new Vert(index); // dummy head node head.setHead(true); 
49     * prevn = head;
50     * index++; // insert next nodes here
51     * </code>
52     * </pre>
53     * 
54     * @param h head node of linked list
55     * @param nn number of nodes in list
56     * 
57     */
58    public Outline(final Vert h, int nn) {
59      super(h, nn);
60      // removeVert(head);
61      this.updateCurvature();
62      this.updateNormals(false);
63    }
64  
65    /**
66     * Blank outline.
67     * 
68     * @param h Initial Vert
69     */
70    public Outline(final Vert h) {
71      super(h);
72      this.updateCurvature();
73    }
74  
75    /**
76     * Copy constructor. Copy properties of Outline. Previous or next points are not copied
77     * 
78     * @param src Source Outline
79     */
80    public Outlines/quimp/Outline.html#Outline">Outline(final Outline src) {
81      super(src);
82    }
83  
84    /**
85     * Conversion constructor.
86     * 
87     * <p>Convert only basic properties. Do not forget that many of Vert properties are set during
88     * ECMM or Q Analysis.
89     * 
90     * <p>Set normals outwards. This can be changed by calling {@link #updateNormals(boolean)}
91     * afterwards.
92     * 
93     * @param src Snake to be converted to Outline
94     */
95    @SuppressWarnings({ "unchecked", "rawtypes" })
96    public Outline(final Snake src) {
97      super((Shape) src, new Vert());
98      this.updateCurvature();
99      setPositions();
100     this.updateNormals(false);
101   }
102 
103   /**
104    * Create an outline from an Roi.
105    * 
106    * @param roi Initial ROI
107    */
108   public Outline(final Roi roi) {
109     head = new Vert(0); // make a dummy head node
110     POINTS = 1;
111     head.setPrev(head); // link head to itself
112     head.setNext(head);
113     head.setHead(true);
114 
115     Vert v = head;
116 
117     FloatPolygon p = roi.getFloatPolygon();
118     for (int i = 0; i < p.npoints; i++) {
119       v = insertVert(v);
120       v.setX(p.xpoints[i]);
121       v.setY(p.ypoints[i]);
122     }
123     removeVert(head); // remove dummy head node
124     updateNormals(false);
125     this.updateCurvature();
126     setPositions();
127     calcCentroid();
128   }
129 
130   /**
131    * Construct Outline object from list of nodes. Head node is always first element from array.
132    * 
133    * @param list list of nodes as Vector2d
134    */
135   public Outline(final List<? extends Tuple2d> list) {
136     super(list, new Vert(0), false);
137     this.updateCurvature();
138   }
139 
140   /**
141    * Construct Outline object from X and Y arrays. Head node is always first element from array.
142    * 
143    * @param x x coordinates of nodes
144    * @param y y coordinates of nodes
145    */
146   public Outline(final double[] x, final double[] y) {
147     super(x, y, new Vert(0), false);
148     this.updateCurvature();
149   }
150 
151   /*
152    * (non-Javadoc)
153    * 
154    * @see java.lang.Object#toString()
155    */
156   @Override
157   public String toString() {
158     return "Outline [POINTS=" + POINTS + ", centroid=" + centroid + ", toString()="
159             + super.toString() + "]";
160   }
161 
162   /**
163    * Old toString.
164    */
165   public void print() {
166     IJ.log("Print verts (" + POINTS + ")");
167     int i = 0;
168     Vert v = head;
169     do {
170       // int x = (int) v.getPoint().getX();
171       // int y = (int) v.getPoint().getY();
172       double x = v.getPoint().getX();
173       double y = v.getPoint().getY();
174       double c = v.coord;
175       double f = v.fCoord;
176 
177       String xx = IJ.d2s(x, 8);
178       String yy = IJ.d2s(y, 8);
179       String cc = IJ.d2s(c, 3);
180       String ff = IJ.d2s(f, 3);
181 
182       String sh = "";
183       String si = "\t";
184       if (v.isHead()) {
185         sh = " isHead";
186       }
187       if (v.isIntPoint()) {
188         si = "\t isIntPoint(" + v.intsectID + ")";
189       }
190 
191       IJ.log("Vert " + v.getTrackNum() + " (" + cc + ")(" + ff + "), x:" + xx + ", y:" + yy + si
192               + sh);
193       v = v.getNext();
194       i++;
195     } while (!v.isHead());
196     if (i != POINTS) {
197       IJ.log("VERTS and linked list dont tally!!");
198     }
199   }
200 
201   /**
202    * Remove selected Vert from list.
203    * 
204    * <p>Perform check if removed Vert was head and if it was, the new head is randomly selected.
205    * Neighbours are linked together
206    * 
207    * @param v Vert to remove
208    */
209   public void removeVert(final Vert v) {
210     if (POINTS <= 3) {
211       LOGGER.error("Outline. 175. Can't remove node. Less than 3 would remain");
212       return;
213     }
214     super.removePoint(v, true);
215     if (POINTS < 3) {
216       IJ.error("Outline.199. WARNING! Nodes less then 3");
217     }
218   }
219 
220   /**
221    * Update curvature for all Vert in Outline.
222    */
223   public void updateCurvature() {
224     Vert v = head;
225     do {
226       v.setCurvatureLocal();
227       v = v.getNext();
228     } while (!v.isHead());
229   }
230 
231   /**
232    * Insert default Vert after Vert v.
233    * 
234    * @param v Vert to insert new Vert after
235    * @return Inserted Vert
236    */
237   public VertVert.html#Vert">Vert insertVert(final Vert v) {
238     return insertPoint(v, new Vert());
239   }
240 
241   /**
242    * Insert a vertex in-between v and v.next with interpolated values
243    * 
244    * <p>Modify current Outline
245    * 
246    * @param v vertex to insert other vertex after it
247    * @return Vertex created between \c v and \c v.next
248    */
249   public Vertrt">Vert insertInterpolatedVert(final Vert v) {
250     Vert newVert = insertVert(v);
251     newVert.setX((newVert.getPrev().getX() + newVert.getNext().getX()) / 2);
252     newVert.setY((newVert.getPrev().getY() + newVert.getNext().getY()) / 2);
253 
254     newVert.updateNormale(true);
255     newVert.distance = (newVert.getPrev().distance + newVert.getNext().distance) / 2;
256     newVert.gCoord = interpolateCoord(newVert.getPrev().gCoord, newVert.getNext().gCoord);
257     newVert.fCoord = interpolateCoord(newVert.getPrev().fCoord, newVert.getNext().fCoord);
258 
259     return newVert;
260   }
261 
262   /**
263    * interpolate between co-ordinates (that run 0-1).
264    * 
265    * @param a start coord
266    * @param b end coord
267    * @return interpolated value between a and b
268    */
269   public double interpolateCoord(double a, double b) {
270     if (a > b) {
271       b = b + 1;
272     }
273     double dis = a + ((b - a) / 2);
274 
275     if (dis >= 1) {
276       dis += -1; // passed zero
277     }
278     return dis;
279   }
280 
281   /**
282    * Evenly spaces a new vertices around the outline by following vectors between verts.
283    * 
284    * @param density density
285    */
286   public void setResolution(double density) {
287     double length = getLength();
288     int numVerts = (int) Math.round((length / density)); // must be round number of verts
289     density = length / numVerts; // re-cal the density
290 
291     double remaining = 0.;
292     double lastPlacement;
293     double currentDis;
294 
295     Vert oldHead = head;
296     Vert v1 = oldHead;
297 
298     // new, higher res outline
299     head = new Vert(v1.getX(), v1.getY(), v1.getTrackNum());
300     head.setHead(true);
301     head.setIntPoint(oldHead.isIntPoint(), oldHead.intsectID);
302     head.setNext(head);
303     head.setPrev(head);
304 
305     head.gCoord = 0.;
306     head.coord = 0.;
307 
308     nextTrackNumber = oldHead.getTrackNum() + 1;
309     POINTS = 1;
310 
311     double coorSpaceing = 1.0 / numVerts;
312     // System.out.println("coord: " + CoorSpaceing);
313     double currentCoor = coorSpaceing;
314 
315     Vert temp;
316     Vert newV;
317     newV = head;
318 
319     int numVertsInserted = 1; // the head
320     ExtendedVector2d uedge;
321     ExtendedVector2d edge;
322     ExtendedVector2d placementVector;
323     do {
324       Vert v2 = v1.getNext();
325       lastPlacement = 0.0;
326       currentDis = 0.0;
327       edge = ExtendedVector2d.vecP2P(v1.getPoint(), v2.getPoint());
328       // edge.print("edge");
329       uedge = ExtendedVector2d.unitVector(v1.getPoint(), v2.getPoint());
330       // uEdge.print("uEdge");
331       if (edge.length() == 0) { // points on top of one another, move on
332         v1 = v1.getNext();
333         continue;
334       }
335 
336       while (true) {
337         placementVector = new ExtendedVector2d(uedge.getX(), uedge.getY());
338         // double mult = (density + currentDis) - remaining;
339         // System.out.println("mult "+mult);
340         // placementVector.print("before Mult");
341         placementVector.multiply((density + currentDis) - remaining);
342 
343         // placementVector.print("\tafter Mult");
344 
345         if (placementVector.length() <= edge.length() && numVertsInserted < numVerts) { // if
346           currentDis = placementVector.length();
347           lastPlacement = currentDis;
348           remaining = 0;
349 
350           placementVector.addVec(v1.getPoint());
351 
352           temp = insertVert(newV);
353           temp.setX(placementVector.getX());
354           temp.setY(placementVector.getY());
355           temp.gCoord = currentCoor;
356           temp.coord = currentCoor;
357 
358           newV = temp;
359 
360           numVertsInserted++;
361           currentCoor += coorSpaceing;
362 
363         } else {
364           if (v2.isHead()) {
365             break; // stop it douplicating the head node if it also an intpoint
366           }
367           if (v2.isIntPoint()) {
368             temp = insertVert(newV);
369             temp.setX(v2.getX());
370             temp.setY(v2.getY());
371             temp.setIntPoint(true, v2.intsectID);
372             newV = temp;
373           }
374           remaining = edge.length() - lastPlacement + remaining;
375           break;
376         }
377       }
378 
379       v1 = v1.getNext();
380     } while (!v1.isHead());
381     oldHead = null;
382     // LOGGER.trace("head =[" + getHead().getX() + "," + getHead().getY() + "]");
383   }
384 
385   /**
386    * Evenly spaces a new vertices around the outline by following vectors between verts.
387    * 
388    * @param numVerts numVerts
389    */
390   public void setResolutionN(double numVerts) {
391     double length = getLength();
392     // int numVerts = (int) Math.round((length / density)); // must be round
393     // number of verts
394     double density = length / numVerts; // re-cal the density
395 
396     double remaining = 0.0;
397     double lastPlacement;
398     double currentDis;
399 
400     Vert oldHead = head;
401     Vert v1 = oldHead;
402 
403     // new, higher res outline
404     head = new Vert(v1.getX(), v1.getY(), v1.getTrackNum());
405     head.setHead(true);
406     head.setIntPoint(oldHead.isIntPoint(), oldHead.intsectID);
407     head.setNext(head);
408     head.setPrev(head);
409 
410     head.gCoord = 0.;
411     head.coord = 0.;
412 
413     nextTrackNumber = oldHead.getTrackNum() + 1;
414     POINTS = 1;
415 
416     double coorSpaceing = 1.0 / numVerts;
417     // System.out.println("coord: " + CoorSpaceing);
418     double currentCoor = coorSpaceing;
419 
420     Vert temp;
421     Vert newV;
422     newV = head;
423 
424     int numVertsInserted = 1; // the head
425     ExtendedVector2d uedge;
426     ExtendedVector2d edge;
427     ExtendedVector2d placementVector;
428     do {
429       Vert v2 = v1.getNext();
430       lastPlacement = 0.0;
431       currentDis = 0.0;
432       edge = ExtendedVector2d.vecP2P(v1.getPoint(), v2.getPoint());
433       // edge.print("edge");
434       uedge = ExtendedVector2d.unitVector(v1.getPoint(), v2.getPoint());
435       // uEdge.print("uEdge");
436       if (edge.length() == 0) { // points on top of one another, move on
437         v1 = v1.getNext();
438         continue;
439       }
440 
441       while (true) {
442         placementVector = new ExtendedVector2d(uedge.getX(), uedge.getY());
443         // double mult = (density + currentDis) - remaining;
444         // System.out.println("mult "+mult);
445         // placementVector.print("before Mult");
446         placementVector.multiply((density + currentDis) - remaining);
447 
448         // placementVector.print("\tafter Mult");
449 
450         if (placementVector.length() <= edge.length() && numVertsInserted < numVerts) { // if
451           currentDis = placementVector.length();
452           lastPlacement = currentDis;
453           remaining = 0;
454 
455           placementVector.addVec(v1.getPoint());
456 
457           temp = insertVert(newV);
458           temp.setX(placementVector.getX());
459           temp.setY(placementVector.getY());
460           temp.gCoord = currentCoor;
461           temp.coord = currentCoor;
462 
463           newV = temp;
464 
465           numVertsInserted++;
466           currentCoor += coorSpaceing;
467 
468         } else {
469           if (v2.isHead()) {
470             break; // stop it duplicating the head node if it also an intpoint
471           }
472           if (v2.isIntPoint()) {
473             temp = insertVert(newV);
474             temp.setX(v2.getX());
475             temp.setY(v2.getY());
476             temp.setIntPoint(true, v2.intsectID);
477             newV = temp;
478           }
479           remaining = edge.length() - lastPlacement + remaining;
480           break;
481         }
482       }
483 
484       v1 = v1.getNext();
485     } while (!v1.isHead());
486     oldHead = null;
487   }
488 
489   /**
490    * calcVolume.
491    * 
492    * @return volume of outline
493    */
494   public double calcVolume() {
495     double sum;
496     sum = 0.0;
497     Vert n = head;
498     Vert np1 = n.getNext();
499     do {
500       sum += (n.getX() * np1.getY()) - (np1.getX() * n.getY());
501       n = n.getNext();
502       np1 = n.getNext(); // note: n is reset on prev line
503 
504     } while (!n.isHead());
505     return 0.5 * sum;
506   }
507 
508   /**
509    * calcArea.
510    * 
511    * @return area of outline
512    */
513   public double calcArea() {
514     double area;
515     double sum;
516     sum = 0.0;
517     Vert n = head;
518     Vert np1 = n.getNext();
519     do {
520       sum += (n.getX() * np1.getY()) - (np1.getX() * n.getY());
521       n = n.getNext();
522       np1 = n.getNext(); // note: n is reset on prev line
523 
524     } while (!n.isHead());
525     area = 0.5 * sum;
526     return area;
527   }
528 
529   /**
530    * getNextIntersect.
531    * 
532    * @param v v
533    * @return next intersection
534    */
535   public static Vert/../../com/github/celldynamics/quimp/Vert.html#Vert">Vert getNextIntersect(Vert v) {
536     do {
537       v = v.getNext();
538     } while (!v.isIntPoint()); // move to next int point
539     return v;
540   }
541 
542   /**
543    * findIntersect.
544    * 
545    * @param v v
546    * @param id id
547    * @return intersection
548    */
549   public static Vert/../../../com/github/celldynamics/quimp/Vert.html#Vert">Vert findIntersect(Vert v, int id) {
550     int count = 0; // debug
551     do {
552       if (v.isIntPoint() && v.intsectID == id) {
553         break;
554       }
555       v = v.getNext();
556       if (count++ > 2000) {
557         System.out.println("Outline->findIntersect - search exceeded 2000");
558         break;
559       }
560     } while (true);
561 
562     return v;
563   }
564 
565   /**
566    * distBetweenInts.
567    * 
568    * @param intA intA
569    * @param intB intB
570    * @return distance between intersections
571    */
572   public static int distBetweenInts(Verthref="../../../../com/github/celldynamics/quimp/Vert.html#Vert">Vert intA, Vert intB) {
573     int d = 0;
574     do {
575       d++;
576       intA = intA.getNext();
577       if (d > 2000) { // debug
578         System.out.println("Outline:distBetween->search exceeded 2000");
579         break;
580       }
581     } while (intA.intsectID != intB.intsectID);
582     return d;
583   }
584 
585   /**
586    * invertsBetween.
587    * 
588    * @param intA intA
589    * @param intB intB
590    * @return ?
591    */
592   public static int invertsBetween(Verthref="../../../../com/github/celldynamics/quimp/Vert.html#Vert">Vert intA, Vert intB) {
593     int i = 0;
594     int count = 0;
595     do {
596       intA = intA.getNext();
597       if (intA.isIntPoint() && intA.intState > 2) {
598         i++;
599       }
600       if (intA.isIntPoint() && intA.intState == 1) {
601         i--;
602       }
603       if (count++ > 2000) { // debug
604         System.out.println("Outline:invertsBetween. search exceeded 2000");
605         break;
606       }
607     } while (intA.intsectID != intB.intsectID);
608     return i;
609   }
610 
611   /**
612    * Insert or remove nodes according to criteria.
613    * 
614    * <p>It does not redistribute nodes equally. Removing has priority over inserting, inserted
615    * vertex can be next removed if it is too close. Does not work vice-versa as inserting does not
616    * push current node forward (to next in list) but removing does. And removing condition is
617    * checked first
618    * 
619    * @param max max allowed distance
620    * @param min min allowed distance
621    */
622   public void correctDensity(double max, double min) {
623     double dist;
624     Vert v = head;
625     boolean canEnd = true;
626     do {
627       dist = ExtendedVector2d.lengthP2P(v.getPoint(), v.getNext().getPoint());
628       canEnd = true;
629       if (dist < min) {
630         if (!v.getNext().isHead()) {
631           removeVert(v.getNext()); // just remove
632           v = v.getNext(); // and go to next
633         } else {
634           removeVert(v.getNext()); // remove, do not know where is new head, can be current or next
635           break; // end loop - next was head so we circulated all
636         }
637       } else if (dist > max) {
638         this.insertInterpolatedVert(v); // insert after v
639         // if head do not end, this is linear approx, inserted can be still farer than max. We check
640         // this in next iter (current node des not change)
641         if (v.isHead()) {
642           canEnd = false;
643         }
644       } else {
645         v = v.getNext();
646       }
647     } while (!(v.isHead() && canEnd));
648   }
649 
650   /**
651    * Done once at the end of each frame to cut out any parts of the contour that self intersect.
652    * 
653    * <p>Similar to cutLoops, but check all edges (interval up to NODES/2) and cuts out the smallest
654    * section
655    * 
656    * @return true if cut something
657    * @see com.github.celldynamics.quimp.Snake#cutLoops()
658    * @see com.github.celldynamics.quimp.Snake#cutIntersects()
659    */
660   public boolean cutSelfIntersects() {
661     boolean icut = false;
662     int interval;
663     int state;
664 
665     Vert na;
666     Vert nb;
667     double[] intersect = new double[2];
668     Vert newN;
669 
670     boolean cutHead;
671 
672     na = head;
673     do {
674       cutHead = (na.getNext().isHead()) ? true : false;
675       nb = na.getNext().getNext(); // don't check the next one along! they touch, not overlap
676       interval = (POINTS > 6) ? POINTS / 2 : 2; // always leave 3 nodes, at least. Check half way
677       for (int i = 2; i < interval; i++) {
678         if (nb.isHead()) {
679           cutHead = true;
680         }
681         intersect = new double[2];
682         state = ExtendedVector2d.segmentIntersection(na.getX(), na.getY(), na.getNext().getX(),
683                 na.getNext().getY(), nb.getX(), nb.getY(), nb.getNext().getX(), nb.getNext().getY(),
684                 intersect);
685 
686         if (state == 1) {
687           icut = true;
688           newN = this.insertInterpolatedVert(na);
689           newN.setX(intersect[0]);
690           newN.setY(intersect[1]);
691 
692           newN.setNext(nb.getNext());
693           nb.getNext().setPrev(newN);
694 
695           newN.updateNormale(true);
696           nb.getNext().updateNormale(true);
697 
698           if (cutHead) {
699             // System.out.println("cut the head");
700             newN.setHead(true); // put a new head in
701             head = newN;
702           }
703 
704           // newN.print("inserted node: ");
705           // System.out.println("C - VERTS : " + VERTS);
706           if (POINTS - (i) < 3) {
707             LOGGER.warn("OUTLINE 594_VERTS WILL BE than 3. i = " + i + ", VERT=" + POINTS);
708           }
709           POINTS -= (i);
710           break;
711         }
712         nb = nb.getNext();
713       }
714       na = na.getNext();
715     } while (!na.isHead());
716 
717     return icut;
718   }
719 
720   /**
721    * Remove really small edges that cause numerical inaccuracy when checking for self intersects.
722    * 
723    * <p>Tends to happen when migrating edges get pushed together
724    * 
725    * @return \c true if deleted something
726    */
727   public boolean removeNanoEdges() {
728     double nano = 0.1;
729     double length;
730     boolean deleted = false;
731 
732     Vert na;
733     Vert nb;
734 
735     na = head;
736     do {
737       do {
738         nb = na.getNext();
739         length = ExtendedVector2d.lengthP2P(na.getPoint(), nb.getPoint());
740         if (length < nano) {
741           this.removeVert(nb);
742           deleted = true;
743         }
744       } while (length < nano);
745 
746       na = na.getNext();
747     } while (!na.isHead());
748 
749     return deleted;
750   }
751 
752   /**
753    * Set head node coord to zero. Make closest landing to zero the head node
754    * 
755    * <p>Prevents circulating of the zero coord
756    */
757   public void coordReset() {
758     Vert vertFirst = findFirstNode('g'); // get first node in terms of fcoord (origin)
759     head.setHead(false);
760     head = vertFirst;
761     head.setHead(true);
762 
763     double length = getLength();
764     double d = 0.;
765 
766     Vert v = head;
767     do {
768       v.coord = d / length;
769       d = d + ExtendedVector2d.lengthP2P(v.getPoint(), v.getNext().getPoint());
770       v = v.getNext();
771     } while (!v.isHead());
772   }
773 
774   /**
775    * Reset all linear coordinates related to node {@link Vert#coord}, {@link Vert#gCoord},
776    * {@link Vert#fCoord}.
777    */
778   public void resetAllCoords() {
779     double length = getLength();
780     double d = 0.;
781 
782     Vert v = head;
783     do {
784       v.coord = d / length;
785       v.gCoord = v.coord;
786       v.fCoord = v.coord;
787       d = d + ExtendedVector2d.lengthP2P(v.getPoint(), v.getNext().getPoint());
788       v = v.getNext();
789     } while (!v.isHead());
790     // LOGGER.trace("head =[" + getHead().getX() + "," + getHead().getY() + "]");
791   }
792 
793   /**
794    * (non-Javadoc).
795    *
796    * @return the object
797    * @see java.lang.Object#clone()
798    * @deprecated Change to copy constructor
799    */
800   public Object clone() {
801     // clone the outline
802     Vert ov = head;
803 
804     Verts/quimp/Vert.html#Vert">Vert nv = new Vert(ov.getX(), ov.getY(), ov.getTrackNum()); // head node
805     nv.coord = ov.coord;
806     nv.fCoord = ov.fCoord;
807     nv.gCoord = ov.gCoord;
808     nv.distance = ov.distance;
809     // nV.fluores = oV.cloneFluo();
810     nv.setFluores(ov.fluores);
811 
812     Outlinequimp/Outline.html#Outline">Outline n = new Outline(nv);
813 
814     ov = ov.getNext();
815     do {
816       nv = n.insertVert(nv);
817       nv.setX(ov.getX());
818       nv.setY(ov.getY());
819       nv.coord = ov.coord;
820       nv.fCoord = ov.fCoord;
821       nv.gCoord = ov.gCoord;
822       nv.distance = ov.distance;
823       // nV.fluores = oV.cloneFluo();
824       nv.setFluores(ov.fluores);
825       nv.setTrackNum(ov.getTrackNum());
826 
827       ov = ov.getNext();
828     } while (!ov.isHead());
829     n.updateNormals(true);
830     n.calcCentroid();
831     n.updateCurvature();
832 
833     return n;
834   }
835 
836   /**
837    * checkCoordErrors.
838    * 
839    * @return true if error found
840    */
841   public boolean checkCoordErrors() {
842     Vert v = head;
843 
844     do {
845       // check for errors in gCoord and fCoord
846       if (v.gCoord >= 1 || v.gCoord < 0 || v.fCoord >= 1 || v.fCoord < 0 || v.coord >= 1
847               || v.coord < 0) {
848         System.out.println("Outline587: Errors in tracking Coordinates\n\t" + "coord=" + v.coord
849                 + ", gCoord= " + v.gCoord + ", fCoord = " + v.fCoord);
850         return true;
851       }
852 
853       v = v.getNext();
854     } while (!v.isHead());
855 
856     return false;
857 
858   }
859 
860   /**
861    * Find the first node in terms of coord (c) or fcoord (f) ie closest to zero.
862    * 
863    * @param c coordinate code
864    * @return First node according to given coordinate code.
865    */
866   public Vert findFirstNode(char c) {
867 
868     Vert v = head;
869     Vert vertFirst = v;
870 
871     double coord;
872     double coordPrev;
873     double dis;
874     double disFirst = 0;
875 
876     do {
877       // coord = (c == 'f') ? v.fCoord : v.coord;
878       // coordPrev = (c == 'f') ? v.getPrev().fCoord : v.getPrev().coord;
879       if (c == 'f') {
880         coord = v.fCoord;
881         coordPrev = v.getPrev().fCoord;
882       } else if (c == 'g') {
883         coord = v.gCoord;
884         coordPrev = v.getPrev().gCoord;
885       } else {
886         coord = v.coord;
887         coordPrev = v.getPrev().coord;
888       }
889 
890       dis = Math.abs(coord - coordPrev);
891       // System.out.println("abs( " + coord + "-"+coordPrev+") = " + dis);
892 
893       if (dis > disFirst) {
894         vertFirst = v;
895         disFirst = dis;
896         // System.out.println("\tchoosen ");
897         // vFirst.print();
898       }
899 
900       v = v.getNext();
901     } while (!v.isHead());
902 
903     // System.out.println("First "+c+"Coord vert: ");
904     // vFirst.print();
905 
906     return vertFirst;
907 
908   }
909 
910   /**
911    * clearFluores.
912    */
913   public void clearFluores() {
914     Vert v = head;
915     do {
916       v.setFluoresChannel(-2, -2, -2, 0);
917       v.setFluoresChannel(-2, -2, -2, 1);
918       v.setFluoresChannel(-2, -2, -2, 2);
919       v = v.getNext();
920     } while (!v.isHead());
921   }
922 
923   /**
924    * findCoordEdge.
925    * 
926    * @param a a
927    * @return ?
928    */
929   public Vert findCoordEdge(double a) {
930     Vert v = head;
931     do {
932 
933       if (v.coord < a && v.coord > a) {
934         return v;
935       }
936 
937       if (v.coord > a && v.getPrev().coord > a && v.getNext().coord > a
938               && v.getNext().getNext().coord > a) {
939         return v;
940       }
941 
942       if (v.coord < a && v.getPrev().coord < a && v.getNext().coord < a
943               && v.getNext().getNext().coord < a) {
944         return v;
945       }
946 
947       v = v.getNext();
948     } while (!v.isHead());
949     return head;
950   }
951 
952   /**
953    * Scale the outline proportionally.
954    * 
955    * <p>Shape is constricted in given number of <tt>steps</tt>. Method updates shape normals
956    * setting them in inner direction. Results can differ (slightly) on each run due to random
957    * selection of head on point remove.
958    * 
959    * <p>Updates centroid and normalised <tt>position</tt>.
960    * 
961    * @param amount scale
962    * @param stepRes shift done in one step
963    * @param angleTh angle threshold
964    * @param freezeTh freeze threshold
965    */
966   public void scaleOutline(double amount, double stepRes, double angleTh, double freezeTh) {
967     int j;
968     updateNormals(true);
969     double steps = Math.abs(amount / stepRes);
970     for (j = 0; j < steps; j++) {
971       if (getNumPoints() <= 3) {
972         break;
973       }
974       super.scale(stepRes);
975       updateNormals(true);
976       removeProx(1.5, 1.5); // constants taken from old removeProx were they were hardcoded
977       freezeProx(angleTh, freezeTh);
978       if (j > MAX_NODES) {
979         LOGGER.warn("shrink (336) hit max iterations!");
980         break;
981       }
982     }
983 
984     if (getNumPoints() < 3) {
985       LOGGER.info("ANA 377_NODES LESS THAN 3 BEFORE CUTS");
986     }
987 
988     if (cutSelfIntersects()) {
989       LOGGER.debug("ANA_(382)...fixed ana intersects");
990     }
991 
992     if (getNumPoints() < 3) {
993       LOGGER.info("ANA 377_NODES LESS THAN 3");
994     }
995     calcCentroid();
996     setPositions();
997   }
998 
999   /**
1000    * Remove close vertexes.
1001    * 
1002    * <p>For each element distances are calculated to next and previous elements. If any of distances
1003    * is
1004    * smaller than given threshold, element is removed (if not frozen).
1005    * 
1006    * @param d1th distance threshold between previous and current
1007    * @param d2th distance threshold between current and next
1008    */
1009   public void removeProx(double d1th, double d2th) {
1010     if (getNumPoints() <= 3) {
1011       return;
1012     }
1013     Vert v;
1014     Vert vl;
1015     Vert vr;
1016     double d1;
1017     double d2;
1018     v = getHead();
1019     vl = v.getPrev();
1020     vr = v.getNext();
1021     do {
1022       d1 = ExtendedVector2d.lengthP2P(v.getPoint(), vl.getPoint());
1023       d2 = ExtendedVector2d.lengthP2P(v.getPoint(), vr.getPoint());
1024 
1025       if ((d1 < d1th || d2 < d2th) && !v.isFrozen()) { // don't remove frozen. May alter angles
1026         removeVert(v);
1027       }
1028       v = v.getNext().getNext();
1029       vl = v.getPrev();
1030       vr = v.getNext();
1031     } while (!v.isHead() && !vl.isHead());
1032 
1033   }
1034 
1035   /**
1036    * Freeze a node and corresponding edge if its to close && close to parallel.
1037    * 
1038    * @param angleTh angle threshold
1039    * @param freezeTh freeze threshold
1040    */
1041   public void freezeProx(double angleTh, double freezeTh) {
1042     Vert v;
1043     Vert vtmp;
1044     ExtendedVector2d closest;
1045     ExtendedVector2d edge;
1046     ExtendedVector2d link;
1047     double dis;
1048     double angle;
1049 
1050     v = getHead();
1051     do {
1052       // if (!v.frozen) {
1053       vtmp = getHead();
1054       do {
1055         if (vtmp.getTrackNum() == v.getTrackNum()
1056                 || vtmp.getNext().getTrackNum() == v.getTrackNum()) {
1057           vtmp = vtmp.getNext();
1058           continue;
1059         }
1060         closest = ExtendedVector2d.pointToSegment(v.getPoint(), vtmp.getPoint(),
1061                 vtmp.getNext().getPoint());
1062         dis = ExtendedVector2d.lengthP2P(v.getPoint(), closest);
1063         // System.out.println("dis: " + dis);
1064         // dis=1;
1065         if (dis < freezeTh) {
1066           edge = ExtendedVector2d.unitVector(vtmp.getPoint(), vtmp.getNext().getPoint());
1067           link = ExtendedVector2d.unitVector(v.getPoint(), closest);
1068           angle = Math.abs(ExtendedVector2d.angle(edge, link));
1069           if (angle > Math.PI) {
1070             angle = angle - Math.PI; // if > 180, shift back around
1071           }
1072           // 180
1073           angle = angle - 1.5708; // 90 degree shift to centre around zero
1074           // System.out.println("angle:" + angle);
1075 
1076           if (angle < angleTh && angle > -angleTh) {
1077             v.freeze();
1078             vtmp.freeze();
1079             vtmp.getNext().freeze();
1080           }
1081 
1082         }
1083         vtmp = vtmp.getNext();
1084       } while (!vtmp.isHead());
1085       // }
1086       v = v.getNext();
1087     } while (!v.isHead());
1088   }
1089 
1090   /*
1091    * (non-Javadoc)
1092    * 
1093    * @see java.lang.Object#hashCode()
1094    */
1095   @SuppressWarnings("unused")
1096   @Override
1097   public int hashCode() {
1098     final int prime = 31;
1099     int result = super.hashCode();
1100     return result;
1101   }
1102 
1103   /*
1104    * (non-Javadoc)
1105    * 
1106    * @see java.lang.Object#equals(java.lang.Object)
1107    */
1108   @SuppressWarnings("unused")
1109   @Override
1110   public boolean equals(Object obj) {
1111     if (this == obj) {
1112       return true;
1113     }
1114     if (!super.equals(obj)) {
1115       return false;
1116     }
1117     if (getClass() != obj.getClass()) {
1118       return false;
1119     }
1120     Outline../../../../com/github/celldynamics/quimp/Outline.html#Outline">Outline other = (Outline) obj;
1121     return true;
1122   }
1123 
1124   /*
1125    * (non-Javadoc)
1126    * 
1127    * @see com.github.celldynamics.quimp.Shape#beforeSerialize()
1128    */
1129   @Override
1130   public void beforeSerialize() {
1131     super.beforeSerialize();
1132     this.updateCurvature();
1133   }
1134 
1135   /*
1136    * (non-Javadoc)
1137    * 
1138    * @see com.github.celldynamics.quimp.Shape#afterSerialize()
1139    */
1140   @Override
1141   public void afterSerialize() throws Exception {
1142     super.afterSerialize();
1143     this.updateCurvature(); // WARN This may be not good idea to override loaded data
1144   }
1145 
1146 }