/
CompositeFloatPoly.java
698 lines (639 loc) · 29.1 KB
/
CompositeFloatPoly.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
package ch.epfl.biop.java.utilities.roi.types;
import ij.gui.OvalRoi;
import ij.gui.PolygonRoi;
import ij.gui.Roi;
import ij.gui.ShapeRoi;
import ij.process.FloatPolygon;
import java.awt.*;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.util.*;
import java.awt.geom.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* Class representing an AREA, a list of floatpolygon
* depending on the polygon orientation (cw or ccw), the polygons are recombined
* when the function getRoi() is called
* PointROI or Line ROI (non area) are only supported if there are not part of a ShapeROI
* calibration unsupported
* curves unsupported
*
* the control points do not include any relative positioning ? to check, maybe basex and base y are important
*
* The list of floatpolygon is generated from the constructor, which depending on the ROI class:
* - if it is something else than ShapeROI, then the ROI is stored as is
* - if it is a ShapeROI, it is splitted into several float polygons
* - the splitting functions (getiterator) are copied and modified from the original ShapeRoi function in order to avoid precision loss (int)
* - however the recombination is loosing the precision (why ?)
* A significant amount of the code is taken from ShapeRoi (see @package ij.gui.ShapeROI). Trying to solve these issues in order to not non regid transformations:
* - removes integer coordinates conversion when getting rois path list
* - being able to retrieve a list of control points
* - being able to set new shapes based on a list of control points
*
* TODO :
* - check whether the polygon is repeating the end point
* - check whether ROIs are representing areas
* - for the moment : write an error message
* - later plan, depending on use, is to handle correctly these non AREA rois
* - manage to do a getRoi() function keeping the float precision in the reconstruction ShapeRoi
*
*/
public class CompositeFloatPoly {
private static final double SHAPE_TO_ROI=-1.0;
/***/
static final int NO_TYPE = 128;
/**Parsing a shape composed of linear segments less than this value will result in Roi objects of type
* {@link ij.gui.Roi#POLYLINE} and {@link ij.gui.Roi#POLYGON} for open and closed shapes, respectively.
* Conversion of shapes open and closed with more than MAXPOLY line segments will result,
* respectively, in {@link ij.gui.Roi#FREELINE} and {@link ij.gui.Roi#FREEROI} (or
* {@link ij.gui.Roi#TRACED_ROI} if {@link #forceTrace} flag is <strong><code>true</code></strong>.
*/
private static final int MAXPOLY = 10; // I hate arbitrary values !!!!
/**Flag which specifies how Roi objects will be constructed from closed (sub)paths having more than
* <code>MAXPOLY</code> and composed exclusively of line segments.
* If <strong><code>true</code></strong> then (sub)path will be parsed into a
* {@link ij.gui.Roi#TRACED_ROI}; else, into a {@link ij.gui.Roi#FREEROI}. */
private boolean forceTrace = false;
/**The instance value of the maximum tolerance (MAXERROR) allowed in calculating the
* length of the curve segments of this ROI's shape.
*/
private double maxerror = CompositeFloatPoly.MAXERROR;
/**The maximum tolerance allowed in calculating the length of the curve segments of this ROI's shape.*/
static final double MAXERROR = 1.0e-3;
/**Flag which specifies if Roi objects constructed from open (sub)paths composed of only two line segments
* will be of type {@link ij.gui.Roi#ANGLE}.
* If <strong><code>true</code></strong> then (sub)path will be parsed into a {@link ij.gui.Roi#ANGLE};
* else, into a {@link ij.gui.Roi#POLYLINE}. */
private boolean forceAngle = false;
double x=0;
double y=0;
ArrayList<FloatPolygon> polys;
public String name;
/**
* TODO Should be replaced by a clone() method
* @param cfp_in
*/
public CompositeFloatPoly(CompositeFloatPoly cfp_in) {
if (cfp_in !=null) {
name = cfp_in.name;
polys = new ArrayList<>();
this.x = cfp_in.x;
this.y = cfp_in.y;
for (FloatPolygon fp:cfp_in.polys) {
polys.add(new FloatPolygon(fp.xpoints.clone(), fp.ypoints.clone()));
}
} else {
System.err.println("Error : null roi given as an input in CompositeFloatPoly constructor.");
}
}
/**
* Initializes a composite Float Poly from an ImageJ Roi
* @param roi
*/
public CompositeFloatPoly(Roi roi) {
if (roi !=null) {
name = roi.getName();
polys = new ArrayList<>();
this.x = roi.getXBase();
this.y = roi.getYBase();
if (roi instanceof ShapeRoi) {
ShapeRoi sr = (ShapeRoi) roi;
Roi[] rois = getRois(sr);
for (Roi r:rois) {
polys.add(r.getFloatPolygon());
}
} else {
// Single ROI
// TODO Error message if it's not an area
polys.add(roi.getFloatPolygon());
}
} else {
System.err.println("Error : null roi given as an input in CompositeFloatPoly constructor.");
}
}
public String toString() {
return name;
}
/**
* Get area, convention is area is positive if the orientation is clockwise, negative otherwise
* TODO Check whether end points are repeated or not
* @param pr
* @return
*/
static public double getArea(FloatPolygon pr) {
if (pr.npoints<3) {
return 0;
}
double area = 0;
for (int i=0;i<pr.npoints-1;i++) {
area+=pr.xpoints[i]*pr.ypoints[i+1]-pr.ypoints[i]*pr.xpoints[i+1];
}
area+=pr.xpoints[pr.npoints-1]*pr.ypoints[0]-pr.ypoints[pr.npoints-1]*pr.xpoints[0];
return area/2;
}
public void smoothenWithConstrains(boolean[][] movablePx) {
// Todo : better handling of edges
for (FloatPolygon pol: this.polys) {
float[] xout = new float [pol.npoints];
float[] yout = new float [pol.npoints];
for (int i = 0; i < pol.npoints; i++) {
int iBefore = i - 1;
if (iBefore < 0) iBefore = pol.npoints - 1;
int iAfter = i + 1;
if (iAfter == pol.npoints) iAfter = 0;
float x = pol.xpoints[i];
float y = pol.ypoints[i];
if (movablePx[(int) (x)][(int) (y)]) {
float xb = pol.xpoints[iBefore];
float xa = pol.xpoints[iAfter];
float yb = pol.ypoints[iBefore];
float ya = pol.ypoints[iAfter];
float dx = xa - xb;
float dy = ya - yb;
float ld = (float) java.lang.Math.sqrt(dx * dx + dy * dy);
dx /= ld;
dy /= ld;
float proj = (x - xb) * dy - (y - yb) * dx;
x += -0.5f * proj * dy;
y += 0.5f * proj * dx;
}
xout[i]=x;
yout[i]=y;
}
for (int i = 0; i < pol.npoints; i++) {
pol.xpoints[i]=xout[i];
pol.ypoints[i]=yout[i];
}
}
}
/**
* Computes the ImageJ ROI (Shape ROI)
* @return
*/
public Roi getRoi() {
if (polys==null) {
return null;
}
if (polys.size()==0) {
return null;
}
if (polys.size()==1) {
FloatPolygon fp = polys.get(0);
return new PolygonRoi(fp.xpoints, fp.ypoints, fp.npoints, Roi.POLYGON);
} else {
Map<Boolean, List<FloatPolygon>> partitionedPolygons =
polys.stream()
.collect(Collectors.partitioningBy((fp) -> getArea(fp)>=0));
// import java.awt.Polygon;
Optional<ShapeRoi> positiveShape = partitionedPolygons.get(true)
.stream()
.map(fp -> getShape(fp))//new PolygonRoi(fp.xpoints, fp.ypoints, fp.npoints, Roi.POLYGON)))
.map(pr -> new ShapeRoi(pr))
.reduce(ShapeRoi::or);
Optional<ShapeRoi> negativeShape = partitionedPolygons.get(false)
.stream()
//.map(fp -> (new PolygonRoi(fp.xpoints, fp.ypoints, fp.npoints, Roi.POLYGON)))
.map(fp -> getShape(fp))
.map(pr -> new ShapeRoi(pr))
.reduce(ShapeRoi::or);
//FloatPolygon fp = this.polys.get(0);
//return new ShapeRoi(new PolygonRoi(fp.xpoints, fp.ypoints, fp.npoints, Roi.POLYGON));
if (positiveShape.isPresent()) {
if (negativeShape.isPresent()) {
Roi roi = positiveShape.get().xor(negativeShape.get());
roi.setName(name);
return roi;
} else {
Roi roi = positiveShape.get();
roi.setName(name);
return roi;
}
} else {
if (negativeShape.isPresent()) {
// Dirty fix : if all area are in the same negative orientation (CCW), they are assumed to define positive areas
Roi roi = negativeShape.get();
roi.setName(name);
return roi;
} else {
System.err.println("Could not build ROI : no positive and negative area defined.");
return null;
}
}
}
}
Shape getShape(FloatPolygon fp) {
GeneralPath polygon =
new GeneralPath(GeneralPath.WIND_EVEN_ODD,
fp.npoints);
polygon.moveTo(fp.xpoints[0], fp.ypoints[0]);
for (int index = 1; index < fp.npoints; index++) {
polygon.lineTo(fp.xpoints[index], fp.ypoints[index]);
};
polygon.closePath();
return polygon;
}
/**
*
* @return total number of points in the shape
*/
public int getNumberOfCtrlPts() {
return polys.stream().mapToInt(fp -> fp.npoints).sum();
}
/**
*
* @return all control points as a list
*/
public ArrayList<Point2D> getControlPoints() {
ArrayList<Point2D> list = new ArrayList<>();
polys.forEach( fp -> {
for (int i=0;i<fp.npoints;i++) {
list.add(new Point2D.Double(fp.xpoints[i],fp.ypoints[i]));
}
});
return list;
}
/**
* @param pts
*/
public void setControlPoints(List<Point2D> pts) {
if (pts.size()==this.getNumberOfCtrlPts()) {
int ptsIndex = 0;
for (FloatPolygon fp : this.polys) {
for (int i = 0; i < fp.npoints; i++) {
Point2D pt = pts.get(ptsIndex);
fp.xpoints[i] = (float) pt.getX();
fp.ypoints[i] = (float) pt.getY();
ptsIndex++;
}
}
} else {
System.err.println("Non identical number of points between the shape and the input point list. SetControlPoints function ignored.");
}
}
Roi[] getRois (ShapeRoi sr) {
Vector rois = new Vector();
Shape shape = sr.getShape();
if (shape instanceof Rectangle2D.Double) {
Roi r = new Roi((int)((Rectangle2D.Double)shape).getX(), (int)((Rectangle2D.Double)shape).getY(), (int)((Rectangle2D.Double)shape).getWidth(), (int)((Rectangle2D.Double)shape).getHeight());
rois.addElement(r);
} else if (shape instanceof Ellipse2D.Double) {
Roi r = new OvalRoi((int)((Ellipse2D.Double)shape).getX(), (int)((Ellipse2D.Double)shape).getY(), (int)((Ellipse2D.Double)shape).getWidth(), (int)((Ellipse2D.Double)shape).getHeight());
rois.addElement(r);
} else if (shape instanceof Line2D.Double) {
Roi r = new ij.gui.Line((int)((Line2D.Double)shape).getX1(), (int)((Line2D.Double)shape).getY1(), (int)((Line2D.Double)shape).getX2(), (int)((Line2D.Double)shape).getY2());
rois.addElement(r);
} else if (shape instanceof Polygon) {
Roi r = new PolygonRoi(((Polygon)shape).xpoints, ((Polygon)shape).ypoints, ((Polygon)shape).npoints, Roi.POLYGON);
rois.addElement(r);
} else if (shape instanceof GeneralPath) {
PathIterator pIter; // assume never flatten
pIter = shape.getPathIterator(new AffineTransform());
parsePath(pIter, null, null, rois, null);
}
Roi[] array = new Roi[rois.size()];
rois.copyInto(array);
return array;
}
boolean parsePath(PathIterator pIter, double[] params, Vector segments, Vector rois, Vector handles) {
if (pIter==null || pIter.isDone())
return false;
boolean result = true;
double pw = 1.0, ph = 1.0;
Vector xCoords = new Vector();
Vector yCoords = new Vector();
if (segments==null) segments = new Vector();
if (handles==null) handles = new Vector();
//if(rois==null) rois = new Vector();
if (params == null) params = new double[1];
boolean shapeToRoi = params[0]==SHAPE_TO_ROI;
int subPaths = 0; // the number of subpaths
int count = 0;// the number of segments in each subpath w/o SEG_CLOSE; resets to one after each SEG_MOVETO
int roiType = Roi.RECTANGLE;
int segType;
boolean closed = false;
boolean linesOnly = true;
boolean curvesOnly = true;
//boolean success = false;
double[] coords; // scaled coordinates of the path segment
double[] ucoords; // unscaled coordinates of the path segment
double sX = Double.NaN; // start x of subpath (scaled)
double sY = Double.NaN; // start y of subpath (scaled)
double x0 = Double.NaN; // last x in the subpath (scaled)
double y0 = Double.NaN; // last y in the subpath (scaled)
double usX = Double.NaN;// unscaled versions of the above
double usY = Double.NaN;
double ux0 = Double.NaN;
double uy0 = Double.NaN;
double pathLength = 0.0;
Shape curve; // temporary reference to a curve segment of the path
boolean done = false;
while (!done) {
coords = new double[6];
ucoords = new double[6];
segType = pIter.currentSegment(coords);
segments.add(new Integer(segType));
count++;
System.arraycopy(coords,0,ucoords,0,coords.length);
switch(segType) {
case PathIterator.SEG_MOVETO:
if (subPaths>0) {
closed = ((int)ux0==(int)usX && (int)uy0==(int)usY);
if (closed && (int)ux0!=(int)usX && (int)uy0!=(int)usY) { // this may only happen after a SEG_CLOSE
xCoords.add(new Double(((Double)xCoords.elementAt(0)).doubleValue()));
yCoords.add(new Double(((Double)yCoords.elementAt(0)).doubleValue()));
}
if (rois!=null) {
roiType = guessType(count, linesOnly, curvesOnly, closed);
Roi r = createRoi(xCoords, yCoords, roiType);
if (r!=null)
rois.addElement(r);
}
xCoords = new Vector();
yCoords = new Vector();
count = 1;
}
subPaths++;
usX = ucoords[0];
usY = ucoords[1];
ux0 = ucoords[0];
uy0 = ucoords[1];
sX = coords[0];
sY = coords[1];
x0 = coords[0];
y0 = coords[1];
handles.add(new Point2D.Double(ucoords[0],ucoords[1]));
xCoords.add(new Double(ucoords[0]));
yCoords.add(new Double(ucoords[1]));
closed = false;
break;
case PathIterator.SEG_LINETO:
linesOnly = linesOnly & true;
curvesOnly = curvesOnly & false;
pathLength += Math.sqrt(Math.pow((y0-coords[1]),2.0)+Math.pow((x0-coords[0]),2.0));
ux0 = ucoords[0];
uy0 = ucoords[1];
x0 = coords[0];
y0 = coords[1];
handles.add(new Point2D.Double(ucoords[0],ucoords[1]));
xCoords.add(new Double(ucoords[0]));
yCoords.add(new Double(ucoords[1]));
closed = ((int)ux0==(int)usX && (int)uy0==(int)usY);
break;
case PathIterator.SEG_QUADTO:
linesOnly = linesOnly & false;
curvesOnly = curvesOnly & true;
curve = new QuadCurve2D.Double(x0,y0,coords[0],coords[2],coords[2],coords[3]);
pathLength += qBezLength((QuadCurve2D.Double)curve);
ux0 = ucoords[2];
uy0 = ucoords[3];
x0 = coords[2];
y0 = coords[3];
handles.add(new Point2D.Double(ucoords[0],ucoords[1]));
handles.add(new Point2D.Double(ucoords[2],ucoords[3]));
xCoords.add(new Double((double)ucoords[2]));
yCoords.add(new Double((double)ucoords[3]));
closed = ((int)ux0==(int)usX && (int)uy0==(int)usY);
break;
case PathIterator.SEG_CUBICTO:
linesOnly = linesOnly & false;
curvesOnly = curvesOnly & true;
curve = new CubicCurve2D.Double(x0,y0,coords[0],coords[1],coords[2],coords[3],coords[4],coords[5]);
pathLength += cBezLength((CubicCurve2D.Double)curve);
ux0 = ucoords[4];
uy0 = ucoords[5];
x0 = coords[4];
y0 = coords[5];
handles.add(new Point2D.Double(ucoords[0],ucoords[1]));
handles.add(new Point2D.Double(ucoords[2],ucoords[3]));
handles.add(new Point2D.Double(ucoords[4],ucoords[5]));
xCoords.add(new Double((double)ucoords[4]));
yCoords.add(new Double((double)ucoords[5]));
closed = ((int)ux0==(int)usX && (int)uy0==(int)usY);
break;
case PathIterator.SEG_CLOSE:
if((int)ux0 != (int)usX && (int)uy0 != (int)usY) pathLength += Math.sqrt(Math.pow((x0-sX),2.0) + Math.pow((y0-sY),2.0));
closed = true;
break;
default:
break;
}
pIter.next();
done = pIter.isDone() || (shapeToRoi&&rois!=null&&rois.size()==1);
if (done) {
if(closed && (int)x0!=(int)sX && (int)y0!=(int)sY) { // this may only happen after a SEG_CLOSE
xCoords.add(new Double(((Double)xCoords.elementAt(0)).doubleValue()));
yCoords.add(new Double(((Double)yCoords.elementAt(0)).doubleValue()));
}
if (rois!=null) {
roiType = shapeToRoi?Roi.TRACED_ROI:guessType(count+1, linesOnly, curvesOnly, closed);
Roi r = createRoi(xCoords, yCoords, roiType);
if (r!=null)
rois.addElement(r);
}
}
}
params[0] = pathLength;
return result;
}
/**Implements the rules of conversion from <code>java.awt.geom.GeneralPath</code> to <code>ij.gui.Roi</code>.
* @param segments The number of segments that compose the path
* @param linesOnly Indicates wether the GeneralPath object is composed only of SEG_LINETO segments
* @param curvesOnly Indicates wether the GeneralPath object is composed only of SEG_CUBICTO and SEG_QUADTO segments
* @param closed Indicates a closed GeneralPath
* @see_#shapeToRois()
* @return a type flag
*/
private int guessType(int segments, boolean linesOnly, boolean curvesOnly, boolean closed) {
closed = true; // lines currently not supported
int roiType = Roi.RECTANGLE;
if (linesOnly) {
switch(segments) {
case 0: roiType = NO_TYPE; break;
case 1: roiType = NO_TYPE; break;
case 2: roiType = (closed ? NO_TYPE : Roi.LINE); break;
case 3: roiType = (closed ? Roi.POLYGON : (forceAngle ? Roi.ANGLE: Roi.POLYLINE)); break;
case 4: roiType = (closed ? Roi.RECTANGLE : Roi.POLYLINE); break;
default:
if (segments <= MAXPOLY)
roiType = closed ? Roi.POLYGON : Roi.POLYLINE;
else
roiType = closed ? (forceTrace ? Roi.TRACED_ROI: Roi.FREEROI): Roi.FREELINE;
break;
}
}
else roiType = segments >=2 ? Roi.COMPOSITE : NO_TYPE;
return roiType;
}
/**Calculates the length of a quadratic Bézier curve specified in double precision.
* The algorithm is based on the theory presented in paper <br>
* "Jens Gravesen. Adaptive subdivision and the length and energy of Bézier curves. Computational Geometry <strong>8:</strong><em>13-31</em> (1997)"
* implemented using <code>java.awt.geom.CubicCurve2D.Double</code>.
* Please visit {@link <a href="http://www.graphicsgems.org/gems.html#gemsiv">Graphics Gems IV</a>} for
* examples of other possible implementations in C and C++.
*/
double qBezLength(QuadCurve2D.Double c) {
double l = 0.0;
double cl = qclength(c);
double pl = qplength(c);
if((pl-cl)/2.0 > maxerror)
{
QuadCurve2D.Double[] cc = qBezSplit(c);
for(int i=0; i<2; i++) l+=qBezLength(cc[i]);
return l;
}
l = (2.0*pl+cl)/3.0;
return l;
}
/**Length of the chord of the arc of the quadratic Bézier curve argument, in double precision.*/
double qclength(QuadCurve2D.Double c)
{ return Math.sqrt(Math.pow((c.x2-c.x1),2.0) + Math.pow((c.y2-c.y1),2.0)); }
/**Creates a Roi object based on the arguments.
* @see_shapeToRois()
* @param xCoords the x coordinates
* @param yCoords the y coordinates
* @param_type the type flag
* @return a ij.gui.Roi object
*/
private Roi createRoi(Vector xCoords, Vector yCoords, int roiType) {
if (roiType==NO_TYPE) return null;
Roi roi = null;
if(xCoords.size() != yCoords.size() || xCoords.size()==0) { return null; }
double[] xPoints = new double[xCoords.size()];
double[] yPoints = new double[yCoords.size()];
for (int i=0; i<xPoints.length; i++) {
xPoints[i] = ((Double)xCoords.elementAt(i)).doubleValue() + x;
yPoints[i] = ((Double)yCoords.elementAt(i)).doubleValue() + y;
}
double startX = 0;
double startY = 0;
double width = 0;
double height = 0;
switch(roiType) {
//case NO_TYPE: roi = this; break; // I do not understand
case Roi.COMPOSITE: System.err.println("Unsupported createRoi operation!"); break;//roi = this; break; // hmmm.....!!!???
case Roi.OVAL:
startX = xPoints[xPoints.length-4];
startY = yPoints[yPoints.length-3];
width = max(xPoints)-min(xPoints);
height = max(yPoints)-min(yPoints);
roi = new OvalRoi(startX, startY, width, height);
break;
case Roi.RECTANGLE:
startX = xPoints[0];
startY = yPoints[0];
width = max(xPoints)-min(xPoints);
height = max(yPoints)-min(yPoints);
roi = new Roi(startX, startY, width, height);
break;
case Roi.LINE: roi = new ij.gui.Line(xPoints[0],yPoints[0],xPoints[1],yPoints[1]); break;
default:
int n = xPoints.length;
roi = new PolygonRoi(toFloatArray(xPoints), toFloatArray(yPoints), n, roiType);
if (roiType==Roi.FREEROI) {
double length = roi.getLength();
double mag = 1.0;
length *= mag;
if (length/n>=15.0) {
roi = new PolygonRoi(toFloatArray(xPoints), toFloatArray(yPoints), n, Roi.POLYGON);
}
}
break;
}
return roi;
}
//https://stackoverflow.com/questions/7513434/convert-a-double-array-to-a-float-array
float[] toFloatArray(double[] arr) {
if (arr == null) return null;
int n = arr.length;
float[] ret = new float[n];
for (int i = 0; i < n; i++) {
ret[i] = (float)arr[i];
}
return ret;
}
/** Returns the element with the smallest value in the array argument.*/
private int min(int[] array) {
int val = array[0];
for (int i=1; i<array.length; i++) val = Math.min(val,array[i]);
return val;
}
/** Returns the element with the largest value in the array argument.*/
private int max(int[] array) {
int val = array[0];
for (int i=1; i<array.length; i++) val = Math.max(val,array[i]);
return val;
}
/** Returns the element with the smallest value in the array argument.*/
private double min(double[] array) {
double val = array[0];
for (int i=1; i<array.length; i++) val = Math.min(val,array[i]);
return val;
}
/** Returns the element with the largest value in the array argument.*/
private double max(double[] array) {
double val = array[0];
for (int i=1; i<array.length; i++) val = Math.max(val,array[i]);
return val;
}
/**Calculates the length of a cubic Bézier curve specified in double precision.
* The algorithm is based on the theory presented in paper <br>
* "Jens Gravesen. Adaptive subdivision and the length and energy of Bézier curves. Computational Geometry <strong>8:</strong><em>13-31</em> (1997)"
* implemented using <code>java.awt.geom.CubicCurve2D.Double</code>.
* Please visit {@link <a href="http://www.graphicsgems.org/gems.html#gemsiv">Graphics Gems IV</a>} for
* examples of other possible implementations in C and C++.
*/
double cBezLength(CubicCurve2D.Double c) {
double l = 0.0;
double cl = cclength(c);
double pl = cplength(c);
if((pl-cl)/2.0 > maxerror)
{
CubicCurve2D.Double[] cc = cBezSplit(c);
for(int i=0; i<2; i++) l+=cBezLength(cc[i]);
return l;
}
l = 0.5*pl+0.5*cl;
return l;
}
/**Length of the chord of the arc of the cubic Bézier curve argument, in double precision.*/
double cclength(CubicCurve2D.Double c)
{ return Math.sqrt(Math.pow((c.x2-c.x1),2.0) + Math.pow((c.y2-c.y1),2.0)); }
/**Length of the control polygon of the cubic Bézier curve argument, in double precision.*/
double cplength(CubicCurve2D.Double c) {
double result = Math.sqrt(Math.pow((c.ctrlx1-c.x1),2.0)+Math.pow((c.ctrly1-c.y1),2.0));
result += Math.sqrt(Math.pow((c.ctrlx2-c.ctrlx1),2.0)+Math.pow((c.ctrly2-c.ctrly1),2.0));
result += Math.sqrt(Math.pow((c.x2-c.ctrlx2),2.0)+Math.pow((c.y2-c.ctrly2),2.0));
return result;
}
/**Splits a cubic Bézier curve in half.
* @param c A cubic Bézier curve to be divided
* @return an array with the left and right cubic Bézier subcurves
*
*/
CubicCurve2D.Double[] cBezSplit(CubicCurve2D.Double c) {
CubicCurve2D.Double[] cc = new CubicCurve2D.Double[2];
for (int i=0; i<2 ; i++) cc[i] = new CubicCurve2D.Double();
c.subdivide(cc[0],cc[1]);
return cc;
}
/**Splits a quadratic Bézier curve in half.
* @param c A quadratic Bézier curve to be divided
* @return an array with the left and right quadratic Bézier subcurves
*
*/
QuadCurve2D.Double[] qBezSplit(QuadCurve2D.Double c) {
QuadCurve2D.Double[] cc = new QuadCurve2D.Double[2];
for(int i=0; i<2; i++) cc[i] = new QuadCurve2D.Double();
c.subdivide(cc[0],cc[1]);
return cc;
}
/**Length of the control polygon of the quadratic Bézier curve argument, in double precision.*/
double qplength(QuadCurve2D.Double c) {
double result = Math.sqrt(Math.pow((c.ctrlx-c.x1),2.0)+Math.pow((c.ctrly-c.y1),2.0));
result += Math.sqrt(Math.pow((c.x2-c.ctrlx),2.0)+Math.pow((c.y2-c.ctrly),2.0));
return result;
}
}