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
20
21
22
23
24
25
26
27
28
29
30 public class Snake extends Shape<Node> implements IQuimpSerialize {
31
32
33
34
35 static final Logger LOGGER = LoggerFactory.getLogger(Snake.class.getName());
36
37
38
39
40
41
42 public boolean alive;
43
44
45
46
47 private int snakeID;
48
49
50
51 public double startingNnodes;
52
53
54
55 private int FROZEN;
56
57
58
59
60 private Rectangle bounds = new Rectangle();
61
62
63
64
65
66
67
68
69
70
71
72
73 public Snake(final Node h, int n, int id) {
74 super(h, n);
75 snakeID = id;
76 this.makeAntiClockwise();
77 this.updateNormals(BOA_.qState.segParam.expandSnake);
78 alive = true;
79 startingNnodes = POINTS / 100.;
80 countFrozen();
81
82 getBounds();
83 }
84
85
86
87
88
89
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
102
103
104
105 public Snakeics/quimp/Snake.html#Snake">Snake(final Snake src) {
106 this(src, src.getSnakeID());
107 }
108
109
110
111
112
113
114
115
116
117 public Snake(final Roi r, int id, boolean direct) throws BoaException {
118
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.;
136 alive = true;
137
138
139 calcCentroid();
140 getBounds();
141 }
142
143
144
145
146
147
148
149
150
151 public Snake(final PolygonRoi r, int id) throws BoaException {
152 snakeID = id;
153 intializeFloat(r.getFloatPolygon());
154 startingNnodes = POINTS / 100.;
155 alive = true;
156
157
158 calcCentroid();
159 getBounds();
160 }
161
162
163
164
165
166
167
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) {
172 throw new BoaException("Not enough points provided");
173 }
174 snakeID = id;
175 this.makeAntiClockwise();
176 updateNormals(BOA_.qState.segParam.expandSnake);
177 startingNnodes = POINTS / 100;
178 alive = true;
179 getBounds();
180 }
181
182
183
184
185
186
187
188
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();
198 updateNormals(BOA_.qState.segParam.expandSnake);
199 startingNnodes = POINTS / 100;
200 alive = true;
201 getBounds();
202 }
203
204
205
206
207
208 public Snake() {
209 super();
210 alive = false;
211 }
212
213
214
215
216
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
234
235
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
273
274
275
276 public int getSnakeID() {
277 return snakeID;
278 }
279
280
281
282
283
284
285 protected void setSnakeID(int snakeID) {
286 this.snakeID = snakeID;
287 }
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304 private void intializeOval(int t, int xc, int yc, int rx, int ry, double s) throws BoaException {
305 head = new Node(t);
306 POINTS = 1;
307 FROZEN = 0;
308 head.setPrev(head);
309 head.setNext(head);
310 head.setHead(true);
311
312 double theta = 2.0 / (double) ((rx + ry) / 2);
313
314
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);
324 this.makeAntiClockwise();
325 updateNormals(BOA_.qState.segParam.expandSnake);
326 }
327
328
329
330
331
332
333
334
335 private void intializePolygon(final FloatPolygon p) throws BoaException {
336
337 head = new Node(0);
338 POINTS = 1;
339 FROZEN = 0;
340 head.setPrev(head);
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));
355 a = new ExtendedVector2d(p.xpoints[i], p.ypoints[i]);
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);
362
363 for (int s = 0; s < nn; s++) {
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);
374 setPositions();
375 this.makeAntiClockwise();
376 updateNormals(BOA_.qState.segParam.expandSnake);
377 }
378
379
380
381
382
383
384
385
386
387 private void intializePolygonDirect(final FloatPolygon p) throws BoaException {
388
389 head = new Node(0);
390 POINTS = 1;
391 FROZEN = 0;
392 head.setPrev(head);
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);
403 setPositions();
404 this.makeAntiClockwise();
405 updateNormals(BOA_.qState.segParam.expandSnake);
406 }
407
408
409
410
411
412
413
414
415 private void intializeFloat(final FloatPolygon p) throws BoaException {
416
417 head = new Node(0);
418 POINTS = 1;
419 FROZEN = 0;
420 head.setPrev(head);
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);
431 setPositions();
432 this.makeAntiClockwise();
433 updateNormals(BOA_.qState.segParam.expandSnake);
434 }
435
436
437
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
458
459 @Override
460 public void unfreezeAll() {
461 super.unfreezeAll();
462 FROZEN = 0;
463 }
464
465
466
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
481
482
483
484 public void freezeNode(Node n) {
485 if (!n.isFrozen()) {
486 n.freeze();
487 FROZEN++;
488 }
489 }
490
491
492
493
494
495
496 public void unfreezeNode(Node n) {
497 if (n.isFrozen()) {
498 n.unfreeze();
499 FROZEN--;
500 }
501 }
502
503
504
505
506
507
508 public boolean isFrozen() {
509 if (FROZEN == POINTS) {
510 return true;
511 } else {
512 return false;
513 }
514 }
515
516
517
518
519
520
521
522
523
524
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
539
540
541
542 public void implode() throws BoaException {
543
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
562
563
564
565 @Deprecated
566 public void blowup() throws Exception {
567 scaleSnake(BOA_.qState.segParam.blowup, 4, true);
568 }
569
570
571
572
573
574
575
576
577
578
579
580
581
582 public void scaleSnake(double amount, double stepRes, boolean correct) throws BoaException {
583 if (amount == 0) {
584 return;
585 }
586
587 Node.setClockwise(true);
588
589 if (amount > 0) {
590 stepRes *= -1;
591 }
592 double steps = Math.abs(amount / stepRes);
593
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
609
610 public void cutLoops() {
611 final int maxInterval = 12;
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();
626
627
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
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
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);
658 head = newN;
659 }
660
661 POINTS -= (i + 2);
662 break;
663 }
664 nodeB = nodeB.getNext();
665 }
666 nodeA = nodeA.getNext();
667 } while (!nodeA.isHead());
668 }
669
670
671
672
673
674
675
676
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();
694 interval = (POINTS > 6) ? POINTS / 2 : 2;
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);
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
733
734
735
736
737 public void correctDistance(boolean shiftNewNode) throws BoaException {
738 Node.randDirection();
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;
752
753 do {
754
755 nl = nc.getPrev();
756 nr = nc.getNext();
757
758
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
768 if (dlr > 2 * BOA_.qState.segParam.getMin_dist()) {
769
770
771 npos = new ExtendedVector2d(tanLR.getX(), tanLR.getY());
772 npos.multiply(0.501);
773 npos.addVec(nl.getPoint());
774
775 nc.setX(npos.getX());
776 nc.setY(npos.getY());
777
778
779
780
781
782 tmp = Math.sin(ExtendedVector2d.angle(tanL, tanLR)) * dl;
783
784
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
795
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
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);
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);
840 }
841
842
843
844
845
846
847
848 public NodeNode.html#Node">Node insertNode(final Node n) {
849 return insertPoint(n, new Node());
850 }
851
852
853
854
855
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
874
875
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
888
889 public void editSnake() {
890 System.out.println("Editing a snake");
891 }
892
893
894
895
896
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
907
908
909
910 @Override
911 public void beforeSerialize() {
912 super.beforeSerialize();
913 getBounds();
914 }
915
916
917
918
919
920
921 @Override
922 public void afterSerialize() throws Exception {
923 super.afterSerialize();
924 getBounds();
925 }
926
927 }