View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.awt.Rectangle;
4   import java.awt.geom.Rectangle2D;
5   import java.util.List;
6   
7   import org.scijava.vecmath.Tuple2d;
8   import org.slf4j.Logger;
9   import org.slf4j.LoggerFactory;
10  
11  import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
12  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
13  
14  import ij.gui.PolygonRoi;
15  import ij.gui.Roi;
16  import ij.process.FloatPolygon;
17  
18  /**
19   * Low level snake definition. Form snake from Node objects. Snake is defined by first head node.
20   * Remaining nodes are in bidirectional linked list.
21   * 
22   * <p>Node list may be modified externally but then method such as findNode(), updateNormals(),
23   * calcCentroid() should be called to update internal fields of Snake. If number of nodes changes it
24   * is recommended to create new object.
25   * 
26   * @author rtyson
27   * @author baniuk
28   *
29   */
30  public class Snake extends Shape<Node> implements IQuimpSerialize {
31  
32    /**
33     * The Constant LOGGER.
34     */
35    static final Logger LOGGER = LoggerFactory.getLogger(Snake.class.getName());
36    /**
37     * true if snake is alive Changed during segmentation and user interaction.
38     * 
39     * <p>snake is killed by {@link SnakeHandler#kill()} method usually called after unsuccessful
40     * segmentation.
41     */
42    public boolean alive;
43    /**
44     * unique ID of snake Given during Snake creation by SnakeHandler. It is possible to set id
45     * explicitly by com.github.celldynamics.quimp.Snake.setSnakeID(int)
46     */
47    private int snakeID;
48    /**
49     * how many nodes at start of segmentation.
50     */
51    public double startingNnodes;
52    /**
53     * number of nodes frozen Changed during segmentation.
54     */
55    private int FROZEN; // name related to QCONF file do not change
56    /**
57     * Snake bounds, updated only on use getBounds(). Even though this field is serialised it is
58     * recalculated in afterSerialzie() and beforeSerialzie()
59     */
60    private Rectangle bounds = new Rectangle();
61  
62    /**
63     * Create a snake from existing linked list (at least one head node).
64     * 
65     * <p>List is referenced only not copied Behaviour of this method was changed. Now it does not
66     * make
67     * copy of Node. In old approach there was dummy node deleted in this constructor.
68     * 
69     * @param h Node of list
70     * @param n Number of nodes
71     * @param id Unique snake ID related to object being segmented.
72     */
73    public Snake(final Node h, int n, int id) {
74      super(h, n);
75      snakeID = id;
76      this.makeAntiClockwise(); // can affect centroid on last positions, so calculate it afterwards
77      this.updateNormals(BOA_.qState.segParam.expandSnake);
78      alive = true;
79      startingNnodes = POINTS / 100.; // as 1%. limit to X%
80      countFrozen(); // set FROZEN
81      // calcOrientation();
82      getBounds();
83    }
84  
85    /**
86     * Copy constructor with new ID.
87     * 
88     * @param src Snake to be duplicated
89     * @param id New id
90     */
91    public Snakeics/quimp/Snake.html#Snake">Snake(final Snake src, int id) {
92      super(src);
93      alive = src.alive;
94      snakeID = id;
95      startingNnodes = src.startingNnodes;
96      countFrozen();
97      bounds = new Rectangle(src.bounds);
98    }
99  
100   /**
101    * Copy constructor.
102    * 
103    * @param src to be duplicated
104    */
105   public Snakeics/quimp/Snake.html#Snake">Snake(final Snake src) {
106     this(src, src.getSnakeID());
107   }
108 
109   /**
110    * Create snake from ROI
111    * 
112    * @param r ROI with object to be segmented
113    * @param id Unique ID of snake related to object being segmented.
114    * @param direct direct
115    * @throws BoaException on wrong number of polygon points
116    */
117   public Snake(final Roi r, int id, boolean direct) throws BoaException {
118     // place nodes in a circle
119     snakeID = id;
120     if (r.getType() == Roi.RECTANGLE || r.getType() == Roi.POLYGON) {
121       if (direct) {
122         intializePolygonDirect(r.getFloatPolygon());
123       } else {
124         intializePolygon(r.getFloatPolygon());
125       }
126     } else {
127       Rectangle rect = r.getBounds();
128       int xc = rect.x + rect.width / 2;
129       int yc = rect.y + rect.height / 2;
130       int rx = rect.width / 2;
131       int ry = rect.height / 2;
132 
133       intializeOval(0, xc, yc, rx, ry, BOA_.qState.segParam.getNodeRes() / 2);
134     }
135     startingNnodes = POINTS / 100.; // as 1%. limit to X%
136     alive = true;
137     // colour = QColor.lightColor();
138     // calcOrientation();
139     calcCentroid();
140     getBounds();
141   }
142 
143   /**
144    * Initialises snake from PolyginRoi.
145    * 
146    * @see #Snake(Roi, int, boolean)
147    * @param r polygon to initialise Snake
148    * @param id id of Snake
149    * @throws BoaException on wrong number of polygon points
150    */
151   public Snake(final PolygonRoi r, int id) throws BoaException {
152     snakeID = id;
153     intializeFloat(r.getFloatPolygon());
154     startingNnodes = POINTS / 100.; // as 1%. limit to X%
155     alive = true;
156     // colour = QColor.lightColor();
157     // calcOrientation();
158     calcCentroid();
159     getBounds();
160   }
161 
162   /**
163    * Construct Snake object from list of nodes. Head node is always first element from array.
164    * 
165    * @param list list of nodes as Vector2d
166    * @param id id of Snake
167    * @throws BoaException on wrong number of array points (<3).
168    */
169   public Snake(final List<? extends Tuple2d> list, int id) throws BoaException {
170     super(list, new Node(0), BOA_.qState.segParam.expandSnake);
171     if (list.size() <= 3) { // compatibility
172       throw new BoaException("Not enough points provided");
173     }
174     snakeID = id;
175     this.makeAntiClockwise(); // specific to snake can affect updateNormals
176     updateNormals(BOA_.qState.segParam.expandSnake); // called in super, here just in case
177     startingNnodes = POINTS / 100;
178     alive = true;
179     getBounds();
180   }
181 
182   /**
183    * Construct Snake object from X and Y arrays. Head node is always first element from array.
184    * 
185    * @param x x coordinates of nodes
186    * @param y y coordinates of nodes
187    * @param id id of Snake
188    * @throws BoaException on wrong number of array points (<3).
189    */
190   public Snake(final double[] x, final double[] y, int id) throws BoaException {
191     super(x, y, new Node(0), BOA_.qState.segParam.expandSnake);
192     if ((x.length != y.length) || x.length <= 3) {
193       throw new BoaException(
194               "Lengths of X and Y arrays are not equal or there is less than 3 nodes");
195     }
196     snakeID = id;
197     this.makeAntiClockwise(); // specific to snake can affect updateNormals
198     updateNormals(BOA_.qState.segParam.expandSnake); // called in super, here just in case
199     startingNnodes = POINTS / 100;
200     alive = true;
201     getBounds();
202   }
203 
204   /**
205    * Construct empty Snake object.
206    * 
207    */
208   public Snake() {
209     super();
210     alive = false;
211   }
212 
213   /*
214    * (non-Javadoc)
215    * 
216    * @see java.lang.Object#hashCode()
217    */
218   @Override
219   public int hashCode() {
220     final int prime = 31;
221     int result = super.hashCode();
222     result = prime * result + FROZEN;
223     result = prime * result + (alive ? 1231 : 1237);
224     result = prime * result + ((bounds == null) ? 0 : bounds.hashCode());
225     result = prime * result + snakeID;
226     long temp;
227     temp = Double.doubleToLongBits(startingNnodes);
228     result = prime * result + (int) (temp ^ (temp >>> 32));
229     return result;
230   }
231 
232   /*
233    * (non-Javadoc)
234    * 
235    * @see java.lang.Object#equals(java.lang.Object)
236    */
237   @Override
238   public boolean equals(Object obj) {
239     if (this == obj) {
240       return true;
241     }
242     if (!super.equals(obj)) {
243       return false;
244     }
245     if (!(obj instanceof Snake)) {
246       return false;
247     }
248     Snake="../../../../com/github/celldynamics/quimp/Snake.html#Snake">Snake other = (Snake) obj;
249     if (FROZEN != other.FROZEN) {
250       return false;
251     }
252     if (alive != other.alive) {
253       return false;
254     }
255     if (bounds == null) {
256       if (other.bounds != null) {
257         return false;
258       }
259     } else if (!bounds.equals(other.bounds)) {
260       return false;
261     }
262     if (snakeID != other.snakeID) {
263       return false;
264     }
265     if (Double.doubleToLongBits(startingNnodes) != Double.doubleToLongBits(other.startingNnodes)) {
266       return false;
267     }
268     return true;
269   }
270 
271   /**
272    * Get ID of Snake.
273    * 
274    * @return the snakeID
275    */
276   public int getSnakeID() {
277     return snakeID;
278   }
279 
280   /**
281    * Change current snakeID. Should be used carefully.
282    * 
283    * @param snakeID the snakeID to set
284    */
285   protected void setSnakeID(int snakeID) {
286     this.snakeID = snakeID;
287   }
288 
289   /**
290    * Initialises Node list from ROIs other than polygons For non-polygon ROIs ellipse is used
291    * as first approximation of segmented shape. Parameters of ellipse are estimated usually using
292    * parameters of bounding box of user ROI This method differs from other initialize* methods
293    * by input data which do not contain nodes but the are defined analytically
294    * 
295    * @param t index of node
296    * @param xc center of ellipse
297    * @param yc center of ellipse
298    * @param rx ellipse diameter
299    * @param ry ellipse diameter
300    * @param s number of nodes
301    * 
302    * @throws BoaException Exception if polygon contains too little nodes.
303    */
304   private void intializeOval(int t, int xc, int yc, int rx, int ry, double s) throws BoaException {
305     head = new Node(t); // make a dummy head node for list initialization
306     POINTS = 1;
307     FROZEN = 0;
308     head.setPrev(head); // link head to itself
309     head.setNext(head);
310     head.setHead(true);
311 
312     double theta = 2.0 / (double) ((rx + ry) / 2);
313 
314     // nodes are added in behind the head node
315     Node node;
316     for (double a = 0.0; a < (2 * Math.PI); a += s * theta) {
317       node = new Node(nextTrackNumber);
318       nextTrackNumber++;
319       node.getPoint().setX((int) (xc + rx * Math.cos(a)));
320       node.getPoint().setY((int) (yc + ry * Math.sin(a)));
321       addPoint(node);
322     }
323     removeNode(head); // remove dummy head node
324     this.makeAntiClockwise();
325     updateNormals(BOA_.qState.segParam.expandSnake);
326   }
327 
328   /**
329    * Initialises Node list from polygon Each edge of input polygon is divided on
330    * {@link BOAState.SegParam#getNodeRes()}
331    * 
332    * @param p Polygon extracted from IJ ROI
333    * @throws BoaException if polygon contains too little nodes.
334    */
335   private void intializePolygon(final FloatPolygon p) throws BoaException {
336     // System.out.println("poly with node distance");
337     head = new Node(0); // make a dummy head node for list initialization
338     POINTS = 1;
339     FROZEN = 0;
340     head.setPrev(head); // link head to itself
341     head.setNext(head);
342     head.setHead(true);
343 
344     Node node;
345     int j;
346     int nn;
347     double x;
348     double y;
349     double spacing;
350     ExtendedVector2d a;
351     ExtendedVector2d b;
352     ExtendedVector2d u;
353     for (int i = 0; i < p.npoints; i++) {
354       j = ((i + 1) % (p.npoints)); // for last i point we turn for first one closing polygon
355       a = new ExtendedVector2d(p.xpoints[i], p.ypoints[i]);// vectors ab define edge
356       b = new ExtendedVector2d(p.xpoints[j], p.ypoints[j]);
357 
358       nn = (int) Math.ceil(ExtendedVector2d.lengthP2P(a, b) / BOA_.qState.segParam.getNodeRes());
359       spacing = ExtendedVector2d.lengthP2P(a, b) / (double) nn;
360       u = ExtendedVector2d.unitVector(a, b);
361       u.multiply(spacing); // required distance between points
362 
363       for (int s = 0; s < nn; s++) { // place nodes along edge
364         node = new Node(nextTrackNumber);
365         nextTrackNumber++;
366         x = a.getX() + (double) s * u.getX();
367         y = a.getY() + (double) s * u.getY();
368         node.setX(x);
369         node.setY(y);
370         addPoint(node);
371       }
372     }
373     removeNode(head); // remove dummy head node new head will be set
374     setPositions();
375     this.makeAntiClockwise();
376     updateNormals(BOA_.qState.segParam.expandSnake);
377   }
378 
379   /**
380    * Initializes Node list from polygon Does not refine points. Use only those nodes available in
381    * polygon.
382    * 
383    * @param p Polygon extracted from IJ ROI
384    * @throws BoaException if polygon contains too little nodes.
385    * @see #intializePolygon(FloatPolygon)
386    */
387   private void intializePolygonDirect(final FloatPolygon p) throws BoaException {
388     // System.out.println("poly direct");
389     head = new Node(0); // make a dummy head node for list initialization
390     POINTS = 1;
391     FROZEN = 0;
392     head.setPrev(head); // link head to itself
393     head.setNext(head);
394     head.setHead(true);
395 
396     Node node;
397     for (int i = 0; i < p.npoints; i++) {
398       node = new Node((double) p.xpoints[i], (double) p.ypoints[i], nextTrackNumber++);
399       addPoint(node);
400     }
401 
402     removeNode(head); // remove dummy head node
403     setPositions();
404     this.makeAntiClockwise();
405     updateNormals(BOA_.qState.segParam.expandSnake);
406   }
407 
408   /**
409    * Create Snake from polygon.
410    * 
411    * @param p polygon to initialise snake from
412    * @see #intializePolygonDirect(FloatPolygon)
413    * @throws BoaException if polygon contains too little nodes.
414    */
415   private void intializeFloat(final FloatPolygon p) throws BoaException {
416     // FIXME This method is the same as intializePolygonDirect(FloatPolygon)
417     head = new Node(0); // make a dummy head node
418     POINTS = 1;
419     FROZEN = 0;
420     head.setPrev(head); // link head to itself
421     head.setNext(head);
422     head.setHead(true);
423 
424     Node node;
425     for (int i = 0; i < p.npoints; i++) {
426       node = new Node((double) p.xpoints[i], (double) p.ypoints[i], nextTrackNumber++);
427       addPoint(node);
428     }
429 
430     removeNode(head); // remove dummy head node
431     setPositions();
432     this.makeAntiClockwise();
433     updateNormals(BOA_.qState.segParam.expandSnake);
434   }
435 
436   /**
437    * Prints the snake.
438    */
439   public void printSnake() {
440     System.out.println("Print Nodes (" + POINTS + ")");
441     int i = 0;
442     Node n = head;
443     do {
444       int x = (int) n.getPoint().getX();
445       int y = (int) n.getPoint().getY();
446       System.out.println(i + " Node " + n.getTrackNum() + ", x:" + x + ", y:" + y + ", vel: "
447               + n.getVel().length());
448       n = n.getNext();
449       i++;
450     } while (!n.isHead());
451     if (i != POINTS) {
452       System.out.println("NODES and linked list dont tally!!");
453     }
454   }
455 
456   /**
457    * Unfreeze all nodes.
458    */
459   @Override
460   public void unfreezeAll() {
461     super.unfreezeAll();
462     FROZEN = 0;
463   }
464 
465   /**
466    * Go through whole list and count Nodes that are frozen. Set FREEZE variable
467    */
468   private void countFrozen() {
469     Node n = head;
470     FROZEN = 0;
471     do {
472       if (n.isFrozen()) {
473         FROZEN++;
474       }
475       n = n.getNext();
476     } while (!n.isHead());
477   }
478 
479   /**
480    * Freeze a specific node.
481    * 
482    * @param n Node to freeze
483    */
484   public void freezeNode(Node n) {
485     if (!n.isFrozen()) {
486       n.freeze();
487       FROZEN++;
488     }
489   }
490 
491   /**
492    * Unfreeze a specific node.
493    * 
494    * @param n Node to unfreeze
495    */
496   public void unfreezeNode(Node n) {
497     if (n.isFrozen()) {
498       n.unfreeze();
499       FROZEN--;
500     }
501   }
502 
503   /**
504    * Check if all nodes are frozen.
505    * 
506    * @return true if all nodes are frozen
507    */
508   public boolean isFrozen() {
509     if (FROZEN == POINTS) {
510       return true;
511     } else {
512       return false;
513     }
514   }
515 
516   /**
517    * Remove selected node from list.
518    * 
519    * <p>Perform check if removed node was head and if it was, the new head is randomly selected.
520    * Neighbors are linked together
521    * 
522    * @param n Node to remove
523    * 
524    * @throws BoaException on insufficient number of nodes
525    */
526   public final void removeNode(Node n) throws BoaException {
527     if (POINTS <= 3) {
528       throw new BoaException("removeNode: Did not remove node. " + POINTS + " nodes remaining.", 0,
529               2);
530     }
531     if (n.isFrozen()) {
532       FROZEN--;
533     }
534     super.removePoint(n, BOA_.qState.segParam.expandSnake);
535   }
536 
537   /**
538    * Implode.
539    *
540    * @throws BoaException if oval could not be initialized.
541    */
542   public void implode() throws BoaException {
543     // calculate centroid
544     double cx;
545     double cy;
546     cx = 0.0;
547     cy = 0.0;
548     Node n = head;
549     do {
550       cx += n.getX();
551       cy += n.getY();
552       n = n.getNext();
553     } while (!n.isHead());
554     cx = cx / POINTS;
555     cy = cy / POINTS;
556 
557     intializeOval(nextTrackNumber, (int) cx, (int) cy, 4, 4, 1);
558   }
559 
560   /**
561    * Blowup.
562    *
563    * @throws Exception the exception
564    */
565   @Deprecated
566   public void blowup() throws Exception {
567     scaleSnake(BOA_.qState.segParam.blowup, 4, true);
568   }
569 
570   /**
571    * Scale current Snake by amount in increments of stepSize.
572    * 
573    * <p>Updates centroid and normalised <tt>position</tt>.
574    * 
575    * @param amount scale
576    * @param stepRes increment
577    * @param correct if true it corrects the node distance
578    * @throws BoaException if node distance correction failed
579    * @see com.github.celldynamics.quimp.Shape#scale(double)
580    * @see Outline#scaleOutline(double, double, double, double)
581    */
582   public void scaleSnake(double amount, double stepRes, boolean correct) throws BoaException {
583     if (amount == 0) {
584       return;
585     }
586     // make sure snake access is clockwise
587     Node.setClockwise(true);
588     // scale the snake by 'amount', in increments of 'stepsize'
589     if (amount > 0) {
590       stepRes *= -1; // scale down if amount negative
591     }
592     double steps = Math.abs(amount / stepRes);
593     // IJ.log(""+steps);
594     int j;
595     for (j = 0; j < steps; j++) {
596       super.scale(stepRes);
597       if (correct) {
598         correctDistance(false);
599       }
600       cutLoops();
601       updateNormals(BOA_.qState.segParam.expandSnake);
602     }
603     calcCentroid();
604     setPositions();
605   }
606 
607   /**
608    * Cut out a loop Insert a new node at cut point.
609    */
610   public void cutLoops() {
611     final int maxInterval = 12; // how far ahead do you check for a loop
612     int interval;
613     int state;
614 
615     Node nodeA;
616     Node nodeB;
617     double[] intersect = new double[2];
618     Node newN;
619 
620     boolean cutHead;
621 
622     nodeA = head;
623     do {
624       cutHead = (nodeA.getNext().isHead()) ? true : false;
625       nodeB = nodeA.getNext().getNext(); // don't check next edge as they can't cross, but do touch
626 
627       // always leave 3 nodes, at least
628       interval = (POINTS > maxInterval + 3) ? maxInterval : (POINTS - 3);
629 
630       for (int i = 0; i < interval; i++) {
631         if (nodeB.isHead()) {
632           cutHead = true;
633         }
634         state = ExtendedVector2d.segmentIntersection(nodeA.getX(), nodeA.getY(),
635                 nodeA.getNext().getX(), nodeA.getNext().getY(), nodeB.getX(), nodeB.getY(),
636                 nodeB.getNext().getX(), nodeB.getNext().getY(), intersect);
637         if (state == 1) {
638           // System.out.println("CutLoops: cut out a loop");
639           newN = this.insertNode(nodeA);
640           newN.setX(intersect[0]);
641           newN.setY(intersect[1]);
642 
643           newN.setNext(nodeB.getNext());
644           nodeB.getNext().setPrev(newN);
645 
646           newN.updateNormale(BOA_.qState.segParam.expandSnake);
647           nodeB.getNext().updateNormale(BOA_.qState.segParam.expandSnake);
648 
649           // set velocity
650           newN.setVel(nodeB.getVel());
651           if (newN.getVel().length() < BOA_.qState.segParam.vel_crit) {
652             newN.getVel().makeUnit();
653             newN.getVel().multiply(BOA_.qState.segParam.vel_crit * 1.5);
654           }
655 
656           if (cutHead) {
657             newN.setHead(true); // put a new head in
658             head = newN;
659           }
660 
661           POINTS -= (i + 2); // the one skipped and the current one
662           break;
663         }
664         nodeB = nodeB.getNext();
665       }
666       nodeA = nodeA.getNext();
667     } while (!nodeA.isHead());
668   }
669 
670   /**
671    * Cut out intersects. Done once at the end of each frame to cut out any parts of the contour
672    * that self intersect. Similar to cutLoops, but check all edges (NODES / 2) and cuts out the
673    * smallest section
674    * 
675    * @see #cutLoops()
676    * @see com.github.celldynamics.quimp.Outline#cutSelfIntersects()
677    */
678   public void cutIntersects() {
679 
680     int interval;
681     int state;
682 
683     Node nodeA;
684     Node nodeB;
685     double[] intersect = new double[2];
686     Node newN;
687 
688     boolean cutHead;
689 
690     nodeA = head;
691     do {
692       cutHead = (nodeA.getNext().isHead()) ? true : false;
693       nodeB = nodeA.getNext().getNext();// don't check next edge as they can't cross, but do touch
694       interval = (POINTS > 6) ? POINTS / 2 : 2; // always leave 3 nodes, at least
695 
696       for (int i = 2; i < interval; i++) {
697         if (nodeB.isHead()) {
698           cutHead = true;
699         }
700 
701         state = ExtendedVector2d.segmentIntersection(nodeA.getX(), nodeA.getY(),
702                 nodeA.getNext().getX(), nodeA.getNext().getY(), nodeB.getX(), nodeB.getY(),
703                 nodeB.getNext().getX(), nodeB.getNext().getY(), intersect);
704 
705         if (state == 1) {
706           newN = this.insertNode(nodeA);
707           newN.setX(intersect[0]);
708           newN.setY(intersect[1]);
709 
710           newN.setNext(nodeB.getNext());
711           nodeB.getNext().setPrev(newN);
712 
713           newN.updateNormale(BOA_.qState.segParam.expandSnake);
714           nodeB.getNext().updateNormale(BOA_.qState.segParam.expandSnake);
715 
716           if (cutHead) {
717             newN.setHead(true); // put a new head in
718             head = newN;
719           }
720 
721           POINTS -= (i);
722           break;
723         }
724         nodeB = nodeB.getNext();
725       }
726 
727       nodeA = nodeA.getNext();
728     } while (!nodeA.isHead());
729   }
730 
731   /**
732    * Ensure nodes are between maxDist and minDist apart, add remove nodes as required.
733    * 
734    * @param shiftNewNode shiftNewNode
735    * @throws BoaException when number of nodes is less than 3 after removal
736    */
737   public void correctDistance(boolean shiftNewNode) throws BoaException {
738     Node.randDirection(); // choose a random direction to process the chain
739 
740     ExtendedVector2d tanL;
741     ExtendedVector2d tanR;
742     ExtendedVector2d tanLR;
743     ExtendedVector2d npos;
744     double dl;
745     double dr;
746     double dlr;
747     double tmp;
748 
749     Node nc = head;
750     Node nl;
751     Node nr; // neighbours
752 
753     do {
754 
755       nl = nc.getPrev(); // left neighbour
756       nr = nc.getNext(); // left neighbour
757 
758       // compute tangent
759       tanL = ExtendedVector2d.vecP2P(nl.getPoint(), nc.getPoint());
760       tanR = ExtendedVector2d.vecP2P(nc.getPoint(), nr.getPoint());
761       tanLR = ExtendedVector2d.vecP2P(nl.getPoint(), nr.getPoint());
762       dl = tanL.length();
763       dr = tanR.length();
764       dlr = tanLR.length();
765 
766       if (dl < BOA_.qState.segParam.getMin_dist() || dr < BOA_.qState.segParam.getMin_dist()) {
767         // nC is to close to a neigbour
768         if (dlr > 2 * BOA_.qState.segParam.getMin_dist()) {
769 
770           // move nC to middle
771           npos = new ExtendedVector2d(tanLR.getX(), tanLR.getY());
772           npos.multiply(0.501); // half
773           npos.addVec(nl.getPoint());
774 
775           nc.setX(npos.getX());
776           nc.setY(npos.getY());
777 
778           // tmp = Math.sqrt((dL*dL) - ((dLR/2.)*(dLR/2.)));
779           // System.out.println("too close, move to middle, tmp:
780           // "+tmp);
781 
782           tmp = Math.sin(ExtendedVector2d.angle(tanL, tanLR)) * dl;
783           // tmp = Vec2d.distPointToSegment(nC.getPoint(),
784           // nL.getPoint(), nR.getPoint());
785           nc.getNormal().multiply(-tmp);
786           nc.getPoint().addVec(nc.getNormal());
787 
788           nc.updateNormale(BOA_.qState.segParam.expandSnake);
789           nl.updateNormale(BOA_.qState.segParam.expandSnake);
790           nr.updateNormale(BOA_.qState.segParam.expandSnake);
791           this.unfreezeNode(nc);
792 
793         } else {
794           // delete nC
795           // System.out.println("delete node");
796           removeNode(nc);
797           nl.updateNormale(BOA_.qState.segParam.expandSnake);
798           nr.updateNormale(BOA_.qState.segParam.expandSnake);
799           if (nr.isHead()) {
800             break;
801           }
802           nc = nr.getNext();
803           continue;
804         }
805       }
806       if (dl > BOA_.qState.segParam.getMax_dist()) {
807 
808         // System.out.println("1357-insert node");
809         Node nins = insertNode(nl);
810         nins.setVel(nl.getVel());
811         nins.getVel().addVec(nc.getVel());
812         nins.getVel().multiply(0.5);
813         if (nins.getVel().length() < BOA_.qState.segParam.vel_crit) {
814           nins.getVel().makeUnit();
815           nins.getVel().multiply(BOA_.qState.segParam.vel_crit * 1.5);
816         }
817 
818         npos = new ExtendedVector2d(tanL.getX(), tanL.getY());
819         npos.multiply(0.51);
820         npos.addVec(nl.getPoint());
821 
822         nins.setX(npos.getX());
823         nins.setY(npos.getY());
824         nins.updateNormale(BOA_.qState.segParam.expandSnake);
825         if (shiftNewNode) {
826           nins.getNormal().multiply(-2); // move out a bit
827           nins.getPoint().addVec(nins.getNormal());
828           nins.updateNormale(BOA_.qState.segParam.expandSnake);
829         }
830         nl.updateNormale(BOA_.qState.segParam.expandSnake);
831         nr.updateNormale(BOA_.qState.segParam.expandSnake);
832         nc.updateNormale(BOA_.qState.segParam.expandSnake);
833 
834       }
835 
836       nc = nc.getNext();
837     } while (!nc.isHead());
838 
839     Node.setClockwise(true); // reset to clockwise (although shouldnt effect things??)
840   }
841 
842   /**
843    * Insert default Node after Node v.
844    * 
845    * @param n Node to insert new Node after
846    * @return Inserted Node
847    */
848   public NodeNode.html#Node">Node insertNode(final Node n) {
849     return insertPoint(n, new Node());
850   }
851 
852   /**
853    * Return current Snake as \b POLYLINE
854    * 
855    * @return ij.gui.PolygonRoi.PolygonRoi as \b POLYLINE type
856    */
857   Roi asPolyLine() {
858     float[] x = new float[POINTS];
859     float[] y = new float[POINTS];
860 
861     Node n = head;
862     int i = 0;
863     do {
864       x[i] = (float) n.getX();
865       y[i] = (float) n.getY();
866       i++;
867       n = n.getNext();
868     } while (!n.isHead());
869     return new PolygonRoi(x, y, POINTS, Roi.POLYLINE);
870   }
871 
872   /**
873    * Gets bounds of snake.
874    * 
875    * @return Bounding box of current Snake object
876    */
877   public Rectangle getBounds() {
878 
879     Rectangle2D.Double rect = getDoubleBounds();
880 
881     bounds.setBounds((int) rect.getMinX(), (int) rect.getMinY(), (int) rect.getWidth(),
882             (int) rect.getHeight());
883     return bounds;
884   }
885 
886   /**
887    * Edits the snake.
888    */
889   public void editSnake() {
890     System.out.println("Editing a snake");
891   }
892 
893   /*
894    * (non-Javadoc)
895    * 
896    * @see java.lang.Object#toString()
897    */
898   @Override
899   public String toString() {
900     return "Snake [alive=" + alive + ", snakeID=" + snakeID + ", startingNnodes=" + startingNnodes
901             + ", FROZEN=" + FROZEN + ", bounds=" + bounds + ", POINTS=" + POINTS + ", centroid="
902             + centroid + ", toString()=" + super.toString() + "]";
903   }
904 
905   /**
906    * Call super and then oo Snake related actions
907    * 
908    * @see com.github.celldynamics.quimp.Shape#beforeSerialize()
909    */
910   @Override
911   public void beforeSerialize() {
912     super.beforeSerialize();
913     getBounds();
914   }
915 
916   /**
917    * Call super and then oo Snake related actions
918    * 
919    * @see com.github.celldynamics.quimp.Shape#afterSerialize()
920    */
921   @Override
922   public void afterSerialize() throws Exception {
923     super.afterSerialize();
924     getBounds();
925   }
926 
927 }