preparing release pom-trakem2-2.0.0, VectorString-2.0.0, TrakEM2_-1.0h
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / Polyline.java
blob91a52240b8293a1b9c86021ccfe8ab2d65cdf0ad
1 /**
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.
21 **/
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;
44 import java.util.Map;
45 import java.util.Random;
47 import org.scijava.vecmath.Point3f;
48 import org.scijava.vecmath.Vector3d;
50 import features.ComputeCurvatures;
51 import ij.ImagePlus;
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;
65 import tracing.Path;
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);
84 addToDatabase();
85 n_points = 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;
91 this.alpha = alpha;
92 this.visible = visible;
93 this.color = color;
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");
102 if (null != ps) {
103 final String lids = ht_attr.get("layer_ids");
104 if (null == lids) {
105 Utils.log("ERROR: found 'd' but not 'layer_ids' in XML entry of Polyline #" + this.id);
106 return;
108 // parse the points
109 // parse the SVG points data
110 // M: Move To
111 // L: Line To
112 // sequence is: M p[0][0],p[1][0] L p[0][1],p[1][1] L p[0][2],p[1][2] ...
113 // first point:
114 int i_start = ps.indexOf('M');
115 int i_L = ps.indexOf('L', i_start+1);
116 int next = 0;
117 while (-1 != i_L) {
118 if (p[0].length == next) enlargeArrays();
119 // parse the point
120 // 'X'
121 final int i_comma = ps.indexOf(',', i_start+1);
122 p[0][next] = Double.parseDouble(ps.substring(i_start+1, i_comma));
123 // 'Y'
124 i_L = ps.indexOf('L', i_comma);
125 int i_end = i_L;
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
130 i_start = i_L;
131 next++;
133 n_points = next;
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() {
149 enlargeArrays(5);
151 synchronized protected void enlargeArrays(final int n_more) {
152 //catch length
153 final int length = p[0].length;
154 //make copies
155 final double[][] p_copy = new double[2][length + n_more];
156 final long[] p_layer_copy = new long[length + n_more];
157 //copy values
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);
161 //assign them
162 this.p = p_copy;
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;
170 int index = -1;
171 double d = (10.0D / mag);
172 if (d < 2) d = 2;
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;
181 double x1, y1, z1;
183 for (int i=1; i<n_points; i++) {
184 x1 = x2;
185 y1 = y2;
186 z1 = z2;
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,
192 x1, y1, z1,
193 x2, y2, z2);
195 if (sq_dist < sq_d && sq_dist < min_sq_dist) {
196 min_sq_dist = sq_dist;
197 index = i-1; // previous
200 return index;
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) {
206 int index = -1;
207 double d = (10.0D / mag);
208 if (d < 2) d = 2;
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) {
213 min_dist = dist;
214 index = i;
217 return index;
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) {
222 int index = -1;
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) {
228 index = i;
229 min_dist = sq_dist;
232 return index;
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:
238 if (index < 0) {
239 return;
240 } else if (n_points - 1 == index) {
241 //last point out
242 n_points--;
243 } else {
244 //one point out (but not the last)
245 --n_points;
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.
250 p[1][i] = p[1][i+1];
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;
260 //update in database
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;
267 p[0][index] += dx;
268 p[1][index] += dy;
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];
297 if (0 != i) {
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);
302 p2[0][i] = x_p;
303 p2[1][i] = y_p;
304 p_layer2[i] = layer_id;
305 if (n_points != i) {
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);
310 p = p2;
311 p_layer = p_layer2;
312 n_points++;
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
318 //check array size
319 if (p[0].length == n_points) {
320 enlargeArrays();
322 p[0][n_points] = x_p;
323 p[1][n_points] = y_p;
324 p_layer[n_points] = layer_id;
325 n_points++;
326 return n_points-1;
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
333 int index = 0;
334 if (n_points > 1) index = findClosestSegment(x_p, y_p, layer_id, magnification);
335 //check array size
336 if (p[0].length == n_points) {
337 enlargeArrays();
339 //decide:
340 if (0 == n_points || 1 == n_points || index + 1 == n_points) {
341 //append at the end
342 p[0][n_points] = x_p;
343 p[1][n_points] = y_p;
344 p_layer[n_points] = layer_id;
345 index = n_points;
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]) {
357 //append at the end
358 p[0][n_points] = x_p;
359 p[1][n_points] = y_p;
360 p_layer[n_points] = layer_id;
361 index = n_points;
363 last_autotrace_start = -1;
364 } else {
365 // prepend at the beginning
366 for (int i=n_points-1; i>-1; i--) {
367 p[0][i+1] = p[0][i];
368 p[1][i+1] = p[1][i];
369 p_layer[i+1] = p_layer[i];
371 p[0][0] = x_p;
372 p[1][0] = y_p;
373 p_layer[0] = layer_id;
374 index = 0;
376 if (-1 != last_autotrace_start) last_autotrace_start++;
378 } else {
379 //insert at index:
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)
389 p[0][index] = x_p;
390 p[1][index] = y_p;
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++;
402 //add one up
403 this.n_points++;
405 return index;
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();
411 p[0][next] = px[i];
412 p[1][next] = py[i];
413 p_layer[next] = p_layer_ids[i];
415 n_points += len;
416 updateInDatabase("points");
419 @Override
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
424 setupForDisplay();
426 //arrange transparency
427 Composite original_composite = null;
428 if (alpha != 1.0f) {
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) {
445 below = Color.red;
446 above = Color.blue;
447 } else {
448 below = this.color;
449 above = this.color;
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.
456 // First point:
457 double z = layer_set.getLayer(p_layer[0]).getZ();
458 boolean paint = true;
459 if (z < z_current) {
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);
466 // Paint half line:
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);
485 g.setTransform(aff);
488 for (int i=1; i<n_points; i++) {
489 // Determine color
490 z = layer_set.getLayer(p_layer[i]).getZ();
491 paint = true;
492 if (z < z_current) {
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);
520 @Override
521 public void keyPressed(final KeyEvent ke) {
522 final int keyCode = ke.getKeyCode();
523 switch (keyCode) {
524 case KeyEvent.VK_D:
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.");
527 return;
529 // Else, remove:
530 final int len = n_points - last_autotrace_start;
531 n_points = last_autotrace_start;
532 last_autotrace_start = -1;
533 repaint(true, null);
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.");
539 return;
540 case KeyEvent.VK_R: // reset tracing
541 tr_map.remove(layer_set);
542 ke.consume();
543 Utils.log("Reset tracing data for Polyline " + this);
544 return;
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;
567 //double scale = 1;
568 ComputeCurvatures hessian = null;
569 TracerThread tracer = null; // catched thread for KeyEvent to attempt to stop it
572 @Override
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);
579 x_p = (int)po.x;
580 y_p = (int)po.y;
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!");
596 return;
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_;
605 if (null == tr_) {
606 synchronized (tr_map) {
607 tr_map.put(layer_set, tr);
611 if (tr.update) {
612 worker[0] = new Worker("Preparing Hessian...") { @Override
613 public void run() {
614 startedWorking();
615 try {
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();
618 //virtual.show();
619 final Calibration cal = virtual.getCalibration();
620 double minimumSeparation = 1;
621 if (cal != null) minimumSeparation = Math.min(cal.pixelWidth,
622 Math.min(cal.pixelHeight,
623 cal.pixelDepth));
624 final ComputeCurvatures hessian = new ComputeCurvatures(virtual, minimumSeparation, null, cal != null);
625 hessian.run();
627 tr.virtual = virtual;
628 //tr.scale = scale;
629 tr.hessian = hessian;
630 tr.update = false;
632 } catch (final Exception e) {
633 IJError.print(e);
635 finishedWorking();
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
660 public void run() {
661 startedWorking();
662 try {
663 if (null != worker[0]) {
664 // Wait until hessian is ready
665 worker[0].join();
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,
676 tr.hessian,
677 null == tr.hessian ? 1 : 4,
678 null,
679 null != tr.hessian);
680 tr.tracer.addProgressListener(new SearchProgressCallback() {
681 @Override
682 public void pointsInSearch(final SearchInterface source, final int inOpen, final int inClosed) {
683 worker[1].setTaskName("Tracing path: open=" + inOpen + " closed=" + inClosed);
685 @Override
686 public void finished(final SearchInterface source, final boolean success) {
687 if (!success) {
688 Utils.logAll("Could NOT trace a path");
691 @Override
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();
700 tr.tracer.run();
702 final Path result = tr.tracer.getResult();
704 tr.tracer = null;
706 if (null == result) {
707 Utils.log("Finding a path failed"); //: "+
708 // not public //SearchThread.exitReasonStrings[tracer.getExitReason()]);
709 return;
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]) {
720 len--;
721 } else break;
723 // Transform points: undo scale, and bring to this Polyline AffineTransform:
724 final AffineTransform aff = new AffineTransform();
725 /* Inverse order: */
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) {
730 po[j] = pos[0][i];
731 po[j+1] = pos[1][i];
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!
741 pox[i] = po2[j];
742 poy[i] = po2[j+1];
745 // Simplify path: to steps of 5 calibration units, or 5 pixels when not calibrated.
746 if (simplify) {
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];
752 len = pox.length;
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); }
763 finishedWorking();
765 Bureaucrat.createAndStart(worker[1], project);
767 index = -1;
768 return;
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);
779 if (-1 != index) {
780 //delete point
781 removePoint(index);
782 index = -1;
783 repaint(false, null);
786 // In any case, terminate
787 return;
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()) {
794 //add a new point
795 index = addPoint(x_p, y_p, layer_id, mag);
796 is_new_point = true;
797 repaint(false, null);
798 return;
803 @Override
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);
808 //x_p = (int)p.x;
809 //y_p = (int)p.y;
810 final Point2D.Double pd = inverseTransformPoint(x_d, y_d);
811 x_d = (int)pd.x;
812 y_d = (int)pd.y;
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);
825 return;
830 @Override
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
840 if (-1 != index) {
841 if (is_new_point) {
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));
846 // Instead:
847 updateInDatabase("points");
848 } else if (index != n_points) { // don't do it when the last point is removed
849 // update all
850 updateInDatabase("points");
852 updateInDatabase("dimensions");
853 } else if (x_r != x_p || y_r != y_p) {
854 updateInDatabase("dimensions");
857 repaint(true, layer);
859 // reset
860 is_new_point = false;
861 index = -1;
864 @Override
865 protected boolean calculateBoundingBox(final Layer la) {
866 return calculateBoundingBox(true, la);
869 synchronized protected boolean calculateBoundingBox(final boolean adjust_position, final Layer la) {
870 if (0 == n_points) {
871 this.width = this.height = 0;
872 updateBucket(la);
873 return true;
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");
891 updateBucket(la);
892 return true;
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;
899 double max_x = 0.0D;
900 double max_y = 0.0D;
901 // check the points
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.*/
912 @Override
913 synchronized public void destroy() {
914 super.destroy();
915 p = null;
916 p_layer = null;
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() {
921 p = null;
922 p_layer = null;
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;
938 // load points
939 /* Database storage not implemented yet
940 if (null == p) {
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();
946 int i = 0;
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();
952 i++;
958 /** The exact perimeter of this polyline, in integer precision. */
959 @Override
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++) {
974 x[i] = (int)p[0][i];
975 y[i] = (int)p[1][i];
977 return new Polygon(x, y, n_points);
980 /** A little square for each pixel in @param layer.*/
981 @Override
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);
989 return a;
992 @Override
993 synchronized public boolean isRoughlyInside(final Layer layer, final Rectangle r) {
994 try {
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) {
1002 IJError.print(e);
1004 return false;
1007 @Override
1008 public boolean isDeletable() {
1009 return 0 == n_points;
1012 /** The number of points in this pipe. */
1013 @Override
1014 public int length() {
1015 if (-1 == n_points) setupForDisplay();
1016 return n_points;
1019 @Override
1020 synchronized public boolean contains(final Layer layer, final double x, final double y) {
1021 final Display front = Display.getFront();
1022 double radius = 10;
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
1030 // make x,y local
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) {
1044 return true;
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) {
1047 return true;
1049 } else if (i > 0) {
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) {
1056 return true;
1061 return false;
1064 /* Scan the Display and link Patch objects that lay under this Pipe's bounding box. */
1065 @Override
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]);
1072 if (null == a) {
1073 a = new ArrayList<Integer>();
1074 m.put(p_layer[i], a);
1076 a.add(i);
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])) {
1088 this.link(patch);
1089 if (patch.locked) must_lock = true;
1090 break;
1096 // set the locked flag to this and all linked ones
1097 if (must_lock && !locked) {
1098 setLocked(true);
1099 return true;
1102 return false;
1105 /** Returns the layer of lowest Z coordinate where this ZDisplayable has a point in, or the creation layer if no points yet. */
1106 @Override
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;
1116 return la;
1119 /** Exports data. */
1120 @Override
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");
1129 if (n_points > 0) {
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;
1151 hs.add(type);
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. */
1159 @Override
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());
1163 // The data:
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();
1170 return copy;
1173 /** Calibrated. */
1174 @Override
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
1186 final int n_points;
1187 final double[][] p;
1188 if (!this.at.isIdentity()) {
1189 final Object[] ob = getTransformedData();
1190 p = (double[][])ob[0];
1191 n_points = p[0].length;
1192 } else {
1193 n_points = this.n_points;
1194 p = this.p;
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)));
1207 if (n_points < 2) {
1208 // Duplicate first point
1209 list.add(list.get(0));
1212 return list;
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};
1221 @Override
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;
1229 return false;
1232 /** Returns a non-calibrated VectorString3D. */
1233 @Override
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;
1252 try {
1253 vs = new VectorString3D(px, py, pz, false);
1254 } catch (final Exception e) { IJError.print(e); }
1255 return vs;
1258 @Override
1259 public String getInfo() {
1260 if (-1 == n_points) setupForDisplay(); //reload
1261 // measure length
1262 double len = 0;
1263 if (n_points > 1) {
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();
1271 @Override
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"});
1276 // measure length
1277 double len = 0;
1278 final Calibration cal = layer_set.getCalibration();
1279 if (n_points > 1) {
1280 final VectorString3D vs = asVectorString3D();
1281 vs.calibrate(cal);
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());
1289 return rt;
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();
1302 // Resample:
1303 VectorString3D vs = new VectorString3D(px, py, pz, false);
1304 Calibration cal = layer_set.getCalibrationCopy();
1305 vs.calibrate(cal);
1306 vs.resample(delta);
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));
1312 vs = null;
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]);
1333 v.normalize();
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;
1344 final double d = 1;
1345 final Random rand = new Random(System.currentTimeMillis());
1347 double current_error = Double.MAX_VALUE;
1348 int i = 0;
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;
1358 path = copy;
1359 //er[i] = error;
1360 //index[i] = i;
1361 missed_in_a_row = 0;
1362 } else {
1363 //er[i] = current_error;
1364 //index[i] = i;
1365 missed_in_a_row++;
1366 if (missed_in_a_row > 10 * path.px.length) {
1367 Utils.log2("Stopped random walk at iteration " + i);
1368 break;
1370 continue;
1373 // If below lowest_error, quit searching
1374 if (current_error < lowest_error) {
1375 Utils.log2("Stopped at iteration " + i);
1376 break;
1382 Plot plot = new Plot("error", "iteration", "error", index, er);
1383 plot.setLineWidth(2);
1384 plot.show();
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) {
1405 this.px = px;
1406 this.py = py;
1407 this.pz = 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);
1415 return this;
1417 final Pth copy() {
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) {
1424 double error = 0;
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;
1434 error += min_dist;
1436 return error;
1440 @Override
1441 final Class<?> getInternalDataPackageClass() {
1442 return DPPolyline.class;
1445 @Override
1446 synchronized Object getDataPackage() {
1447 return new DPPolyline(this);
1450 static private final class DPPolyline extends Displayable.DataPackage {
1451 final double[][] p;
1452 final long[] p_layer;
1454 DPPolyline(final Polyline polyline) {
1455 super(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);
1460 @Override
1461 final boolean to2(final Displayable d) {
1462 super.to1(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);
1468 return true;
1472 /** Retain the data within the layer range, and through out all the rest. */
1473 @Override
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])) {
1481 removePoint(i);
1482 i--;
1485 calculateBoundingBox(true, null);
1486 return true;
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);
1497 return sub;
1500 synchronized public void reverse() {
1501 Utils.reverse(p[0]);
1502 Utils.reverse(p[1]);
1503 Utils.reverse(p_layer);
1505 @Override
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]) {
1510 removePoint(i);
1511 i--;
1514 return true;
1517 @Override
1518 synchronized public boolean apply(final Layer la, final Area roi, final mpicbg.models.CoordinateTransform ict) throws Exception {
1519 double[] fp = null;
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);
1532 fp = new double[2];
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.
1539 return true;
1541 @Override
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);
1551 break;
1556 calculateBoundingBox(true, vlocal.layer);
1557 return true;
1560 @Override
1561 synchronized public Collection<Long> getLayerIds() {
1562 return Utils.asList(p_layer, 0, n_points);