fixed improper generic parameter use in Tree.duplicateAs > Map, necessary
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / Tree.java
bloba0b785431524998ee63933a2148679d9245a0822
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2009 Albert Cardona.
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.Component;
28 import java.awt.Composite;
29 import java.awt.Dimension;
30 import java.awt.Event;
31 import java.awt.Graphics2D;
32 import java.awt.GridBagConstraints;
33 import java.awt.GridBagLayout;
34 import java.awt.Insets;
35 import java.awt.Point;
36 import java.awt.Polygon;
37 import java.awt.Rectangle;
38 import java.awt.Scrollbar;
39 import java.awt.Stroke;
40 import java.awt.event.ActionEvent;
41 import java.awt.event.ActionListener;
42 import java.awt.event.KeyAdapter;
43 import java.awt.event.KeyEvent;
44 import java.awt.event.MouseAdapter;
45 import java.awt.event.MouseEvent;
46 import java.awt.event.MouseListener;
47 import java.awt.event.MouseWheelEvent;
48 import java.awt.event.WindowAdapter;
49 import java.awt.event.WindowEvent;
50 import java.awt.geom.AffineTransform;
51 import java.awt.geom.Area;
52 import java.awt.geom.NoninvertibleTransformException;
53 import java.awt.geom.Point2D;
54 import java.awt.image.IndexColorModel;
55 import java.io.File;
56 import java.util.ArrayList;
57 import java.util.Collection;
58 import java.util.Collections;
59 import java.util.Comparator;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.LinkedList;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.TreeSet;
68 import java.util.concurrent.Callable;
69 import java.util.concurrent.ExecutorService;
70 import java.util.concurrent.Executors;
71 import java.util.concurrent.Future;
72 import java.util.regex.Pattern;
74 import javax.swing.JButton;
75 import javax.swing.JFrame;
76 import javax.swing.JLabel;
77 import javax.swing.JMenuItem;
78 import javax.swing.JPanel;
79 import javax.swing.JPopupMenu;
80 import javax.swing.JScrollPane;
81 import javax.swing.JTabbedPane;
82 import javax.swing.JTable;
83 import javax.swing.JTextField;
84 import javax.swing.KeyStroke;
85 import javax.swing.SwingUtilities;
86 import javax.swing.table.AbstractTableModel;
87 import javax.swing.table.DefaultTableCellRenderer;
89 import org.scijava.vecmath.Color3f;
90 import org.scijava.vecmath.Point3f;
92 import fiji.geom.AreaCalculations;
93 import ij.ImagePlus;
94 import ij.gui.GenericDialog;
95 import ij.gui.StackWindow;
96 import ij.io.FileSaver;
97 import ij.io.Opener;
98 import ij.measure.Calibration;
99 import ij.measure.ResultsTable;
100 import ini.trakem2.Project;
101 import ini.trakem2.analysis.Centrality;
102 import ini.trakem2.analysis.Vertex;
103 import ini.trakem2.parallel.Process;
104 import ini.trakem2.parallel.TaskFactory;
105 import ini.trakem2.persistence.XMLOptions;
106 import ini.trakem2.utils.Bureaucrat;
107 import ini.trakem2.utils.IJError;
108 import ini.trakem2.utils.M;
109 import ini.trakem2.utils.ProjectToolbar;
110 import ini.trakem2.utils.Utils;
111 import ini.trakem2.utils.Worker;
113 // To remove the warnings, both the Node and the Tree would have to know about the type of Node used.
114 // So: a recursive declaration of Node<T, N extends Node<T,N>> is required.
116 /** A sequence of points ordered in a set of connected branches. */
117 public abstract class Tree<T> extends ZDisplayable implements VectorData {
119 protected final Map<Layer,Set<Node<T>>> node_layer_map = new HashMap<Layer,Set<Node<T>>>();
121 protected final Set<Node<T>> end_nodes = new HashSet<Node<T>>();
123 protected Node<T> root = null;
125 protected Tree(final Project project, final String title) {
126 super(project, title, 0, 0);
129 /** Reconstruct from XML. */
130 protected Tree(final Project project, final long id, final HashMap<String,String> ht_attr, final HashMap<Displayable,String> ht_links) {
131 super(project, id, ht_attr, ht_links);
134 /** For cloning purposes, does not call addToDatabase(), neither creates any root node. */
135 protected Tree(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) {
136 super(project, id, title, locked, at, width, height);
137 this.alpha = alpha;
138 this.visible = visible;
139 this.color = color;
142 /** Get a copy of the {@link Set} of {@link Node} that exist at {@param layer}; the {@link Node} instances are the originals.
143 * Returns an empty {@link Set} if none found. */
144 public Set<Node<T>> getNodesAt(final Layer layer) {
145 synchronized (node_layer_map) {
146 final Set<Node<T>> s = node_layer_map.get(layer);
147 return null == s ? new HashSet<Node<T>>() : new HashSet<Node<T>>(s);
151 final protected Set<Node<T>> getNodesToPaint(final Layer active_layer) {
152 return getNodesToPaint(active_layer, active_layer.getParent().getColorCueLayerRange(active_layer));
155 final protected Set<Node<T>> getNodesToPaint(final Layer active_layer, final List<Layer> color_cue_layers) {
156 synchronized (node_layer_map) {
157 // Determine which layers to paint
158 if (layer_set.color_cues) {
159 Set<Node<T>> nodes = null;
160 if (-1 == layer_set.n_layers_color_cue) {
161 // All layers
162 nodes = new HashSet<Node<T>>();
163 for (final Set<Node<T>> ns : node_layer_map.values()) nodes.addAll(ns);
164 } else {
165 for (final Layer la : color_cue_layers) {
166 final Set<Node<T>> ns = node_layer_map.get(la);
167 if (null != ns) {
168 if (null == nodes) nodes = new HashSet<Node<T>>();
169 nodes.addAll(ns);
173 return nodes;
175 // Else, just the active layer, if any
176 final Set<Node<T>> nodeSet = node_layer_map.get(active_layer);
177 return null == nodeSet? null : new HashSet<Node<T>>(nodeSet);
181 @Override
182 final 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) {
183 paint(g, srcRect, magnification, active, channels, active_layer, layers, layer_set.paint_arrows, layer_set.paint_tags);
185 final 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, final boolean with_arrows, final boolean with_tags) {
186 if (null == root) {
187 setupForDisplay();
188 if (null == root) return;
191 Composite original_composite = null;
192 AffineTransform gt = null;
193 Stroke stroke = null;
195 final Color below, above;
196 if (layer_set.use_color_cue_colors) {
197 below = Color.red;
198 above = Color.blue;
199 } else {
200 below = this.color;
201 above = this.color;
204 synchronized (node_layer_map) {
205 // Determine which layers to paint
206 final Set<Node<T>> nodes = getNodesToPaint(active_layer, layers);
207 if (null != nodes) {
208 // Filter nodes outside the srcRect
209 // The DisplayNavigator and the snapshot panels call paint with the full srcRect
210 // so avoid filtering for them:
211 if (srcRect.x > 0 && srcRect.y > 0
212 && srcRect.width < (int)layer_set.getLayerWidth()
213 && srcRect.height < (int)layer_set.getLayerHeight()) {
214 try {
215 final Rectangle localRect = this.at.createInverse().createTransformedShape(srcRect).getBounds();
216 for (final Iterator<Node<T>> it = nodes.iterator(); it.hasNext(); ) {
217 final Node<T> nd = it.next();
218 if (nd.isRoughlyInside(localRect)) continue;
219 it.remove();
221 } catch (final NoninvertibleTransformException nite) {
222 IJError.print(nite);
225 // Arrange transparency
226 if (alpha != 1.0f) {
227 original_composite = g.getComposite();
228 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
230 // Clear transform and stroke
231 gt = g.getTransform();
232 g.setTransform(DisplayCanvas.DEFAULT_AFFINE);
233 stroke = g.getStroke();
234 g.setStroke(DisplayCanvas.DEFAULT_STROKE);
236 final AffineTransform to_screen = new AffineTransform();
237 to_screen.scale(magnification, magnification);
238 to_screen.translate(-srcRect.x, -srcRect.y);
239 to_screen.concatenate(this.at);
241 final Node<T>[] handles = active ? new Node[nodes.size()] : null;
242 int next = 0;
243 final ArrayList<Runnable> tags_tasks = new ArrayList<Runnable>();
245 for (final Node<T> nd : nodes) {
246 final Runnable task = nd.paint(g, active_layer, active, srcRect, magnification, nodes, this, to_screen, with_arrows, with_tags, layer_set.paint_edge_confidence_boxes, true, above, below);
247 if (null != task) tags_tasks.add(task);
248 if (nd == marked) {
249 if (null == MARKED_CHILD) createMarks();
250 final Composite c = g.getComposite();
251 g.setXORMode(Color.green);
252 final float[] fps = new float[]{nd.x, nd.y};
253 this.at.transform(fps, 0, fps, 0, 1);
254 final AffineTransform aff = new AffineTransform();
255 aff.translate((fps[0] - srcRect.x) * magnification, (fps[1] - srcRect.y) * magnification);
256 g.fill(aff.createTransformedShape(active ? MARKED_PARENT : MARKED_CHILD));
257 g.setComposite(c);
259 if (active && active_layer == nd.la) handles[next++] = nd;
261 for (final Runnable task : tags_tasks) task.run();
262 if (active) {
263 for (int i=0; i<next; i++) {
264 handles[i].paintHandle(g, srcRect, magnification, this);
270 // restore
271 if (null != gt) {
272 g.setTransform(gt);
273 g.setStroke(stroke);
276 //Transparency: fix alpha composite back to original.
277 if (null != original_composite) {
278 g.setComposite(original_composite);
282 protected Rectangle getPaintingBounds() {
283 Rectangle box = null;
284 synchronized (node_layer_map) {
285 for (final Collection<Node<T>> nodes : node_layer_map.values()) {
286 final Rectangle b = getBounds(nodes);
287 if (null == box) box = b;
288 else if (null != b) box.add(b);
291 return box;
294 @Override
295 public Rectangle getBounds(final Rectangle tmp, final Layer layer) {
296 synchronized (node_layer_map) {
297 final Collection<Node<T>> nodes = node_layer_map.get(layer);
298 if (null == nodes) {
299 if (null == tmp) return new Rectangle(); // 0 width and 0 height: no data
300 tmp.setBounds(0, 0, 0, 0);
301 return tmp;
303 final Rectangle r = getBounds(nodes);
304 if (null == tmp) {
305 if (null == r) return new Rectangle();
306 return r;
307 } else {
308 if (null == r) tmp.setRect(0, 0, 0, 0);
309 else tmp.setRect(r);
310 return tmp;
315 // Call always from within a synchronized (node_layer_map) block.
316 protected Rectangle getBounds(final Collection<? extends Node<T>> nodes) {
317 Rectangle b = null;
318 for (final Node<T> nd : nodes) {
319 if (null == b) b = new Rectangle((int)nd.x, (int)nd.y, 1, 1);
320 else b.add((int)nd.x, (int)nd.y);
322 return b;
325 @Override
326 public boolean calculateBoundingBox(final Layer la) {
327 try {
328 if (null == root) {
329 this.at.setToIdentity();
330 this.width = 0;
331 this.height = 0;
332 return false;
335 final Rectangle box = getPaintingBounds();
337 this.width = box.width;
338 this.height = box.height;
340 if (0 == box.x && 0 == box.y) {
341 // No need to translate
342 return false;
345 synchronized (node_layer_map) {
346 // now adjust points to make min_x,min_y be the x,y
347 for (final Collection<Node<T>> nodes : node_layer_map.values()) {
348 for (final Node<T> nd : nodes) {
349 nd.translate(-box.x, -box.y); }}
351 this.at.translate(box.x, box.y); // not using super.translate(...) because a preConcatenation is not needed; here we deal with the data.
353 return true;
354 } finally {
355 updateBucket(la);
359 /**Repaints in the given ImageCanvas only the area corresponding to the bounding box of this Pipe. */
360 public void repaint(final boolean repaint_navigator, final Layer la) {
361 //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.
362 final Rectangle box = getBoundingBox(null);
363 calculateBoundingBox(la);
364 box.add(getBoundingBox(null));
365 Display.repaint(layer_set, this, box, 10, repaint_navigator);
368 /**Make this object ready to be painted.*/
369 synchronized private void setupForDisplay() {
370 // TODO
373 @Override
374 public boolean intersects(final Area area, final double z_first, final double z_last) {
375 if (null == root) return false;
376 synchronized (node_layer_map) {
377 // Area to local coords
378 try {
379 final Area a = area.createTransformedArea(this.at.createInverse());
380 // find layers between z_first and z_last
381 for (final Map.Entry<Layer,Set<Node<T>>> e : node_layer_map.entrySet()) {
382 final double z = e.getKey().getZ();
383 if (z >= z_first && z <= z_last) {
384 for (final Node<T> nd : e.getValue()) {
385 if (nd.intersects(a)) return true;
389 } catch (final Exception e) {
390 IJError.print(e);
393 return false;
396 @Override
397 public Layer getFirstLayer() {
398 if (null == root) return null;
399 synchronized (node_layer_map) {
400 final ArrayList<Layer> las = new ArrayList<Layer>(node_layer_map.keySet());
401 Collections.sort(las, Layer.COMPARATOR);
402 return las.get(0);
406 private final List<Node<T>> tolink = new ArrayList<Node<T>>();
408 protected final void addToLinkLater(final Node<T> nd) {
409 synchronized (tolink) {
410 tolink.add(nd);
413 protected final void removeFromLinkLater(final Node<T> nd) {
414 synchronized (tolink) {
415 tolink.remove(nd);
419 @Override
420 public boolean linkPatches() {
421 if (null == root) return false;
422 // Obtain local copy and clear 'tolink':
423 final ArrayList<Node<T>> tolink;
424 synchronized (this.tolink) {
425 tolink = new ArrayList<Node<T>>(this.tolink);
426 this.tolink.clear();
428 if (tolink.isEmpty()) return true;
430 boolean must_lock = false;
432 for (final Node<T> nd : tolink) {
433 for (final Patch patch : (Collection<Patch>) (Collection) nd.findLinkTargets(this.at)) {
434 link(patch);
435 if (patch.locked) must_lock = true;
439 if (must_lock && !locked) {
440 setLocked(true);
441 return true;
443 return false;
446 /** Create a new instance, intialized with same ZDisplayable-level parameters (affine, color, title, etc.). */
447 abstract protected Tree<T> newInstance();
449 abstract protected Node<T> newNode(float lx, float ly, Layer layer, Node<?> modelNode);
451 /** Create a new node, copying some properties from the modelNode such as radius or color.
452 * The modelNode should be the node that will become the parent of the new node,
453 * but it doesn't have to be. */
454 protected Node<T> createNewNode(final float lx, final float ly, final Layer layer, final Node<?> modelNode) {
455 final Node<T> nd = newNode(lx, ly, layer, modelNode);
456 if (null == modelNode) return nd;
457 nd.setColor(modelNode.getColor());
458 return nd;
461 /** To reconstruct from XML. */
462 abstract public Node<T> newNode(HashMap<String,String> ht_attr);
464 @Override
465 public boolean isDeletable() {
466 return null == root;
469 /** Exports to type t2_treeline. */
470 static public void exportDTD(final StringBuilder sb_header, final HashSet<String> hs, final String indent) {
471 final String type = "t2_node";
472 if (hs.contains(type)) return;
473 hs.add(type);
474 sb_header.append(indent).append("<!ELEMENT t2_tag EMPTY>\n");
475 sb_header.append(indent).append(TAG_ATTR1).append("t2_tag name").append(TAG_ATTR2);
476 sb_header.append(indent).append(TAG_ATTR1).append("t2_tag key").append(TAG_ATTR2);
477 sb_header.append(indent).append("<!ELEMENT t2_node (t2_area*,t2_tag*)>\n");
478 sb_header.append(indent).append(TAG_ATTR1).append("t2_node x").append(TAG_ATTR2)
479 .append(indent).append(TAG_ATTR1).append("t2_node y").append(TAG_ATTR2)
480 .append(indent).append(TAG_ATTR1).append("t2_node lid").append(TAG_ATTR2)
481 .append(indent).append(TAG_ATTR1).append("t2_node c").append(TAG_ATTR2)
482 .append(indent).append(TAG_ATTR1).append("t2_node r NMTOKEN #IMPLIED>\n")
486 @Override
487 public void exportXML(final StringBuilder sb_body, final String indent, final XMLOptions options) {
488 final String type = "t2_" + getClass().getSimpleName().toLowerCase();
489 sb_body.append(indent).append("<").append(type).append('\n');
490 final String in = indent + "\t";
491 super.exportXML(sb_body, in, options);
492 final String[] RGB = Utils.getHexRGBColor(color);
493 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");
494 sb_body.append(indent).append(">\n");
495 super.restXML(sb_body, in, options);
496 if (null != root) exportXML(this, in, sb_body, root);
497 sb_body.append(indent).append("</").append(type).append(">\n");
500 /** One day, java will get tail-call optimization (i.e. no more stack overflow errors) and I will laugh at this function. */
501 private final void exportXML(final Tree<T> tree, final String indent_base, final StringBuilder sb, final Node<T> root) {
502 // Simulating recursion
504 // write depth-first, closing as children get written
505 final LinkedList<Node<T>> list = new LinkedList<Node<T>>();
506 list.add(root);
507 final Map<Node<T>,Integer> table = new HashMap<Node<T>,Integer>();
509 final StringBuilder indent = new StringBuilder(indent_base);
511 while (!list.isEmpty()) {
512 final Node<T> node = list.getLast();
513 if (null == node.children) {
514 // Processing end point
515 dataNodeXML(tree, indent, sb, node);
516 list.removeLast();
517 continue;
518 } else {
519 final Integer ii = table.get(node);
520 if (null == ii) {
521 // Never yet processed a child, add first
522 dataNodeXML(tree, indent, sb, node);
523 table.put(node, 0);
524 list.add(node.children[0]);
525 continue;
526 } else {
527 final int i = ii.intValue();
528 // Are there any more children to process?
529 if (i == node.children.length -1) {
530 // No more children to process
531 closeNodeXML(indent, sb);
532 list.removeLast();
533 table.remove(node);
534 continue;
535 } else {
536 // Process the next child
537 list.add(node.children[i+1]);
538 table.put(node, i+1);
545 private final void dataNodeXML(final Tree<T> tree, final StringBuilder indent, final StringBuilder sb, final Node<T> node) {
546 sb.append(indent)
547 .append("<t2_node x=\"").append(node.x)
548 .append("\" y=\"").append(node.y)
549 .append("\" lid=\"").append(node.la.getId()).append('\"');
551 if (null != node.parent) {
552 final byte conf = node.getConfidence();
553 if (Node.MAX_EDGE_CONFIDENCE != conf) sb.append(" c=\"").append(conf).append('\"');
555 if (null != node.color) {
556 sb.append(" color=\"");
557 Utils.asHexRGBColor(sb, node.color);
558 sb.append('\"');
561 tree.exportXMLNodeAttributes(indent, sb, node); // may not add anything
562 sb.append(">\n");
563 // ... so accumulated potentially extra chars are 3: \">\n
565 indent.append(' ');
566 final boolean data = tree.exportXMLNodeData(indent, sb, node);
567 if (data) {
568 if (null != node.tags) exportTags(node, sb, indent);
569 if (null == node.children) {
570 indent.setLength(indent.length() -1);
571 sb.append(indent).append("</t2_node>\n");
572 return;
574 } else if (null == node.children) {
575 if (null != node.tags) {
576 exportTags(node, sb, indent);
577 sb.append(indent).append("</t2_node>\n");
578 } else {
579 sb.setLength(sb.length() -3); // remove "\">\n"
580 sb.append("\" />\n");
582 } else if (null != node.tags) {
583 exportTags(node, sb, indent);
585 indent.setLength(indent.length() -1);
587 abstract protected boolean exportXMLNodeAttributes(StringBuilder indent, StringBuilder sb, Node<T> node);
588 abstract protected boolean exportXMLNodeData(StringBuilder indent, StringBuilder sb, Node<T> node);
590 private final void exportTags(final Node<T> node, final StringBuilder sb, final StringBuilder indent) {
591 for (final Tag tag : (Collection<Tag>) node.getTags()) {
592 sb.append(indent).append("<t2_tag name=\"").append(Displayable.getXMLSafeValue(tag.toString()))
593 .append("\" key=\"").append((char)tag.getKeyCode()).append("\" />\n");
597 static private final void closeNodeXML(final StringBuilder indent, final StringBuilder sb) {
598 sb.append(indent).append("</t2_node>\n");
601 /** @see generateSkeleton */
602 @Deprecated
603 public List<Point3f> generateTriangles(final double scale_, final int parallels, final int resample) {
604 return generateSkeleton(scale_, parallels, resample).verts;
607 /** @return a CustomLineMesh.PAIRWISE list for a LineMesh. */
608 public MeshData generateSkeleton(final double scale_, final int parallels, final int resample) {
609 if (null == root) return null;
610 final ArrayList<Point3f> list = new ArrayList<Point3f>();
611 final ArrayList<Color3f> colors = new ArrayList<Color3f>();
613 // Simulate recursion
614 final LinkedList<Node<T>> todo = new LinkedList<Node<T>>();
615 todo.add(root);
617 final float scale = (float)scale_;
618 final Calibration cal = layer_set.getCalibration();
619 final float pixelWidthScaled = (float) cal.pixelWidth * scale;
620 final float pixelHeightScaled = (float) cal.pixelHeight * scale;
621 final int sign = cal.pixelDepth < 0 ? -1 : 1;
622 final float[] fps = new float[2];
623 final Map<Node<T>,Point3f> points = new HashMap<Node<T>,Point3f>();
625 // A few performance tests are needed:
626 // 1 - if the map caching of points helps or recomputing every time is cheaper than lookup
627 // 2 - if removing no-longer-needed points from the map helps lookup or overall slows down
629 // The method, by the way, is very parallelizable: each is independent.
631 final HashMap<Color,Color3f> cached_colors = new HashMap<Color, Color3f>();
632 final Color3f cf = new Color3f(this.color);
633 cached_colors.put(this.color, cf);
635 boolean go = true;
636 while (go) {
637 final Node<T> node = todo.removeFirst();
638 // Add children to todo list if any
639 if (null != node.children) {
640 for (final Node<T> nd : node.children) todo.add(nd);
642 go = !todo.isEmpty();
643 // Get node's 3D coordinate
644 Point3f p = points.get(node);
645 if (null == p) {
646 fps[0] = node.x;
647 fps[1] = node.y;
648 this.at.transform(fps, 0, fps, 0, 1);
649 p = new Point3f(fps[0] * pixelWidthScaled,
650 fps[1] * pixelHeightScaled,
651 (float)node.la.getZ() * pixelWidthScaled * sign);
652 points.put(node, p);
654 if (null != node.parent) {
655 // Create a line to the parent
656 list.add(points.get(node.parent));
657 list.add(p);
658 if (null == node.color) {
659 colors.add(cf);
660 colors.add(cf); // twice: a line segment
661 } else {
662 Color3f c = cached_colors.get(node.color);
663 if (null == c) {
664 c = new Color3f(node.color);
665 cached_colors.put(node.color, c);
667 colors.add(c);
668 colors.add(c); // twice: a line segment
670 if (go && node.parent != todo.getFirst().parent) {
671 // node.parent point no longer needed (last child just processed)
672 points.remove(node.parent);
676 //Utils.log2("Skeleton MeshData lists of same length: " + (list.size() == colors.size()));
677 return new MeshData(list, colors);
680 @Override
681 final Class<?> getInternalDataPackageClass() {
682 return DPTree.class;
685 @Override
686 synchronized Object getDataPackage() {
687 return new DPTree(this);
690 private final class DPTree extends Displayable.DataPackage {
691 final Node<T> root;
692 DPTree(final Tree<T> t) {
693 super(t);
694 this.root = null == t.root ? null : t.root.clone(t.project);
696 @Override
697 final boolean to2(final Displayable d) {
698 super.to1(d);
699 final Tree<T> t = (Tree<T>)d;
700 if (null != this.root) {
701 t.root = this.root.clone(t.project);
702 t.clearCache();
703 t.cacheSubtree(t.root.getSubtreeNodes());
704 t.updateView();
706 return true;
710 /** Reroots at the point closest to the x,y,layer_id world coordinate.
711 * @return true on success. */
712 public boolean reRoot(float x, float y, final Layer layer, final double magnification) {
713 if (!this.at.isIdentity()) {
714 final Point2D.Double po = inverseTransformPoint(x, y);
715 x = (float)po.x;
716 y = (float)po.y;
718 synchronized (node_layer_map) {
719 // Search within the nodes in layer
720 Set<Node<T>> nodes = node_layer_map.get(layer);
721 if (null == nodes || nodes.isEmpty()) {
722 Utils.log("No node at " + x + ", " + y + ", " + layer);
723 return false;
725 nodes = null;
726 // Find a node near the coordinate
727 final Node<T> nd = findNode(x, y, layer, magnification);
728 if (null == nd) {
729 Utils.log("No node near " + x + ", " + y + ", " + layer);
730 return false;
733 return reRoot(nd);
737 /** @param nd A node of this Tree. */
738 public boolean reRoot(final Node<T> nd) {
739 if (null == nd) return false;
740 synchronized (node_layer_map) {
741 final Set<Node<T>> nodes = node_layer_map.get(nd.la);
742 if (null == nodes || !nodes.contains(nd)) return false;
743 end_nodes.add(this.root);
744 end_nodes.remove(nd);
745 nd.setRoot();
746 this.root = nd;
748 updateView();
749 return true;
752 /** Split the Tree into new Tree at the point closest to the x,y,layer world coordinate.
753 * @return null if no node was found near the x,y,layer point with precision dependent on magnification. */
754 public List<Tree<T>> splitNear(float x, float y, final Layer layer, final double magnification) {
755 try {
756 if (!this.at.isIdentity()) {
757 final Point2D.Double po = inverseTransformPoint(x, y);
758 x = (float)po.x;
759 y = (float)po.y;
761 synchronized (node_layer_map) {
762 // Search within the nodes in layer
763 Set<Node<T>> nodes = node_layer_map.get(layer);
764 if (null == nodes || nodes.isEmpty()) {
765 Utils.log("No nodes at " + x + ", " + y + ", " + layer);
766 return null;
768 nodes = null;
769 // Find a node near the coordinate
770 final Node<T> nd = findNode(x, y, layer, magnification);
771 if (null == nd) {
772 Utils.log("No node near " + x + ", " + y + ", " + layer + ", mag=" + magnification);
773 return null;
775 if (null == nd.parent) {
776 Utils.log("Cannot split at a root point!");
777 return null;
779 return splitAt(nd);
781 } catch (final Exception e) {
782 IJError.print(e);
784 return null;
787 /** @param nd A node of this Tree. */
788 public List<Tree<T>> splitAt(final Node<T> nd) {
789 if (null == nd) return null;
790 try {
791 ArrayList<Tree<T>> a;
792 synchronized (node_layer_map) {
793 // Sanity check:
794 final Set<Node<T>> nodes = node_layer_map.get(nd.la);
795 if (null == nodes || !nodes.contains(nd)) return null;
796 // Cache the children of 'nd'
797 final Collection<Node<T>> subtree_nodes = new ArrayList<Node<T>>(nd.getSubtreeNodes());
798 // Remove any review stacks for the nodes in the subtree
799 for (final Node<T> node : subtree_nodes) {
800 removeReview(node);
802 // Remove all children nodes of found node 'nd' from the Tree cache arrays:
803 removeNode(nd, subtree_nodes);
804 // Set the found node 'nd' as a new root: (was done by removeNode/Node.remove anyway)
805 nd.parent = null;
806 // With the found nd, now a root, create a new Tree
807 final Tree<T> t = newInstance();
808 t.addToDatabase();
809 t.root = nd;
810 // ... and fill its cache arrays
811 t.cacheSubtree(subtree_nodes); // includes nd itself
812 // Recompute bounds -- TODO: must translate the second properly, or apply the transforms and then recalculate bounding boxes and transforms.
813 t.calculateBoundingBox(null);
814 // Done!
815 a = new ArrayList<Tree<T>>();
816 a.add(this);
817 a.add(t);
819 this.calculateBoundingBox(null); // outside synch
820 return a;
821 } catch (final Exception e) {
822 IJError.print(e);
824 return null;
827 protected void cacheSubtree(final Iterable<Node<T>> nodes) {
828 cache(nodes, end_nodes, node_layer_map);
830 protected void clearCache() {
831 end_nodes.clear();
832 node_layer_map.clear();
833 setLastAdded(null);
834 setLastEdited(null);
835 setLastMarked(null);
836 setLastVisited(null);
839 /** Take @param nodes and add them to @param end_nodes and @param node_layer_map as appropriate. */
840 private final void cache(final Iterable<Node<T>> nodes, final Collection<Node<T>> end_nodes, final Map<Layer,Set<Node<T>>> node_layer_map) {
841 for (final Node<T> child : nodes) {
842 if (null == child.children) end_nodes.add(child);
843 Set<Node<T>> nds = node_layer_map.get(child.la);
844 if (null == nds) {
845 nds = new HashSet<Node<T>>();
846 node_layer_map.put(child.la, nds);
848 nds.add(child);
852 /** Update the internal {@link Node} cache; you want to invoke this operation
853 * after altering programmatically the {@link Layer} pointers of any of the
854 * {@link Node} of this {@link Tree}.
856 public void updateCache() {
857 synchronized (node_layer_map) {
858 clearCache();
859 if (null == root) return;
860 cacheSubtree(this.root.getSubtreeNodes());
864 /** Returns true if the given point falls within a certain distance of any of the treeline segments,
865 * where a segment is defined as the line between a clicked point and the next. */
866 @Override
867 public boolean contains(final Layer layer, final double x, final double y) {
868 if (null == root) return false;
869 final Display front = Display.getFront();
870 synchronized (node_layer_map) {
871 final Set<Node<T>> nodes = node_layer_map.get(layer);
872 if (null == nodes) return false;
873 float radius = 10;
874 if (null != front) {
875 final double mag = front.getCanvas().getMagnification();
876 radius = (float)(10 / mag);
877 if (radius < 2) radius = 2;
879 final Point2D.Double po = inverseTransformPoint(x, y);
880 return isAnyNear(nodes, (float)po.x, (float)po.y, radius * radius);
884 protected boolean isAnyNear(final Collection<Node<T>> nodes, final float lx, final float ly, final float radius) {
885 for (final Node<T> nd : nodes) {
886 if (nd.isNear(lx, ly, radius)) return true;
888 return false;
891 public Node<T> getRoot() {
892 return root;
895 protected Coordinate<Node<T>> createCoordinate(final Node<T> nd) {
896 if (null == nd) return null;
897 float x = nd.x;
898 float y = nd.y;
899 if (!this.at.isIdentity()) {
900 final float[] dps = new float[]{x, y};
901 this.at.transform(dps, 0, dps, 0, 1);
902 x = dps[0];
903 y = dps[1];
905 return new Coordinate<Node<T>>(x, y, nd.la, nd);
908 public Coordinate<Node<T>> findPreviousBranchOrRootPoint(final float x, final float y, final Layer layer, final DisplayCanvas dc) {
909 final Node<T> nd = findNodeNear(x, y, layer, dc);
910 if (null == nd) return null;
911 return createCoordinate(nd.findPreviousBranchOrRootPoint());
913 /** If the node found near x,y,layer is a branch point, returns it; otherwise the next down
914 * the chain; on reaching an end point, returns it. */
915 public Coordinate<Node<T>> findNextBranchOrEndPoint(final float x, final float y, final Layer layer, final DisplayCanvas dc) {
916 final Node<T> nd = findNodeNear(x, y, layer, dc);
917 if (null == nd) return null;
918 return createCoordinate(nd.findNextBranchOrEndPoint());
921 protected Coordinate<Node<T>> findNearAndGetNext(float x, float y, final Layer layer, final DisplayCanvas dc) {
922 Node<T> nd = findNodeNear(x, y, layer, dc);
923 if (null == nd) nd = last_visited;
924 if (null == nd) return null;
925 final int n_children = nd.getChildrenCount();
926 if (0 == n_children) return null;
927 if (1 == n_children) {
928 setLastVisited(nd.children[0]);
929 return createCoordinate(nd.children[0]);
931 // else, find the closest child edge
932 if (!this.at.isIdentity()) {
933 final Point2D.Double po = inverseTransformPoint(x, y);
934 x = (float)po.x;
935 y = (float)po.y;
937 nd = findNearestChildEdge(nd, x, y);
938 if (null != nd) setLastVisited(nd);
939 return createCoordinate(nd);
941 protected Coordinate<Node<T>> findNearAndGetPrevious(final float x, final float y, final Layer layer, final DisplayCanvas dc) {
942 Node<T> nd = findNodeNear(x, y, layer, dc);
943 if (null == nd) nd = last_visited;
944 if (null == nd || null == nd.parent) return null;
945 setLastVisited(nd.parent);
946 return createCoordinate(nd.parent);
949 public Coordinate<Node<T>> getLastEdited() {
950 return createCoordinate(last_edited);
952 public Coordinate<Node<T>> getLastAdded() {
953 return createCoordinate(last_added);
956 /** Find an edge near the world coords x,y,layer with precision depending upon magnification,
957 * and adjust its confidence to @param confidence.
958 * @return the node whose parent edge is altered, or null if none found. */
959 protected Node<T> setEdgeConfidence(final byte confidence) {
960 synchronized (node_layer_map) {
961 if (null == last_visited) return null;
962 last_visited.setConfidence(confidence);
963 updateViewData(last_visited);
964 return last_visited;
968 /** Expects world coordinates. */
969 protected Node<T> adjustEdgeConfidence(final int inc, final float x, final float y, final Layer layer, final DisplayCanvas dc) {
970 Node<T> nearest;
971 synchronized (node_layer_map) {
972 nearest = findNodeConfidenceBox(x, y, layer, dc.getMagnification());
973 if (null == nearest) nearest = findNodeNear(x, y, layer, dc, true);
974 if (null == nearest) return null;
975 if (!nearest.adjustConfidence(inc)) {
976 return null;
979 if (null != nearest) updateViewData(nearest);
980 return nearest;
983 /** Find the node whose confidence box for the parent edge is closest to x,y,layer, if any. */
984 private Node<T> findNodeConfidenceBox(float x, float y, final Layer layer, final double magnification) {
985 final Set<Node<T>> nodes = node_layer_map.get(layer);
986 if (null == nodes) return null;
988 final Point2D.Double po = inverseTransformPoint(x, y);
989 x = (float)po.x;
990 y = (float)po.y;
992 float radius = (float)(10 / magnification);
993 if (radius < 2) radius = 2;
994 radius *= radius; // squared
996 float min_sq_dist = Float.MAX_VALUE;
997 Node<T> nearest = null;
998 for (final Node<T> nd : nodes) {
999 if (null == nd.parent) continue;
1000 final float d = (float)(Math.pow((nd.parent.x + nd.x)/2 - x, 2) + Math.pow((nd.parent.y + nd.y)/2 - y, 2));
1001 if (d < min_sq_dist && d < radius) {
1002 min_sq_dist = d;
1003 nearest = nd;
1006 return nearest;
1009 /** Find a node in @param layer near the local coords lx,ly, with precision depending on magnification. */
1010 public Node<T> findNode(final float lx, final float ly, final Layer layer, final double magnification) {
1011 synchronized (node_layer_map) {
1012 return findClosestNode(node_layer_map.get(layer), lx, ly, magnification);
1016 /** Expects world coords; with precision depending on magnification. */
1017 public Node<T> findClosestNodeW(final float wx, final float wy, final Layer layer, final double magnification) {
1018 if (null == root) return null;
1019 synchronized (node_layer_map) {
1020 final Set<Node<T>> nodes = node_layer_map.get(layer);
1021 if (null == nodes) return null;
1022 return findClosestNodeW(nodes, wx, wy, magnification);
1026 /** Expects world coords; with precision depending on magnification. */
1027 public Node<T> findClosestNodeW(final Collection<Node<T>> nodes, final float wx, final float wy, final double magnification) {
1028 float lx = wx,
1029 ly = wy;
1030 if (!this.at.isIdentity()) {
1031 final Point2D.Double po = inverseTransformPoint(wx, wy);
1032 lx = (float)po.x;
1033 ly = (float)po.y;
1035 return findClosestNode(nodes, lx, ly, magnification);
1038 /** Also sets the last visited and the receiver node. This is a GUI method. */
1039 protected Layer toClosestPaintedNode(final Layer active_layer, final float wx, final float wy, final double magnification) {
1040 final Node<T> nd = findClosestNodeW(getNodesToPaint(active_layer), wx, wy, magnification);
1041 if (null != nd) {
1042 setLastVisited(nd);
1043 return nd.la;
1045 return null;
1048 /** Expects local coords; with precision depending on magnification. */
1049 public Node<T> findClosestNode(final Collection<Node<T>> nodes, final float lx, final float ly, final double magnification) {
1050 if (null == nodes || nodes.isEmpty()) return null;
1051 double d = (10.0D / magnification);
1052 if (d < 2) d = 2;
1053 float min_dist = Float.MAX_VALUE;
1054 Node<T> nd = null;
1055 for (final Node<T> node : nodes) {
1056 final float dist = Math.abs(node.x - lx) + Math.abs(node.y - ly);
1057 if (dist < min_dist) {
1058 min_dist = dist;
1059 nd = node;
1062 return min_dist < d ? nd : null;
1065 /** Find the spatially closest node, in calibrated coords; expects local coords. */
1066 public Node<T> findNearestNode(final float lx, final float ly, final Layer layer) {
1067 synchronized (node_layer_map) {
1068 final Set<Node<T>> nodes = node_layer_map.get(layer);
1069 if (null == nodes) return null;
1070 return findNearestNode(lx, ly, (float)layer.getZ(), layer.getParent().getCalibration(), nodes);
1074 private final Node<T> findNearestNode(final float lx, final float ly, final float lz, final Calibration cal, final Collection<Node<T>> nodes) {
1075 if (null == nodes) return null;
1076 // A distance map would help here
1077 final float pixelWidth = (float) cal.pixelWidth;
1078 final float pixelHeight = (float) cal.pixelHeight;
1079 Node<T> nearest = null;
1080 float sqdist = Float.MAX_VALUE;
1081 for (final Node<T> nd : nodes) {
1082 final float d = (float) (Math.pow(pixelWidth * (nd.x - lx), 2) + Math.pow(pixelHeight * (nd.y -ly), 2) + Math.pow(pixelWidth * (nd.la.getZ() - lz), 2));
1083 if (d < sqdist) {
1084 sqdist = d;
1085 nearest = nd;
1088 return nearest;
1091 /** Find the spatially closest node, in calibrated coords. */
1092 public Node<T> findNearestEndNode(final float lx, final float ly, final Layer layer) {
1093 synchronized (node_layer_map) {
1094 return findNearestNode(lx, ly, (float)layer.getZ(), layer.getParent().getCalibration(), end_nodes);
1098 public boolean insertNode(final Node<T> parent, final Node<T> child, final Node<T> in_between, final byte confidence) {
1099 synchronized (node_layer_map) {
1100 final byte b = parent.getConfidence(child);
1101 parent.remove(child);
1102 parent.add(in_between, b);
1103 in_between.add(child, confidence);
1104 // cache
1105 final Collection<Node<T>> subtree = in_between.getSubtreeNodes();
1106 cacheSubtree(subtree);
1107 // If child was in end_nodes, remains there
1109 setLastAdded(in_between);
1111 updateView();
1112 addToLinkLater(in_between);
1113 return true;
1116 /** Considering only the set of consecutive layers currently painted, find a point near an edge
1117 * with accuracy depending upon magnification.
1118 * @return null if none of the edges is close enough, or an array of parent and child describing the edge. */
1119 public Node<T>[] findNearestEdge(final float x_pl, final float y_pl, final Layer layer, final double magnification) {
1120 if (null == root) return null;
1121 // Don't traverse all, just look into nodes currently being painted according to layer_set.n_layers_color_cue
1122 final Collection<Node<T>> nodes = getNodesToPaint(layer);
1123 if (null == nodes) return null;
1125 double d = (10.0D / magnification);
1126 if (d < 2) d = 2;
1127 double min_dist = Double.MAX_VALUE;
1128 final Node<T>[] ns = new Node[2]; // parent and child
1130 for (final Node<T> node : nodes) {
1131 if (null == node.children) continue;
1132 // Examine if the point is closer to the 2D-projected edge than any other so far:
1133 // TODO it's missing edges with parents beyond the set of painted layers,
1134 // and it's doing edges to children beyond the set of painted layers.
1135 for (final Node<T> child : node.children) {
1136 final double dist = M.distancePointToSegment(x_pl, y_pl,
1137 node.x, node.y,
1138 child.x, child.y);
1139 if (dist < min_dist && dist < d) {
1140 min_dist = dist;
1141 ns[0] = node;
1142 ns[1] = child;
1146 if (null == ns[0]) return null;
1147 return ns;
1150 /** In projected 2D only, since that's the perspective of the user. */
1151 private Node<T> findNearestChildEdge(final Node<T> parent, final float lx, final float ly) {
1152 if (null == parent || null == parent.children) return null;
1154 Node<T> nd = null;
1155 double min_dist = Double.MAX_VALUE;
1157 for (final Node<T> child : parent.children) {
1158 final double dist = M.distancePointToSegment(lx, ly,
1159 parent.x, parent.y,
1160 child.x, child.y);
1161 if (dist < min_dist) {
1162 min_dist = dist;
1163 nd = child;
1166 return nd;
1169 /** Will call calculateBoundingBox and repaint. */
1170 public boolean addNode(final Node<T> parent, final Node<T> child, final byte confidence) {
1172 boolean added = false;
1173 Collection<Node<T>> subtree = null;
1174 synchronized (node_layer_map) {
1175 Set<Node<T>> nodes = node_layer_map.get(child.la);
1176 if (null == nodes) {
1177 nodes = new HashSet<Node<T>>();
1178 node_layer_map.put(child.la, nodes);
1180 if (nodes.add(child)) {
1181 if (null != parent) {
1182 if (!parent.hasChildren() && !end_nodes.remove(parent)) {
1183 Utils.log("WARNING: parent wasn't in end_nodes list!");
1185 parent.add(child, confidence);
1187 if (null == child.children && !end_nodes.add(child)) {
1188 Utils.log("WARNING: child was already in end_nodes list!");
1190 subtree = child.getSubtreeNodes();
1191 cacheSubtree(subtree);
1193 setLastAdded(child);
1195 added = true;
1197 } else if (0 == nodes.size()) {
1198 node_layer_map.remove(child.la);
1201 if (added) {
1202 repaint(true, child.la);
1203 updateView();
1205 if (null != subtree) {
1206 synchronized (tolink) {
1207 tolink.addAll(subtree);
1210 return true;
1212 return false;
1215 /** Remove a node only (not its subtree).
1216 * @return true on success. Will return false when the node has 2 or more children.
1217 * The new edge confidence is that of the parent to the @param node. */
1218 public boolean popNode(final Node<T> node) {
1219 switch (node.getChildrenCount()) {
1220 case 0:
1221 // End node:
1222 removeNode(node, null);
1223 return true;
1224 case 1:
1225 if (null == node.parent) {
1226 // Make its child the new root
1227 root = node.children[0];
1228 root.parent = null;
1229 root.confidence = Node.MAX_EDGE_CONFIDENCE; // with its now non-existent parent
1230 if (node == last_visited) setLastVisited(root);
1231 } else {
1232 node.parent.children[node.parent.indexOf(node)] = node.children[0];
1233 node.children[0].parent = node.parent;
1234 if (node == last_visited) setLastVisited(node.parent);
1236 synchronized (node_layer_map) {
1237 node_layer_map.get(node.la).remove(node);
1239 fireNodeRemoved(node);
1240 updateView();
1241 return true;
1242 default:
1243 return false;
1247 /** If the tree is a cyclic graph, it may destroy all. */
1248 public void removeNode(final Node<T> node) {
1249 removeNode(node, node.getSubtreeNodes());
1252 private void removeNode(final Node<T> node, final Collection<Node<T>> subtree_nodes) {
1253 synchronized (node_layer_map) {
1254 if (null == node.parent) {
1255 root = null;
1256 clearCache();
1257 } else {
1258 // if not an end-point, update cached lists
1259 if (null != node.children) {
1260 Utils.log2("Removing children of node " + node);
1261 for (final Node<T> nd : subtree_nodes) { // includes the node itself
1262 node_layer_map.get(nd.la).remove(nd);
1263 if (null == nd.children && !end_nodes.remove(nd)) {
1264 Utils.log2("WARNING: node to remove doesn't have any children but wasn't in end_nodes list!");
1267 } else {
1268 Utils.log2("Just removing node " + node);
1269 end_nodes.remove(node);
1270 node_layer_map.get(node.la).remove(node);
1272 if (1 == node.parent.getChildrenCount()) {
1273 end_nodes.add(node.parent);
1275 // Update receiver:
1276 setLastVisited(node.parent);
1277 // Finally, remove from parent node
1278 node.parent.remove(node);
1280 fireNodeRemoved(node);
1282 synchronized (tolink) {
1283 if (null != subtree_nodes) {
1284 tolink.removeAll(subtree_nodes);
1285 } else tolink.remove(node);
1287 updateView();
1290 /** Check if it is possible to join all given Trees into this,
1291 * by using the first one as the receiver (which should be this),
1292 * and all the others as the ones to be merged into the receiver.
1293 * Requires each Tree to have a non-null marked Node; otherwise, returns false. */
1294 public boolean canJoin(final List<? extends Tree<T>> ts) {
1295 if (null == marked) {
1296 Utils.log("No marked node in to-be parent Tree " + this);
1297 return false;
1299 if (null == this.root) {
1300 Utils.log("The root of this tree is null!");
1301 return false;
1303 if (1 == ts.size()) {
1304 Utils.log("No other trees to join!");
1305 return false;
1307 for (final Tree<T> tl : ts) {
1308 if (this == tl) continue;
1309 if (null == tl.root) {
1310 Utils.log("Can't join: tree #" + tl.id + " does not have any nodes!");
1311 return false;
1313 if (getClass() != tl.getClass()) {
1314 Utils.log("For joining, all trees must be of the same kind!");
1315 return false;
1317 if (null == tl.marked) {
1318 Utils.log("No marked node in to-be child treeline " + tl);
1319 return false;
1322 return true;
1325 /* Requires each Tree to have a non-null marked Node; otherwise, returns false. */
1326 public boolean join(final List<? extends Tree<T>> ts) {
1327 if (!canJoin(ts)) return false;
1328 // Preconditions passed: all Tree in ts have a marked node and are of the same kind
1329 for (final Tree<T> tl : ts) {
1330 if (this == tl) continue;
1331 tl.marked.setRoot();
1332 // transform nodes from there to here
1333 final AffineTransform at_inv;
1334 try {
1335 at_inv = this.at.createInverse();
1336 } catch (final NoninvertibleTransformException nite) {
1337 IJError.print(nite);
1338 return false;
1340 final AffineTransform aff = new AffineTransform(tl.at); // 1 - to world coords
1341 aff.preConcatenate(at_inv); // 2 - to this local coords
1342 final float[] fps = new float[2];
1343 for (final Node<T> nd : tl.marked.getSubtreeNodes()) {
1344 fps[0] = nd.x;
1345 fps[1] = nd.y;
1346 aff.transform(fps, 0, fps, 0, 1);
1347 nd.x = fps[0];
1348 nd.y = fps[1];
1349 nd.transformData(aff);
1350 // Remove review stack if any
1351 removeReview(nd);
1353 addNode(this.marked, tl.marked, Node.MAX_EDGE_CONFIDENCE); // will calculateBoundingBox, hence at_inv has to be recomputed every time
1354 // Remove from tl pointers
1355 tl.root = null; // stolen!
1356 tl.setLastMarked(null);
1357 // Remove from tl cache
1358 synchronized (tl.node_layer_map) {
1359 tl.node_layer_map.clear();
1361 tl.end_nodes.clear();
1364 // Don't clear this.marked
1366 updateView();
1368 return true;
1371 protected Node<T> findNodeNear(final float x, final float y, final Layer layer, final DisplayCanvas dc) {
1372 return findNodeNear(x, y, layer, dc, false);
1375 /** Expects world coordinates. If no node is near x,y but there is only one node in the current Display view of the layer, then it returns that node. */
1376 protected Node<T> findNodeNear(float x, float y, final Layer layer, final DisplayCanvas dc, final boolean use_receiver_when_null) {
1377 if (!this.at.isIdentity()) {
1378 final Point2D.Double po = inverseTransformPoint(x, y);
1379 x = (float)po.x;
1380 y = (float)po.y;
1382 synchronized (node_layer_map) {
1383 // Search within the nodes in layer
1384 final Set<Node<T>> nodes = node_layer_map.get(layer);
1385 if (null == nodes || nodes.isEmpty()) {
1386 Utils.log("No nodes at " + x + ", " + y + ", " + layer);
1387 return null;
1389 // Find a node near the coordinate
1390 Node<T> nd = findNode(x, y, layer, dc.getMagnification());
1391 // If that fails, try any node show all by itself in the display:
1393 if (null == nd) {
1394 // Is there only one node within the srcRect?
1395 final Area a;
1396 try {
1397 a = new Area(dc.getSrcRect()).createTransformedArea(this.at.createInverse());
1398 } catch (final NoninvertibleTransformException nite) {
1399 IJError.print(nite);
1400 return null;
1402 int count = 0;
1403 for (final Node<T> node : nodes) {
1404 if (node.intersects(a)) {
1405 nd = node;
1406 count++;
1407 if (count > 1) {
1408 nd = null;
1409 break;
1415 final Node<T> receiver = last_visited;
1416 if (null == nd && use_receiver_when_null && null != receiver && receiver.la == layer) {
1417 final float[] f = new float[]{receiver.x, receiver.y};
1418 at.transform(f, 0, f, 0, 1);
1419 if (dc.getSrcRect().contains((int)f[0], (int)f[1])) {
1420 nd = receiver;
1425 if (null == nd) {
1426 Utils.log("No node near " + x + ", " + y + ", " + layer + ", mag=" + magnification);
1427 return null;
1430 return nd;
1434 public boolean markNear(float x, float y, final Layer layer, final double magnification) {
1435 if (!this.at.isIdentity()) {
1436 final Point2D.Double po = inverseTransformPoint(x, y);
1437 x = (float)po.x;
1438 y = (float)po.y;
1440 synchronized (node_layer_map) {
1441 // Search within the nodes in layer
1442 Set<Node<T>> nodes = node_layer_map.get(layer);
1443 if (null == nodes || nodes.isEmpty()) {
1444 Utils.log("No nodes at " + x + ", " + y + ", " + layer);
1445 return false;
1447 nodes = null;
1448 // Find a node near the coordinate
1449 final Node<T> found = findNode(x, y, layer, magnification);
1450 if (null == found) {
1451 Utils.log("No node near " + x + ", " + y + ", " + layer + ", mag=" + magnification);
1452 return false;
1454 setLastMarked(found);
1455 return true;
1458 public boolean unmark() {
1459 if (null != marked) {
1460 setLastMarked(null);
1461 return true;
1463 return false;
1466 /** The node currently being dragged or edited in some way. */
1467 protected void setActive(final Node<T> nd) {
1468 this.active = nd;
1470 /** The node currently being dragged or edited in some way. */
1471 protected Node<T> getActive() { return active; }
1473 protected void setLastEdited(final Node<T> nd) {
1474 this.last_edited = nd;
1475 setLastVisited(nd);
1477 protected void setLastAdded(final Node<T> nd) {
1478 this.last_added = nd;
1479 setLastVisited(nd);
1481 public void setLastMarked(final Node<T> nd) {
1482 this.marked = nd;
1483 setLastVisited(nd);
1485 /** The node that paints in green, which is the receiver of events. */
1486 protected void setLastVisited(final Node<T> nd) {
1487 this.last_visited = nd;
1490 public Node<T> getMarked() {
1491 return marked;
1494 public Node<T> getLastVisited() {
1495 return last_visited;
1498 @Override
1499 public void deselect() {
1500 setLastVisited(null);
1503 protected void fireNodeRemoved(final Node<T> nd) {
1504 if (nd == marked) marked = null;
1505 if (nd == last_added) last_added = null;
1506 if (nd == last_edited) last_edited = null;
1507 if (nd == last_visited) {
1508 if (null != nd.parent) last_visited = nd.parent;
1509 else if (nd.getChildrenCount() > 0) last_visited = nd.children[0];
1510 else last_visited = null;
1512 removeFromLinkLater(nd);
1513 removeReview(nd);
1516 protected void clearState() {
1517 // clear:
1518 marked = last_added = last_edited = last_visited = null;
1521 /** The Node double-clicked on, for join operations. */
1522 private Node<T> marked = null;
1523 /** The Node clicked on, for mouse operations. */
1524 private Node<T> active = null;
1525 /** The last added node */
1526 private Node<T> last_added = null;
1527 /** The last edited node, which will be the last added as well until some other node is edited. */
1528 private Node<T> last_edited = null;
1529 /** The last visited node, either navigating or editing.
1530 * It's the only node that can receive new children by clicking*/
1531 private Node<T> last_visited = null;
1533 // TODO: last_visited and receiver overlap TOTALLY
1535 static private Polygon MARKED_PARENT, MARKED_CHILD;
1537 static private final void createMarks() {
1538 MARKED_PARENT = new Polygon(new int[]{0, -1, -2, -4, -18, -18, -4, -2, -1},
1539 new int[]{0, -2, -3, -4, -4, 4, 4, 3, 2}, 9);
1540 MARKED_CHILD = new Polygon(new int[]{0, 10, 12, 12, 22, 22, 12, 12, 10},
1541 new int[]{0, 10, 10, 4, 4, -4, -4, -10, -10}, 9);
1544 @Override
1545 public void mousePressed(final MouseEvent me, final Layer layer, final int x_p, final int y_p, final double mag) {
1546 if (ProjectToolbar.PEN != ProjectToolbar.getToolId()) {
1547 return;
1550 if (null != root) {
1551 // transform the x_p, y_p to the local coordinates
1552 int x_pl = x_p;
1553 int y_pl = y_p;
1554 if (!this.at.isIdentity()) {
1555 final Point2D.Double po = inverseTransformPoint(x_p, y_p);
1556 x_pl = (int)po.x;
1557 y_pl = (int)po.y;
1560 Node<T> found = findNode(x_pl, y_pl, layer, mag);
1561 setActive(found);
1563 if (null != found) {
1564 if (2 == me.getClickCount()) {
1565 setLastMarked(found);
1566 setActive(null);
1567 return;
1569 if (me.isShiftDown() && Utils.isControlDown(me)) {
1570 if (me.isAltDown()) {
1571 // Remove point and its subtree
1572 removeNode(found);
1573 } else {
1574 // Just remove the slab point, joining parent with child
1575 if (!popNode(found)) {
1576 Utils.log("Can't pop out branch point!\nUse shift+control+alt+click to remove a branch point and its subtree.");
1577 setActive(null);
1578 return;
1581 repaint(false, layer); // keep larger size for repainting, will call calculateBoundingBox on mouseRelesed
1582 setActive(null);
1583 return;
1585 } else {
1586 if (2 == me.getClickCount()) {
1587 setLastMarked(null);
1588 return;
1590 if (me.isAltDown()) {
1591 return;
1593 // Add new point
1594 if (me.isShiftDown()) {
1595 final Node<T>[] ns = findNearestEdge(x_pl, y_pl, layer, mag);
1596 if (null != ns) {
1597 found = createNewNode(x_pl, y_pl, layer, ns[0]);
1598 insertNode(ns[0], ns[1], found, ns[0].getConfidence(ns[1]));
1599 setActive(found);
1601 } else {
1602 final Node<T> nearest = last_visited;
1603 if (null == nearest) {
1604 Utils.showMessage("Before adding a new node, please activate an existing node\nby clicking on it, or pushing 'g' on it.");
1605 return;
1607 // Find the point closest to any other starting or ending point in all branches
1608 //Node<T> nearest = findNearestEndNode(x_pl, y_pl, layer); // at least the root exists, so it has to find a node, any node
1609 // append new child; inherits radius from parent
1610 found = createNewNode(x_pl, y_pl, layer, nearest);
1611 addNode(nearest, found, Node.MAX_EDGE_CONFIDENCE);
1612 setActive(found);
1613 repaint(true, layer);
1615 return;
1617 } else {
1618 // First point
1619 root = createNewNode(x_p, y_p, layer, null); // world coords, so calculateBoundingBox will do the right thing
1620 addNode(null, root, (byte)0);
1621 setActive(root);
1625 @Override
1626 public void mouseDragged(final MouseEvent me, final Layer la, final int x_p, final int y_p, final int x_d, final int y_d, final int x_d_old, final int y_d_old) {
1627 translateActive(me, la, x_d, y_d, x_d_old, y_d_old);
1630 @Override
1631 public void mouseReleased(final MouseEvent me, final Layer la, final int x_p, final int y_p, final int x_d, final int y_d, final int x_r, final int y_r) {
1632 final int tool = ProjectToolbar.getToolId();
1634 translateActive(me, la, x_r, y_d, x_d, y_d);
1636 if (ProjectToolbar.PEN == tool || ProjectToolbar.PENCIL == tool) {
1637 repaint(true, la); //needed at least for the removePoint
1640 updateViewData(active);
1642 setLastVisited(active);
1643 setActive(null);
1646 private final void translateActive(final MouseEvent me, final Layer la, int x_d, int y_d, int x_d_old, int y_d_old) {
1647 if (null == active || me.isAltDown() || Utils.isControlDown(me)) return;
1648 // shiftDown is ok: when dragging a newly branched node.
1650 // transform to the local coordinates
1651 if (!this.at.isIdentity()) {
1652 final Point2D.Double pd = inverseTransformPoint(x_d, y_d);
1653 x_d = (int)pd.x;
1654 y_d = (int)pd.y;
1655 final Point2D.Double pdo = inverseTransformPoint(x_d_old, y_d_old);
1656 x_d_old = (int)pdo.x;
1657 y_d_old = (int)pdo.y;
1660 active.translate(x_d - x_d_old, y_d - y_d_old);
1661 repaint(false, la);
1662 setLastEdited(active);
1665 static private Node<?> to_tag = null;
1666 static private Node<?> to_untag = null;
1667 static private boolean show_tag_dialogs = false;
1669 protected boolean isTagging() { return null != to_tag || null != to_untag; }
1671 @Override
1672 public void keyPressed(final KeyEvent ke) {
1674 switch (ProjectToolbar.getToolId()) {
1675 case ProjectToolbar.PEN:
1676 case ProjectToolbar.BRUSH:
1677 break;
1678 default:
1679 // Reject
1680 return;
1683 final Object source = ke.getSource();
1684 if (! (source instanceof DisplayCanvas)) return;
1686 final int keyCode = ke.getKeyCode();
1687 final DisplayCanvas dc = (DisplayCanvas)source;
1689 if (null != to_tag || null != to_untag) {
1690 // Can only add a tag for A-Z or numbers!
1691 if (! (Character.isLetterOrDigit((char)keyCode) && (Character.isDigit((char)keyCode) || Character.isUpperCase((char)keyCode)))) { // VK_F1, F2 ... are lower case letters! Evil!
1692 // Cancel tagging
1693 Utils.showStatus("Canceled tagging");
1694 to_tag = null;
1695 to_untag = null;
1696 ke.consume();
1697 return;
1699 if (!show_tag_dialogs && KeyEvent.VK_0 == keyCode) {
1700 // force dialogs for next key
1701 show_tag_dialogs = true;
1702 ke.consume();
1703 return;
1706 final boolean untag = null != to_untag;
1707 final Node<?> target = untag ? to_untag : to_tag;
1709 try {
1711 layer_set.addPreDataEditStep(this);
1713 if (show_tag_dialogs) {
1714 if (untag) {
1715 if (layer_set.askToRemoveTag(keyCode)) { // if removed from tag namespace, it will be removed from all nodes that have it
1716 layer_set.addDataEditStep(this);
1718 } else {
1719 final Tag t = layer_set.askForNewTag(keyCode);
1720 if (null != t) {
1721 target.addTag(t);
1722 Display.repaint(layer_set);
1723 layer_set.addDataEditStep(this); // no 'with' macros ... without half a dozen layers of cruft.
1726 show_tag_dialogs = false;
1727 return;
1730 TreeSet<Tag> ts = layer_set.getTags(keyCode);
1731 if (ts.isEmpty()) {
1732 if (untag) return;
1733 if (null == layer_set.askForNewTag(keyCode)) return;
1734 ts = layer_set.getTags(keyCode);
1736 // Ask to chose one, if more than one
1737 final Set<Tag> target_tags = target.getTags();
1738 if (untag && (null == target_tags || target_tags.isEmpty())) {
1739 // nothing to untag
1740 return;
1742 if (ts.size() > 1) {
1743 // if the target_tags has only one tag for the given keycode, just remove it
1744 if (untag && null != target_tags) {
1745 int count = 0;
1746 Tag t = null;
1747 for (final Tag tag : target_tags) {
1748 if (tag.getKeyCode() == keyCode) {
1749 count++;
1750 t = tag;
1753 if (1 == count) {
1754 // just remove it
1755 target.removeTag(t);
1756 Display.repaint(layer_set);
1757 return;
1760 final JPopupMenu popup = new JPopupMenu();
1761 popup.add(new JLabel(untag ? "Untag:" : "Tag:"));
1762 int i = 1;
1763 for (final Tag tag : ts) {
1764 final JMenuItem item = new JMenuItem(tag.toString());
1765 popup.add(item);
1766 if (i < 10) {
1767 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0 + i, 0, true));
1769 i++;
1770 if (null != target_tags) {
1771 if (untag) item.setEnabled(target_tags.contains(tag));
1772 else item.setEnabled(!target_tags.contains(tag));
1774 item.addActionListener(new ActionListener() {
1775 @Override
1776 public void actionPerformed(final ActionEvent ae) {
1777 if (untag) target.removeTag(tag);
1778 else target.addTag(tag);
1779 Display.repaint(layer_set);
1780 layer_set.addDataEditStep(Tree.this);
1781 updateViewData(target);
1785 popup.addSeparator();
1786 final JMenuItem item = new JMenuItem(untag ? "Remove tag..." : "Define new tag...");
1787 popup.add(item);
1788 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, 0, true));
1789 item.addActionListener(new ActionListener() {
1790 @Override
1791 public void actionPerformed(final ActionEvent ae) {
1792 if (untag) {
1793 layer_set.askToRemoveTag(keyCode);
1794 } else {
1795 final Tag t = layer_set.askForNewTag(keyCode);
1796 if (null == t) return;
1797 target.addTag(t);
1798 Display.repaint(layer_set);
1800 layer_set.addDataEditStep(Tree.this);
1801 updateViewData(target);
1805 // Show the popup on the Display, under the node
1806 final float[] fp = new float[]{target.x, target.y};
1807 this.at.transform(fp, 0, fp, 0, 1);
1808 final Rectangle srcRect = dc.getSrcRect();
1809 final double magnification = dc.getMagnification();
1810 final int x = (int)((fp[0] - srcRect.x) * magnification);
1811 final int y = (int)((fp[1] - srcRect.y) * magnification);
1812 popup.show(dc, x, y);
1813 } else {
1814 if (untag) target.removeTag(ts.first());
1815 else target.addTag(ts.first());
1816 Display.repaint(layer_set);
1817 layer_set.addDataEditStep(this);
1819 return;
1820 } finally {
1821 updateViewData(untag ? to_untag : to_tag);
1822 to_tag = null;
1823 to_untag = null;
1824 ke.consume();
1828 final Point po = dc.getCursorLoc(); // as offscreen coords
1830 // Set confidence of the receiver node
1831 if (keyCode >= KeyEvent.VK_0 && keyCode <= (KeyEvent.VK_0 + Node.MAX_EDGE_CONFIDENCE)) {
1832 if (null != setEdgeConfidence((byte)(keyCode - KeyEvent.VK_0))) {
1833 Display.repaint(layer_set);
1834 ke.consume();
1836 return;
1839 final int modifiers = ke.getModifiers();
1840 final Display display = Display.getFront();
1841 final Layer layer = display.getLayer();
1843 Node<T> nd = null;
1844 Coordinate<Node<T>> c = null;
1845 try {
1847 switch (keyCode) {
1848 case KeyEvent.VK_T:
1849 if (0 == modifiers) {
1850 to_tag = findNodeNear(po.x, po.y, layer, dc, true);
1851 } else if (0 == (modifiers ^ KeyEvent.SHIFT_MASK)) {
1852 to_untag = findNodeNear(po.x, po.y, layer, dc, true);
1854 ke.consume();
1855 return;
1858 if (0 == modifiers) {
1859 switch (keyCode) {
1860 case KeyEvent.VK_R:
1861 nd = root;
1862 display.center(createCoordinate(root));
1863 ke.consume();
1864 return;
1865 case KeyEvent.VK_B:
1866 c = findPreviousBranchOrRootPoint(po.x, po.y, layer, dc);
1867 if (null == c) return;
1868 nd = c.object;
1869 display.center(c);
1870 ke.consume();
1871 return;
1872 case KeyEvent.VK_N:
1873 c = findNextBranchOrEndPoint(po.x, po.y, layer, dc);
1874 if (null == c) return;
1875 nd = c.object;
1876 display.center(c);
1877 ke.consume();
1878 return;
1879 case KeyEvent.VK_L:
1880 c = getLastAdded();
1881 if (null == c) return;
1882 nd = c.object;
1883 display.center(c);
1884 ke.consume();
1885 return;
1886 case KeyEvent.VK_E:
1887 c = getLastEdited();
1888 if (null == c) return;
1889 nd = c.object;
1890 display.center(c);
1891 ke.consume();
1892 return;
1893 case KeyEvent.VK_CLOSE_BRACKET:
1894 display.animateBrowsingTo(findNearAndGetNext(po.x, po.y, layer, dc));
1895 ke.consume();
1896 return;
1897 case KeyEvent.VK_OPEN_BRACKET:
1898 display.animateBrowsingTo(findNearAndGetPrevious(po.x, po.y, layer, dc));
1899 ke.consume();
1900 return;
1901 case KeyEvent.VK_G:
1902 nd = findClosestNodeW(getNodesToPaint(layer), po.x, po.y, dc.getMagnification());
1903 if (null != nd) {
1904 display.toLayer(nd.la);
1905 if (nd != last_visited) {
1906 setLastVisited(nd);
1907 display.getCanvas().repaint(false);
1909 ke.consume();
1910 return;
1912 break;
1915 if (ProjectToolbar.PEN == ProjectToolbar.getToolId() && 0 == (modifiers ^ Event.SHIFT_MASK) && KeyEvent.VK_C == keyCode) {
1916 nd = findClosestNodeW(getNodesToPaint(layer), po.x, po.y, dc.getMagnification());
1917 if (null == nd) {
1918 final Node<T> last = getLastVisited();
1919 if (null != last && layer == last.getLayer()) nd = last;
1921 if (null != nd && adjustNodeColors(nd)) {
1922 ke.consume();
1923 return;
1926 } finally {
1927 if (null != nd) setLastVisited(nd);
1931 protected boolean adjustNodeColors(final Node<T> nd) {
1932 final Color color = null == nd.color ? this.color : nd.color;
1933 final GenericDialog gd = new GenericDialog("Node colors");
1934 gd.addSlider("Red: ", 0, 255, color.getRed());
1935 gd.addSlider("Green: ", 0, 255, color.getGreen());
1936 gd.addSlider("Blue: ", 0, 255, color.getBlue());
1937 final Scrollbar red = (Scrollbar)gd.getSliders().get(0);
1938 final Scrollbar green = (Scrollbar)gd.getSliders().get(1);
1939 final Scrollbar blue = (Scrollbar)gd.getSliders().get(2);
1940 final Color original = nd.color; // may be null
1941 final SliderListener slc = new SliderListener() {
1942 @Override
1943 public void update() {
1944 nd.setColor(new Color(red.getValue(), green.getValue(), blue.getValue()));
1945 Display.repaint();
1948 red.addAdjustmentListener(slc);
1949 green.addAdjustmentListener(slc);
1950 blue.addAdjustmentListener(slc);
1951 final String[] choices = {"this node only", "nodes until next branch or end node", "entire subtree"};
1952 gd.addChoice("Apply to:", choices, choices[0]);
1953 gd.showDialog();
1954 if (gd.wasCanceled()) {
1955 nd.setColor(original);
1956 Display.repaint();
1957 return false;
1959 try {
1960 layer_set.addDataEditStep(this);
1961 final Color c = new Color(red.getValue(), green.getValue(), blue.getValue());
1962 switch (gd.getNextChoiceIndex()) {
1963 case 0:
1964 // this node only: already done
1965 return true;
1966 case 1:
1967 // the downstream slab:
1968 for (final Node<T> node : new Node.NodeCollection<T>(nd, Node.SlabIterator.class)) {
1969 node.setColor(c);
1971 return true;
1972 case 2:
1973 // the entire subtree:
1974 for (final Node<T> node: new Node.NodeCollection<T>(nd, Node.BreadthFirstSubtreeIterator.class)) {
1975 node.setColor(c);
1977 return true;
1978 default:
1979 layer_set.removeLastUndoStep();
1981 } finally {
1982 layer_set.addDataEditStep(this);
1983 Display.repaint();
1985 return true;
1988 @Override
1989 public void mouseWheelMoved(final MouseWheelEvent mwe) {
1990 final int modifiers = mwe.getModifiers();
1991 if (0 == (MouseWheelEvent.SHIFT_MASK ^ modifiers)) {
1992 final Object source = mwe.getSource();
1993 if (! (source instanceof DisplayCanvas)) return;
1994 final DisplayCanvas dc = (DisplayCanvas)source;
1995 final Layer la = dc.getDisplay().getLayer();
1996 final int rotation = mwe.getWheelRotation();
1997 final double magnification = dc.getMagnification();
1998 final Rectangle srcRect = dc.getSrcRect();
1999 final float x = (float)((mwe.getX() / magnification) + srcRect.x);
2000 final float y = (float)((mwe.getY() / magnification) + srcRect.y);
2002 adjustEdgeConfidence(rotation > 0 ? 1 : -1, x, y, la, dc);
2003 Display.repaint(this);
2004 mwe.consume();
2008 /** Used when reconstructing from XML. */
2009 public void setRoot(final Node<T> new_root) {
2010 this.root = new_root;
2011 synchronized (node_layer_map) {
2012 if (null == new_root) clearCache();
2013 else cacheSubtree(new_root.getSubtreeNodes());
2017 @Override
2018 public void paintSnapshot(final Graphics2D g, final Layer layer, final List<Layer> layers, final Rectangle srcRect, final double mag) {
2019 switch (layer_set.getSnapshotsMode()) {
2020 case 0:
2021 // Paint without arrows
2022 paint(g, srcRect, mag, false, 0xffffffff, layer, layers, false, false);
2023 return;
2024 case 1:
2025 paintAsBox(g);
2026 return;
2027 default: return; // case 2: // disabled, no paint
2031 public Set<Node<T>> getEndNodes() {
2032 return new HashSet<Node<T>>(end_nodes);
2035 /** Fly-through image stack from source node to mark node.
2036 * @param type is ImagePlus.GRAY8 or .COLOR_RGB */
2037 public ImagePlus flyThroughMarked(final int width, final int height, final double magnification, final int type, final String dir) {
2038 if (null == marked) return null;
2039 return flyThrough(root, marked, width, height, magnification, type, dir);
2042 public LinkedList<Region<Node<T>>> generateRegions(final Node<T> first, final Node<T> last, final int width, final int height, final double magnification) {
2043 final LinkedList<Region<Node<T>>> regions = new LinkedList<Region<Node<T>>>();
2044 Node<T> node = last;
2045 final float[] fps = new float[2];
2046 while (null != node) {
2047 fps[0] = node.x;
2048 fps[1] = node.y;
2049 this.at.transform(fps, 0, fps, 0, 1);
2050 regions.addFirst(new Region<Node<T>>(new Rectangle((int)fps[0] - width/2,
2051 (int)fps[1] - height/2,
2052 width, height),
2053 node.la,
2054 node));
2055 if (first == node) break;
2056 node = node.parent;
2058 return regions;
2061 /** Fly-through image stack from first to last node. If first is not lower order than last, then start to last is returned.
2062 * @param type is ImagePlus.GRAY8 or .COLOR_RGB */
2063 public ImagePlus flyThrough(final Node<T> first, final Node<T> last, final int width, final int height, final double magnification, final int type, final String dir) {
2064 return project.getLoader().createFlyThrough(generateRegions(first, last, width, height, magnification), magnification, type, dir);
2067 /** Measures number of branch points and end points, and total cable length.
2068 * Cable length is measured as:
2069 * Cable length: the sum of all distances between all consecutive pairs of nodes.
2070 * Lower-bound cable length: the sum of all distances between all end points to branch points, branch points to other branch points, and first branch point to root. */
2071 @Override
2072 public ResultsTable measure(ResultsTable rt) {
2073 if (null == root) return rt;
2074 double cable = 0,
2075 lb_cable = 0;
2076 int branch_points = 0;
2077 final Calibration cal = layer_set.getCalibration();
2078 final double pixelWidth = cal.pixelWidth;
2079 final double pixelHeight = cal.pixelHeight;
2081 final float[] fps = new float[4];
2082 final float[] fpp = new float[2];
2084 synchronized (node_layer_map) {
2085 for (final Collection<Node<T>> nodes : node_layer_map.values()) {
2086 for (final Node<T> nd : nodes) {
2087 if (nd.getChildrenCount() > 1) branch_points++;
2088 // Skip the root node
2089 if (null == nd.parent) continue;
2091 fps[0] = nd.x; fps[2] = nd.parent.x;
2092 fps[1] = nd.y; fps[3] = nd.parent.y;
2093 this.at.transform(fps, 0, fps, 0, 2);
2094 cable += Math.sqrt(Math.pow( (fps[0] - fps[2]) * pixelWidth, 2)
2095 + Math.pow( (fps[1] - fps[3]) * pixelHeight, 2)
2096 + Math.pow( (nd.la.getZ() - nd.parent.la.getZ()) * pixelWidth, 2));
2098 // Lower bound cable length:
2099 if (1 == nd.getChildrenCount()) continue; // include only end nodes and branch nodes
2100 else {
2101 final Node<T> prev = nd.findPreviousBranchOrRootPoint();
2102 if (null == prev) {
2103 Utils.log("ERROR: Can't find the previous branch or root point for node " + nd);
2104 continue;
2106 fpp[0] = prev.x;
2107 fpp[1] = prev.y;
2108 this.at.transform(fpp, 0, fpp, 0, 1);
2109 lb_cable += Math.sqrt(Math.pow( (fpp[0] - fps[0]) * pixelWidth, 2)
2110 + Math.pow( (fpp[1] - fps[1]) * pixelHeight, 2)
2111 + Math.pow( (nd.la.getZ() - nd.parent.la.getZ()) * pixelWidth, 2));
2117 if (null == rt) rt = Utils.createResultsTable("Tree results", new String[]{"id", "N branch points", "N end points", "Cable length", "LB Cable length"});
2118 rt.incrementCounter();
2119 rt.addLabel("units", cal.getUnit());
2120 rt.addValue(0, this.id);
2121 rt.addValue(1, branch_points);
2122 rt.addValue(2, end_nodes.size());
2123 rt.addValue(3, cable);
2124 rt.addValue(4, lb_cable);
2126 return rt;
2129 /** Expects Rectangle in world coords. */
2130 @Override
2131 public boolean intersects(final Layer layer, final Rectangle r) {
2132 final Set<Node<T>> nodes = node_layer_map.get(layer);
2133 if (null == nodes || nodes.isEmpty()) return false;
2134 try {
2135 return null != findFirstIntersectingNode(nodes, new Area(r).createTransformedArea(this.at.createInverse()));
2136 } catch (final NoninvertibleTransformException e) {
2137 IJError.print(e);
2139 return false;
2141 /** Expects Area in world coords. */
2142 @Override
2143 public boolean intersects(final Layer layer, final Area area) {
2144 return null != firstIntersectingNode(layer, area);
2146 /** Expects Area in world coords.
2147 * @return The first {@link Node} that intersects the {@param area} at the given {@param layer}, or null if none do. */
2148 public Node<T> firstIntersectingNode(final Layer layer, final Area area) {
2149 final Set<Node<T>> nodes = node_layer_map.get(layer);
2150 if (null == nodes || nodes.isEmpty()) return null;
2151 try {
2152 return findFirstIntersectingNode(nodes, area.createTransformedArea(this.at.createInverse()));
2153 } catch (final NoninvertibleTransformException e) {
2154 IJError.print(e);
2156 return null;
2159 /** Expects an Area in local coordinates. */
2160 protected Node<T> findFirstIntersectingNode(final Set<Node<T>> nodes, final Area a) {
2161 for (final Node<T> nd : nodes) if (nd.intersects(a)) return nd;
2162 return null;
2165 @Override
2166 public boolean paintsAt(final Layer layer) {
2167 synchronized (node_layer_map) {
2168 final Collection<Node<T>> nodes = node_layer_map.get(layer);
2169 return null != nodes && nodes.size() > 0;
2173 @Override
2174 void removeTag(final Tag tag) {
2175 synchronized (node_layer_map) {
2176 for (final Map.Entry<Layer,Set<Node<T>>> e : node_layer_map.entrySet()) {
2177 for (final Node<T> nd : e.getValue()) {
2178 nd.removeTag(tag);
2184 private TreeNodesDataView tndv = null;
2186 /** Create a GUI to list, in three tabs: starting point, branch points, end points, and all points.
2187 * The table has columns for X, Y, Z, data (radius or area), Layer, and tags.
2188 * Double-click on a row positions the front display at that coordinate.
2189 * An extra tab has a search field, to list nodes for a given typed-in (regex) tag. */
2190 public Future<JFrame> createMultiTableView() {
2191 if (null == root) return null;
2192 return project.getLoader().doLater(new Callable<JFrame>() { @Override
2193 public JFrame call() {
2194 synchronized (Tree.this) {
2195 try {
2196 if (null == tndv) {
2197 tndv = new TreeNodesDataView(root);
2198 return tndv.frame;
2199 } else {
2200 tndv.show();
2201 return tndv.frame;
2203 } catch (final Exception e) {
2204 IJError.print(e);
2206 return null;
2208 }});
2211 protected void updateView() {
2212 if (null == tndv) return;
2213 synchronized (tndv) {
2214 tndv.recreate(this.root);
2217 protected void updateViewData(final Node<?> node) {
2218 if (null == tndv) return;
2219 synchronized (tndv) {
2220 tndv.updateData(node);
2224 @Override
2225 public boolean remove2(final boolean check) {
2226 if (super.remove2(check)) {
2227 synchronized (this) {
2228 if (null != tndv) {
2229 tndv.frame.dispose();
2230 tndv = null;
2233 return true;
2235 return false;
2238 private class TreeNodesDataView {
2239 private JFrame frame;
2240 private List<Node<T>> /*branchnodes,
2241 endnodes,*/
2242 allnodes,
2243 searchnodes;
2244 private final Table table_branchnodes = new Table(),
2245 table_endnodes = new Table(),
2246 table_allnodes = new Table(),
2247 table_searchnodes = new Table();
2248 private NodeTableModel model_branchnodes,
2249 model_endnodes,
2250 model_allnodes,
2251 model_searchnodes;
2252 private final HashMap<Node<T>,NodeData> nodedata = new HashMap<Node<T>,NodeData>();
2253 private final HashSet<Node<T>> visited_reviews = new HashSet<Node<T>>();
2255 TreeNodesDataView(final Node<T> root) {
2256 create(root);
2257 createGUI();
2259 private final class CustomCellRenderer extends DefaultTableCellRenderer {
2260 @Override
2261 public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
2262 final Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
2263 // Colorize visited review cells
2264 if (8 == column && visited_reviews.contains(((NodeTableModel)table.getModel()).nodes.get(row))) {
2265 c.setForeground(Color.white);
2266 c.setBackground(Color.green);
2267 } else {
2268 if (isSelected) {
2269 setForeground(table.getSelectionForeground());
2270 setBackground(table.getSelectionBackground());
2271 } else {
2272 c.setForeground(Color.black);
2273 c.setBackground(Color.white);
2276 return c;
2279 private final class Table extends JTable {
2280 private int last_sorted_column = -1;
2281 private boolean last_sorting_order = true; // descending == true
2282 Table() {
2283 super();
2284 getTableHeader().addMouseListener(new MouseAdapter() {
2285 @Override
2286 public void mouseClicked(final MouseEvent me) { // mousePressed would fail to repaint due to asynch issues
2287 if (2 != me.getClickCount()) return;
2288 final int viewColumn = getColumnModel().getColumnIndexAtX(me.getX());
2289 final int column = convertColumnIndexToModel(viewColumn);
2290 if (-1 == column) return;
2291 ((NodeTableModel)getModel()).sortByColumn(column, me.isShiftDown());
2292 last_sorted_column = column;
2293 last_sorting_order = me.isShiftDown();
2296 this.addMouseListener(new MouseAdapter() {
2297 @Override
2298 public void mousePressed(final MouseEvent me) {
2299 final int row = Table.this.rowAtPoint(me.getPoint());
2300 if (2 == me.getClickCount()) {
2301 go(row);
2302 } else if (Utils.isPopupTrigger(me)) {
2303 if (!project.isInputEnabled()) {
2304 Utils.showMessage("Please wait until the current task completes!");
2305 return;
2307 final JPopupMenu popup = new JPopupMenu();
2308 final JMenuItem go = new JMenuItem("Go"); popup.add(go);
2309 final JMenuItem review = new JMenuItem("Review"); popup.add(review);
2310 review.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0, true));
2311 final JMenuItem rm_review = new JMenuItem("Remove review stack"); popup.add(rm_review);
2312 popup.addSeparator();
2313 final JMenuItem generate = new JMenuItem("Generate all review stacks"); popup.add(generate);
2314 final JMenuItem gsub = new JMenuItem("Generate review stacks for subtree"); popup.add(gsub);
2315 final JMenuItem rm_reviews = new JMenuItem("Remove all reviews"); popup.add(rm_reviews);
2316 popup.addSeparator();
2317 final JMenuItem mark_as_reviewed = new JMenuItem("Mark selected as reviewed"); popup.add(mark_as_reviewed);
2318 final JMenuItem clear_visited_reviews = new JMenuItem("Unmark all reviewed"); popup.add(clear_visited_reviews);
2320 final ActionListener listener = new ActionListener() {
2321 @Override
2322 public void actionPerformed(final ActionEvent ae) {
2323 final Object src = ae.getSource();
2324 if (go == src) go(row);
2325 else if (generate == src) {
2326 if (!Utils.check("Really generate all review stacks?")) {
2327 return;
2329 generateSubtreeReviewStacks(root);
2331 else if (gsub == src) {
2332 if (!Utils.check("Really generate review stacks for the subtree?")) {
2333 return;
2335 generateSubtreeReviewStacks(((NodeTableModel)getModel()).nodes.get(getSelectedRow()));
2337 else if (review == src) review(row);
2338 else if (rm_reviews == src) {
2339 if (!Utils.check("Really remove all review tags and associated stacks?")) {
2340 return;
2342 removeReviews();
2343 visited_reviews.clear();
2345 else if (rm_review == src) {
2346 if (Utils.check("Really remove review stack for " + getReviewTags(row))) {
2347 removeReview(row); // will remove the node from visited_reviews
2350 else if (clear_visited_reviews == src) {
2351 visited_reviews.clear();
2352 repaint();
2354 else if (mark_as_reviewed == src) {
2355 // Get multiple selection
2356 final NodeTableModel m = (NodeTableModel)getModel();
2357 for (final int row : getSelectedRows()) {
2358 final Node<T> nd = m.nodes.get(row);
2359 if (!"".equals(m.getNodeData(nd).reviews)) {
2360 visited_reviews.add(nd);
2363 repaint();
2367 go.addActionListener(listener);
2368 review.addActionListener(listener);
2369 review.setEnabled(hasReviewTag(row));
2370 rm_review.addActionListener(listener);
2371 rm_review.setEnabled(hasReviewTag(row));
2372 generate.addActionListener(listener);
2373 rm_reviews.addActionListener(listener);
2374 clear_visited_reviews.addActionListener(listener);
2375 mark_as_reviewed.addActionListener(listener);
2376 popup.show(Table.this, me.getX(), me.getY());
2380 this.addKeyListener(new KeyAdapter() {
2381 @Override
2382 public void keyPressed(final KeyEvent ke) {
2383 final int keyCode = ke.getKeyCode();
2384 final int row = getSelectedRow();
2385 if (keyCode == KeyEvent.VK_G) {
2386 if (-1 != row) go(row);
2387 } else if (keyCode == KeyEvent.VK_R && 0 == ke.getModifiers()) {
2388 // If there is a review stack, open it
2389 if (-1 != row) review(row);
2390 } else if (keyCode == KeyEvent.VK_W && (0 == (Utils.getControlModifier() ^ ke.getModifiers()))) {
2391 frame.dispose();
2397 Node<T> getNode(int row) {
2398 return ((NodeTableModel)this.getModel()).nodes.get(row);
2402 String getNodeTags(int row) {
2403 NodeTableModel m = (NodeTableModel)this.getModel();
2404 Node<T> nd = m.nodes.get(row);
2405 return m.getNodeData(nd).tags;
2408 String getReviewTags(final int row) {
2409 final Node<T> nd = ((NodeTableModel)this.getModel()).nodes.get(row);
2410 final Set<Tag> tags = nd.getTags();
2411 if (null == tags) return null;
2412 final StringBuilder sb = new StringBuilder();
2413 for (final Tag t : tags) {
2414 if (t.toString().startsWith("#R")) {
2415 sb.append(t.toString()).append(", ");
2418 if (0 == sb.length()) return null;
2419 sb.setLength(sb.length() -2);
2420 return sb.toString();
2422 void go(final int row) {
2423 final Node<T> node = ((NodeTableModel)this.getModel()).nodes.get(row);
2424 setLastVisited(node);
2425 Display.centerAt(Tree.this.createCoordinate(node));
2427 void resort() {
2428 if (-1 != last_sorted_column) {
2429 ((NodeTableModel)getModel()).sortByColumn(last_sorted_column, last_sorting_order);
2432 private boolean hasReviewTag(final int row) {
2433 final Node<T> nd = ((NodeTableModel)this.getModel()).nodes.get(row);
2434 final Set<Tag> tags = nd.getTags();
2435 if (null == tags) return false;
2436 for (final Tag tag : tags) if (tag.toString().startsWith("#R-")) return true;
2437 return false;
2439 private void review(final int row) {
2440 final Node<T> nd = ((NodeTableModel)this.getModel()).nodes.get(row);
2441 // See if there are any tags
2442 final Set<Tag> tags = nd.getTags();
2443 if (null == tags) {
2444 Utils.log("Node without review tag!");
2445 return;
2447 // Find a review tag, if any
2448 Tag review_tag = null;
2449 for (final Tag tag : tags) {
2450 if (tag.toString().startsWith("#R-")) {
2451 review_tag = tag;
2452 break;
2455 if (null == review_tag) {
2456 Utils.log("Node without review tag!");
2457 return;
2459 visited_reviews.add(nd);
2460 // Find a stack for the review tag, and open it
2461 Tree.this.openImage(getReviewTagPath(review_tag), nd);
2462 repaint();
2464 private void removeReview(final int row) {
2465 final Node<T> nd = ((NodeTableModel)this.getModel()).nodes.get(row);
2466 if (null == nd) return;
2467 if (Tree.this.removeReview(nd)) {
2468 visited_reviews.remove(nd);
2470 Display.repaint(getLayerSet());
2473 void show() {
2474 frame.pack();
2475 frame.setVisible(true);
2476 frame.toFront();
2478 private void createGUI() {
2479 this.frame = new JFrame("Nodes for " + Tree.this);
2480 frame.addWindowListener(new WindowAdapter() {
2481 @Override
2482 public void windowClosing(final WindowEvent we) {
2483 Tree.this.tndv = null;
2486 final JTabbedPane tabs = new JTabbedPane();
2487 tabs.setPreferredSize(new Dimension(500, 500));
2488 tabs.add("All nodes", new JScrollPane(table_allnodes));
2489 tabs.add("Branch nodes", new JScrollPane(table_branchnodes));
2490 tabs.add("End nodes", new JScrollPane(table_endnodes));
2492 final JTextField search = new JTextField(14);
2493 search.addKeyListener(new KeyAdapter() {
2494 @Override
2495 public void keyPressed(final KeyEvent ke) {
2496 if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
2497 search(search.getText());
2501 final JButton b = new JButton("Search");
2502 b.addActionListener(new ActionListener() {
2503 @Override
2504 public void actionPerformed(final ActionEvent ae) {
2505 search(search.getText());
2508 final JPanel pane = new JPanel();
2509 final GridBagLayout gb = new GridBagLayout();
2510 pane.setLayout(gb);
2511 final GridBagConstraints c = new GridBagConstraints();
2512 c.gridx = 0;
2513 c.gridy = 0;
2514 c.weightx = 1;
2515 c.gridwidth = 1;
2516 c.anchor = GridBagConstraints.NORTH;
2517 c.fill = GridBagConstraints.BOTH;
2518 c.insets = new Insets(4,10,5,2);
2519 gb.setConstraints(search, c);
2520 pane.add(search);
2521 c.gridx = 1;
2522 c.weightx = 0;
2523 c.fill = GridBagConstraints.NONE;
2524 c.insets = new Insets(4,0,5,10);
2525 gb.setConstraints(b, c);
2526 pane.add(b);
2527 c.gridx = 0;
2528 c.gridy = 1;
2529 c.gridwidth = 2;
2530 c.weighty = 1;
2531 c.fill = GridBagConstraints.BOTH;
2532 final JScrollPane scp = new JScrollPane(table_searchnodes);
2533 c.insets = new Insets(0,0,0,0);
2534 gb.setConstraints(scp, c);
2535 pane.add(scp);
2536 tabs.add("Search", pane);
2538 frame.getContentPane().add(tabs);
2539 frame.pack();
2540 ij.gui.GUI.center(frame);
2541 frame.setVisible(true);
2543 private synchronized void create(final Node<T> root) {
2544 final ArrayList<Node<T>> branchnodes = new ArrayList<Node<T>>(),
2545 endnodes = new ArrayList<Node<T>>(),
2546 searchnodes = new ArrayList<Node<T>>(),
2547 allnodes;
2548 synchronized (node_layer_map) {
2549 allnodes = null == root ? new ArrayList<Node<T>>() : new ArrayList<Node<T>>(root.getSubtreeNodes());
2551 for (final Node<T> nd : allnodes) {
2552 switch (nd.getChildrenCount()) {
2553 case 0: endnodes.add(nd); break;
2554 case 1: continue; // slab
2555 default: branchnodes.add(nd); break;
2558 // Remove nodes no longer present:
2559 visited_reviews.retainAll(allnodes);
2561 // Swap:
2563 this.branchnodes = branchnodes;
2564 this.endnodes = endnodes;
2566 this.allnodes = allnodes;
2567 this.searchnodes = searchnodes;
2569 this.model_branchnodes = new NodeTableModel(branchnodes, nodedata);
2570 this.model_endnodes = new NodeTableModel(endnodes, nodedata);
2571 this.model_allnodes = new NodeTableModel(allnodes, nodedata);
2572 this.model_searchnodes = new NodeTableModel(searchnodes, nodedata);
2574 this.table_branchnodes.setModel(this.model_branchnodes);
2575 this.table_endnodes.setModel(this.model_endnodes);
2576 this.table_allnodes.setModel(this.model_allnodes);
2577 this.table_searchnodes.setModel(this.model_searchnodes);
2580 try {
2581 final CustomCellRenderer ccr = new CustomCellRenderer();
2582 setCellRenderer(table_branchnodes, ccr);
2583 setCellRenderer(table_endnodes, ccr);
2584 setCellRenderer(table_allnodes, ccr);
2585 setCellRenderer(table_searchnodes, ccr);
2586 } catch (final Exception e) {
2587 IJError.print(e);
2590 void setCellRenderer(final JTable t, final DefaultTableCellRenderer ccr) {
2591 t.setDefaultRenderer(t.getColumnClass(8), ccr);
2593 void recreate(final Node<T> root) {
2594 SwingUtilities.invokeLater(new Runnable() { @Override
2595 public void run() {
2596 create(root);
2597 table_branchnodes.resort();
2598 table_searchnodes.resort();
2599 table_endnodes.resort();
2600 table_allnodes.resort();
2601 Utils.revalidateComponent(frame);
2602 }});
2604 void updateData(final Node<?> node) {
2605 synchronized (nodedata) {
2606 nodedata.remove(node);
2608 SwingUtilities.invokeLater(new Runnable() { @Override
2609 public void run() {
2610 Utils.revalidateComponent(frame);
2611 }});
2613 private void search(final String regex) {
2614 final StringBuilder sb = new StringBuilder();
2615 if (!regex.startsWith("^")) sb.append("^.*");
2616 sb.append(regex);
2617 if (!regex.endsWith("$")) sb.append(".*$");
2618 try {
2619 final Pattern pat = Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
2620 this.searchnodes = new ArrayList<Node<T>>();
2621 for (final Node<T> nd : allnodes) {
2622 final Collection<Tag> tags = nd.getTags();
2623 if (null == tags) continue;
2624 for (final Tag tag : tags) {
2625 if (pat.matcher(tag.toString()).matches()) {
2626 this.searchnodes.add(nd);
2627 break;
2631 this.model_searchnodes = new NodeTableModel(this.searchnodes, this.nodedata);
2632 this.table_searchnodes.setModel(this.model_searchnodes);
2633 } catch (final Exception e) {
2634 IJError.print(e);
2639 private final class NodeData {
2640 final double x, y, z;
2641 final String data, tags, conf, reviews;
2642 NodeData(final Node<T> nd) {
2643 final float[] fp = new float[]{nd.x, nd.y};
2644 Tree.this.at.transform(fp, 0, fp, 0, 1);
2645 final Calibration cal = Tree.this.layer_set.getCalibration();
2646 this.x = fp[0] * cal.pixelHeight;
2647 this.y = fp[1] * cal.pixelWidth;
2648 this.z = nd.la.getZ() * cal.pixelWidth;
2649 // Assumes only RadiusNode and AreaNode exist
2650 if (nd.getClass() == AreaTree.AreaNode.class) {
2651 this.data = new StringBuilder
2652 (Utils.cutNumber
2653 (Math.abs
2654 (AreaCalculations.area
2655 (((AreaTree.AreaNode)nd).getData().getPathIterator(null)))
2656 * cal.pixelWidth * cal.pixelHeight, 1)).append(' ').append(cal.getUnits()).append('^').append(2).toString();
2657 } else {
2658 this.data = new StringBuilder(Utils.cutNumber(((Treeline.RadiusNode)nd).getData(), 1)).append(' ').append(cal.getUnits()).toString();
2660 this.conf = null == nd.parent ? "root" : Byte.toString(nd.parent.getEdgeConfidence(nd));
2662 final Set<Tag> ts = nd.getTags();
2663 if (null != ts) {
2664 final StringBuilder sb = new StringBuilder();
2665 final StringBuilder sbr = new StringBuilder();
2666 for (final Tag t : ts) {
2667 final String s = t.toString();
2668 if ('#' == s.charAt(0) && 'R' == s.charAt(1)) sbr.append(s).append(", ");
2669 else sb.append(s).append(", ");
2671 if (sb.length() > 0) sb.setLength(sb.length() -2);
2672 if (sbr.length() > 0) sbr.setLength(sbr.length() - 2);
2673 this.tags = sb.toString();
2674 this.reviews = sbr.toString();
2675 } else {
2676 this.tags = this.reviews = "";
2681 private class NodeTableModel extends AbstractTableModel {
2682 List<Node<T>> nodes;
2683 final HashMap<Node<T>,NodeData> nodedata;
2685 private NodeTableModel(final List<Node<T>> nodes, final HashMap<Node<T>,NodeData> nodedata) {
2686 this.nodes = nodes;
2687 this.nodedata = nodedata; // a cache
2689 private String getDataName() {
2690 if (nodes.isEmpty()) return "Data";
2691 if (nodes.get(0) instanceof Treeline.RadiusNode) return "Radius";
2692 if (nodes.get(0) instanceof AreaTree.AreaNode) return "Area";
2693 return "Data";
2695 @Override
2696 public String getColumnName(final int col) {
2697 switch (col) {
2698 case 0: return ""; // listing
2699 case 1: return "X";
2700 case 2: return "Y";
2701 case 3: return "Z";
2702 case 4: return "Layer";
2703 case 5: return "Edge confidence";
2704 case 6: return getDataName();
2705 case 7: return "Tags";
2706 case 8: return "Reviews";
2707 default: return null; // should be an error
2710 @Override
2711 public int getRowCount() { return nodes.size(); }
2712 @Override
2713 public int getColumnCount() { return 9; }
2714 public Object getRawValueAt(final int row, final int col) {
2715 if (0 == nodes.size()) return null;
2716 final Node<T> nd = nodes.get(row);
2717 switch (col) {
2718 case 0: return row+1;
2719 case 1: return getNodeData(nd).x;
2720 case 2: return getNodeData(nd).y;
2721 case 3: return getNodeData(nd).z;
2722 case 4: return nd.la.getParent().indexOf(nd.la) + 1;
2723 case 5: return getNodeData(nd).conf;
2724 case 6: return getNodeData(nd).data;
2725 case 7: return getNodeData(nd).tags;
2726 case 8: return getNodeData(nd).reviews;
2727 default: return null;
2730 @Override
2731 public Object getValueAt(final int row, final int col) {
2732 final Object o = getRawValueAt(row, col);
2733 return o instanceof Double ? Utils.cutNumber(((Double)o).doubleValue(), 1) : o;
2735 private NodeData getNodeData(final Node<T> nd) {
2736 synchronized (nodedata) {
2737 NodeData ndat = nodedata.get(nd);
2738 if (null == ndat) {
2739 ndat = new NodeData(nd);
2740 nodedata.put(nd, ndat);
2742 return ndat;
2745 @Override
2746 public boolean isCellEditable(final int row, final int col) {
2747 return false;
2749 @Override
2750 public void setValueAt(final Object value, final int row, final int col) {}
2751 public void sortByColumn(final int col, final boolean descending) {
2752 final ArrayList<Node<T>> nodes = new ArrayList<Node<T>>(NodeTableModel.this.nodes);
2753 Collections.sort(nodes, new Comparator<Node<T>>() {
2754 @Override
2755 public int compare(Node<T> nd1, Node<T> nd2) {
2756 if (descending) {
2757 final Node<T> tmp = nd1;
2758 nd1 = nd2;
2759 nd2 = tmp;
2761 Object val1 = getRawValueAt(nodes.indexOf(nd1), col);
2762 Object val2 = getRawValueAt(nodes.indexOf(nd2), col);
2763 if (col > 6) { // 7 and 8 are tags
2764 // Replace empty strings with a row of z
2765 val1 = fixStrings(val1);
2766 val2 = fixStrings(val2);
2768 return ((Comparable)val1).compareTo((Comparable)val2);
2771 this.nodes = nodes; // swap
2772 fireTableDataChanged();
2773 fireTableStructureChanged();
2775 private final Object fixStrings(final Object val) {
2776 if (val.getClass() == String.class) {
2777 if (0 == ((String)val).length()) return "zzzzzz";
2778 else return ((String)val).toLowerCase();
2780 return val;
2784 @Override
2785 synchronized protected boolean layerRemoved(final Layer la) {
2786 super.layerRemoved(la);
2787 final Set<Node<T>> nodes;
2788 synchronized (node_layer_map) {
2789 nodes = node_layer_map.remove(la);
2791 if (null == nodes) return true;
2792 for (final Iterator<Node<T>> it = nodes.iterator(); it.hasNext(); ) {
2793 final Node<T> nd = it.next();
2794 it.remove();
2795 fireNodeRemoved(nd);
2796 if (null == nd.parent) {
2797 switch (nd.getChildrenCount()) {
2798 case 1:
2799 this.root = nd.children[0];
2800 this.root.parent = null;
2801 nd.children[0] = null; // does not matter
2802 break;
2803 case 0:
2804 this.root = null;
2805 break;
2806 default:
2807 // split: the first child remains as root:
2808 this.root = nd.children[0];
2809 this.root.parent = null;
2810 nd.children[0] = null;
2811 // ... and the rest of children become children of the new root
2812 for (int i=1; i<nd.children.length; i++) {
2813 nd.children[i].parent = null;
2814 this.root.add(nd.children[i], nd.children[i].confidence);
2815 nd.children[i] = null; // does not matter
2817 break;
2819 } else {
2820 if (null == nd.children) {
2821 end_nodes.remove(nd);
2822 } else {
2823 // add all its children to the parent
2824 for (int i=0; i<nd.children.length; i++) {
2825 nd.children[i].parent = null;
2826 nd.parent.add(nd.children[i], nd.children[i].confidence);
2829 nd.parent.remove(nd);
2832 this.calculateBoundingBox(la);
2833 updateView();
2834 return true;
2837 @Override
2838 public boolean apply(final Layer la, final Area roi, final mpicbg.models.CoordinateTransform ict) throws Exception {
2839 mpicbg.models.CoordinateTransform chain = null;
2840 synchronized (node_layer_map) {
2841 if (null == root) return true;
2842 final Set<Node<T>> nodes = node_layer_map.get(la);
2843 if (null == nodes || nodes.isEmpty()) return true;
2844 final AffineTransform inverse = this.at.createInverse();
2845 final Area localroi = roi.createTransformedArea(inverse);
2846 for (final Node<T> nd : nodes) {
2847 if (nd.intersects(localroi)) {
2848 if (null == chain) {
2849 chain = M.wrap(this.at, ict, inverse);
2852 nd.apply(chain, roi);
2855 if (null != chain) calculateBoundingBox(la);
2856 return true;
2858 @Override
2859 public boolean apply(final VectorDataTransform vdt) throws Exception {
2860 synchronized (node_layer_map) {
2861 if (null == root) return true;
2862 final Set<Node<T>> nodes = node_layer_map.get(vdt.layer);
2863 if (null == nodes || nodes.isEmpty()) return true;
2864 final VectorDataTransform vlocal = vdt.makeLocalTo(this);
2865 for (final Node<T> nd : nodes) {
2866 nd.apply(vlocal);
2869 calculateBoundingBox(vdt.layer);
2870 return true;
2873 @Override
2874 public Collection<Long> getLayerIds() {
2875 synchronized (node_layer_map) {
2876 final ArrayList<Long> ids = new ArrayList<Long>(node_layer_map.size());
2877 for (final Layer la : node_layer_map.keySet()) ids.add(la.getId());
2878 return ids;
2881 @Override
2882 public Collection<Layer> getLayersWithData() {
2883 synchronized (node_layer_map) {
2884 return new ArrayList<Layer>(node_layer_map.keySet());
2888 /** In world coordinates. Returns an empty area when there aren't any nodes in @param layer. */
2889 @Override
2890 public Area getAreaAt(final Layer layer) {
2891 synchronized (node_layer_map) {
2892 final Area a = new Area();
2893 final Set<Node<T>> nodes = node_layer_map.get(layer);
2894 if (null == nodes) return a; // empty
2895 for (final Node<T> nd : nodes) a.add(nd.getArea()); // all local
2896 a.transform(this.at);
2897 return a;
2901 /** Fast and dirty, never returns a false negative but may return a false positive. */
2902 @Override
2903 protected boolean isRoughlyInside(final Layer layer, final Rectangle box) {
2904 synchronized (node_layer_map) {
2905 final Set<Node<T>> nodes = node_layer_map.get(layer);
2906 if (null == nodes) return false;
2907 try {
2908 final Rectangle local = this.at.createInverse().createTransformedShape(box).getBounds();
2909 for (final Node<T> nd : nodes) {
2910 // May not be enough for lots of corner cases
2911 // such as:
2912 // * parent and child node outside, but paint inside
2913 // * data of an outside node spills inside the box
2915 // if (local.contains((int)nd.x, (int)nd.y)) return true;
2917 // A bit more careful:
2918 if (nd.isRoughlyInside(local)) return true;
2920 return false;
2921 } catch (final NoninvertibleTransformException nite) {
2922 IJError.print(nite);
2923 return false;
2928 /** Retain the data within the layer range, and through out all the rest.
2929 * Does NOT call calculateBoundingBox or updateBucket; that is your responsibility. */
2930 @Override
2931 public boolean crop(final List<Layer> range) {
2932 synchronized (node_layer_map) {
2933 // Iterate nodes and when a node sits on a Layer that doesn't belong to the range, then remove it and give its children, if any, to the parent node.
2934 final HashSet<Layer> keep = new HashSet<Layer>(range);
2935 for (final Iterator<Map.Entry<Layer,Set<Node<T>>>> it = node_layer_map.entrySet().iterator(); it.hasNext(); ) {
2936 final Map.Entry<Layer,Set<Node<T>>> e = it.next();
2937 if (keep.contains(e.getKey())) continue;
2938 else {
2939 // Else, remove the set of nodes for that layer
2940 it.remove();
2941 // ... and remove all nodes from their parents, merging their children
2942 for (final Node<T> nd : e.getValue()) {
2943 // if end node, just remove it from its parent
2944 if (null == nd.parent) {
2945 // The current root:
2946 if (null == nd.children) {
2947 this.root = null;
2948 continue; // a tree of 1 node
2949 } else {
2950 // First child as new root:
2951 nd.children[0].parent = null; // the new root
2952 this.root = nd.children[0];
2953 // ... and gets any other children of the root
2954 for (int i=1; i<nd.children.length; i++) {
2955 nd.children[i].parent = null;
2956 nd.children[0].add(nd.children[i], nd.children[i].confidence);
2959 } else {
2960 // Remove from its parent
2961 final Node<T> nd_parent = nd.parent; //cached, since the statement below will nullify it
2962 nd.parent.remove(nd);
2963 // ... and handle its children:
2964 if (null == nd.children) {
2965 // An end point
2966 continue;
2967 } else {
2968 // Else, add all its children to its parent
2969 for (int i=0; i<nd.children.length; i++) {
2970 nd.children[i].parent = null; // so it can't be rejected when adding it to a node
2971 nd_parent.add(nd.children[i], nd.children[i].confidence);
2978 clearState();
2979 return true;
2983 /** Open an image in a separate thread and returns the thread. Frees up to 1 Gb for it. */
2984 private Future<ImagePlus> openImage(final String path, final Node<T> last) {
2985 return project.getLoader().doLater(new Callable<ImagePlus>() {
2986 @Override
2987 public ImagePlus call() {
2988 try {
2989 if (!new File(path).exists()) {
2990 Utils.log("Could not find file " + path);
2991 return null;
2993 project.getLoader().releaseToFit(1000000000L); // 1 Gb : can't tell for jpegs and tif-jpg, TODO would have to read the header.
2994 final Opener op = new Opener();
2995 op.setSilentMode(true);
2996 final ImagePlus imp = op.openImage(path); // TODO WARNING should go via the Loader
2997 if (null == imp) {
2998 Utils.log("ERROR: could not open " + path);
2999 } else {
3000 final StackWindow stack = new StackWindow(imp);
3001 final MouseListener[] ml = stack.getCanvas().getMouseListeners();
3002 for (final MouseListener m : ml) stack.getCanvas().removeMouseListener(m);
3003 stack.getCanvas().addMouseListener(new MouseAdapter() {
3004 @Override
3005 public void mousePressed(final MouseEvent me) {
3006 if (2 == me.getClickCount()) {
3007 me.consume();
3008 // Go to the node
3009 // Slices are 1-based: 1<=i<=N
3010 final int slice = imp.getCurrentSlice();
3011 if (slice == imp.getNSlices()) {
3012 Display.centerAt(createCoordinate(last));
3013 } else {
3014 Node<T> parent = last.getParent();
3015 int count = imp.getNSlices() -1;
3016 while (null != parent) {
3017 if (count == slice) {
3018 Display.centerAt(createCoordinate(parent));
3019 break;
3021 // next cycle
3022 count--;
3023 parent = parent.getParent();
3028 @Override
3029 public void mouseDragged(final MouseEvent me) {
3030 if (2 == me.getClickCount()) {
3031 me.consume();
3034 @Override
3035 public void mouseReleased(final MouseEvent me) {
3036 if (2 == me.getClickCount()) {
3037 me.consume();
3041 for (final MouseListener m : ml) stack.getCanvas().addMouseListener(m);
3043 return imp;
3044 } catch (final Exception e) {
3045 IJError.print(e);
3047 return null;
3052 public Bureaucrat generateAllReviewStacks() {
3053 return generateSubtreeReviewStacks(root);
3056 public Bureaucrat generateSubtreeReviewStacks(final Node<T> root) {
3057 return Bureaucrat.createAndStart(new Worker.Task("Generating review stacks") {
3058 @Override
3059 public void exec() {
3060 if (null == root) return;
3062 // Find all end nodes and branch nodes
3063 // Add review tags to end nodes and branch nodes, named: "#R-<x>", where <x> is a number.
3064 // Generate a fly-through stack from each found node to its previous branch point or root
3065 final int nproc = Runtime.getRuntime().availableProcessors();
3066 final ExecutorService exe = Executors.newFixedThreadPool(Math.max(1, Math.min(4, nproc)));
3067 // Above, use maximum 4 threads. I/O bound operations don't deal well with more.
3069 // Disable window
3070 final TreeNodesDataView tndv = Tree.this.tndv;
3071 if (null != tndv && null != tndv.frame) Utils.setEnabled(tndv.frame.getContentPane(), false);
3072 try {
3073 final List<Future<?>> fus = new ArrayList<Future<?>>();
3074 final Object dirsync = new Object();
3076 final ArrayList<Node<T>> be_nodes = new ArrayList<Node<T>>();
3077 // Remove all reviews, if any
3078 for (final Node<T> nd : root.getSubtreeNodes()) {
3079 removeReview(nd);
3080 if (1 != nd.getChildrenCount()) be_nodes.add(nd);
3083 final Runnable[] rs = new Runnable[be_nodes.size()];
3084 final int n_digits = Integer.toString(rs.length).length();
3085 int k = 0;
3086 for (final Node<T> nd : be_nodes) {
3087 if (Thread.currentThread().isInterrupted()) return;
3088 final Tag tag = new Tag("#R-" + Utils.zeroPad(k+1, n_digits), KeyEvent.VK_R);
3089 nd.addTag(tag);
3090 updateViewData(nd);
3091 rs[k] = new Runnable() {
3092 @Override
3093 public void run() {
3094 final String filepath = getReviewTagPath(tag);
3095 synchronized (dirsync) {
3096 if (!Utils.ensure(filepath)) {
3097 Utils.log("Did NOT create review stack for tag " + tag);
3098 return;
3101 createReviewStack(nd.findPreviousBranchOrRootPoint(), nd, tag, filepath, 512, 512, 1.0, ImagePlus.COLOR_RGB);
3104 k++;
3106 Display.repaint(getLayerSet());
3107 // Now that all tags exists (and will get painted), generate the stacks
3108 for (int i=0; i<rs.length; i++) fus.add(exe.submit(rs[i]));
3109 Utils.wait(fus);
3110 } catch (final Exception e) {
3111 IJError.print(e);
3112 } finally {
3113 if (null != tndv && null != tndv.frame) Utils.setEnabled(tndv.frame.getContentPane(), true);
3114 exe.shutdown();
3115 Display.repaint(getLayerSet());
3117 }}, getProject());
3120 /** The behavior is undefined if @param last is not a descendant of @param first. */
3121 public void createReviewStack(final Node<T> first, final Node<T> last, final Tag tag, final String filepath, final int width, final int height, final double magnification, final int image_type) {
3122 try {
3123 final ImagePlus imp = project.getLoader().createLazyFlyThrough(generateRegions(first, last, width, height, magnification), magnification, image_type, this);
3124 imp.setTitle(imp.getTitle() + tag.toString());
3125 ij.IJ.redirectErrorMessages();
3126 new FileSaver(imp).saveAsZip(filepath);
3127 } catch (final Exception e) {
3128 IJError.print(e);
3129 Utils.log("\nERROR: NOT created review stack for " + tag.toString());
3130 return;
3135 private String getReviewTagPath(final Tag tag) {
3136 // Remove the "#" from the tag name
3137 return getProject().getLoader().getUNUIdFolder() + "tree.review.stacks/" + getId() + "/" + tag.toString().substring(1) + ".zip";
3139 boolean removeReview(final Node<T> nd) {
3140 final Set<Tag> tags = nd.getTags();
3141 if (null == tags) return true;
3142 for (final Tag tag : tags) {
3143 final String s = tag.toString();
3144 if (s.startsWith("#R-")) {
3145 try {
3146 final String path = getReviewTagPath(tag);
3147 final File f = new File(path);
3148 if (f.exists()) {
3149 if (!f.delete()) {
3150 Utils.log("FAILED to delete: " + path + "\n did NOT remove tag " + tag);
3151 return false;
3154 // Remove tag:
3155 nd.removeTag(tag);
3156 updateViewData(nd);
3157 } catch (final Exception ee) {
3158 IJError.print(ee);
3162 return true;
3164 public Bureaucrat removeReviews() {
3165 return Bureaucrat.createAndStart(new Worker.Task("Removing review stacks") { // .. and tags
3166 @Override
3167 public void exec() {
3169 // Remove all review tags
3170 // Remove all .zip stacks for this Treeline
3171 boolean success = true;
3172 for (final Map.Entry<Layer,Set<Node<T>>> e : node_layer_map.entrySet()) {
3173 for (final Node<T> nd : e.getValue()) {
3174 if (Thread.currentThread().isInterrupted()) return;
3175 success = success && removeReview(nd);
3178 final File f = new File(getProject().getLoader().getUNUIdFolder() + "tree.review.stacks/" + getId());
3179 if (success) {
3180 // Remove directory (even if not empty)
3181 Utils.removeFile(f);
3182 } else {
3183 Utils.log("Could not delete some review stacks.\n --> Directory remains: " + f.getAbsolutePath());
3185 Display.repaint(getLayerSet());
3187 }}, getProject());
3190 public Map<Node<T>,Collection<Displayable>> findIntersecting(final Class<?> c) throws Exception {
3191 final HashMap<Node<T>,Collection<Displayable>> m = new HashMap<Node<T>,Collection<Displayable>>();
3192 Process.progressive(root.getSubtreeNodes(),
3193 new TaskFactory<Node<T>,Object>() {
3194 @Override
3195 public Object process(final Node<T> nd) {
3196 final Area a = nd.getArea();
3197 a.transform(Tree.this.at);
3198 final Collection<Displayable> col = layer_set.find(c, nd.la, a, false, true);
3199 if (col.isEmpty()) return null;
3200 synchronized (m) {
3201 m.put(nd, col);
3203 return null;
3206 return m;
3209 /** Returns an array of two Collection of connectors: the first one has the outgoing connectors, and the second one has the incoming connectors. */
3210 @SuppressWarnings("unchecked")
3211 public List<Connector>[] findConnectors() throws Exception {
3212 final ArrayList<Connector> outgoing = new ArrayList<Connector>();
3213 final ArrayList<Connector> incoming = new ArrayList<Connector>();
3214 if (null != root) {
3215 Process.progressive(root.getSubtreeNodes(),
3216 new TaskFactory<Node<T>,Object>() {
3217 @Override
3218 public Object process(final Node<T> nd) {
3219 final Area a = nd.getArea();
3220 a.transform(Tree.this.at);
3221 final Collection<Displayable> col = layer_set.findZDisplayables(Connector.class, nd.la, a, false, false);
3222 if (col.isEmpty()) return null;
3223 // Outgoing or incoming?
3224 for (final Connector c : (Collection<Connector>)(Collection)col) {
3225 if (c.intersectsOrigin(a, nd.la)) {
3226 synchronized (outgoing) {
3227 outgoing.add(c);
3229 } else {
3230 synchronized (incoming) {
3231 incoming.add(c);
3235 return null;
3239 return (List<Connector>[]) new List[]{outgoing, incoming};
3242 @Override
3243 public String getShortTitle() {
3244 final String title = getTitle();
3245 if (null != title && !getClass().getSimpleName().toLowerCase().equals(title.toLowerCase())) return title;
3246 if (null == root) return "Empty";
3247 final Point3f p = getOriginPoint(true);
3248 return new StringBuilder("Root: x=").append(p.x).append(", y=" + p.y).append(" z=").append(p.z).toString();
3251 public Point3f getOriginPoint(final boolean calibrated) {
3252 if (null == root) return null;
3253 return fix(root.asPoint(), calibrated, new float[2]);
3256 /** Return the {@link Node} as a point in space.
3257 * @param nd The Node to extract coordinates from.
3258 * @param calibrated Whether to calibrate or not the point.
3259 * @return The Point3f representing the node. */
3260 public Point3f asPoint(final Node<T> nd, final boolean calibrated) {
3261 return fix(nd.asPoint(), calibrated, new float[2]);
3264 /** Expects a non-null float[] for reuse, and modifies @param p in place. */
3265 protected Point3f fix(final Point3f p, final boolean calibrated, final float[] f) {
3266 f[0] = p.x;
3267 f[1] = p.y;
3268 this.at.transform(f, 0, f, 0, 1);
3269 p.x = f[0];
3270 p.y = f[1];
3271 if (calibrated) {
3272 final Calibration cal = layer_set.getCalibration();
3273 p.x *= cal.pixelWidth;
3274 p.y *= cal.pixelHeight;
3275 p.z *= cal.pixelWidth; // not pixelDepth!
3277 return p;
3280 /** Takes a collection of Tree instances and creates duplicate siblings of the target class.
3281 * Will ignore any non-tree Displayable instances in the Collection.
3282 * The duplicated trees are added to the ProjectTree as siblings of the originals, and to the LayerSet.
3283 * @return The map of original trees vs copies in target class form. */
3284 public static Map<Tree<?>,Tree<?>> duplicateAs(final Collection<Displayable> col, final Class<? extends Tree<?>> target) throws Exception {
3285 final HashMap<Tree<?>,Tree<?>> m = new HashMap<Tree<?>, Tree<?>>();
3286 for (final Displayable d : col) {
3287 if (target.isInstance(d)) {
3288 Utils.log(d + " is already of class " + target.getSimpleName());
3289 continue;
3291 if (!(d instanceof Tree<?>)) {
3292 Utils.log("Ignoring " + d + ": not a Tree subclass");
3293 continue;
3295 final Tree<?> src = (Tree<?>)d;
3296 if (null == src.root) {
3297 Utils.log("Ignoring empty tree " + src);
3298 continue;
3300 if (Treeline.class == target) {
3302 // Specific for a target Treeline:
3304 final Treeline t = new Treeline(src.project, src.title);
3305 t.at.setTransform(src.at);
3307 final Map<Node<?>,Treeline.RadiusNode> rel = new HashMap<Node<?>,Treeline.RadiusNode>();
3308 final LinkedList<Node<?>> todo = new LinkedList<Node<?>>();
3309 //t.root = new Treeline.RadiusNode(src.root.x, src.root.y, src.root.la);
3310 todo.add(src.root);
3311 while (!todo.isEmpty()) {
3312 final Node<?> a = todo.removeLast();
3313 // Put all children nodes to the end of the todo list
3314 if (null != a.children)
3315 for (final Node<?> child : a.children)
3316 todo.add(child);
3317 // Copy the content of the 'a' node
3318 Treeline.RadiusNode copy = new Treeline.RadiusNode(a.x, a.y, a.la);
3319 copy.copyProperties(a);
3320 // Store relationship between original and copy
3321 rel.put(a, copy);
3322 // Find parent if any
3323 if (null == a.parent) continue;
3324 // .. and if found, add the copy to the copied parent:
3325 rel.get(a.parent).add(copy, copy.confidence);
3329 m.put(src, copyAs(src, Treeline.class, Treeline.RadiusNode.class));
3330 } else if (AreaTree.class == target) {
3331 m.put(src, copyAs(src, AreaTree.class, AreaTree.AreaNode.class));
3332 } else {
3333 Utils.log("Ignoring " + src);
3335 final Tree<?> copy = m.get(src);
3336 if (null != copy) {
3337 src.layer_set.add(copy);
3338 if (null == src.project.getProjectTree().addSibling(src, copy)) {
3339 Utils.log("Could not add " + src.getClass().getSimpleName() + " as " + target.getSimpleName());
3340 m.remove(src);
3341 src.layer_set.remove(copy);
3345 return m;
3348 /** Can copy a Treeline to an AreaTree and viceversa.
3349 * Copies the transform, the nodes (with tags and confidence), and the color. The transparency, locked stated and links are not copied. */
3350 static public<A extends Tree<?>, B extends Node<?>> A copyAs(final Tree<?> src, final Class<A> tree_class, final Class<B> node_class) throws Exception {
3351 final String title = "copy of " + src.title + " #" + src.id;
3352 final A t = tree_class.getConstructor(Project.class, String.class).newInstance(src.project, title);
3353 t.at.setTransform(src.at);
3354 t.color = src.color;
3355 t.width = src.width;
3356 t.height = src.height;
3358 final Map<Node<?>,B> rel = new HashMap<Node<?>,B>();
3359 final LinkedList<Node<?>> todo = new LinkedList<Node<?>>();
3360 //t.root = new Treeline.RadiusNode(src.root.x, src.root.y, src.root.la);
3361 todo.add(src.root);
3362 while (!todo.isEmpty()) {
3363 final Node<?> a = todo.removeLast();
3364 // Put all children nodes to the end of the todo list
3365 if (null != a.children)
3366 for (final Node<?> child : a.children)
3367 todo.add(child);
3368 // Copy the content of the 'a' node
3369 final B copy = node_class.getConstructor(Float.TYPE, Float.TYPE, Layer.class).newInstance(a.x, a.y, a.la);
3370 copy.copyProperties(a);
3371 // Store relationship between original and copy
3372 rel.put(a, copy);
3373 // Find parent if any
3374 if (null == a.parent) {
3375 // Set the copy as the root
3376 t.root = (Node)copy; // need to cast
3377 continue;
3379 // .. and if found, add the copy to the copied parent:
3380 rel.get(a.parent).add((Node)copy, copy.confidence); // TODO no other way than to cast?
3382 // create internals
3383 t.cacheSubtree((Collection)t.root.getSubtreeNodes());
3384 return t;
3387 /** One color per vertex. */
3388 static public class MeshData {
3389 final public List<Color3f> colors;
3390 final public List<Point3f> verts;
3391 public MeshData(final List<Point3f> v, final List<Color3f> c) {
3392 this.verts = v;
3393 this.colors = c;
3397 private Node<T> guiFindNode(final float x, final float y, final Layer layer, final double magnification) {
3398 final Collection<Node<T>> nodes;
3399 synchronized (node_layer_map) {
3400 nodes = node_layer_map.get(layer);
3402 if (null == nodes) {
3403 Utils.log("No nodes in layer " + layer);
3404 return null;
3406 final Node<T> node = findClosestNodeW(nodes, x, y, magnification);
3407 if (null == node) {
3408 Utils.log("Could not find any node! Zoom in for better precision.");
3410 return node;
3413 /** @return null if no node is near @param x, @param y */
3414 public Bureaucrat generateReviewStackForSlab(final float x, final float y, final Layer layer, final double magnification) {
3415 return generateReviewStackForSlab(guiFindNode(x, y, layer, magnification));
3418 public Bureaucrat generateSubtreeReviewStacks(final int x, final int y, final Layer layer, final double magnification) {
3419 return generateSubtreeReviewStacks(guiFindNode(x, y, layer, magnification));
3422 /** Generate a review stack from the previous branch node or root, to the next branch node or end node. */
3423 public Bureaucrat generateReviewStackForSlab(final Node<T> node) {
3424 return Bureaucrat.createAndStart(new Worker.Task("Create review stack") {
3425 @Override
3426 public void exec() {
3427 if (null == node) return;
3428 final Node<T> first = node.findPreviousBranchOrRootPoint();
3429 final Node<T> last = node.findNextBranchOrEndPoint();
3430 // Check if 'last' already has a review tag
3431 final Set<Tag> tags = last.getTags();
3432 String name = "#R-slab-1";
3433 if (null != tags && !tags.isEmpty()) {
3434 final ArrayList<Integer> a = new ArrayList<Integer>();
3435 for (final Tag t : tags) {
3436 if (t.toString().startsWith("#R-slab-")) {
3437 a.add(Integer.parseInt(t.toString().substring(7)));
3440 if (a.isEmpty()) name += 1;
3441 else {
3442 Collections.sort(a);
3443 name = name + (a.get(a.size()-1) + 1);
3446 final Tag tag = new Tag(name, KeyEvent.VK_R);
3447 last.addTag(tag);
3448 final String filepath = getReviewTagPath(tag);
3449 Utils.ensure(filepath);
3450 createReviewStack(first, last, tag, filepath, 512, 512, 1.0, ImagePlus.COLOR_RGB);
3451 }}, project);
3454 static public final class MeasurePathDistance<I> {
3455 private double dist = 0;
3456 private int branch_points = 0;
3457 final private float[] fpA = new float[2],
3458 fpB = new float[2];
3459 final private float firstx,
3460 firsty;
3461 final private List<Node<I>> path;
3462 final private Calibration cal;
3463 final private Tree<I> tree;
3464 final private Node<I> a, b;
3466 public double getDistance() { return dist; }
3467 public List<Node<I>> getPath() { return path; }
3468 public int getBranchNodesInPath() { return branch_points; }
3469 public Point3f getFirstNodeCoordinates() {
3470 return new Point3f(firstx, firsty, (float)(path.get(0).la.getZ() * cal.pixelWidth));
3472 public Point3f getLastNodeCoordinates() {
3473 if (1 == path.size()) {
3474 return getFirstNodeCoordinates();
3476 return new Point3f(fpB[0], fpB[1], (float)(path.get(path.size()-1).la.getZ() * cal.pixelWidth));
3478 public MeasurePathDistance(final Tree<I> tree, final Node<I> a, final Node<I> b) {
3479 this(tree, a, b, Node.findPath(a, b));
3481 /** @throws an Exception if a path cannot be found between @param a and @param b. */
3482 private MeasurePathDistance(final Tree<I> tree, final Node<I> a, final Node<I> b, final List<Node<I>> path) {
3483 this.path = path;
3484 this.cal = tree.layer_set.getCalibrationCopy();
3485 this.tree = tree;
3486 this.a = a;
3487 this.b = b;
3488 final Iterator<Node<I>> it = path.iterator();
3490 Node<?> first = it.next();
3491 if (first.getChildrenCount() > 1) branch_points++;
3492 fpA[0] = first.x;
3493 fpA[1] = first.y;
3494 tree.at.transform(fpA, 0, fpA, 0, 1);
3495 double zA = first.la.getZ();
3497 firstx = fpA[0];
3498 firsty = fpA[1];
3500 if (1 == path.size()) {
3501 fpB[0] = fpA[0];
3502 fpB[1] = fpA[1];
3505 while (it.hasNext()) {
3506 final Node<?> second = it.next();
3507 if (second.getChildrenCount() > 1) branch_points++;
3508 fpB[0] = second.x;
3509 fpB[1] = second.y;
3510 tree.at.transform(fpB, 0, fpB, 0, 1);
3511 final double zB = second.la.getZ();
3512 dist += Math.sqrt(Math.pow((fpB[0] - fpA[0]) * cal.pixelWidth, 2)
3513 + Math.pow((fpB[1] - fpA[1]) * cal.pixelHeight, 2)
3514 + Math.pow((zB - zA) * cal.pixelWidth, 2));
3515 // prepare next iteration
3516 first = second;
3517 fpA[0] = fpB[0];
3518 fpA[1] = fpB[1];
3519 zA = zB;
3522 /** Reuses @param rt unless it is null, in which case it creates a new one. */
3523 public ResultsTable show(ResultsTable rt) {
3524 if (null == rt) rt = Utils.createResultsTable("Tree path measurements", new String[]{"id", "XA", "YA", "Layer A", "XB", "YB", "Layer B", "distance", "N nodes", "N branch points"});
3525 rt.incrementCounter();
3526 rt.addLabel("units", cal.getUnit());
3527 rt.addValue(0, tree.id);
3528 rt.addValue(1, firstx);
3529 rt.addValue(2, firsty);
3530 rt.addValue(3, tree.layer_set.indexOf(a.la) + 1); // 1-based, not zero-based!
3531 rt.addValue(4, fpB[0]);
3532 rt.addValue(5, fpB[1]);
3533 rt.addValue(6, tree.layer_set.indexOf(b.la) + 1);
3534 rt.addValue(7, dist);
3535 rt.addValue(8, path.size());
3536 rt.addValue(9, branch_points);
3537 return rt;
3541 /** Reuses @param rt unless it is null, in which case it creates a new one.
3542 * Will check if both nodes belong to this tree.
3543 * @return The used ResultsTable instance. */
3544 public ResultsTable measurePathDistance(final Node<T> a, final Node<T> b, final ResultsTable rt) {
3545 synchronized (node_layer_map) {
3546 // Do both nodes belong to this Tree?
3547 final Set<Node<T>> nodes1 = node_layer_map.get(a.la);
3548 if (null == nodes1 || !nodes1.contains(a)) {
3549 Utils.log("Tree.measurePathDistance: node " + a + " does not belong to tree " + this);
3550 return rt;
3552 final Set<Node<T>> nodes2 = node_layer_map.get(b.la);
3553 if (null == nodes2 || !nodes2.contains(b)) {
3554 Utils.log("Tree.measurePathDistance: node " + b + " does not belong to tree " + this);
3555 return rt;
3557 try {
3558 return new MeasurePathDistance<T>(this, a, b).show(rt);
3559 } catch (final Exception e) {
3560 IJError.print(e);
3562 return rt;
3565 /** Measure the distance, in calibrated units, between nodes a and b of this tree.
3566 * Does not check if the nodes really belong to this tree. */
3567 public double measurePathDistance(final Node<T> a, final Node<T> b) throws Exception {
3568 return new MeasurePathDistance<T>(this, a, b).getDistance();
3571 /** Search all nodes for unique tags and returns them. */
3572 public Set<Tag> findTags() {
3573 final HashSet<Tag> tags = new HashSet<Tag>();
3574 synchronized (node_layer_map) {
3575 for (final Set<Node<T>> nodes : node_layer_map.values()) {
3576 for (final Node<T> node : nodes) {
3577 final Set<Tag> t = node.getTags();
3578 if (null == t) continue;
3579 tags.addAll(t);
3583 return tags;
3586 @Override
3587 public void destroy() {
3588 super.destroy();
3589 TreeConnectorsView.dispose(this);
3592 public HashMap<Node<T>,Integer> computeAllDegrees() {
3593 if (null == root) return new HashMap<Node<T>,Integer>();
3594 return root.computeAllDegrees();
3597 public Collection<Node<T>> getBranchNodes() {
3598 if (null == root) return new ArrayList<Node<T>>();
3599 return root.getBranchNodes();
3601 public Collection<Node<T>> getBranchAndEndNodes() {
3602 if (null == root) return new ArrayList<Node<T>>();
3603 return root.getBranchAndEndNodes();
3606 static private final<T> Collection<Vertex<Node<T>>> findNeighbors(final Node<T> node, final HashMap<Node<T>,Vertex<Node<T>>> m) {
3607 final Node<T> parent = node.getParent();
3608 final Collection<Vertex<Node<T>>> neighbors = new ArrayList<Vertex<Node<T>>>();
3609 if (null != parent) neighbors.add(m.get(parent));
3610 for (final Node<T> child : node.getChildrenNodes()) {
3611 neighbors.add(m.get(child));
3613 return neighbors;
3616 /** Return a representation of this Tree with Vertex instead of Node. */
3617 public HashMap<Node<T>,Vertex<Node<T>>> asVertices() {
3618 final HashMap<Node<T>,Vertex<Node<T>>> m = new HashMap<Node<T>,Vertex<Node<T>>>();
3619 if (null == root) return m;
3620 // Create one Vertex per Node<T>
3621 for (final Node<T> node : this.getRoot().getSubtreeNodes()) {
3622 m.put(node, new Vertex<Node<T>>(node));
3624 // Determine the neighbors of that Vertex
3625 for (final Map.Entry<Node<T>,Vertex<Node<T>>> e : m.entrySet()) {
3626 e.getValue().neighbors.addAll(findNeighbors(e.getKey(), m));
3628 return m;
3631 /** Computes betweenness centrality of each node in the tree,
3632 * using Ulrik Brandes betweenness centrality algorithm. */
3633 public HashMap<Node<T>,Float> computeCentrality() {
3634 final HashMap<Node<T>,Float> cs = new HashMap<Node<T>,Float>();
3635 if (null == root) return cs;
3637 final HashMap<Node<T>,Vertex<Node<T>>> m = asVertices();
3638 Centrality.compute(m.values());
3640 for (final Map.Entry<Node<T>,Vertex<Node<T>>> e : m.entrySet()) {
3641 cs.put(e.getKey(), e.getValue().centrality);
3643 return cs;
3646 public void colorizeByNodeBetweennessCentrality() {
3647 if (null == root) return;
3648 final HashMap<Node<T>,Vertex<Node<T>>> m = asVertices();
3649 Centrality.compute(m.values());
3650 final IndexColorModel cm = Utils.fireLUT();
3651 final Map<Integer,Color> colors = new HashMap<Integer,Color>();
3652 double max = 0;
3653 for (final Vertex<?> v : m.values()) max = Math.max(max, v.centrality);
3654 for (final Map.Entry<Node<T>, Vertex<Node<T>>> e : m.entrySet()) {
3655 final int i = (int)(255 * (e.getValue().centrality / max) + 0.5f);
3656 Color c = colors.get(i);
3657 if (null == c) {
3658 c = new Color(cm.getRed(i), cm.getGreen(i), cm.getBlue(i));
3659 colors.put(i, c);
3661 e.getKey().setColor(c);
3665 public void colorizeByBranchBetweennessCentrality(final int etching_multiplier) {
3666 if (null == root) return;
3667 final Collection<Vertex<Node<T>>> vs = asVertices().values();
3668 Centrality.branchWise(vs, etching_multiplier);
3669 final IndexColorModel cm = Utils.fireLUT();
3670 final Map<Integer,Color> colors = new HashMap<Integer,Color>();
3671 double max = 0;
3672 for (final Vertex<?> v : vs) max = Math.max(max, v.centrality);
3673 if (0 == max) {
3674 Utils.logAll("Branch centrality: all have zero!");
3675 return;
3677 for (final Vertex<Node<T>> v : vs) {
3678 final int i = (int)(255 * (v.centrality / max) + 0.5f);
3679 Utils.log("branch centrality: " + v.centrality + " , i: " + i);
3680 Color c = colors.get(i);
3681 if (null == c) {
3682 c = new Color(cm.getRed(i), cm.getGreen(i), cm.getBlue(i));
3683 colors.put(i, c);
3685 v.data.setColor(c);
3689 public class Pair {
3690 /** Two nodes of a tree; there is a unique path that goes from a to b. */
3691 public Node<T> a, b;
3693 public Pair(final Node<T> a, final Node<T> b) {
3694 this.a = a;
3695 this.b = b;
3698 /** The calibrated distance from a to b. */
3699 public double measureDistance() throws Exception {
3700 return new MeasurePathDistance<T>(Tree.this, a, b).getDistance();
3703 /** The list of associated data element with each node. */
3704 private List<T> getData(final List<Node<T>> path) {
3705 final ArrayList<T> d = new ArrayList<T>();
3706 for (final Node<T> nd : path) {
3707 d.add(nd.getData());
3709 return d;
3712 /** The list of data elements associated with each node in the path, ordered from a to b (both included). */
3713 public List<T> getData() {
3714 return getData(Node.findPath(a, b));
3718 public class NodePath extends Pair {
3719 /** The ordered list of nodes from a to b, both included. */
3720 final protected List<Node<T>> path;
3722 public NodePath(final Node<T> a, final Node<T> b) {
3723 this(a, b, Node.findPath(a, b));
3726 /** Assumes that a is the first element in path, and b the last,
3727 * and that a has a lower degree than b (that is, a is upstream of b). */
3728 public NodePath(final Node<T> a, final Node<T> b, final List<Node<T>> path) {
3729 super(a, b);
3730 this.path = path;
3733 public List<Node<T>> getPath() {
3734 return path;
3738 public abstract class MeasurementPair extends NodePath
3740 /** The calibrated path distance between nodes a and b, measured
3741 * as the sum of all the distances between all consecutive pairs
3742 * of nodes between a and b. */
3743 final public double distance;
3744 /** The ordered list of calibrated data elements of each node in the path
3745 * of nodes between a and b, both included. */
3746 final public List<T> data;
3747 /** The ordered list of calibrated coordinates of all nodes in the path
3748 * of nodes between a and b, both included. */
3749 final public List<Point3f> coords;
3751 public MeasurementPair(final NodePath np) {
3752 this(np.a, np.b, np.path);
3754 public MeasurementPair(final Node<T> a, final Node<T> b, final List<Node<T>> path) {
3755 super(a, b, path);
3756 this.distance = new MeasurePathDistance<T>(Tree.this, a, b, path).getDistance();
3757 this.data = calibratedData();
3758 this.coords = new ArrayList<Point3f>();
3759 final AffineTransform aff = toCalibration();
3760 final float[] fp = new float[2];
3761 for (final Node<T> nd : path) {
3762 fp[0] = nd.x;
3763 fp[1] = nd.y;
3764 aff.transform(fp, 0, fp, 0, 1);
3765 coords.add(new Point3f(fp[0], fp[1], (float)nd.getLayer().getCalibratedZ()));
3768 abstract protected List<T> calibratedData();
3769 abstract public ResultsTable toResultsTable(ResultsTable rt, int index, double scale, int resample);
3770 abstract public MeshData createMesh(final double scale, final int resample);
3771 abstract public String getResultsTableTitle();
3772 @Override
3773 public double measureDistance() {
3774 return distance;
3776 @Override
3777 public List<T> getData() {
3778 return data;
3780 /** Concatenate the affine of the Tree and an affine that expresses the x,y calibration. */
3781 protected final AffineTransform toCalibration() {
3782 final AffineTransform aff = new AffineTransform(Tree.this.at);
3783 final Calibration cal = layer_set.getCalibration();
3784 aff.preConcatenate(new AffineTransform(cal.pixelWidth, 0, 0, cal.pixelHeight, 0, 0));
3785 return aff;
3789 protected abstract MeasurementPair createMeasurementPair(NodePath np);
3791 public List<NodePath> findTaggedPairs(final Tag upstream, final Tag downstream) {
3792 final ArrayList<NodePath> pairs = new ArrayList<NodePath>();
3793 if (null == root) return pairs;
3794 for (final Node<T> nd : root.getSubtreeNodes()) {
3795 if (nd.hasTag(downstream)) {
3796 final List<Node<T>> path = new ArrayList<Node<T>>();
3797 path.add(nd);
3798 Node<T> parent = nd.getParent();
3799 while (!parent.hasTag(upstream)) {
3800 path.add(parent);
3801 parent = parent.getParent();
3802 if (null == parent) break;
3804 if (null == parent) continue;
3805 path.add(parent);
3806 Collections.reverse(path);
3807 pairs.add(new NodePath(parent, nd, path));
3810 return pairs;
3813 public List<MeasurementPair> measureTaggedPairs(final Tag upstream, final Tag downstream) {
3814 final ArrayList<MeasurementPair> pairs = new ArrayList<MeasurementPair>();
3815 for (final NodePath np : findTaggedPairs(upstream, downstream)) {
3816 pairs.add(createMeasurementPair(np));
3818 return pairs;
3821 /** Drop all tags from the nodes of this tree. */
3822 public void dropAllTags() {
3823 if (null == root) return;
3824 for (final Node<T> nd : root.getSubtreeNodes()) {
3825 nd.removeAllTags();