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
25
26
27
28
29
30
31
32
33
34 public abstract class Shape<T extends PointsList<T>> implements IQuimpSerialize, Iterable<T> {
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 private static double threshold = 0.5;
53
54
55
56 static final Logger LOGGER = LoggerFactory.getLogger(Shape.class.getName());
57
58
59
60 protected int nextTrackNumber = 1;
61
62
63
64 protected T head;
65
66
67
68 protected int POINTS;
69
70
71
72
73 protected ExtendedVector2d centroid = null;
74
75
76
77
78 public static final int MAX_NODES = 10000;
79
80
81
82
83
84 public static final int LIST_OK = 0;
85
86
87
88
89
90 public static final int BAD_HEAD = 1;
91
92
93
94
95 public static final int NO_HEAD = 2;
96
97
98
99
100 public static final int BAD_LINKING = 4;
101
102
103
104
105
106 public static final int BAD_NUM_POINTS = 8;
107
108
109
110
111 private ArrayList<T> Elements = null;
112
113
114
115
116 public Shape() {
117 POINTS = 0;
118 head = null;
119 }
120
121
122
123
124
125
126
127
128
129
130 public Shape(T h, int n) {
131 head = h;
132 T he = checkIsHead();
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
153
154
155
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
170
171
172
173
174 public Shape(final Shape<T> src) {
175 this(src, src.getHead());
176 }
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193 @SuppressWarnings("unchecked")
194 public Shape(final Shape<T> src, T destType) {
195 T tmpHead = src.getHead();
196 Class<?> templateClass = tmpHead.getClass();
197 LOGGER.trace("Src class: " + templateClass.getName());
198 try {
199
200
201 Constructor<?> ctor = destType.getClass().getDeclaredConstructor(templateClass);
202
203 head = (T) ctor.newInstance(src.getHead());
204 T srcn = src.getHead();
205 T n = head;
206
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
214 n.setNext(head);
215 head.setPrev(n);
216 } catch (SecurityException | NoSuchMethodException | InstantiationException
217 | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
218 throw new RuntimeException(e);
219 }
220
221 POINTS = src.POINTS;
222 nextTrackNumber = src.nextTrackNumber;
223 centroid = new ExtendedVector2d(src.centroid);
224 }
225
226
227
228
229
230
231
232
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)));
252
253 } catch (SecurityException | NoSuchMethodException | InstantiationException
254 | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
255 throw new RuntimeException(e);
256 }
257 updateNormals(inner);
258 calcCentroid();
259 setPositions();
260 }
261
262
263
264
265
266
267
268
269
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]));
289
290 } catch (SecurityException | NoSuchMethodException | InstantiationException
291 | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
292 throw new RuntimeException(e);
293 }
294 updateNormals(inner);
295 calcCentroid();
296 setPositions();
297 }
298
299
300
301
302
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
317
318
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 {
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
341
342
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 {
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
392
393
394
395 public ExtendedVector2d getCentroid() {
396 if (centroid == null) {
397 calcCentroid();
398 }
399 return centroid;
400 }
401
402
403
404
405
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
441
442
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
475
476
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();
488
489 } while (!n.isHead());
490 area = 0.5 * sum;
491 return area;
492 }
493
494
495
496
497
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
511
512
513
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
529
530
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
542
543
544
545 public T getHead() {
546 return head;
547 }
548
549
550
551
552
553
554
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
583
584
585
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
606
607
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
636
637
638
639
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;
651 }
652 T p = n.getPrev();
653 n = n.getNext();
654 if (n == null || p == null) {
655 throw new IllegalArgumentException("List is not looped");
656 }
657 } while (!n.isHead());
658 return n;
659 }
660
661
662
663
664
665
666
667
668
669 public int validateShape() {
670 int ret = LIST_OK;
671 if (head == null || head.isHead() == false) {
672 ret = ret | BAD_HEAD;
673 }
674 try {
675 if (checkIsHead() == null) {
676 ret = ret | NO_HEAD;
677 }
678 } catch (IllegalArgumentException e) {
679 ret = ret | BAD_LINKING;
680 }
681 if (ret == 0) {
682 if (countPoints() != POINTS) {
683 ret = ret | BAD_NUM_POINTS;
684 }
685 }
686 return ret;
687 }
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
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
725
726
727
728
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
743
744
745
746
747
748
749
750
751 public void removePoint(final T n, boolean inner) {
752 n.getPrev().setNext(n.getNext());
753 n.getNext().setPrev(n.getPrev());
754
755
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
772
773 }
774
775
776
777
778
779
780 public int getNumPoints() {
781 return POINTS;
782 }
783
784
785
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);
798 }
799 }
800
801
802
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
817
818
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
833
834
835
836 public List<Point2d> asList() {
837 List<Point2d> al = new ArrayList<Point2d>(POINTS);
838
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
849
850
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
869
870
871
872 public Roi asIntRoi() {
873 Polygon p = asPolygon();
874 Roi r = new PolygonRoi(p, PolygonRoi.POLYGON);
875 return r;
876 }
877
878
879
880
881
882
883
884
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
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
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
925
926
927
928
929
930
931
932
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
948
949
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
966
967
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
984
985
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;
995 private transient boolean traversed = false;
996
997
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;
1006 }
1007 if (nextToReturn.isHead() && traversed == true) {
1008 return false;
1009 } else {
1010 return true;
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) {
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
1034
1035
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();
1045 do {
1046 Elements.add(n);
1047 n = n.getNext();
1048 } while (!n.isHead());
1049 }
1050
1051
1052
1053
1054
1055
1056 @Override
1057 public void afterSerialize() throws Exception {
1058 if (Elements != null && Elements.size() > 0) {
1059
1060
1061 T first = head;
1062 Class<?> templateClass = head.getClass();
1063 try {
1064 Constructor<?> ctor = head.getClass().getDeclaredConstructor(templateClass);
1065 for (int i = 0; i < Elements.size(); i++) {
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);
1078 }
1079 }
1080 clearElements();
1081 calcCentroid();
1082 setPositions();
1083 updateNormals(true);
1084 makeAntiClockwise();
1085 }
1086
1087
1088
1089
1090
1091
1092
1093 public void clearElements() {
1094 if (Elements != null) {
1095 Elements.clear();
1096 }
1097 Elements = null;
1098 }
1099
1100 }