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.
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
;
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
;
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
;
94 import ij
.gui
.GenericDialog
;
95 import ij
.gui
.StackWindow
;
96 import ij
.io
.FileSaver
;
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
);
138 this.visible
= visible
;
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
) {
162 nodes
= new HashSet
<Node
<T
>>();
163 for (final Set
<Node
<T
>> ns
: node_layer_map
.values()) nodes
.addAll(ns
);
165 for (final Layer la
: color_cue_layers
) {
166 final Set
<Node
<T
>> ns
= node_layer_map
.get(la
);
168 if (null == nodes
) nodes
= new HashSet
<Node
<T
>>();
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
);
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
) {
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
) {
204 synchronized (node_layer_map
) {
205 // Determine which layers to paint
206 final Set
<Node
<T
>> nodes
= getNodesToPaint(active_layer
, layers
);
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()) {
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;
221 } catch (final NoninvertibleTransformException nite
) {
225 // Arrange transparency
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;
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
);
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
));
259 if (active
&& active_layer
== nd
.la
) handles
[next
++] = nd
;
261 for (final Runnable task
: tags_tasks
) task
.run();
263 for (int i
=0; i
<next
; i
++) {
264 handles
[i
].paintHandle(g
, srcRect
, magnification
, this);
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
);
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
);
299 if (null == tmp
) return new Rectangle(); // 0 width and 0 height: no data
300 tmp
.setBounds(0, 0, 0, 0);
303 final Rectangle r
= getBounds(nodes
);
305 if (null == r
) return new Rectangle();
308 if (null == r
) tmp
.setRect(0, 0, 0, 0);
315 // Call always from within a synchronized (node_layer_map) block.
316 protected Rectangle
getBounds(final Collection
<?
extends Node
<T
>> nodes
) {
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
);
326 public boolean calculateBoundingBox(final Layer la
) {
329 this.at
.setToIdentity();
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
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.
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() {
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
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
) {
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
);
406 private final List
<Node
<T
>> tolink
= new ArrayList
<Node
<T
>>();
408 protected final void addToLinkLater(final Node
<T
> nd
) {
409 synchronized (tolink
) {
413 protected final void removeFromLinkLater(final Node
<T
> nd
) {
414 synchronized (tolink
) {
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
);
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
)) {
435 if (patch
.locked
) must_lock
= true;
439 if (must_lock
&& !locked
) {
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());
461 /** To reconstruct from XML. */
462 abstract public Node
<T
> newNode(HashMap
<String
,String
> ht_attr
);
465 public boolean isDeletable() {
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;
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")
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
>>();
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
);
519 final Integer ii
= table
.get(node
);
521 // Never yet processed a child, add first
522 dataNodeXML(tree
, indent
, sb
, node
);
524 list
.add(node
.children
[0]);
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
);
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
) {
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
);
561 tree
.exportXMLNodeAttributes(indent
, sb
, node
); // may not add anything
563 // ... so accumulated potentially extra chars are 3: \">\n
566 final boolean data
= tree
.exportXMLNodeData(indent
, sb
, node
);
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");
574 } else if (null == node
.children
) {
575 if (null != node
.tags
) {
576 exportTags(node
, sb
, indent
);
577 sb
.append(indent
).append("</t2_node>\n");
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 */
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
>>();
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
);
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
);
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
);
654 if (null != node
.parent
) {
655 // Create a line to the parent
656 list
.add(points
.get(node
.parent
));
658 if (null == node
.color
) {
660 colors
.add(cf
); // twice: a line segment
662 Color3f c
= cached_colors
.get(node
.color
);
664 c
= new Color3f(node
.color
);
665 cached_colors
.put(node
.color
, 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
);
681 final Class
<?
> getInternalDataPackageClass() {
686 synchronized Object
getDataPackage() {
687 return new DPTree(this);
690 private final class DPTree
extends Displayable
.DataPackage
{
692 DPTree(final Tree
<T
> t
) {
694 this.root
= null == t
.root ?
null : t
.root
.clone(t
.project
);
697 final boolean to2(final Displayable d
) {
699 final Tree
<T
> t
= (Tree
<T
>)d
;
700 if (null != this.root
) {
701 t
.root
= this.root
.clone(t
.project
);
703 t
.cacheSubtree(t
.root
.getSubtreeNodes());
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
);
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
);
726 // Find a node near the coordinate
727 final Node
<T
> nd
= findNode(x
, y
, layer
, magnification
);
729 Utils
.log("No node near " + x
+ ", " + y
+ ", " + layer
);
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
);
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
) {
756 if (!this.at
.isIdentity()) {
757 final Point2D
.Double po
= inverseTransformPoint(x
, 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
);
769 // Find a node near the coordinate
770 final Node
<T
> nd
= findNode(x
, y
, layer
, magnification
);
772 Utils
.log("No node near " + x
+ ", " + y
+ ", " + layer
+ ", mag=" + magnification
);
775 if (null == nd
.parent
) {
776 Utils
.log("Cannot split at a root point!");
781 } catch (final Exception e
) {
787 /** @param nd A node of this Tree. */
788 public List
<Tree
<T
>> splitAt(final Node
<T
> nd
) {
789 if (null == nd
) return null;
791 ArrayList
<Tree
<T
>> a
;
792 synchronized (node_layer_map
) {
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
) {
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)
806 // With the found nd, now a root, create a new Tree
807 final Tree
<T
> t
= newInstance();
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);
815 a
= new ArrayList
<Tree
<T
>>();
819 this.calculateBoundingBox(null); // outside synch
821 } catch (final Exception e
) {
827 protected void cacheSubtree(final Iterable
<Node
<T
>> nodes
) {
828 cache(nodes
, end_nodes
, node_layer_map
);
830 protected void clearCache() {
832 node_layer_map
.clear();
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
);
845 nds
= new HashSet
<Node
<T
>>();
846 node_layer_map
.put(child
.la
, nds
);
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
) {
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. */
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;
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;
891 public Node
<T
> getRoot() {
895 protected Coordinate
<Node
<T
>> createCoordinate(final Node
<T
> nd
) {
896 if (null == nd
) return null;
899 if (!this.at
.isIdentity()) {
900 final float[] dps
= new float[]{x
, y
};
901 this.at
.transform(dps
, 0, dps
, 0, 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
);
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
);
968 /** Expects world coordinates. */
969 protected Node
<T
> adjustEdgeConfidence(final int inc
, final float x
, final float y
, final Layer layer
, final DisplayCanvas dc
) {
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
)) {
979 if (null != nearest
) updateViewData(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
);
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
) {
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
) {
1030 if (!this.at
.isIdentity()) {
1031 final Point2D
.Double po
= inverseTransformPoint(wx
, wy
);
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
);
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
);
1053 float min_dist
= Float
.MAX_VALUE
;
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
) {
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));
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
);
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
);
1112 addToLinkLater(in_between
);
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
);
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
,
1139 if (dist
< min_dist
&& dist
< d
) {
1146 if (null == ns
[0]) return null;
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;
1155 double min_dist
= Double
.MAX_VALUE
;
1157 for (final Node
<T
> child
: parent
.children
) {
1158 final double dist
= M
.distancePointToSegment(lx
, ly
,
1161 if (dist
< min_dist
) {
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
);
1197 } else if (0 == nodes
.size()) {
1198 node_layer_map
.remove(child
.la
);
1202 repaint(true, child
.la
);
1205 if (null != subtree
) {
1206 synchronized (tolink
) {
1207 tolink
.addAll(subtree
);
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()) {
1222 removeNode(node
, null);
1225 if (null == node
.parent
) {
1226 // Make its child the new root
1227 root
= node
.children
[0];
1229 root
.confidence
= Node
.MAX_EDGE_CONFIDENCE
; // with its now non-existent parent
1230 if (node
== last_visited
) setLastVisited(root
);
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
);
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
) {
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!");
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
);
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
);
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);
1299 if (null == this.root
) {
1300 Utils
.log("The root of this tree is null!");
1303 if (1 == ts
.size()) {
1304 Utils
.log("No other trees to join!");
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!");
1313 if (getClass() != tl
.getClass()) {
1314 Utils
.log("For joining, all trees must be of the same kind!");
1317 if (null == tl
.marked
) {
1318 Utils
.log("No marked node in to-be child treeline " + tl
);
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
;
1335 at_inv
= this.at
.createInverse();
1336 } catch (final NoninvertibleTransformException nite
) {
1337 IJError
.print(nite
);
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()) {
1346 aff
.transform(fps
, 0, fps
, 0, 1);
1349 nd
.transformData(aff
);
1350 // Remove review stack if any
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
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
);
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
);
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:
1394 // Is there only one node within the srcRect?
1397 a
= new Area(dc
.getSrcRect()).createTransformedArea(this.at
.createInverse());
1398 } catch (final NoninvertibleTransformException nite
) {
1399 IJError
.print(nite
);
1403 for (final Node
<T
> node
: nodes
) {
1404 if (node
.intersects(a
)) {
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])) {
1426 Utils.log("No node near " + x + ", " + y + ", " + layer + ", mag=" + magnification);
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
);
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
);
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
);
1454 setLastMarked(found
);
1458 public boolean unmark() {
1459 if (null != marked
) {
1460 setLastMarked(null);
1466 /** The node currently being dragged or edited in some way. */
1467 protected void setActive(final Node
<T
> 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
;
1477 protected void setLastAdded(final Node
<T
> nd
) {
1478 this.last_added
= nd
;
1481 public void setLastMarked(final Node
<T
> 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() {
1494 public Node
<T
> getLastVisited() {
1495 return last_visited
;
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
);
1516 protected void clearState() {
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);
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()) {
1551 // transform the x_p, y_p to the local coordinates
1554 if (!this.at
.isIdentity()) {
1555 final Point2D
.Double po
= inverseTransformPoint(x_p
, y_p
);
1560 Node
<T
> found
= findNode(x_pl
, y_pl
, layer
, mag
);
1563 if (null != found
) {
1564 if (2 == me
.getClickCount()) {
1565 setLastMarked(found
);
1569 if (me
.isShiftDown() && Utils
.isControlDown(me
)) {
1570 if (me
.isAltDown()) {
1571 // Remove point and its subtree
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.");
1581 repaint(false, layer
); // keep larger size for repainting, will call calculateBoundingBox on mouseRelesed
1586 if (2 == me
.getClickCount()) {
1587 setLastMarked(null);
1590 if (me
.isAltDown()) {
1594 if (me
.isShiftDown()) {
1595 final Node
<T
>[] ns
= findNearestEdge(x_pl
, y_pl
, layer
, mag
);
1597 found
= createNewNode(x_pl
, y_pl
, layer
, ns
[0]);
1598 insertNode(ns
[0], ns
[1], found
, ns
[0].getConfidence(ns
[1]));
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.");
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
);
1613 repaint(true, layer
);
1619 root
= createNewNode(x_p
, y_p
, layer
, null); // world coords, so calculateBoundingBox will do the right thing
1620 addNode(null, root
, (byte)0);
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
);
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
);
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
);
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
);
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
; }
1672 public void keyPressed(final KeyEvent ke
) {
1674 switch (ProjectToolbar
.getToolId()) {
1675 case ProjectToolbar
.PEN
:
1676 case ProjectToolbar
.BRUSH
:
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!
1693 Utils
.showStatus("Canceled tagging");
1699 if (!show_tag_dialogs
&& KeyEvent
.VK_0
== keyCode
) {
1700 // force dialogs for next key
1701 show_tag_dialogs
= true;
1706 final boolean untag
= null != to_untag
;
1707 final Node
<?
> target
= untag ? to_untag
: to_tag
;
1711 layer_set
.addPreDataEditStep(this);
1713 if (show_tag_dialogs
) {
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);
1719 final Tag t
= layer_set
.askForNewTag(keyCode
);
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;
1730 TreeSet
<Tag
> ts
= layer_set
.getTags(keyCode
);
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())) {
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
) {
1747 for (final Tag tag
: target_tags
) {
1748 if (tag
.getKeyCode() == keyCode
) {
1755 target
.removeTag(t
);
1756 Display
.repaint(layer_set
);
1760 final JPopupMenu popup
= new JPopupMenu();
1761 popup
.add(new JLabel(untag ?
"Untag:" : "Tag:"));
1763 for (final Tag tag
: ts
) {
1764 final JMenuItem item
= new JMenuItem(tag
.toString());
1767 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_0
+ i
, 0, true));
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() {
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...");
1788 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_0
, 0, true));
1789 item
.addActionListener(new ActionListener() {
1791 public void actionPerformed(final ActionEvent ae
) {
1793 layer_set
.askToRemoveTag(keyCode
);
1795 final Tag t
= layer_set
.askForNewTag(keyCode
);
1796 if (null == t
) return;
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
);
1814 if (untag
) target
.removeTag(ts
.first());
1815 else target
.addTag(ts
.first());
1816 Display
.repaint(layer_set
);
1817 layer_set
.addDataEditStep(this);
1821 updateViewData(untag ? to_untag
: to_tag
);
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
);
1839 final int modifiers
= ke
.getModifiers();
1840 final Display display
= Display
.getFront();
1841 final Layer layer
= display
.getLayer();
1844 Coordinate
<Node
<T
>> c
= null;
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);
1858 if (0 == modifiers
) {
1862 display
.center(createCoordinate(root
));
1866 c
= findPreviousBranchOrRootPoint(po
.x
, po
.y
, layer
, dc
);
1867 if (null == c
) return;
1873 c
= findNextBranchOrEndPoint(po
.x
, po
.y
, layer
, dc
);
1874 if (null == c
) return;
1881 if (null == c
) return;
1887 c
= getLastEdited();
1888 if (null == c
) return;
1893 case KeyEvent
.VK_CLOSE_BRACKET
:
1894 display
.animateBrowsingTo(findNearAndGetNext(po
.x
, po
.y
, layer
, dc
));
1897 case KeyEvent
.VK_OPEN_BRACKET
:
1898 display
.animateBrowsingTo(findNearAndGetPrevious(po
.x
, po
.y
, layer
, dc
));
1902 nd
= findClosestNodeW(getNodesToPaint(layer
), po
.x
, po
.y
, dc
.getMagnification());
1904 display
.toLayer(nd
.la
);
1905 if (nd
!= last_visited
) {
1907 display
.getCanvas().repaint(false);
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());
1918 final Node
<T
> last
= getLastVisited();
1919 if (null != last
&& layer
== last
.getLayer()) nd
= last
;
1921 if (null != nd
&& adjustNodeColors(nd
)) {
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() {
1943 public void update() {
1944 nd
.setColor(new Color(red
.getValue(), green
.getValue(), blue
.getValue()));
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]);
1954 if (gd
.wasCanceled()) {
1955 nd
.setColor(original
);
1960 layer_set
.addDataEditStep(this);
1961 final Color c
= new Color(red
.getValue(), green
.getValue(), blue
.getValue());
1962 switch (gd
.getNextChoiceIndex()) {
1964 // this node only: already done
1967 // the downstream slab:
1968 for (final Node
<T
> node
: new Node
.NodeCollection
<T
>(nd
, Node
.SlabIterator
.class)) {
1973 // the entire subtree:
1974 for (final Node
<T
> node
: new Node
.NodeCollection
<T
>(nd
, Node
.BreadthFirstSubtreeIterator
.class)) {
1979 layer_set
.removeLastUndoStep();
1982 layer_set
.addDataEditStep(this);
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);
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());
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()) {
2021 // Paint without arrows
2022 paint(g
, srcRect
, mag
, false, 0xffffffff, layer
, layers
, false, false);
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
) {
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,
2055 if (first
== node
) break;
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. */
2072 public ResultsTable
measure(ResultsTable rt
) {
2073 if (null == root
) return rt
;
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
2101 final Node
<T
> prev
= nd
.findPreviousBranchOrRootPoint();
2103 Utils
.log("ERROR: Can't find the previous branch or root point for node " + nd
);
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
);
2129 /** Expects Rectangle in world coords. */
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;
2135 return null != findFirstIntersectingNode(nodes
, new Area(r
).createTransformedArea(this.at
.createInverse()));
2136 } catch (final NoninvertibleTransformException e
) {
2141 /** Expects Area in world coords. */
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;
2152 return findFirstIntersectingNode(nodes
, area
.createTransformedArea(this.at
.createInverse()));
2153 } catch (final NoninvertibleTransformException e
) {
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
;
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;
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()) {
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) {
2197 tndv
= new TreeNodesDataView(root
);
2203 } catch (final Exception e
) {
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
);
2225 public boolean remove2(final boolean check
) {
2226 if (super.remove2(check
)) {
2227 synchronized (this) {
2229 tndv
.frame
.dispose();
2238 private class TreeNodesDataView
{
2239 private JFrame frame
;
2240 private List
<Node
<T
>> /*branchnodes,
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
,
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
) {
2259 private final class CustomCellRenderer
extends DefaultTableCellRenderer
{
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
);
2269 setForeground(table
.getSelectionForeground());
2270 setBackground(table
.getSelectionBackground());
2272 c
.setForeground(Color
.black
);
2273 c
.setBackground(Color
.white
);
2279 private final class Table
extends JTable
{
2280 private int last_sorted_column
= -1;
2281 private boolean last_sorting_order
= true; // descending == true
2284 getTableHeader().addMouseListener(new MouseAdapter() {
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() {
2298 public void mousePressed(final MouseEvent me
) {
2299 final int row
= Table
.this.rowAtPoint(me
.getPoint());
2300 if (2 == me
.getClickCount()) {
2302 } else if (Utils
.isPopupTrigger(me
)) {
2303 if (!project
.isInputEnabled()) {
2304 Utils
.showMessage("Please wait until the current task completes!");
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() {
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?")) {
2329 generateSubtreeReviewStacks(root
);
2331 else if (gsub
== src
) {
2332 if (!Utils
.check("Really generate review stacks for the subtree?")) {
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?")) {
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();
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
);
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() {
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()))) {
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
));
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;
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();
2444 Utils
.log("Node without review tag!");
2447 // Find a review tag, if any
2448 Tag review_tag
= null;
2449 for (final Tag tag
: tags
) {
2450 if (tag
.toString().startsWith("#R-")) {
2455 if (null == review_tag
) {
2456 Utils
.log("Node without review tag!");
2459 visited_reviews
.add(nd
);
2460 // Find a stack for the review tag, and open it
2461 Tree
.this.openImage(getReviewTagPath(review_tag
), nd
);
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());
2475 frame
.setVisible(true);
2478 private void createGUI() {
2479 this.frame
= new JFrame("Nodes for " + Tree
.this);
2480 frame
.addWindowListener(new WindowAdapter() {
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() {
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() {
2504 public void actionPerformed(final ActionEvent ae
) {
2505 search(search
.getText());
2508 final JPanel pane
= new JPanel();
2509 final GridBagLayout gb
= new GridBagLayout();
2511 final GridBagConstraints c
= new GridBagConstraints();
2516 c
.anchor
= GridBagConstraints
.NORTH
;
2517 c
.fill
= GridBagConstraints
.BOTH
;
2518 c
.insets
= new Insets(4,10,5,2);
2519 gb
.setConstraints(search
, c
);
2523 c
.fill
= GridBagConstraints
.NONE
;
2524 c
.insets
= new Insets(4,0,5,10);
2525 gb
.setConstraints(b
, c
);
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
);
2536 tabs
.add("Search", pane
);
2538 frame
.getContentPane().add(tabs
);
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
>>(),
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
);
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
);
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
) {
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
2597 table_branchnodes
.resort();
2598 table_searchnodes
.resort();
2599 table_endnodes
.resort();
2600 table_allnodes
.resort();
2601 Utils
.revalidateComponent(frame
);
2604 void updateData(final Node
<?
> node
) {
2605 synchronized (nodedata
) {
2606 nodedata
.remove(node
);
2608 SwingUtilities
.invokeLater(new Runnable() { @Override
2610 Utils
.revalidateComponent(frame
);
2613 private void search(final String regex
) {
2614 final StringBuilder sb
= new StringBuilder();
2615 if (!regex
.startsWith("^")) sb
.append("^.*");
2617 if (!regex
.endsWith("$")) sb
.append(".*$");
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
);
2631 this.model_searchnodes
= new NodeTableModel(this.searchnodes
, this.nodedata
);
2632 this.table_searchnodes
.setModel(this.model_searchnodes
);
2633 } catch (final Exception 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
2654 (AreaCalculations
.area
2655 (((AreaTree
.AreaNode
)nd
).getData().getPathIterator(null)))
2656 * cal
.pixelWidth
* cal
.pixelHeight
, 1)).append(' ').append(cal
.getUnits()).append('^').append(2).toString();
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();
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();
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
) {
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";
2696 public String
getColumnName(final int col
) {
2698 case 0: return ""; // listing
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
2711 public int getRowCount() { return nodes
.size(); }
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
);
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;
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
);
2739 ndat
= new NodeData(nd
);
2740 nodedata
.put(nd
, ndat
);
2746 public boolean isCellEditable(final int row
, final int col
) {
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
>>() {
2755 public int compare(Node
<T
> nd1
, Node
<T
> nd2
) {
2757 final Node
<T
> tmp
= nd1
;
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();
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();
2795 fireNodeRemoved(nd
);
2796 if (null == nd
.parent
) {
2797 switch (nd
.getChildrenCount()) {
2799 this.root
= nd
.children
[0];
2800 this.root
.parent
= null;
2801 nd
.children
[0] = null; // does not matter
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
2820 if (null == nd
.children
) {
2821 end_nodes
.remove(nd
);
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
);
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
);
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
) {
2869 calculateBoundingBox(vdt
.layer
);
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());
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. */
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
);
2901 /** Fast and dirty, never returns a false negative but may return a false positive. */
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;
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
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;
2921 } catch (final NoninvertibleTransformException nite
) {
2922 IJError
.print(nite
);
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. */
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;
2939 // Else, remove the set of nodes for that layer
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
) {
2948 continue; // a tree of 1 node
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
);
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
) {
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
);
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
>() {
2987 public ImagePlus
call() {
2989 if (!new File(path
).exists()) {
2990 Utils
.log("Could not find file " + path
);
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
2998 Utils
.log("ERROR: could not open " + path
);
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() {
3005 public void mousePressed(final MouseEvent me
) {
3006 if (2 == me
.getClickCount()) {
3009 // Slices are 1-based: 1<=i<=N
3010 final int slice
= imp
.getCurrentSlice();
3011 if (slice
== imp
.getNSlices()) {
3012 Display
.centerAt(createCoordinate(last
));
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
));
3023 parent
= parent
.getParent();
3029 public void mouseDragged(final MouseEvent me
) {
3030 if (2 == me
.getClickCount()) {
3035 public void mouseReleased(final MouseEvent me
) {
3036 if (2 == me
.getClickCount()) {
3041 for (final MouseListener m
: ml
) stack
.getCanvas().addMouseListener(m
);
3044 } catch (final Exception e
) {
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") {
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.
3070 final TreeNodesDataView tndv
= Tree
.this.tndv
;
3071 if (null != tndv
&& null != tndv
.frame
) Utils
.setEnabled(tndv
.frame
.getContentPane(), false);
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()) {
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();
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
);
3091 rs
[k
] = new Runnable() {
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
);
3101 createReviewStack(nd
.findPreviousBranchOrRootPoint(), nd
, tag
, filepath
, 512, 512, 1.0, ImagePlus
.COLOR_RGB
);
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
]));
3110 } catch (final Exception e
) {
3113 if (null != tndv
&& null != tndv
.frame
) Utils
.setEnabled(tndv
.frame
.getContentPane(), true);
3115 Display
.repaint(getLayerSet());
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
) {
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
) {
3129 Utils
.log("\nERROR: NOT created review stack for " + tag
.toString());
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-")) {
3146 final String path
= getReviewTagPath(tag
);
3147 final File f
= new File(path
);
3150 Utils
.log("FAILED to delete: " + path
+ "\n did NOT remove tag " + tag
);
3157 } catch (final Exception ee
) {
3164 public Bureaucrat
removeReviews() {
3165 return Bureaucrat
.createAndStart(new Worker
.Task("Removing review stacks") { // .. and tags
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());
3180 // Remove directory (even if not empty)
3181 Utils
.removeFile(f
);
3183 Utils
.log("Could not delete some review stacks.\n --> Directory remains: " + f
.getAbsolutePath());
3185 Display
.repaint(getLayerSet());
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
>() {
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;
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
>();
3215 Process
.progressive(root
.getSubtreeNodes(),
3216 new TaskFactory
<Node
<T
>,Object
>() {
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
) {
3230 synchronized (incoming
) {
3239 return (List
<Connector
>[]) new List
[]{outgoing
, incoming
};
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
) {
3268 this.at
.transform(f
, 0, f
, 0, 1);
3272 final Calibration cal
= layer_set
.getCalibration();
3273 p
.x
*= cal
.pixelWidth
;
3274 p
.y
*= cal
.pixelHeight
;
3275 p
.z
*= cal
.pixelWidth
; // not pixelDepth!
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());
3291 if (!(d
instanceof Tree
<?
>)) {
3292 Utils
.log("Ignoring " + d
+ ": not a Tree subclass");
3295 final Tree
<?
> src
= (Tree
<?
>)d
;
3296 if (null == src
.root
) {
3297 Utils
.log("Ignoring empty tree " + src
);
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);
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)
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
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));
3333 Utils
.log("Ignoring " + src
);
3335 final Tree
<?
> copy
= m
.get(src
);
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());
3341 src
.layer_set
.remove(copy
);
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);
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
)
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
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
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?
3383 t
.cacheSubtree((Collection
)t
.root
.getSubtreeNodes());
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
) {
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
);
3406 final Node
<T
> node
= findClosestNodeW(nodes
, x
, y
, magnification
);
3408 Utils
.log("Could not find any node! Zoom in for better precision.");
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") {
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;
3442 Collections
.sort(a
);
3443 name
= name
+ (a
.get(a
.size()-1) + 1);
3446 final Tag tag
= new Tag(name
, KeyEvent
.VK_R
);
3448 final String filepath
= getReviewTagPath(tag
);
3449 Utils
.ensure(filepath
);
3450 createReviewStack(first
, last
, tag
, filepath
, 512, 512, 1.0, ImagePlus
.COLOR_RGB
);
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],
3459 final private float firstx
,
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
) {
3484 this.cal
= tree
.layer_set
.getCalibrationCopy();
3488 final Iterator
<Node
<I
>> it
= path
.iterator();
3490 Node
<?
> first
= it
.next();
3491 if (first
.getChildrenCount() > 1) branch_points
++;
3494 tree
.at
.transform(fpA
, 0, fpA
, 0, 1);
3495 double zA
= first
.la
.getZ();
3500 if (1 == path
.size()) {
3505 while (it
.hasNext()) {
3506 final Node
<?
> second
= it
.next();
3507 if (second
.getChildrenCount() > 1) branch_points
++;
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
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
);
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);
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);
3558 return new MeasurePathDistance
<T
>(this, a
, b
).show(rt
);
3559 } catch (final Exception e
) {
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;
3587 public void 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
));
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
));
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
);
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
>();
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
);
3658 c
= new Color(cm
.getRed(i
), cm
.getGreen(i
), cm
.getBlue(i
));
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
>();
3672 for (final Vertex
<?
> v
: vs
) max
= Math
.max(max
, v
.centrality
);
3674 Utils
.logAll("Branch centrality: all have zero!");
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
);
3682 c
= new Color(cm
.getRed(i
), cm
.getGreen(i
), cm
.getBlue(i
));
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
) {
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());
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
) {
3733 public List
<Node
<T
>> getPath() {
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
) {
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
) {
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();
3773 public double measureDistance() {
3777 public List
<T
> getData() {
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));
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
>>();
3798 Node
<T
> parent
= nd
.getParent();
3799 while (!parent
.hasTag(upstream
)) {
3801 parent
= parent
.getParent();
3802 if (null == parent
) break;
3804 if (null == parent
) continue;
3806 Collections
.reverse(path
);
3807 pairs
.add(new NodePath(parent
, nd
, path
));
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
));
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()) {