3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2008-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.display
;
25 import java
.awt
.AlphaComposite
;
26 import java
.awt
.Color
;
27 import java
.awt
.Composite
;
28 import java
.awt
.Graphics2D
;
29 import java
.awt
.Polygon
;
30 import java
.awt
.Rectangle
;
31 import java
.awt
.event
.KeyEvent
;
32 import java
.awt
.event
.MouseEvent
;
33 import java
.awt
.geom
.AffineTransform
;
34 import java
.awt
.geom
.Area
;
35 import java
.awt
.geom
.NoninvertibleTransformException
;
36 import java
.awt
.geom
.Point2D
;
37 import java
.awt
.geom
.Rectangle2D
;
38 import java
.util
.ArrayList
;
39 import java
.util
.Collection
;
40 import java
.util
.HashMap
;
41 import java
.util
.HashSet
;
42 import java
.util
.Iterator
;
43 import java
.util
.List
;
45 import java
.util
.Random
;
47 import org
.scijava
.vecmath
.Point3f
;
48 import org
.scijava
.vecmath
.Vector3d
;
50 import features
.ComputeCurvatures
;
52 import ij
.measure
.Calibration
;
53 import ij
.measure
.ResultsTable
;
54 import ini
.trakem2
.Project
;
55 import ini
.trakem2
.imaging
.LayerStack
;
56 import ini
.trakem2
.imaging
.Segmentation
;
57 import ini
.trakem2
.persistence
.XMLOptions
;
58 import ini
.trakem2
.utils
.Bureaucrat
;
59 import ini
.trakem2
.utils
.IJError
;
60 import ini
.trakem2
.utils
.M
;
61 import ini
.trakem2
.utils
.ProjectToolbar
;
62 import ini
.trakem2
.utils
.Utils
;
63 import ini
.trakem2
.utils
.Worker
;
64 import ini
.trakem2
.vector
.VectorString3D
;
66 import tracing
.SearchInterface
;
67 import tracing
.SearchProgressCallback
;
68 import tracing
.TracerThread
;
71 /** A sequence of points that make multiple chained line segments. */
72 public class Polyline
extends ZDisplayable
implements Line3D
, VectorData
{
74 /**The number of points.*/
75 protected int n_points
;
76 /**The array of clicked x,y points as [2][n].*/
77 protected double[][] p
= new double[2][0];
78 /**The array of Layers over which the points of this pipe live */
79 protected long[] p_layer
= new long[0];
81 /** New empty Polyline. */
82 public Polyline(final Project project
, final String title
) {
83 super(project
, title
, 0, 0);
88 public Polyline(final Project project
, final long id
, final String title
, final float width
, final float height
, final float alpha
, final boolean visible
, final Color color
, final boolean locked
, final AffineTransform at
) {
89 super(project
, id
, title
, locked
, at
, width
, height
);
90 this.visible
= visible
;
92 this.visible
= visible
;
94 this.n_points
= -1; //used as a flag to signal "I have points, but unloaded"
97 /** Reconstruct from XML. */
98 public Polyline(final Project project
, final long id
, final HashMap
<String
,String
> ht_attr
, final HashMap
<Displayable
,String
> ht_links
) {
99 super(project
, id
, ht_attr
, ht_links
);
100 // parse specific data
101 final String ps
= ht_attr
.get("d");
103 final String lids
= ht_attr
.get("layer_ids");
105 Utils
.log("ERROR: found 'd' but not 'layer_ids' in XML entry of Polyline #" + this.id
);
109 // parse the SVG points data
112 // sequence is: M p[0][0],p[1][0] L p[0][1],p[1][1] L p[0][2],p[1][2] ...
114 int i_start
= ps
.indexOf('M');
115 int i_L
= ps
.indexOf('L', i_start
+1);
118 if (p
[0].length
== next
) enlargeArrays();
121 final int i_comma
= ps
.indexOf(',', i_start
+1);
122 p
[0][next
] = Double
.parseDouble(ps
.substring(i_start
+1, i_comma
));
124 i_L
= ps
.indexOf('L', i_comma
);
126 if (-1 == i_L
) i_end
= ps
.length();
127 p
[1][next
] = Double
.parseDouble(ps
.substring(i_comma
+1, i_end
));
129 // prepare next point
134 // scale arrays back, so minimal size and also same size as p_layer
135 p
= new double[][]{Utils
.copy(p
[0], n_points
), Utils
.copy(p
[1], n_points
)};
137 // parse comma-separated list of layer ids. Creates empty Layer instances with the proper id, that will be replaced later.
138 final String
[] layer_ids
= lids
.replaceAll(" ", "").trim().split(",");
139 this.p_layer
= new long[layer_ids
.length
];
140 for (int i
=0; i
<layer_ids
.length
; i
++) {
141 if (i
== p_layer
.length
) enlargeArrays();
142 this.p_layer
[i
] = Long
.parseLong(layer_ids
[i
]);
147 /**Increase the size of the arrays by 5.*/
148 synchronized protected void enlargeArrays() {
151 synchronized protected void enlargeArrays(final int n_more
) {
153 final int length
= p
[0].length
;
155 final double[][] p_copy
= new double[2][length
+ n_more
];
156 final long[] p_layer_copy
= new long[length
+ n_more
];
158 System
.arraycopy(p
[0], 0, p_copy
[0], 0, length
);
159 System
.arraycopy(p
[1], 0, p_copy
[1], 0, length
);
160 System
.arraycopy(p_layer
, 0, p_layer_copy
, 0, length
);
163 this.p_layer
= p_layer_copy
;
166 /** Returns the index of the first point in the segment made of any two consecutive points. */
167 synchronized protected int findClosestSegment(final int x_p
, final int y_p
, final long layer_id
, final double mag
) {
168 if (1 == n_points
) return -1;
169 if (0 == n_points
) return -1;
171 double d
= (10.0D
/ mag
);
173 final double sq_d
= d
*d
;
174 double min_sq_dist
= Double
.MAX_VALUE
;
175 final Calibration cal
= layer_set
.getCalibration();
176 final double z
= layer_set
.getLayer(layer_id
).getZ() * cal
.pixelWidth
;
178 double x2
= p
[0][0] * cal
.pixelWidth
;
179 double y2
= p
[1][0] * cal
.pixelHeight
;
180 double z2
= layer_set
.getLayer(p_layer
[0]).getZ() * cal
.pixelWidth
;
183 for (int i
=1; i
<n_points
; i
++) {
187 x2
= p
[0][i
] * cal
.pixelWidth
;
188 y2
= p
[1][i
] * cal
.pixelHeight
;
189 z2
= layer_set
.getLayer(p_layer
[i
]).getZ() * cal
.pixelWidth
;
191 final double sq_dist
= M
.distancePointToSegmentSq(x_p
* cal
.pixelWidth
, y_p
* cal
.pixelHeight
, z
,
195 if (sq_dist
< sq_d
&& sq_dist
< min_sq_dist
) {
196 min_sq_dist
= sq_dist
;
197 index
= i
-1; // previous
204 /**Find a point in an array, with a precision dependent on the magnification. Only points in the given layer are considered, the rest are ignored. Returns -1 if none found. */
205 synchronized protected int findPoint(final int x_p
, final int y_p
, final long layer_id
, final double mag
) {
207 double d
= (10.0D
/ mag
);
209 double min_dist
= Double
.MAX_VALUE
;
210 for (int i
=0; i
<n_points
; i
++) {
211 final double dist
= Math
.abs(x_p
- p
[0][i
]) + Math
.abs(y_p
- p
[1][i
]);
212 if (layer_id
== p_layer
[i
] && dist
<= d
&& dist
<= min_dist
) {
220 /** Find closest point within the current layer. */
221 synchronized protected int findNearestPoint(final int x_p
, final int y_p
, final long layer_id
) {
223 double min_dist
= Double
.MAX_VALUE
;
224 for (int i
=0; i
<n_points
; i
++) {
225 if (layer_id
!= p_layer
[i
]) continue;
226 final double sq_dist
= Math
.pow(p
[0][i
] - x_p
, 2) + Math
.pow(p
[1][i
] - y_p
, 2);
227 if (sq_dist
< min_dist
) {
235 /**Remove a point from the bezier backbone and its two associated control points.*/
236 synchronized protected void removePoint(final int index
) {
237 // check preconditions:
240 } else if (n_points
- 1 == index
) {
244 //one point out (but not the last)
247 // shift all points after 'index' one position to the left:
248 for (int i
=index
; i
<n_points
; i
++) {
249 p
[0][i
] = p
[0][i
+1]; //the +1 doesn't fail ever because the n_points has been adjusted above, but the arrays are still the same size. The case of deleting the last point is taken care above.
251 p_layer
[i
] = p_layer
[i
+1];
255 // Reset or fix autotracing records
256 if (index
< last_autotrace_start
&& n_points
> 0) {
257 last_autotrace_start
--;
258 } else last_autotrace_start
= -1;
261 updateInDatabase("points");
264 /**Move backbone point by the given deltas.*/
265 public void dragPoint(final int index
, final int dx
, final int dy
) {
266 if (index
< 0 || index
>= n_points
) return;
270 // Reset autotracing records
271 if (-1 != last_autotrace_start
&& index
>= last_autotrace_start
) {
272 last_autotrace_start
= -1;
276 /** @param x_p,y_p in local coords. */
277 protected double[] sqDistanceToEndPoints(final double x_p
, final double y_p
, final long layer_id
) {
278 final Calibration cal
= layer_set
.getCalibration();
279 final double lz
= layer_set
.getLayer(layer_id
).getZ();
280 final double p0z
=layer_set
.getLayer(p_layer
[0]).getZ();
281 final double pNz
=layer_set
.getLayer(p_layer
[n_points
-1]).getZ();
282 final double sqdist0
= (p
[0][0] - x_p
) * (p
[0][0] - x_p
) * cal
.pixelWidth
* cal
.pixelWidth
283 + (p
[1][0] - y_p
) * (p
[1][0] - y_p
) * cal
.pixelHeight
* cal
.pixelHeight
284 + (lz
- p0z
) * (lz
- p0z
) * cal
.pixelWidth
* cal
.pixelWidth
; // double multiplication by pixelWidth, ok, since it's what it's used to compute the pixel position in Z
285 final double sqdistN
= (p
[0][n_points
-1] - x_p
) * (p
[0][n_points
-1] - x_p
) * cal
.pixelWidth
* cal
.pixelWidth
286 + (p
[1][n_points
-1] - y_p
) * (p
[1][n_points
-1] - y_p
) * cal
.pixelHeight
* cal
.pixelHeight
287 + (lz
- pNz
) * (lz
- pNz
) * cal
.pixelWidth
* cal
.pixelWidth
;
289 return new double[]{sqdist0
, sqdistN
};
292 synchronized public void insertPoint(final int i
, final int x_p
, final int y_p
, final long layer_id
) {
293 if (-1 == n_points
) setupForDisplay(); //reload
294 if (p
[0].length
== n_points
) enlargeArrays();
295 final double[][] p2
= new double[2][p
[0].length
];
296 final long[] p_layer2
= new long[p_layer
.length
];
298 System
.arraycopy(p
[0], 0, p2
[0], 0, i
);
299 System
.arraycopy(p
[1], 0, p2
[1], 0, i
);
300 System
.arraycopy(p_layer
, 0, p_layer2
, 0, i
);
304 p_layer2
[i
] = layer_id
;
306 System
.arraycopy(p
[0], i
, p2
[0], i
+1, n_points
-i
);
307 System
.arraycopy(p
[1], i
, p2
[1], i
+1, n_points
-i
);
308 System
.arraycopy(p_layer
, i
, p_layer2
, i
+1, n_points
-i
);
315 /** Append a point at the end. Returns the index of the new point. */
316 synchronized protected int appendPoint(final int x_p
, final int y_p
, final long layer_id
) {
317 if (-1 == n_points
) setupForDisplay(); //reload
319 if (p
[0].length
== n_points
) {
322 p
[0][n_points
] = x_p
;
323 p
[1][n_points
] = y_p
;
324 p_layer
[n_points
] = layer_id
;
329 /**Add a point either at the end or between two existing points, with accuracy depending on magnification. The width of the new point is that of the closest point after which it is inserted.*/
330 synchronized protected int addPoint(final int x_p
, final int y_p
, final long layer_id
, final double magnification
) {
331 if (-1 == n_points
) setupForDisplay(); //reload
332 //lookup closest point and then get the closest clicked point to it
334 if (n_points
> 1) index
= findClosestSegment(x_p
, y_p
, layer_id
, magnification
);
336 if (p
[0].length
== n_points
) {
340 if (0 == n_points
|| 1 == n_points
|| index
+ 1 == n_points
) {
342 p
[0][n_points
] = x_p
;
343 p
[1][n_points
] = y_p
;
344 p_layer
[n_points
] = layer_id
;
347 last_autotrace_start
= -1;
348 } else if (-1 == index
) {
349 // decide whether to append at the end or prepend at the beginning
350 // compute distance in the 3D space to the first and last points
351 final double[] sqd0N
= sqDistanceToEndPoints(x_p
, y_p
, layer_id
);
352 //final double sqdist0 = sqd0N[0];
353 //final double sqdistN = sqd0N[1];
355 //if (sqdistN < sqdist0)
356 if (sqd0N
[1] < sqd0N
[0]) {
358 p
[0][n_points
] = x_p
;
359 p
[1][n_points
] = y_p
;
360 p_layer
[n_points
] = layer_id
;
363 last_autotrace_start
= -1;
365 // prepend at the beginning
366 for (int i
=n_points
-1; i
>-1; i
--) {
369 p_layer
[i
+1] = p_layer
[i
];
373 p_layer
[0] = layer_id
;
376 if (-1 != last_autotrace_start
) last_autotrace_start
++;
380 index
++; //so it is added after the closest point;
381 // 1 - copy second half of array
382 final int sh_length
= n_points
-index
;
383 final double[][] p_copy
= new double[2][sh_length
];
384 final long[] p_layer_copy
= new long[sh_length
];
385 System
.arraycopy(p
[0], index
, p_copy
[0], 0, sh_length
);
386 System
.arraycopy(p
[1], index
, p_copy
[1], 0, sh_length
);
387 System
.arraycopy(p_layer
, index
, p_layer_copy
, 0, sh_length
);
388 // 2 - insert value into 'p' (the two control arrays get the same value)
391 p_layer
[index
] = layer_id
;
392 // 3 - copy second half into the array
393 System
.arraycopy(p_copy
[0], 0, p
[0], index
+1, sh_length
);
394 System
.arraycopy(p_copy
[1], 0, p
[1], index
+1, sh_length
);
395 System
.arraycopy(p_layer_copy
, 0, p_layer
, index
+1, sh_length
);
397 // Reset autotracing records
398 if (index
< last_autotrace_start
) {
399 last_autotrace_start
++;
408 synchronized protected void appendPoints(final double[] px
, final double[] py
, final long[] p_layer_ids
, final int len
) {
409 for (int i
=0, next
=n_points
; i
<len
; i
++, next
++) {
410 if (next
== p
[0].length
) enlargeArrays();
413 p_layer
[next
] = p_layer_ids
[i
];
416 updateInDatabase("points");
420 public void paint(final Graphics2D g
, final Rectangle srcRect
, final double magnification
, final boolean active
, final int channels
, final Layer active_layer
, final List
<Layer
> layers
) {
421 if (0 == n_points
) return;
422 if (-1 == n_points
) {
423 // load points from the database
426 //arrange transparency
427 Composite original_composite
= null;
429 original_composite
= g
.getComposite();
430 g
.setComposite(AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, alpha
));
433 // local pointers, since they may be transformed
434 int n_points
= this.n_points
;
435 double[][] p
= this.p
;
436 if (!this.at
.isIdentity()) {
437 final Object
[] ob
= getTransformedData();
438 p
= (double[][])ob
[0];
439 n_points
= p
[0].length
;
442 final boolean no_color_cues
= !layer_set
.color_cues
;
443 final Color below
, above
;
444 if (layer_set
.use_color_cue_colors
) {
452 final long layer_id
= active_layer
.getId();
453 final double z_current
= active_layer
.getZ();
455 // Different approach: a point paints itself and towards the next, except the last point.
457 double z
= layer_set
.getLayer(p_layer
[0]).getZ();
458 boolean paint
= true;
460 if (no_color_cues
) paint
= false;
461 else g
.setColor(below
);
462 } else if (z
== z_current
) g
.setColor(this.color
);
463 else if (no_color_cues
) paint
= false;
464 else g
.setColor(above
);
467 if (paint
&& n_points
> 1) {
468 g
.drawLine((int)p
[0][0], (int)p
[1][0],
469 (int)((p
[0][0] + p
[0][1])/2), (int)((p
[1][0] + p
[1][1])/2));
472 // Paint handle if active and in the current layer
473 if (active
&& layer_id
== p_layer
[0]) {
474 g
.setColor(this.color
);
475 DisplayCanvas
.drawHandle(g
, p
[0][0], p
[1][0], srcRect
, magnification
);
476 // Label the first point distinctively
477 final Composite comp
= g
.getComposite();
478 final AffineTransform aff
= g
.getTransform();
479 g
.setTransform(new AffineTransform());
480 g
.setColor(Color
.white
);
481 g
.setXORMode(Color
.green
);
482 g
.drawString("1", (int)( (p
[0][0] - srcRect
.x
)*magnification
+ (4.0 / magnification
)),
483 (int)( (p
[1][0] - srcRect
.y
)*magnification
)); // displaced 4 screen pixels to the right
484 g
.setComposite(comp
);
488 for (int i
=1; i
<n_points
; i
++) {
490 z
= layer_set
.getLayer(p_layer
[i
]).getZ();
493 if (no_color_cues
) paint
= false;
494 else g
.setColor(below
);
495 } else if (z
== z_current
) g
.setColor(this.color
);
496 else if (no_color_cues
) paint
= false;
497 else g
.setColor(above
);
498 if (!paint
) continue;
499 // paint half line towards previous point:
500 g
.drawLine((int)p
[0][i
], (int)p
[1][i
],
501 (int)((p
[0][i
] + p
[0][i
-1])/2), (int)((p
[1][i
] + p
[1][i
-1])/2));
502 // paint half line towards next point:
503 if (i
< n_points
-1) {
504 g
.drawLine((int)p
[0][i
], (int)p
[1][i
],
505 (int)((p
[0][i
] + p
[0][i
+1])/2), (int)((p
[1][i
] + p
[1][i
+1])/2));
507 // Paint handle if active and in the current layer
508 if (active
&& layer_id
== p_layer
[i
]) {
509 g
.setColor(this.color
);
510 DisplayCanvas
.drawHandle(g
, p
[0][i
], p
[1][i
], srcRect
, magnification
);
514 //Transparency: fix alpha composite back to original.
515 if (null != original_composite
) {
516 g
.setComposite(original_composite
);
521 public void keyPressed(final KeyEvent ke
) {
522 final int keyCode
= ke
.getKeyCode();
525 if (-1 == last_autotrace_start
) {
526 if (0 > n_points
) Utils
.log("Cannot remove last set of autotraced points:\n Manual editions exist, or never autotraced.");
530 final int len
= n_points
- last_autotrace_start
;
531 n_points
= last_autotrace_start
;
532 last_autotrace_start
= -1;
534 // update buckets for layers of all points from n_points to last_autotrace_start
535 final HashSet
<Long
> hs
= new HashSet
<Long
>();
536 for (int i
= n_points
+1; i
< n_points
+len
; i
++) hs
.add(p_layer
[i
]);
537 for (final Long l
: hs
) updateBucket(layer_set
.getLayer(l
.longValue()));
538 Utils
.log("Removed " + len
+ " autotraced points.");
540 case KeyEvent
.VK_R
: // reset tracing
541 tr_map
.remove(layer_set
);
543 Utils
.log("Reset tracing data for Polyline " + this);
548 /**Helper vars for mouse events. It's safe to have them static since only one Pipe will be edited at a time.*/
549 static protected int index
;
550 static private boolean is_new_point
= false;
552 final static private HashMap
<LayerSet
,TraceParameters
> tr_map
= new HashMap
<LayerSet
,TraceParameters
>();
553 private int last_autotrace_start
= -1;
555 static public void flushTraceCache(final Project project
) {
556 synchronized (tr_map
) {
557 for (final Iterator
<LayerSet
> it
= tr_map
.keySet().iterator(); it
.hasNext(); ) {
558 if (it
.next().getProject() == project
) it
.remove();
563 /** Shared between all Polyline of the same LayerSet. The issue of locking doesn't arise because there is only one source of mouse input. If you try to run it programatically with synthetic MouseEvent, that's your problem. */
564 static private class TraceParameters
{
565 boolean update
= true;
566 ImagePlus virtual
= null;
568 ComputeCurvatures hessian
= null;
569 TracerThread tracer
= null; // catched thread for KeyEvent to attempt to stop it
573 public void mousePressed(final MouseEvent me
, final Layer layer
, int x_p
, int y_p
, final double mag
) {
574 // transform the x_p, y_p to the local coordinates
575 final int x_pd
= x_p
;
576 final int y_pd
= y_p
;
577 if (!this.at
.isIdentity()) {
578 final Point2D
.Double po
= inverseTransformPoint(x_p
, y_p
);
583 final int tool
= ProjectToolbar
.getToolId();
585 final Display display
= ((DisplayCanvas
)me
.getSource()).getDisplay();
586 final long layer_id
= layer
.getId();
588 index
= findPoint(x_p
, y_p
, layer_id
, mag
);
590 if (ProjectToolbar
.PENCIL
== tool
&& n_points
> 0 && -1 == index
&& !me
.isShiftDown() && !Utils
.isControlDown(me
)) {
591 // Use Mark Longair's tracing: from the clicked point to the last one
593 // Check that there are any images -- otherwise may hang. TODO
594 if (layer_set
.getDisplayables(Patch
.class).isEmpty()) {
595 Utils
.log("No images are present!");
599 final double scale
= layer_set
.getVirtualizationScale();
600 // Ok now with all found images, create a virtual stack that provides access to them all, with caching.
601 final Worker
[] worker
= new Worker
[2];
603 final TraceParameters tr_
= tr_map
.get(layer_set
);
604 final TraceParameters tr
= null == tr_ ?
new TraceParameters() : tr_
;
606 synchronized (tr_map
) {
607 tr_map
.put(layer_set
, tr
);
612 worker
[0] = new Worker("Preparing Hessian...") { @Override
616 Utils
.log("Push ESCAPE key to cancel autotrace anytime.");
617 final ImagePlus virtual
= new LayerStack(layer_set
, scale
, ImagePlus
.GRAY8
, Patch
.class, display
.getDisplayChannelAlphas(), Segmentation
.fmp
.SNT_invert_image
).getImagePlus();
619 final Calibration cal
= virtual
.getCalibration();
620 double minimumSeparation
= 1;
621 if (cal
!= null) minimumSeparation
= Math
.min(cal
.pixelWidth
,
622 Math
.min(cal
.pixelHeight
,
624 final ComputeCurvatures hessian
= new ComputeCurvatures(virtual
, minimumSeparation
, null, cal
!= null);
627 tr
.virtual
= virtual
;
629 tr
.hessian
= hessian
;
632 } catch (final Exception e
) {
637 Bureaucrat
.createAndStart(worker
[0], project
);
640 final Point2D
.Double po
= transformPoint(p
[0][n_points
-1], p
[1][n_points
-1]);
641 final int start_x
= (int)po
.x
;
642 final int start_y
= (int)po
.y
;
643 final int start_z
= layer_set
.indexOf(layer_set
.getLayer(p_layer
[n_points
-1])); // 0-based
644 final int goal_x
= (int)(x_pd
* scale
); // must transform into virtual space
645 final int goal_y
= (int)(y_pd
* scale
);
646 final int goal_z
= layer_set
.indexOf(layer
);
649 Utils.log2("x_pd, y_pd : " + x_pd + ", " + y_pd);
650 Utils.log2("scale: " + scale);
651 Utils.log2("start: " + start_x + "," + start_y + ", " + start_z);
652 Utils.log2("goal: " + goal_x + "," + goal_y + ", " + goal_z);
653 Utils.log2("virtual: " + tr.virtual);
657 final boolean simplify
= me
.isAltDown();
659 worker
[1] = new Worker("Tracer - waiting on hessian") { @Override
663 if (null != worker
[0]) {
664 // Wait until hessian is ready
667 setTaskName("Tracing path");
668 final int reportEveryMilliseconds
= 2000;
669 tr
.tracer
= new TracerThread(tr
.virtual
, 0, 255,
670 120, // timeout seconds
671 reportEveryMilliseconds
,
672 start_x
, start_y
, start_z
,
673 goal_x
, goal_y
, goal_z
,
674 true, // reciproal pix values at start and goal
675 tr
.virtual
.getStackSize() == 1,
677 null == tr
.hessian ?
1 : 4,
680 tr
.tracer
.addProgressListener(new SearchProgressCallback() {
682 public void pointsInSearch(final SearchInterface source
, final int inOpen
, final int inClosed
) {
683 worker
[1].setTaskName("Tracing path: open=" + inOpen
+ " closed=" + inClosed
);
686 public void finished(final SearchInterface source
, final boolean success
) {
688 Utils
.logAll("Could NOT trace a path");
692 public void threadStatus(final SearchInterface source
, final int currentStatus
) {
693 // This method gets called every reportEveryMilliseconds
694 if (worker
[1].hasQuitted()) {
695 source
.requestStop();
702 final Path result
= tr
.tracer
.getResult();
706 if (null == result
) {
707 Utils
.log("Finding a path failed"); //: "+
708 // not public //SearchThread.exitReasonStrings[tracer.getExitReason()]);
712 // TODO: precise_x_positions etc are likely to be broken (calibrated or something)
715 // Remove bogus points: those at the end with 0,0 coords
716 int len
= result
.points
;
717 final double[][] pos
= result
.getXYZUnscaled();
718 for (int i
=len
-1; i
>-1; i
--) {
719 if (0 == pos
[0][i
] && 0 == pos
[1][i
]) {
723 // Transform points: undo scale, and bring to this Polyline AffineTransform:
724 final AffineTransform aff
= new AffineTransform();
726 /* 2 */ aff
.concatenate(Polyline
.this.at
.createInverse());
727 /* 1 */ aff
.scale(1/scale
, 1/scale
);
728 final double[] po
= new double[len
* 2];
729 for (int i
=0, j
=0; i
<len
; i
++, j
+=2) {
733 final double[] po2
= new double[len
* 2];
734 aff
.transform(po
, 0, po2
, 0, len
); // what a stupid format: consecutive x,y pairs
736 long[] p_layer_ids
= new long[len
];
737 double[] pox
= new double[len
];
738 double[] poy
= new double[len
];
739 for (int i
=0, j
=0; i
<len
; i
++, j
+=2) {
740 p_layer_ids
[i
] = layer_set
.getLayer((int)pos
[2][i
]).getId(); // z_positions in 0-(N-1), not in 1-N like slices!
745 // Simplify path: to steps of 5 calibration units, or 5 pixels when not calibrated.
747 setTaskName("Simplifying path");
748 final Object
[] ob
= Polyline
.simplify(pox
, poy
, p_layer_ids
, 10000, layer_set
);
749 pox
= (double[])ob
[0];
750 poy
= (double[])ob
[1];
751 p_layer_ids
= (long[])ob
[2];
755 // Record the first newly-added autotraced point index:
756 last_autotrace_start
= Polyline
.this.n_points
;
757 Polyline
.this.appendPoints(pox
, poy
, p_layer_ids
, len
);
759 Polyline
.this.repaint(true, null);
760 Utils
.logAll("Added " + len
+ " new points.");
762 } catch (final Exception e
) { IJError
.print(e
); }
765 Bureaucrat
.createAndStart(worker
[1], project
);
772 if (ProjectToolbar
.PEN
== tool
|| ProjectToolbar
.PENCIL
== tool
) {
774 if (Utils
.isControlDown(me
) && me
.isShiftDown()) {
775 final long lid
= Display
.getFrontLayer(this.project
).getId();
776 if (-1 == index
|| lid
!= p_layer
[index
]) {
777 index
= findNearestPoint(x_p
, y_p
, layer_id
);
783 repaint(false, null);
786 // In any case, terminate
790 if (-1 != index
&& layer_id
!= p_layer
[index
]) index
= -1; // disable!
792 //if no conditions are met, attempt to add point
793 else if (-1 == index
&& !me
.isShiftDown() && !me
.isAltDown()) {
795 index
= addPoint(x_p
, y_p
, layer_id
, mag
);
797 repaint(false, null);
804 public void mouseDragged(final MouseEvent me
, final Layer layer
, final int x_p
, final int y_p
, int x_d
, int y_d
, int x_d_old
, int y_d_old
) {
805 // transform to the local coordinates
806 if (!this.at
.isIdentity()) {
807 //final Point2D.Double p = inverseTransformPoint(x_p, y_p);
810 final Point2D
.Double pd
= inverseTransformPoint(x_d
, y_d
);
813 final Point2D
.Double pdo
= inverseTransformPoint(x_d_old
, y_d_old
);
814 x_d_old
= (int)pdo
.x
;
815 y_d_old
= (int)pdo
.y
;
818 final int tool
= ProjectToolbar
.getToolId();
820 if (ProjectToolbar
.PEN
== tool
|| ProjectToolbar
.PENCIL
== tool
) {
821 //if a point in the backbone is found, then:
822 if (-1 != index
&& !me
.isAltDown() && !me
.isShiftDown()) {
823 dragPoint(index
, x_d
- x_d_old
, y_d
- y_d_old
);
824 repaint(false, layer
);
831 public void mouseReleased(final MouseEvent me
, final Layer layer
, final int x_p
, final int y_p
, final int x_d
, final int y_d
, final int x_r
, final int y_r
) {
833 final int tool
= ProjectToolbar
.getToolId();
835 if (ProjectToolbar
.PEN
== tool
|| ProjectToolbar
.PENCIL
== tool
) {
836 repaint(true, layer
); //needed at least for the removePoint
839 //update points in database if there was any change
842 // update all points, since the index may have changed
843 updateInDatabase("points");
844 } else if (-1 != index
&& index
!= n_points
) { //second condition happens when the last point has been removed
845 // not implemented // updateInDatabase(getUpdatePointForSQL(index));
847 updateInDatabase("points");
848 } else if (index
!= n_points
) { // don't do it when the last point is removed
850 updateInDatabase("points");
852 updateInDatabase("dimensions");
853 } else if (x_r
!= x_p
|| y_r
!= y_p
) {
854 updateInDatabase("dimensions");
857 repaint(true, layer
);
860 is_new_point
= false;
865 protected boolean calculateBoundingBox(final Layer la
) {
866 return calculateBoundingBox(true, la
);
869 synchronized protected boolean calculateBoundingBox(final boolean adjust_position
, final Layer la
) {
871 this.width
= this.height
= 0;
875 final double[] m
= calculateDataBoundingBox();
877 this.width
= (float)(m
[2] - m
[0]); // max_x - min_x;
878 this.height
= (float)(m
[3] - m
[1]); // max_y - min_y;
880 if (adjust_position
) {
881 // now readjust points to make min_x,min_y be the x,y
882 for (int i
=0; i
<n_points
; i
++) {
883 p
[0][i
] -= m
[0]; // min_x;
884 p
[1][i
] -= m
[1]; // min_y;
886 this.at
.translate(m
[0], m
[1]) ; // (min_x, min_y); // not using super.translate(...) because a preConcatenation is not needed; here we deal with the data.
887 updateInDatabase("transform");
889 updateInDatabase("dimensions");
895 /** Returns min_x, min_y, max_x, max_y. */
896 protected double[] calculateDataBoundingBox() {
897 double min_x
= Double
.MAX_VALUE
;
898 double min_y
= Double
.MAX_VALUE
;
902 for (int i
=0; i
<n_points
; i
++) {
903 if (p
[0][i
] < min_x
) min_x
= p
[0][i
];
904 if (p
[1][i
] < min_y
) min_y
= p
[1][i
];
905 if (p
[0][i
] > max_x
) max_x
= p
[0][i
];
906 if (p
[1][i
] > max_y
) max_y
= p
[1][i
];
908 return new double[]{min_x
, min_y
, max_x
, max_y
};
911 /**Release all memory resources taken by this object.*/
913 synchronized public void destroy() {
919 /**Release memory resources used by this object: namely the arrays of points, which can be reloaded with a call to setupForDisplay()*/
920 synchronized public void flush() {
923 n_points
= -1; // flag that points exist but are not loaded
926 /**Repaints in the given ImageCanvas only the area corresponding to the bounding box of this Pipe. */
927 public void repaint(final boolean repaint_navigator
, final Layer la
) {
928 //TODO: this could be further optimized to repaint the bounding box of the last modified segments, i.e. the previous and next set of interpolated points of any given backbone point. This would be trivial if each segment of the Bezier curve was an object.
929 final Rectangle box
= getBoundingBox(null);
930 calculateBoundingBox(true, la
);
931 box
.add(getBoundingBox(null));
932 Display
.repaint(layer_set
, this, box
, 5, repaint_navigator
);
935 /**Make this object ready to be painted.*/
936 synchronized private void setupForDisplay() {
937 if (-1 == n_points
) n_points
= 0;
939 /* Database storage not implemented yet
941 ArrayList al = project.getLoader().fetchPolylinePoints(id);
942 n_points = al.size();
943 p = new double[2][n_points];
944 p_layer = new long[n_points];
945 Iterator it = al.iterator();
947 while (it.hasNext()) {
948 Object[] ob = (Object[])it.next();
949 p[0][i] = ((Double)ob[0]).doubleValue();
950 p[1][i] = ((Double)ob[1]).doubleValue();
951 p_layer[i] = ((Long)ob[7]).longValue();
958 /** The exact perimeter of this polyline, in integer precision. */
960 synchronized public Polygon
getPerimeter() {
961 if (null == p
|| p
[0].length
< 2) return new Polygon();
963 // local pointers, since they may be transformed
964 int n_points
= this.n_points
;
965 double[][] p
= this.p
;
966 if (!this.at
.isIdentity()) {
967 final Object
[] ob
= getTransformedData();
968 p
= (double[][])ob
[0];
969 n_points
= p
[0].length
;
971 final int[] x
= new int[n_points
];
972 final int[] y
= new int[n_points
];
973 for (int i
=0; i
<n_points
; i
++) {
977 return new Polygon(x
, y
, n_points
);
980 /** A little square for each pixel in @param layer.*/
982 synchronized public Area
getAreaAt(final Layer layer
) {
983 final Area a
= new Area();
984 for (int i
=0; i
<n_points
; i
++) {
985 if (p_layer
[i
] != layer
.getId()) continue;
986 a
.add(new Area(new Rectangle2D
.Float((float)p
[0][i
], (float)p
[1][i
], 1, 1)));
988 a
.transform(this.at
);
993 synchronized public boolean isRoughlyInside(final Layer layer
, final Rectangle r
) {
995 final Rectangle box
= this.at
.createInverse().createTransformedShape(r
).getBounds();
996 for (int i
=0; i
<n_points
; i
++) {
997 if (p_layer
[i
] == layer
.getId()) {
998 if (box
.contains(p
[0][i
], p
[1][i
])) return true;
1001 } catch (final NoninvertibleTransformException e
) {
1008 public boolean isDeletable() {
1009 return 0 == n_points
;
1012 /** The number of points in this pipe. */
1014 public int length() {
1015 if (-1 == n_points
) setupForDisplay();
1020 synchronized public boolean contains(final Layer layer
, final double x
, final double y
) {
1021 final Display front
= Display
.getFront();
1023 if (null != front
) {
1024 final double mag
= front
.getCanvas().getMagnification();
1025 radius
= (10.0D
/ mag
);
1026 if (radius
< 2) radius
= 2;
1028 // else assume fixed radius of 10 around the line
1031 final Point2D
.Double po
= inverseTransformPoint(x
, y
);
1032 return containsLocal(layer
, po
.x
, po
.y
, radius
);
1035 protected boolean containsLocal(final Layer layer
, final double x
, final double y
, final double radius
) {
1037 final long lid
= layer
.getId();
1038 final double z
= layer
.getZ();
1040 for (int i
=0; i
<n_points
; i
++) {
1041 if (lid
== p_layer
[i
]) {
1042 // check both lines:
1043 if (i
> 0 && M
.distancePointToLine(x
, y
, p
[0][i
-1], p
[1][i
-1], p
[0][i
], p
[1][i
]) < radius
) {
1046 if (i
< (n_points
-1) && M
.distancePointToLine(x
, y
, p
[0][i
], p
[1][i
], p
[0][i
+1], p
[1][i
+1]) < radius
) {
1050 final double z1
= layer_set
.getLayer(p_layer
[i
-1]).getZ();
1051 final double z2
= layer_set
.getLayer(p_layer
[i
]).getZ();
1052 if ( (z1
< z
&& z
< z2
)
1053 || (z2
< z
&& z
< z1
) ) {
1054 // line between j and j-1 crosses the given layer
1055 if (M
.distancePointToLine(x
, y
, p
[0][i
-1], p
[1][i
-1], p
[0][i
], p
[1][i
]) < radius
) {
1064 /* Scan the Display and link Patch objects that lay under this Pipe's bounding box. */
1066 public boolean linkPatches() { // TODO needs to check all layers!!
1067 unlinkAll(Patch
.class);
1068 // sort points by layer id
1069 final HashMap
<Long
,ArrayList
<Integer
>> m
= new HashMap
<Long
,ArrayList
<Integer
>>();
1070 for (int i
=0; i
<n_points
; i
++) {
1071 ArrayList
<Integer
> a
= m
.get(p_layer
[i
]);
1073 a
= new ArrayList
<Integer
>();
1074 m
.put(p_layer
[i
], a
);
1078 boolean must_lock
= false;
1079 // For each layer id, search patches whose perimeter includes
1080 // one of the backbone points in this path:
1081 for (final Map
.Entry
<Long
,ArrayList
<Integer
>> e
: m
.entrySet()) {
1082 final Layer layer
= layer_set
.getLayer(e
.getKey().longValue());
1083 for (final Displayable patch
: layer
.getDisplayables(Patch
.class)) {
1084 final Polygon perimeter
= patch
.getPerimeter();
1085 for (final Integer in
: e
.getValue()) {
1086 final int i
= in
.intValue();
1087 if (perimeter
.contains(p
[0][i
], p
[1][i
])) {
1089 if (patch
.locked
) must_lock
= true;
1096 // set the locked flag to this and all linked ones
1097 if (must_lock
&& !locked
) {
1105 /** Returns the layer of lowest Z coordinate where this ZDisplayable has a point in, or the creation layer if no points yet. */
1107 public Layer
getFirstLayer() {
1108 if (0 == n_points
) return this.layer
;
1109 if (-1 == n_points
) setupForDisplay(); //reload
1110 Layer la
= this.layer
;
1111 final double z
= Double
.MAX_VALUE
;
1112 for (int i
=0; i
<n_points
; i
++) {
1113 final Layer layer
= layer_set
.getLayer(p_layer
[i
]);
1114 if (layer
.getZ() < z
) la
= layer
;
1119 /** Exports data. */
1121 synchronized public void exportXML(final StringBuilder sb_body
, final String indent
, final XMLOptions options
) {
1122 sb_body
.append(indent
).append("<t2_polyline\n");
1123 final String in
= indent
+ "\t";
1124 super.exportXML(sb_body
, in
, options
);
1125 if (-1 == n_points
) setupForDisplay(); // reload
1126 //if (0 == n_points) return;
1127 final String
[] RGB
= Utils
.getHexRGBColor(color
);
1128 sb_body
.append(in
).append("style=\"fill:none;stroke-opacity:").append(alpha
).append(";stroke:#").append(RGB
[0]).append(RGB
[1]).append(RGB
[2]).append(";stroke-width:1.0px;stroke-opacity:1.0\"\n");
1130 sb_body
.append(in
).append("d=\"M");
1131 for (int i
=0; i
<n_points
-1; i
++) {
1132 sb_body
.append(" ").append(p
[0][i
]).append(",").append(p
[1][i
]).append(" L");
1134 sb_body
.append(" ").append(p
[0][n_points
-1]).append(',').append(p
[1][n_points
-1]).append("\"\n");
1135 sb_body
.append(in
).append("layer_ids=\""); // different from 'layer_id' in superclass
1136 for (int i
=0; i
<n_points
; i
++) {
1137 sb_body
.append(p_layer
[i
]);
1138 if (n_points
-1 != i
) sb_body
.append(",");
1140 sb_body
.append("\"\n");
1142 sb_body
.append(indent
).append(">\n");
1143 super.restXML(sb_body
, in
, options
);
1144 sb_body
.append(indent
).append("</t2_polyline>\n");
1147 /** Exports to type t2_polyline. */
1148 static public void exportDTD(final StringBuilder sb_header
, final HashSet
<String
> hs
, final String indent
) {
1149 final String type
= "t2_polyline";
1150 if (hs
.contains(type
)) return;
1152 sb_header
.append(indent
).append("<!ELEMENT t2_polyline (").append(Displayable
.commonDTDChildren()).append(")>\n");
1153 Displayable
.exportDTD(type
, sb_header
, hs
, indent
);
1154 sb_header
.append(indent
).append(TAG_ATTR1
).append(type
).append(" d").append(TAG_ATTR2
)
1158 /** Performs a deep copy of this object, without the links. */
1160 synchronized public Displayable
clone(final Project pr
, final boolean copy_id
) {
1161 final long nid
= copy_id ?
this.id
: pr
.getLoader().getNextId();
1162 final Polyline copy
= new Polyline(pr
, nid
, null != title ? title
.toString() : null, width
, height
, alpha
, this.visible
, new Color(color
.getRed(), color
.getGreen(), color
.getBlue()), this.locked
, (AffineTransform
)this.at
.clone());
1164 if (-1 == n_points
) setupForDisplay(); // load data
1165 copy
.n_points
= n_points
;
1166 copy
.p
= new double[][]{(double[])this.p
[0].clone(), (double[])this.p
[1].clone()};
1167 copy
.p_layer
= (long[])this.p_layer
.clone();
1168 copy
.addToDatabase();
1175 synchronized public List
<Point3f
> generateTriangles(final double scale
, final int parallels
, final int resample
) {
1176 return generateTriangles(scale
, parallels
, resample
, layer_set
.getCalibrationCopy());
1179 /** Returns a list of Point3f that define a polyline in 3D, for usage with an ij3d CustomLineMesh CONTINUOUS. @param parallels is ignored. */
1180 synchronized public List
<Point3f
> generateTriangles(final double scale
, final int parallels
, final int resample
, final Calibration cal
) {
1181 if (-1 == n_points
) setupForDisplay();
1183 if (0 == n_points
) return null;
1185 // local pointers, since they may be transformed
1188 if (!this.at
.isIdentity()) {
1189 final Object
[] ob
= getTransformedData();
1190 p
= (double[][])ob
[0];
1191 n_points
= p
[0].length
;
1193 n_points
= this.n_points
;
1197 final ArrayList
<Point3f
> list
= new ArrayList
<Point3f
>();
1198 final double KW
= scale
* cal
.pixelWidth
* resample
;
1199 final double KH
= scale
* cal
.pixelHeight
* resample
;
1201 for (int i
=0; i
<n_points
; i
++) {
1202 list
.add(new Point3f((float) (p
[0][i
] * KW
),
1203 (float) (p
[1][i
] * KH
),
1204 (float) (layer_set
.getLayer(p_layer
[i
]).getZ() * KW
)));
1208 // Duplicate first point
1209 list
.add(list
.get(0));
1215 synchronized private Object
[] getTransformedData() {
1216 final int n_points
= this.n_points
;
1217 final double[][] p
= transformPoints(this.p
, n_points
);
1218 return new Object
[]{p
};
1222 public boolean intersects(final Area area
, final double z_first
, final double z_last
) {
1223 if (-1 == n_points
) setupForDisplay();
1224 for (int i
=0; i
<n_points
; i
++) {
1225 final double z
= layer_set
.getLayer(p_layer
[i
]).getZ();
1226 if (z
< z_first
|| z
> z_last
) continue;
1227 if (area
.contains(p
[0][i
], p
[1][i
])) return true;
1232 /** Returns a non-calibrated VectorString3D. */
1234 synchronized public VectorString3D
asVectorString3D() {
1235 // local pointers, since they may be transformed
1236 int n_points
= this.n_points
;
1237 double[][] p
= this.p
;
1238 if (!this.at
.isIdentity()) {
1239 final Object
[] ob
= getTransformedData();
1240 p
= (double[][])ob
[0];
1241 n_points
= p
[0].length
;
1243 final double[] z_values
= new double[n_points
];
1244 for (int i
=0; i
<n_points
; i
++) {
1245 z_values
[i
] = layer_set
.getLayer(p_layer
[i
]).getZ();
1248 final double[] px
= p
[0];
1249 final double[] py
= p
[1];
1250 final double[] pz
= z_values
;
1251 VectorString3D vs
= null;
1253 vs
= new VectorString3D(px
, py
, pz
, false);
1254 } catch (final Exception e
) { IJError
.print(e
); }
1259 public String
getInfo() {
1260 if (-1 == n_points
) setupForDisplay(); //reload
1264 final VectorString3D vs
= asVectorString3D();
1265 vs
.calibrate(this.layer_set
.getCalibration());
1266 len
= vs
.computeLength(); // no resampling
1268 return new StringBuilder("Length: ").append(Utils
.cutNumber(len
, 2, true)).append(' ').append(this.layer_set
.getCalibration().getUnits()).append('\n').toString();
1272 public ResultsTable
measure(ResultsTable rt
) {
1273 if (-1 == n_points
) setupForDisplay(); //reload
1274 if (0 == n_points
) return rt
;
1275 if (null == rt
) rt
= Utils
.createResultsTable("Polyline results", new String
[]{"id", "length", "name-id"});
1278 final Calibration cal
= layer_set
.getCalibration();
1280 final VectorString3D vs
= asVectorString3D();
1282 len
= vs
.computeLength(); // no resampling
1284 rt
.incrementCounter();
1285 rt
.addLabel("units", cal
.getUnit());
1286 rt
.addValue(0, this.id
);
1287 rt
.addValue(1, len
);
1288 rt
.addValue(2, getNameId());
1292 /** Resample the curve to, first, a number of points as resulting from resampling to a point interdistance of delta, and second, as adjustment by random walk of those points towards the original points. */
1293 static public Object
[] simplify(final double[] px
, final double[] py
, final long[] p_layer_ids
, /*final double delta, final double allowed_error_per_point,*/ final int max_iterations
, final LayerSet layer_set
) throws Exception
{
1294 if (px
.length
!= py
.length
|| py
.length
!= p_layer_ids
.length
) return null;
1296 final double[] pz
= new double[px
.length
];
1297 for (int i
=0; i
<pz
.length
; i
++) {
1298 pz
[i
] = layer_set
.getLayer(p_layer_ids
[i
]).getZ();
1303 VectorString3D vs = new VectorString3D(px, py, pz, false);
1304 Calibration cal = layer_set.getCalibrationCopy();
1307 cal.pixelWidth = 1 / cal.pixelWidth;
1308 cal.pixelHeight = 1 / cal.pixelHeight;
1309 vs.calibrate(cal); // undo it, since source points are in pixels
1311 Pth path = new Pth(vs.getPoints(0), vs.getPoints(1), vs.getPoints(2));
1314 // The above fails with strangely jagged lines.
1317 // Instead, just a line:
1318 final Calibration cal
= layer_set
.getCalibrationCopy();
1319 final double one_unit
= 1 / cal
.pixelWidth
; // in pixels
1320 final double traced_length
= M
.distance(px
[0], py
[0], pz
[0],
1321 px
[px
.length
-1], py
[py
.length
-1], pz
[pz
.length
-1]);
1322 final double segment_length
= (one_unit
* 5);
1323 final int n_new_points
= (int)(traced_length
/ segment_length
) + 1;
1324 double[] rx
= new double[n_new_points
];
1325 double[] ry
= new double[n_new_points
];
1326 double[] rz
= new double[n_new_points
];
1327 rx
[0] = px
[0]; rx
[rx
.length
-1] = px
[px
.length
-1];
1328 ry
[0] = py
[0]; ry
[ry
.length
-1] = py
[py
.length
-1];
1329 rz
[0] = pz
[0]; rz
[rz
.length
-1] = pz
[pz
.length
-1];
1330 final Vector3d v
= new Vector3d(rx
[n_new_points
-1] - rx
[0],
1331 ry
[n_new_points
-1] - ry
[0],
1332 rz
[n_new_points
-1] - rz
[0]);
1334 v
.scale(segment_length
);
1335 for (int i
=1; i
<n_new_points
-1; i
++) {
1336 rx
[i
] = rx
[0] + v
.x
* i
;
1337 ry
[i
] = ry
[0] + v
.y
* i
;
1338 rz
[i
] = rz
[0] + v
.z
* i
;
1340 Pth path
= new Pth(rx
, ry
, rz
);
1341 rx
= ry
= rz
= null;
1343 //final double lowest_error = px.length * allowed_error_per_point;
1345 final Random rand
= new Random(System
.currentTimeMillis());
1347 double current_error
= Double
.MAX_VALUE
;
1349 //double[] er = new double[max_iterations];
1350 //double[] index = new double[max_iterations];
1351 int missed_in_a_row
= 0;
1352 for (; i
<max_iterations
; i
++) {
1353 final Pth copy
= path
.copy().shakeUpOne(d
, rand
);
1354 final double error
= copy
.measureErrorSq(px
, py
, pz
);
1355 // If less error, keep the copy
1356 if (error
< current_error
) {
1357 current_error
= error
;
1361 missed_in_a_row
= 0;
1363 //er[i] = current_error;
1366 if (missed_in_a_row
> 10 * path
.px
.length
) {
1367 Utils
.log2("Stopped random walk at iteration " + i
);
1373 // If below lowest_error, quit searching
1374 if (current_error < lowest_error) {
1375 Utils.log2("Stopped at iteration " + i);
1382 Plot plot = new Plot("error", "iteration", "error", index, er);
1383 plot.setLineWidth(2);
1387 if (max_iterations
== i
) {
1388 Utils
.log2("Reached max iterations -- current error: " + current_error
);
1391 // Approximate new Z to a layer id:
1392 final long[] plids
= new long[path
.px
.length
];
1393 plids
[0] = p_layer_ids
[0]; // first point untouched
1394 for (int k
=1; k
<path
.pz
.length
; k
++) {
1395 plids
[k
] = layer_set
.getNearestLayer(path
.pz
[k
]).getId();
1398 return new Object
[]{path
.px
, path
.py
, plids
};
1401 /** A path of points in 3D. */
1402 static private class Pth
{
1403 final double[] px
, py
, pz
;
1404 Pth(final double[] px
, final double[] py
, final double[] pz
) {
1409 /** Excludes first and last points. */
1410 final Pth
shakeUpOne(final double d
, final Random rand
) {
1411 final int i
= rand
.nextInt(px
.length
-1) + 1;
1412 px
[i
] += d
* (rand
.nextBoolean() ?
1 : -1);
1413 py
[i
] += d
* (rand
.nextBoolean() ?
1 : -1);
1414 pz
[i
] += d
* (rand
.nextBoolean() ?
1 : -1);
1418 return new Pth( Utils
.copy(px
, px
.length
),
1419 Utils
.copy(py
, py
.length
),
1420 Utils
.copy(pz
, pz
.length
) );
1422 /** Excludes first and last points. */
1423 final double measureErrorSq(final double[] ox
, final double[] oy
, final double[] oz
) {
1425 for (int i
=1; i
<ox
.length
-1; i
++) {
1426 double min_dist
= Double
.MAX_VALUE
;
1427 for (int j
=1; j
<px
.length
; j
++) {
1428 // distance from a original point to a line defined by two consecutive new points
1429 final double dist
= M
.distancePointToSegmentSq(ox
[i
], oy
[i
], oz
[i
],
1430 px
[j
-1], py
[j
-1], pz
[j
-1],
1431 px
[j
], py
[j
], pz
[j
]);
1432 if (dist
< min_dist
) min_dist
= dist
;
1441 final Class
<?
> getInternalDataPackageClass() {
1442 return DPPolyline
.class;
1446 synchronized Object
getDataPackage() {
1447 return new DPPolyline(this);
1450 static private final class DPPolyline
extends Displayable
.DataPackage
{
1452 final long[] p_layer
;
1454 DPPolyline(final Polyline polyline
) {
1456 // store copies of all arrays
1457 this.p
= new double[][]{Utils
.copy(polyline
.p
[0], polyline
.n_points
), Utils
.copy(polyline
.p
[1], polyline
.n_points
)};
1458 this.p_layer
= new long[polyline
.n_points
]; System
.arraycopy(polyline
.p_layer
, 0, this.p_layer
, 0, polyline
.n_points
);
1461 final boolean to2(final Displayable d
) {
1463 final Polyline polyline
= (Polyline
)d
;
1464 final int len
= p
[0].length
; // == n_points, since it was cropped on copy
1465 polyline
.p
= new double[][]{Utils
.copy(p
[0], len
), Utils
.copy(p
[1], len
)};
1466 polyline
.n_points
= p
[0].length
;
1467 polyline
.p_layer
= new long[len
]; System
.arraycopy(p_layer
, 0, polyline
.p_layer
, 0, len
);
1472 /** Retain the data within the layer range, and through out all the rest. */
1474 synchronized public boolean crop(final List
<Layer
> range
) {
1475 final HashSet
<Long
> lids
= new HashSet
<Long
>();
1476 for (final Layer l
: range
) {
1477 lids
.add(l
.getId());
1479 for (int i
=0; i
<n_points
; i
++) {
1480 if (!lids
.contains(p_layer
[i
])) {
1485 calculateBoundingBox(true, null);
1489 /** Create a shorter Polyline, from start to end (inclusive); not added to the LayerSet. */
1490 synchronized public Polyline
sub(final int start
, final int end
) {
1491 final Polyline sub
= new Polyline(project
, title
);
1492 sub
.n_points
= end
- start
+ 1;
1493 sub
.p
[0] = Utils
.copy(this.p
[0], start
, sub
.n_points
);
1494 sub
.p
[1] = Utils
.copy(this.p
[1], start
, sub
.n_points
);
1495 sub
.p_layer
= new long[sub
.n_points
];
1496 System
.arraycopy(this.p_layer
, start
, sub
.p_layer
, 0, sub
.n_points
);
1500 synchronized public void reverse() {
1501 Utils
.reverse(p
[0]);
1502 Utils
.reverse(p
[1]);
1503 Utils
.reverse(p_layer
);
1506 synchronized protected boolean layerRemoved(final Layer la
) {
1507 super.layerRemoved(la
);
1508 for (int i
=0; i
<p_layer
.length
; i
++) {
1509 if (la
.getId() == p_layer
[i
]) {
1518 synchronized public boolean apply(final Layer la
, final Area roi
, final mpicbg
.models
.CoordinateTransform ict
) throws Exception
{
1520 mpicbg
.models
.CoordinateTransform chain
= null;
1521 Area localroi
= null;
1522 AffineTransform inverse
= null;
1523 for (int i
=0; i
<n_points
; i
++) {
1524 if (p_layer
[i
] == la
.getId()) {
1525 if (null == localroi
) {
1526 inverse
= this.at
.createInverse();
1527 localroi
= roi
.createTransformedArea(inverse
);
1529 if (localroi
.contains(p
[0][i
], p
[1][i
])) {
1530 if (null == chain
) {
1531 chain
= M
.wrap(this.at
, ict
, inverse
);
1534 M
.apply(chain
, p
, i
, fp
);
1538 if (null != chain
) calculateBoundingBox(true, la
); // may be called way too many times, but avoids lots of headaches.
1542 public boolean apply(final VectorDataTransform vdt
) throws Exception
{
1543 final double[] fp
= new double[2];
1544 final VectorDataTransform vlocal
= vdt
.makeLocalTo(this);
1545 for (int i
=0; i
<n_points
; i
++) {
1546 if (vdt
.layer
.getId() == p_layer
[i
]) {
1547 for (final VectorDataTransform
.ROITransform rt
: vlocal
.transforms
) {
1548 if (rt
.roi
.contains(p
[0][i
], p
[1][i
])) {
1549 // Transform the point
1550 M
.apply(rt
.ct
, p
, i
, fp
);
1556 calculateBoundingBox(true, vlocal
.layer
);
1561 synchronized public Collection
<Long
> getLayerIds() {
1562 return Utils
.asList(p_layer
, 0, n_points
);