View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.awt.Polygon;
4   import java.awt.geom.Rectangle2D;
5   import java.lang.reflect.Constructor;
6   import java.lang.reflect.InvocationTargetException;
7   import java.util.ArrayList;
8   import java.util.Iterator;
9   import java.util.List;
10  import java.util.NoSuchElementException;
11  
12  import org.scijava.vecmath.Point2d;
13  import org.scijava.vecmath.Tuple2d;
14  import org.slf4j.Logger;
15  import org.slf4j.LoggerFactory;
16  
17  import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
18  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
19  
20  import ij.gui.PolygonRoi;
21  import ij.gui.Roi;
22  
23  /**
24   * Form abstract shape from bidirectional list of points.
25   * 
26   * <p>This abstract class keeps head point of Shape and control number of points in Shape, allows
27   * for inserting points to the Shape. Generally assumes that Shape is closed, so PointsList is
28   * looped
29   * 
30   * @author p.baniukiewicz
31   *
32   * @param <T> Type of point, currently can be Node or Vert
33   */
34  public abstract class Shape<T extends PointsList<T>> implements IQuimpSerialize, Iterable<T> {
35  
36    /**
37     * Threshold value for choosing new head as next or previous element on list if old head is
38     * deleted.
39     * 
40     * <p>This should be used for determining the new head node only in tests, accessed by reflection.
41     * 
42     * <pre>
43     * <code>
44     * Field f = Shape.class.getDeclaredField("threshold");
45     * f.setAccessible(true);
46     * f.setDouble(Shape.class, 1.0);
47     * </code>
48     * </pre>
49     * 
50     * @see #removePoint(PointsList, boolean)
51     */
52    private static double threshold = 0.5;
53    /**
54     * The Constant LOGGER.
55     */
56    static final Logger LOGGER = LoggerFactory.getLogger(Shape.class.getName());
57    /**
58     * Next node ID's. Initialised in constructor, changed during modification of shape.
59     */
60    protected int nextTrackNumber = 1;
61    /**
62     * first node in double linked list, always maintained, initialised in constructor.
63     */
64    protected T head;
65    /**
66     * number of points. Initialised in constructor, changed on Shape modification.
67     */
68    protected int POINTS;
69    /**
70     * Centroid point of the Shape. Updated by {@link #calcCentroid()} called. after change of the
71     * Shape and also in {@link #afterSerialize()} and {@link #beforeSerialize()}
72     */
73    protected ExtendedVector2d centroid = null;
74  
75    /**
76     * Max number of nodes allowed in Shape.
77     */
78    public static final int MAX_NODES = 10000;
79    /**
80     * List is correct: has head, looped.
81     * 
82     * @see #validateShape()
83     */
84    public static final int LIST_OK = 0;
85    /**
86     * List contains node marked as head but without proper flag or null.
87     * 
88     * @see #validateShape()
89     */
90    public static final int BAD_HEAD = 1;
91    /**
92     * No head found among first {@value #MAX_NODES}.
93     * {@link #validateShape()}
94     */
95    public static final int NO_HEAD = 2;
96    /**
97     * List is not closed. At least one nod has not previous or next neighbour.
98     * {@link #validateShape()}
99     */
100   public static final int BAD_LINKING = 4;
101   /**
102    * Number of list elements does match to the number stored in {@link #POINTS}.
103    * 
104    * <p>This can happen when {@link #Shape(PointsList, int)} is used. See {@link #validateShape()}.
105    */
106   public static final int BAD_NUM_POINTS = 8;
107   /**
108    * Elements of Shape as List. Initialised on Serialise. Temporary array to store linked list as
109    * array to allow serialisation
110    */
111   private ArrayList<T> Elements = null;
112 
113   /**
114    * Default constructor, creates empty Shape.
115    */
116   public Shape() {
117     POINTS = 0;
118     head = null;
119   }
120 
121   /**
122    * Create Shape from existing list of points (can be one point as well).
123    * 
124    * <p>List of points must be looped.
125    * 
126    * @param h head point of the list
127    * @param n number of points in the list
128    * @see #validateShape()
129    */
130   public Shape(T h, int n) {
131     head = h;
132     T he = checkIsHead(); // can override head
133     if (he == null) {
134       LOGGER.info("No head in list. Selecting " + h + "as head");
135       h.setHead(true);
136       head = h;
137     } else {
138       if (he != h) {
139         LOGGER.info("Head at different position than given initial element. Selecting " + he
140                 + "as head");
141       }
142       head = he;
143     }
144 
145     POINTS = n;
146     nextTrackNumber = n + 1;
147     calcCentroid();
148     setPositions();
149   }
150 
151   /**
152    * Create Shape from one point, created Shape will be looped. If <tt>h</tt> is a list, only
153    * <tt>h</tt> will be maintained and list will be unlinked.
154    * 
155    * @param h head point of the list
156    */
157   public Shape(final T h) {
158     head = h;
159     head.setHead(true);
160     head.setNext(head);
161     head.setPrev(head);
162     nextTrackNumber = head.getTrackNum() + 1;
163     POINTS = 1;
164     calcCentroid();
165     setPositions();
166   }
167 
168   /**
169    * Copy constructor. Calculates centroid as well and positions.
170    * 
171    * @param src source Shape to copy from
172    * @throws RuntimeException when T does no have copy constructor
173    */
174   public Shape(final Shape<T> src) {
175     this(src, src.getHead());
176   }
177 
178   /**
179    * Conversion and copy constructor.
180    * 
181    * <p>Converts between different types of PointsList. <tt>src</tt> is source Shape of type T to
182    * convert to other Shape based on <tt>PointsList</tt> other type (but in general extended from
183    * PointsList) Typical approach is to convert Snake to Outline ({@link Node} to
184    * {@link Vert}).
185    * 
186    * <p>Can be used as copy constructor.
187    * 
188    * @param src input Shape to convert.
189    * @param destType object of base node that PointsList is composed from
190    * @throws RuntimeException when T does no have copy constructor
191    * 
192    */
193   @SuppressWarnings("unchecked")
194   public Shape(final Shape<T> src, T destType) {
195     T tmpHead = src.getHead(); // get head as representative object
196     Class<?> templateClass = tmpHead.getClass(); // get class under Shape (T)
197     LOGGER.trace("Src class: " + templateClass.getName());
198     try {
199       // Constructor of T as type can not be called directly, use reflection
200       // get Constructor of T with one parameter of Type src (conversion constructor)
201       Constructor<?> ctor = destType.getClass().getDeclaredConstructor(templateClass);
202       // create copy of head
203       head = (T) ctor.newInstance(src.getHead());
204       T srcn = src.getHead();
205       T n = head;
206       // iterate over whole list making copies of T elements
207       for (srcn = srcn.getNext(); !srcn.isHead(); srcn = srcn.getNext()) {
208         T next = (T) ctor.newInstance(srcn);
209         n.setNext(next);
210         next.setPrev(n);
211         n = next;
212       }
213       // loop list
214       n.setNext(head);
215       head.setPrev(n);
216     } catch (SecurityException | NoSuchMethodException | InstantiationException
217             | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
218       throw new RuntimeException(e); // change to unchecked exception
219     }
220     // copy rest of params
221     POINTS = src.POINTS;
222     nextTrackNumber = src.nextTrackNumber;
223     centroid = new ExtendedVector2d(src.centroid);
224   }
225 
226   /**
227    * Create Shape of elements <tt>elInst</tt> with coordinates <tt>p</tt>.
228    * 
229    * @param p list of coordinates
230    * @param elInst instance of requested element type
231    * @param inner direction of normales. For Outlines set to true, for snakes to
232    *        BOA_.qState.segParam.expandSnake
233    */
234   @SuppressWarnings("unchecked")
235   public Shape(final List<? extends Tuple2d> p, T elInst, boolean inner) {
236     try {
237       Constructor<?> ctor = elInst.getClass().getDeclaredConstructor(int.class);
238       Constructor<?> ctorPoint =
239               elInst.getClass().getDeclaredConstructor(double.class, double.class, int.class);
240       head = (T) ctor.newInstance(0);
241       POINTS = 1;
242       head.setPrev(head);
243       head.setNext(head);
244       head.setHead(true);
245       T node;
246       for (Tuple2d el : p) {
247         node = (T) ctorPoint.newInstance(el.getX(), el.getY(), nextTrackNumber++);
248         addPoint(node);
249       }
250       removePoint(head, inner);
251       setHeadClosestTo(new ExtendedVector2d(p.get(0))); // first point from list is head
252 
253     } catch (SecurityException | NoSuchMethodException | InstantiationException
254             | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
255       throw new RuntimeException(e); // change to unchecked exception
256     }
257     updateNormals(inner);
258     calcCentroid();
259     setPositions();
260   }
261 
262   /**
263    * Create Shape of elements <tt>elInst</tt> with coordinates <tt>x</tt> and <tt>y</tt>.
264    * 
265    * @param x coordinates
266    * @param y coordinates
267    * @param elInst instance of requested element type
268    * @param inner direction of normals. For Outlines set to true, for snakes to
269    *        BOA_.qState.segParam.expandSnake
270    */
271   @SuppressWarnings("unchecked")
272   public Shape(final double[] x, final double[] y, T elInst, boolean inner) {
273     try {
274       Constructor<?> ctor = elInst.getClass().getDeclaredConstructor(int.class);
275       Constructor<?> ctorPoint =
276               elInst.getClass().getDeclaredConstructor(double.class, double.class, int.class);
277       head = (T) ctor.newInstance(0);
278       POINTS = 1;
279       head.setPrev(head);
280       head.setNext(head);
281       head.setHead(true);
282       T node;
283       for (int i = 0; i < x.length; i++) {
284         node = (T) ctorPoint.newInstance(x[i], y[i], nextTrackNumber++);
285         addPoint(node);
286       }
287       removePoint(head, inner);
288       setHeadClosestTo(new ExtendedVector2d(x[0], y[0])); // first point from list is head
289 
290     } catch (SecurityException | NoSuchMethodException | InstantiationException
291             | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
292       throw new RuntimeException(e); // change to unchecked exception
293     }
294     updateNormals(inner);
295     calcCentroid();
296     setPositions();
297   }
298 
299   /**
300    * Print Shape nodes.
301    * 
302    * @return String representation of Shape
303    */
304   @Override
305   public String toString() {
306     T v = this.head;
307     String out = "Coords: ";
308     do {
309       out = out.concat(" {" + v.getX() + "," + v.getY() + "}");
310       v = v.getNext();
311     } while (!v.isHead());
312     return out;
313   }
314 
315   /*
316    * (non-Javadoc)
317    * 
318    * @see java.lang.Object#hashCode()
319    */
320   @Override
321   public int hashCode() {
322     final int prime = 31;
323     int result = 1;
324     result = prime * result + POINTS;
325     result = prime * result + ((centroid == null) ? 0 : centroid.hashCode());
326     if (head == null) {
327       result = prime * result + 0;
328     } else { // go through the whole list
329       T n = head;
330       do {
331         result = prime * result + n.hashCode();
332         n = n.getNext();
333       } while (!n.isHead());
334     }
335     result = prime * result + nextTrackNumber;
336     return result;
337   }
338 
339   /*
340    * (non-Javadoc)
341    * 
342    * @see java.lang.Object#equals(java.lang.Object)
343    */
344   @Override
345   public boolean equals(Object obj) {
346     if (this == obj) {
347       return true;
348     }
349     if (obj == null) {
350       return false;
351     }
352     if (!(obj instanceof Shape)) {
353       return false;
354     }
355     @SuppressWarnings("unchecked")
356     Shape<T> other = (Shape<T>) obj;
357     if (POINTS != other.POINTS) {
358       return false;
359     }
360     if (centroid == null) {
361       if (other.centroid != null) {
362         return false;
363       }
364     } else if (!centroid.equals(other.centroid)) {
365       return false;
366     }
367     if (head == null) {
368       if (other.head != null) {
369         return false;
370       }
371     } else { // iterate over list of nodes compare all
372       T n = head;
373       T nobj = other.getHead();
374       boolean status = true;
375       do {
376         status &= n.equals(nobj);
377         n = n.getNext();
378         nobj = nobj.getNext();
379       } while (!n.isHead());
380       if (!status) {
381         return false;
382       }
383     }
384     if (nextTrackNumber != other.nextTrackNumber) {
385       return false;
386     }
387     return true;
388   }
389 
390   /**
391    * Getter for <tt>centroid</tt>.
392    * 
393    * @return centroid
394    */
395   public ExtendedVector2d getCentroid() {
396     if (centroid == null) {
397       calcCentroid();
398     }
399     return centroid;
400   }
401 
402   /**
403    * Calculate centroid of Shape.
404    * 
405    * <p>This method modifies internal field centroid.
406    */
407   public void calcCentroid() {
408     if (POINTS == 0) {
409       centroid = null;
410       return;
411     }
412     if (POINTS == 1) {
413       centroid = new ExtendedVector2d(head.getX(), head.getY());
414       return;
415     }
416     centroid = new ExtendedVector2d(0, 0);
417     T v = head;
418     double x;
419     double y;
420     double g;
421     do {
422       g = (v.getX() * v.getNext().getY()) - (v.getNext().getX() * v.getY());
423       x = (v.getX() + v.getNext().getX()) * g;
424       y = (v.getY() + v.getNext().getY()) * g;
425       centroid.setX(centroid.getX() + x);
426       centroid.setY(centroid.getY() + y);
427       v = v.getNext();
428     } while (!v.isHead());
429 
430     double area = this.calcArea();
431     if (area != 0) {
432       centroid.multiply(1d / (6 * this.calcArea()));
433     } else {
434       centroid.setX(0);
435       centroid.setY(0);
436     }
437   }
438 
439   /**
440    * Get bounds of Shape.
441    * 
442    * @return Bounding box of current Snake object as Double
443    */
444   public Rectangle2D.Double getDoubleBounds() {
445     double minX;
446     double minY;
447     double maxX;
448     double maxY;
449     T n = getHead();
450     minX = n.getX();
451     maxX = n.getX();
452     minY = n.getY();
453     maxY = n.getY();
454     n = n.getNext();
455     do {
456       if (n.getX() > maxX) {
457         maxX = n.getX();
458       }
459       if (n.getX() < minX) {
460         minX = n.getX();
461       }
462       if (n.getY() > maxY) {
463         maxY = n.getY();
464       }
465       if (n.getY() < minY) {
466         minY = n.getY();
467       }
468       n = n.getNext();
469     } while (!n.isHead());
470     return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
471   }
472 
473   /**
474    * Calculate area of the Shape.
475    * 
476    * @return Area
477    */
478   private double calcArea() {
479     double area;
480     double sum;
481     sum = 0.0;
482     T n = head;
483     T np1 = n.getNext();
484     do {
485       sum += (n.getX() * np1.getY()) - (np1.getX() * n.getY());
486       n = n.getNext();
487       np1 = n.getNext(); // note: n is reset on prev line
488 
489     } while (!n.isHead());
490     area = 0.5 * sum;
491     return area;
492   }
493 
494   /**
495    * Add up lengths between all verts.
496    * 
497    * @return length of Shape
498    */
499   public double getLength() {
500     T v = head;
501     double length = 0.0;
502     do {
503       length += ExtendedVector2d.lengthP2P(v.getPoint(), v.getNext().getPoint());
504       v = v.getNext();
505     } while (!v.isHead());
506     return length;
507   }
508 
509   /**
510    * Calculate position of Shape element expressed as distance of element on Shape perimeter from
511    * <b>head</b>.
512    * 
513    * <p>First element has position 0, last 1. Element at position 0.5 is in half length of perimeter
514    */
515   public void setPositions() {
516     double length = getLength();
517     double d = 0.;
518 
519     T v = head;
520     do {
521       v.position = d / length;
522       d = d + ExtendedVector2d.lengthP2P(v.getPoint(), v.getNext().getPoint());
523       v = v.getNext();
524     } while (!v.isHead());
525   }
526 
527   /**
528    * Update all node normals. Called after modification of Shape nodes.
529    * 
530    * @param inner Direction of normals. If <tt>false</tt> they are set outwards the shape.
531    */
532   public void updateNormals(boolean inner) {
533     T v = head;
534     do {
535       v.updateNormale(inner);
536       v = v.getNext();
537     } while (!v.isHead());
538   }
539 
540   /**
541    * Get head of current Shape.
542    * 
543    * @return Point representing head of Shape
544    */
545   public T getHead() {
546     return head;
547   }
548 
549   /**
550    * Set head of the shape to given element of the list.
551    * 
552    * <p>Element must be referenced on list.
553    * 
554    * @param newHead reference of new head.
555    */
556   public void setHead(T newHead) {
557     T oldHead = getHead();
558     T tmp;
559     T v = oldHead;
560     boolean status = false;
561     if (oldHead == newHead) {
562       return;
563     }
564     do {
565       tmp = v.getNext();
566       if (tmp == newHead) {
567         tmp.setHead(true);
568         head = tmp;
569         oldHead.setHead(false);
570         status = true;
571         break;
572       }
573       v = v.getNext();
574     } while (!v.isHead());
575 
576     if (!status) {
577       throw new IllegalArgumentException("Given element has not been found on list");
578     }
579   }
580 
581   /**
582    * Assign head to node nodeIndex. Do not change head if nodeIndex is not found or there is no head
583    * in list.
584    * 
585    * @param nodeIndex Index of node of new head
586    */
587   public void setHead(int nodeIndex) {
588     if (checkIsHead() == null) {
589       return;
590     }
591     T n = head;
592     T oldhead = n;
593     do {
594       n = n.getNext();
595     } while (n.getTrackNum() != nodeIndex && !n.isHead());
596     n.setHead(true);
597     if (oldhead != n) {
598       oldhead.setHead(false);
599     }
600     head = n;
601     LOGGER.debug("New head is: " + getHead().toString());
602   }
603 
604   /**
605    * Set head to element closest to given coordinates.
606    * 
607    * @param phead point to set head closest
608    */
609   public void setHeadClosestTo(ExtendedVector2d phead) {
610     double dis;
611     double curDis;
612     T v = head;
613     T closestV = head;
614     curDis = ExtendedVector2d.lengthP2P(phead, v.getPoint());
615 
616     do {
617       dis = ExtendedVector2d.lengthP2P(phead, v.getPoint());
618       if (dis < curDis) {
619         curDis = dis;
620         closestV = v;
621       }
622       v = v.getNext();
623     } while (!v.isHead());
624 
625     if (closestV.isHead()) {
626       return;
627     }
628 
629     head.setHead(false);
630     closestV.setHead(true);
631     head = closestV;
632   }
633 
634   /**
635    * Check if there is a head node.
636    * 
637    * <p>Traverse along first {@value #MAX_NODES} elements and check if any of them is head.
638    * 
639    * @return found head or null if not found
640    */
641   public T checkIsHead() {
642     if (head == null) {
643       return null;
644     }
645     T n = head;
646     int count = 0;
647     do {
648       if (count++ > MAX_NODES) {
649         LOGGER.warn("Head lost!!!!");
650         return null; // list looped but no head node
651       }
652       T p = n.getPrev(); // prev to current
653       n = n.getNext(); // next to current
654       if (n == null || p == null) { // each current must have next and previous
655         throw new IllegalArgumentException("List is not looped");
656       }
657     } while (!n.isHead());
658     return n;
659   }
660 
661   /**
662    * Validate correctness of the Shape.
663    * 
664    * <p>Check if Shape has head, it is correct and all nodes have previous and next neighbour.
665    * 
666    * @return {@value #LIST_OK} or combination of {@value #NO_HEAD}, {@value #BAD_LINKING},
667    *         {@value #BAD_HEAD}, {@value #BAD_NUM_POINTS}
668    */
669   public int validateShape() {
670     int ret = LIST_OK;
671     if (head == null || head.isHead() == false) {
672       ret = ret | BAD_HEAD; // bad head
673     }
674     try {
675       if (checkIsHead() == null) {
676         ret = ret | NO_HEAD; // no head
677       }
678     } catch (IllegalArgumentException e) {
679       ret = ret | BAD_LINKING; // bad linking
680     }
681     if (ret == 0) { // check number of points that can be different if Shape.Shape(T, int) was used
682       if (countPoints() != POINTS) {
683         ret = ret | BAD_NUM_POINTS;
684       }
685     }
686     return ret;
687   }
688 
689   /**
690    * Add node before head node assuring that list is a closed loop.
691    * 
692    * <p>If initial list condition is defined in such way:
693    * 
694    * <pre>
695    * {@code
696    * head = new Node(0); //make a dummy head node NODES = 1; FROZEN = 0;
697    * head.setPrev(head); // link head to itself head.setNext(head);
698    * head.setHead(true);
699    * }
700    * </pre>
701    * 
702    * <p>The <tt>addPoint</tt> will produce closed bidirectional linked list. From first Node it is
703    * possible to reach last one by calling
704    * {@link com.github.celldynamics.quimp.PointsList#getNext()}
705    * and from the last one, first should be accessible by calling
706    * {@link com.github.celldynamics.quimp.PointsList#getPrev()}.
707    * 
708    * <p>For initialisation only.
709    * 
710    * @param n Node to be added to list
711    * 
712    */
713   public void addPoint(final T n) {
714     T prevNode = head.getPrev();
715     n.setPrev(prevNode);
716     n.setNext(head);
717 
718     head.setPrev(n);
719     prevNode.setNext(n);
720     POINTS++;
721   }
722 
723   /**
724    * Insert point ne after point n.
725    * 
726    * @param n reference point
727    * @param ne point to be inserted after reference.
728    * @return new node
729    */
730   public T insertPoint(final T n, final T ne) {
731     T newNode = ne;
732     ne.setTrackNum(nextTrackNumber++);
733     newNode.setNext(n.getNext());
734     newNode.setPrev(n);
735     n.getNext().setPrev(newNode);
736     n.setNext(newNode);
737     POINTS++;
738     return newNode;
739   }
740 
741   /**
742    * Remove selected point from list.
743    * 
744    * <p>Check if removed point was head and if it was, the new head is randomly selected. Neighbors
745    * are linked together. There is no protection here against removing last node at all.
746    * 
747    * @param n point to remove
748    * @param inner direction of normal vectors of Shape
749    * @see #Shape(List, PointsList, boolean)
750    */
751   public void removePoint(final T n, boolean inner) {
752     n.getPrev().setNext(n.getNext());
753     n.getNext().setPrev(n.getPrev());
754 
755     // if removing head randomly assign a neighbour as new head
756     if (n.isHead()) {
757       if (Math.random() > threshold) {
758         LOGGER.trace("removePoint - getNext");
759         head = n.getNext();
760       } else {
761         LOGGER.trace("removePoint - getPrev");
762         head = n.getPrev();
763       }
764       head.setHead(true);
765     }
766 
767     POINTS--;
768 
769     n.getPrev().updateNormale(inner);
770     n.getNext().updateNormale(inner);
771     // WARN This may have influence to other parts of project. In original n was cleaned
772     // n = null;
773   }
774 
775   /**
776    * Get number of points in Shape.
777    * 
778    * @return Number of points
779    */
780   public int getNumPoints() {
781     return POINTS;
782   }
783 
784   /**
785    * Make Shape anti-clockwise.
786    */
787   public void makeAntiClockwise() {
788     double sum = 0;
789     T v = head;
790     do {
791       sum += (v.getNext().getX() - v.getX()) * (v.getNext().getY() + v.getY());
792       v = v.getNext();
793     } while (!v.isHead());
794     if (sum > 0) {
795       LOGGER.trace("Warning. Was clockwise, reversed");
796       this.reverseShape();
797       this.updateNormals(true); // WARN This was in Outline but not in Snake
798     }
799   }
800 
801   /**
802    * Turn Shape back anti clockwise.
803    */
804   public void reverseShape() {
805     T tmp;
806     T v = head;
807     do {
808       tmp = v.getNext();
809       v.setNext(v.getPrev());
810       v.setPrev(tmp);
811       v = v.getNext();
812     } while (!v.isHead());
813   }
814 
815   /**
816    * Return current Shape as Java polygon.
817    * 
818    * @return current Shape as java.awt.Polygon
819    */
820   public Polygon asPolygon() {
821     Polygon pol = new Polygon();
822     T n = head;
823     do {
824       pol.addPoint((int) Math.floor(n.getX() + 0.5), (int) Math.floor(n.getY() + 0.5));
825       n = n.getNext();
826     } while (!n.isHead());
827 
828     return pol;
829   }
830 
831   /**
832    * Returns current Shape as list of points (copy).
833    * 
834    * @return List of Point2d objects representing coordinates of T
835    */
836   public List<Point2d> asList() {
837     List<Point2d> al = new ArrayList<Point2d>(POINTS);
838     // iterate over nodes at Shape
839     T n = head;
840     do {
841       al.add(new Point2d(n.getX(), n.getY()));
842       n = n.getNext();
843     } while (!n.isHead());
844     return al;
845   }
846 
847   /**
848    * Return current Shape as ImageJ float number polygon.
849    * 
850    * @return current Shape as PolygonRoi
851    */
852   public Roi asFloatRoi() {
853     float[] x = new float[POINTS];
854     float[] y = new float[POINTS];
855 
856     T n = head;
857     int i = 0;
858     do {
859       x[i] = (float) n.getX();
860       y[i] = (float) n.getY();
861       i++;
862       n = n.getNext();
863     } while (!n.isHead());
864     return new PolygonRoi(x, y, POINTS, Roi.POLYGON);
865   }
866 
867   /**
868    * Return current Shape as ImageJ ROI object.
869    * 
870    * @return current Shape as ROI
871    */
872   public Roi asIntRoi() {
873     Polygon p = asPolygon();
874     Roi r = new PolygonRoi(p, PolygonRoi.POLYGON);
875     return r;
876   }
877 
878   /**
879    * Count number of Points in Shape.
880    * 
881    * <p>Number of Points is stored in local POINTS field as well. This method can verify if that
882    * field contains correct value.
883    * 
884    * @return number of Points in Shape or negative value if {@value #MAX_NODES} was reached
885    */
886   public int countPoints() {
887     T v = head;
888     int c = 0;
889     do {
890       c++;
891       v = v.getNext();
892     } while (!v.isHead() && c < MAX_NODES);
893 
894     if (c == MAX_NODES) {
895       return -1;
896     } else {
897       return c;
898     }
899   }
900 
901   /**
902    * Unfreeze all nodes in Shape.
903    */
904   public void unfreezeAll() {
905     T v = head;
906     do {
907       v.unfreeze();
908       v = v.getNext();
909     } while (!v.isHead());
910   }
911 
912   /**
913    * Freeze all nodes in Shape.
914    */
915   public void freezeAll() {
916     T v = head;
917     do {
918       v.freeze();
919       v = v.getNext();
920     } while (!v.isHead());
921   }
922 
923   /**
924    * Scale current Shape by <tt>stepSize</tt>. Centroid and normals need to be updated afterwards.
925    * 
926    * <p>Direction of scaling depends on direction of normals.
927    * 
928    * @param stepSize increment
929    * @see PointsList#updateNormale(boolean)
930    * @see #calcCentroid()
931    * @see #setPositions()
932    * @see PointsList#setClockwise(boolean)
933    */
934   public void scale(double stepSize) {
935     T n;
936     n = head;
937     do {
938       if (!n.isFrozen()) {
939         n.setX(n.getX() + stepSize * n.getNormal().getX());
940         n.setY(n.getY() + stepSize * n.getNormal().getY());
941       }
942       n = n.getNext();
943     } while (!n.isHead());
944   }
945 
946   /**
947    * Convert coordinate of Shape to array.
948    * 
949    * @return x-coordinates of Outline as array
950    */
951   public double[] xtoArr() {
952     double[] arry = new double[POINTS];
953 
954     T v = head;
955     int i = 0;
956     do {
957       arry[i] = v.getX();
958       i++;
959       v = v.getNext();
960     } while (!v.isHead());
961     return arry;
962   }
963 
964   /**
965    * Convert coordinate of Shape to array.
966    * 
967    * @return y-coordinates of Outline as array
968    */
969   public double[] ytoArr() {
970     double[] arry = new double[POINTS];
971 
972     T v = head;
973     int i = 0;
974     do {
975       arry[i] = v.getY();
976       i++;
977       v = v.getNext();
978     } while (!v.isHead());
979     return arry;
980   }
981 
982   /*
983    * (non-Javadoc)
984    * 
985    * @see java.lang.Iterable#iterator()
986    */
987   @Override
988   public Iterator<T> iterator() {
989     int ret = validateShape();
990     if (ret != 0) {
991       LOGGER.warn("Shape is broken, iterator may be unpredicable. Reason: " + ret);
992     }
993     Iterator<T> it = new Iterator<T>() {
994       private transient T nextToReturn = head; // element to return by next()
995       private transient boolean traversed = false; // true if we started iterating, used for
996       // detection that first head should be returned but second not (second - we approached head
997       // from left side)
998 
999       @Override
1000       public boolean hasNext() {
1001         if (nextToReturn == null || nextToReturn.getNext() == null) {
1002           return false;
1003         }
1004         if (POINTS == 1 && nextToReturn.isHead()) {
1005           return true; // only head linked to itself
1006         }
1007         if (nextToReturn.isHead() && traversed == true) {
1008           return false; // is head and we started iterating, so approached it from left
1009         } else {
1010           return true; // iterating not started - return at least current element (head)
1011         }
1012       }
1013 
1014       @Override
1015       public T next() {
1016         if (hasNext() == false) {
1017           throw new NoSuchElementException("No more elements");
1018         }
1019         T ret = nextToReturn;
1020         if (POINTS == 1) { // if only head
1021           nextToReturn = null;
1022         } else {
1023           nextToReturn = nextToReturn.getNext();
1024         }
1025         traversed = true;
1026         return ret;
1027       }
1028     };
1029     return it;
1030   }
1031 
1032   /*
1033    * (non-Javadoc)
1034    * 
1035    * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#beforeSerialize()
1036    */
1037   @Override
1038   public void beforeSerialize() {
1039     calcCentroid();
1040     setPositions();
1041     updateNormals(true);
1042     makeAntiClockwise();
1043     Elements = new ArrayList<>();
1044     T n = getHead().getNext(); // do not store head as it is stored in head variable
1045     do {
1046       Elements.add(n);
1047       n = n.getNext();
1048     } while (!n.isHead());
1049   }
1050 
1051   /*
1052    * (non-Javadoc)
1053    * 
1054    * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#afterSerialize()
1055    */
1056   @Override
1057   public void afterSerialize() throws Exception {
1058     if (Elements != null && Elements.size() > 0) {
1059       // head is saved as non-transitive field, so it is recreated on load and this object
1060       // exists already
1061       T first = head; // remember it
1062       Class<?> templateClass = head.getClass(); // get class name under Shape (T)
1063       try {
1064         Constructor<?> ctor = head.getClass().getDeclaredConstructor(templateClass);
1065         for (int i = 0; i < Elements.size(); i++) { // iterate over list from second position
1066           @SuppressWarnings("unchecked")
1067           T next = (T) ctor.newInstance(Elements.get(i));
1068           head.setNext(next);
1069           next.setPrev(head);
1070           head = next;
1071         }
1072         head.setNext(first);
1073         first.setPrev(head);
1074         head = first;
1075       } catch (SecurityException | NoSuchMethodException | InstantiationException
1076               | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
1077         throw new RuntimeException(e); // change to unchecked exception
1078       }
1079     }
1080     clearElements();
1081     calcCentroid(); // WARN Updating saved data - may be wrong
1082     setPositions();
1083     updateNormals(true);
1084     makeAntiClockwise();
1085   }
1086 
1087   /**
1088    * Clear <tt>Elements</tt> array that stores list of {Node, Snake} in ArrayList form. It is used
1089    * and initialized on Serialization. This method simply delete this array saving memory.
1090    * 
1091    * <p>It should be called after every serialisation.
1092    */
1093   public void clearElements() {
1094     if (Elements != null) {
1095       Elements.clear();
1096     }
1097     Elements = null;
1098   }
1099 
1100 }