package ini.trakem2.display; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.lang.reflect.InvocationTargetException; import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.scijava.vecmath.Point3f; import ini.trakem2.Project; import ini.trakem2.utils.IJError; import ini.trakem2.utils.M; import ini.trakem2.utils.Utils; /** Can only have one parent, so there aren't cyclic graphs. */ public abstract class Node<T> implements Taggable { /** Maximum possible confidence in an edge (ranges from 0 to 5, inclusive).*/ static public final byte MAX_EDGE_CONFIDENCE = 5; protected Node<T> parent = null; public Node<T> getParent() { return parent; } protected float x, y; public float getX() { return x; } public float getY() { return y; } protected Color color; public Color getColor() { return this.color; } public void setColor(final Color c) { this.color = c; } public void setPosition(final float x, final float y) { this.x = x; this.y = y; } /** Expects two dimensions. */ public void setPosition(final float[] p) { this.x = p[0]; this.y = p[1]; } /** The confidence value of the edge towards the parent; * in other words, how much this node can be trusted to continue from its parent node. * Defaults to MAX_EDGE_CONFIDENCE for full trust, and 0 for none. */ protected byte confidence = MAX_EDGE_CONFIDENCE; public byte getConfidence() { return confidence; } protected Layer la; public Layer getLayer() { return la; } protected Node<T>[] children = null; public ArrayList<Node<T>> getChildrenNodes() { final ArrayList<Node<T>> a = new ArrayList<Node<T>>(); if (null == children) return a; for (int i=0; i<children.length; i++) a.add(children[i]); return a; } /** @return a map of child node vs edge confidence to that child. */ public Map<Node<T>,Byte> getChildren() { final HashMap<Node<T>,Byte> m = new HashMap<Node<T>,Byte>(); if (null == children) return m; for (int i=0; i<children.length; i++) m.put(children[i], children[i].confidence); return m; } public List<Byte> getEdgeConfidence() { final ArrayList<Byte> a = new ArrayList<Byte>(); if (null == children) return a; for (int i=0; i<children.length; i++) a.add(children[i].confidence); return a; } public byte getEdgeConfidence(final Node<T> child) { if (null == children) return (byte)0; for (int i=0; i<children.length; i++) { if (child == children[i]) return children[i].confidence; } return (byte)0; } @Override public String toString() { return new StringBuilder("{:x ").append(x).append(" :y ").append(y).append(" :layer ").append(la.getId()).append('}').toString(); } /** * @param x The X in local coordinates. * @param y The Y in local coordinates. * @param la The Layer where the point represented by this Node sits. */ public Node(final float x, final float y, final Layer la) { this.x = x; this.y = y; this.la = la; } /** To reconstruct from XML, without a layer. * WARNING this method doesn't do any error checking. If "x" or "y" are not present in @param attr, it will simply fail with a NumberFormatException. */ public Node(final HashMap<String,String> attr) { this.x = Float.parseFloat(attr.get("x")); this.y = Float.parseFloat(attr.get("y")); this.la = null; } public void setLayer(final Layer la) { this.la = la; } /** Returns -1 when not added (e.g. if child is null). */ synchronized public final int add(final Node<T> child, final byte conf) { if (null == child) return -1; if (null != child.parent) { Utils.log("WARNING: tried to add a node that already had a parent!"); return -1; } if (null != children) { for (final Node<T> nd : children) { if (nd == child) { Utils.log("WARNING: tried to add a node to a parent that already had the node as a child!"); return -1; } } } enlargeArrays(1); this.children[children.length-1] = child; child.confidence = conf; child.parent = this; return children.length -1; } synchronized public final boolean remove(final Node<T> child) { if (null == children) { Utils.log("WARNING: tried to remove a child from a childless node!"); return false; // no children! } // find its index int k = -1; for (int i=0; i<children.length; i++) { if (child == children[i]) { k = i; break; } } if (-1 == k) { Utils.log("Not a child!"); return false; // not a child! } child.parent = null; if (1 == children.length) { children = null; return true; } // Else, rearrange arrays: final Node<T>[] ch = (Node<T>[])new Node[children.length-1]; System.arraycopy(children, 0, ch, 0, k); System.arraycopy(children, k+1, ch, k, children.length - k -1); children = ch; return true; } private final void enlargeArrays(final int n_more) { if (null == children) { children = (Node<T>[])new Node[n_more]; } else { final Node<T>[] ch = (Node<T>[])new Node[children.length + n_more]; System.arraycopy(children, 0, ch, 0, children.length); final byte[] co = new byte[children.length + n_more]; children = ch; } } /** Paint this node, and edges to parent and children varies according to whether they are included in the to_paint list. * Returns a task (or null) to paint the tags. */ final Runnable paint(final Graphics2D g, final Layer active_layer, final boolean active, final Rectangle srcRect, final double magnification, final Collection<Node<T>> to_paint, final Tree<T> tree, final AffineTransform to_screen, final boolean with_arrows, final boolean with_tags, final boolean with_confidence_boxes, final boolean with_data, Color above, Color below) { // The fact that this method is called indicates that this node is to be painted and by definition is inside the Set to_paint. final double actZ = active_layer.getZ(); final double thisZ = this.la.getZ(); final Color node_color; if (null == this.color) { // this node doesn't have its color set, so use tree color and given above/below colors node_color = tree.color; } else { node_color = this.color; // Depth cue colors may not be in use: if (tree.color == above) above = this.color; if (tree.color == below) below = this.color; } // Which edge color? final Color local_edge_color; if (active_layer == this.la) { local_edge_color = node_color; } // default color else if (actZ > thisZ) { local_edge_color = below; } else if (actZ < thisZ) local_edge_color = above; else local_edge_color = node_color; if (with_data) paintData(g, srcRect, tree, to_screen, local_edge_color, active_layer); //if (null == children && !paint) return null; //final boolean paint = with_arrows && null != tags; // with_arrows acts as a flag for both arrows and tags //if (null == parent && !paint) return null; final float[] fps = new float[4]; final int parent_x, parent_y; fps[0] = this.x; fps[1] = this.y; if (null == parent) { parent_x = parent_y = 0; tree.at.transform(fps, 0, fps, 0, 1); } else { fps[2] = parent.x; fps[3] = parent.y; tree.at.transform(fps, 0, fps, 0, 2); parent_x = (int)((fps[2] - srcRect.x) * magnification); parent_y = (int)((fps[3] - srcRect.y) * magnification); } // To screen coords: final int x = (int)((fps[0] - srcRect.x) * magnification); final int y = (int)((fps[1] - srcRect.y) * magnification); final Runnable tagsTask; if (with_tags && null != tags) { tagsTask = new Runnable() { @Override public void run() { paintTags(g, x, y, local_edge_color); } }; } else tagsTask = null; //if (null == parent) return tagsTask; synchronized (this) { if (null != parent) { // Does the line from parent to this cross the srcRect? // Or what is the same, does the line from parent to this cross any of the edges of the srcRect? // Paint full edge, but perhaps in two halves of different colors if (parent.la == this.la && this.la == active_layer) { // in treeline color // Full edge in local color g.setColor(local_edge_color); g.drawLine(x, y, parent_x, parent_y); if (with_arrows) g.fill(M.createArrowhead(parent_x, parent_y, x, y, magnification)); } else if (this.la == active_layer) { // Proximal half in this color g.setColor(local_edge_color); g.drawLine(parent_x + (x - parent_x)/2, parent_y + (y - parent_y)/2, x, y); if (with_arrows) g.fill(M.createArrowhead(parent_x, parent_y, x, y, magnification)); // Distal either red or blue: Color c = local_edge_color; // If other towards higher Z: if (actZ < parent.la.getZ()) c = above; // If other towards lower Z: else if (actZ > parent.la.getZ()) c = below; // g.setColor(c); g.drawLine(parent_x, parent_y, parent_x + (x - parent_x)/2, parent_y + (y - parent_y)/2); } else if (parent.la == active_layer) { // Distal half in the Displayable or Node color g.setColor(node_color); g.drawLine(parent_x, parent_y, parent_x + (x - parent_x)/2, parent_y + (y - parent_y)/2); // Proximal half in either red or blue: g.setColor(local_edge_color); g.drawLine(parent_x + (x - parent_x)/2, parent_y + (y - parent_y)/2, x, y); if (with_arrows) g.fill(M.createArrowhead(parent_x, parent_y, x, y, magnification)); } else if (thisZ < actZ && actZ < parent.la.getZ()) { // proximal half in red g.setColor(below); g.drawLine(x, y, parent_x + (x - parent_x)/2, (parent_y + (y - parent_y)/2)); if (with_arrows) g.fill(M.createArrowhead(parent_x, parent_y, x, y, magnification)); // distal half in blue g.setColor(above); g.drawLine(parent_x + (x - parent_x)/2, parent_y + (y - parent_y)/2, parent_x, parent_y); } else if (thisZ > actZ && actZ > parent.la.getZ()) { // proximal half in blue g.setColor(above); g.drawLine(x, y, parent_x + (x - parent_x)/2, parent_y + (y - parent_y)/2); if (with_arrows) g.fill(M.createArrowhead(parent_x, parent_y, x, y, magnification)); // distal half in red g.setColor(below); g.drawLine(parent_x + (x - parent_x)/2, parent_y + (y - parent_y)/2, parent_x, parent_y); } else if ((thisZ < actZ && parent.la.getZ() < actZ) || (thisZ > actZ && parent.la.getZ() > actZ)) { g.setColor(local_edge_color); if (to_paint.contains(parent)) { // full edge g.drawLine(x, y, parent_x, parent_y); } else { // paint proximal half g.drawLine(x, y, parent_x + (x - parent_x)/2, parent_y + (y - parent_y)/2); } if (with_arrows) g.fill(M.createArrowhead(parent_x, parent_y, x, y, magnification)); } } else if (with_arrows && !active) { // paint a gray handle for the root g.setColor(active_layer == this.la ? Color.gray : local_edge_color); g.fillOval((int)x - 6, (int)y - 6, 11, 11); g.setColor(Color.black); g.drawString("S", (int)x -3, (int)y + 4); // TODO ensure Font is proper } if (null != children) { final float[] fp = new float[2]; for (final Node<T> child : children) { if (to_paint.contains(child)) continue; fp[0] = child.x; fp[1] = child.y; tree.at.transform(fp, 0, fp, 0, 1); final int cx = (int)(((int)fp[0] - srcRect.x) * magnification), cy = (int)(((int)fp[1] - srcRect.y) * magnification); if (child.la == this.la){ // child in same layer but outside the field of view // paint full edge to it g.setColor(null == child.color ? tree.color : child.color); g.drawLine(x, y, cx, cy); if (with_arrows) g.fill(M.createArrowhead(x, y, cx, cy, magnification)); } else { if (child.la.getZ() < actZ) g.setColor(Color.red); else if (child.la.getZ() > actZ) g.setColor(Color.blue); // paint half edge to the child g.drawLine(x, y, x + (cx - x)/2, y + (cy - y)/2); } } } if (null != parent && active && with_confidence_boxes && (active_layer == this.la || active_layer == parent.la || (thisZ < actZ && actZ < parent.la.getZ()))) { // Draw confidence half-way through the edge final String s = Integer.toString(confidence); final Dimension dim = Utils.getDimensions(s, g.getFont()); g.setColor(Color.white); final int xc = (int)(parent_x + (x - parent_x)/2), yc = (int)(parent_y + (y - parent_y)/2); // y + 0.5*chy - 0.5y = (y + chy)/2 g.fillRect(xc, yc, dim.width+2, dim.height+2); g.setColor(Color.black); g.drawString(s, xc+1, yc+dim.height+1); } } return tagsTask; } static private final Color receiver_color = Color.green.brighter(); protected void paintHandle(final Graphics2D g, final Rectangle srcRect, final double magnification, final Tree<T> t) { paintHandle(g, srcRect, magnification, t, false); } /** Paint in the context of offscreen space, without transformations. */ protected void paintHandle(final Graphics2D g, final Rectangle srcRect, final double magnification, final Tree<T> t, final boolean paint_background) { final Point2D.Double po = t.transformPoint(this.x, this.y); final float x = (float)((po.x - srcRect.x) * magnification); final float y = (float)((po.y - srcRect.y) * magnification); final Color receiver = t.getLastVisited() == this ? Node.receiver_color : null; // paint the node as a draggable point if (null == parent) { // As origin g.setColor(null == receiver ? Color.magenta : receiver); g.fillOval((int)x - 6, (int)y - 6, 11, 11); g.setColor(Color.black); g.drawString("S", (int)x -3, (int)y + 4); // TODO ensure Font is proper } else if (null == children) { // as end point g.setColor(null == receiver ? Color.white : receiver); g.fillOval((int)x - 6, (int)y - 6, 11, 11); g.setColor(Color.black); g.drawString("e", (int)x -4, (int)y + 3); // TODO ensure Font is proper } else if (1 == children.length) { // as a slab: no branches if (paint_background) { g.setColor(Color.black); g.fillOval((int)x - 4, (int)y - 4, 9, 9); } g.setColor(null == receiver ? (null == this.color ? t.getColor() : this.color) : receiver); g.fillOval((int)x - 3, (int)y - 3, 7, 7); } else { // As branch point g.setColor(null == receiver ? Color.yellow : receiver); g.fillOval((int)x - 6, (int)y - 6, 11, 11); g.setColor(Color.black); g.drawString("Y", (int)x -4, (int)y + 4); // TODO ensure Font is proper } } /** Returns a lazy read-only Collection of the nodes belonging to the subtree of this node, including the node itself as the root. * Non-recursive, avoids potential stack overflow. */ public final Collection<Node<T>> getSubtreeNodes() { /* final List<Node<T>> nodes = new ArrayList<Node<T>>(); final LinkedList<Node<T>> todo = new LinkedList<Node<T>>(); todo.add(this); while (!todo.isEmpty()) { // Grab one node from the todo list and add it final Node<T> nd = todo.removeFirst(); nodes.add(nd); // Then add all its children to the todo list if (null != nd.children) { for (final Node<T> child : nd.children) todo.add(child); } } return nodes; */ return new NodeCollection<T>(this, BreadthFirstSubtreeIterator.class); } /** Returns a lazy read-only Collection of the nodes from this node up to the next branch node or end node, inclusive. */ public final Collection<Node<T>> getSlabNodes() { return new NodeCollection<T>(this, SlabIterator.class); } /** Returns a lazy read-only Collection of all branch and end nodes under this node. */ public final Collection<Node<T>> getBranchAndEndNodes() { return new NodeCollection<T>(this, BranchAndEndNodeIterator.class); } /** Returns a lazy read-only Collection of all branch nodes under this node. */ public final Collection<Node<T>> getBranchNodes() { return new NodeCollection<T>(this, BranchNodeIterator.class); } /** Returns a lazy read-only Collection of all end nodes under this node. */ public final Collection<Node<T>> getEndNodes() { return new NodeCollection<T>(this, EndNodeIterator.class); } /** Only this node, not any of its children. */ final public void translate(final float dx, final float dy) { x += dx; y += dy; } /** Returns a recursive copy of this Node subtree, where the copy of this Node is the root. * Non-recursive to avoid stack overflow. */ final public Node<T> clone(final Project pr) { // todo list containing packets of a copied node and the lists of original children and confidence to clone into it final LinkedList<Object[]> todo = new LinkedList<Object[]>(); final Node<T> root = newInstance(x, y, la); root.setData(this.getDataCopy()); root.tags = getTagsCopy(); if (null != this.children) { todo.add(new Object[]{root, this.children}); } final HashMap<Long,Layer> ml; if (pr != la.getProject()) { // Layers must be replaced by their corresponding clones ml = new HashMap<Long,Layer>(); for (final Layer layer : pr.getRootLayerSet().getLayers()) { ml.put(layer.getId(), layer); } } else ml = null; if (null != ml) root.la = ml.get(root.la.getId()); // replace Layer pointer in the copy while (!todo.isEmpty()) { final Object[] o = todo.removeFirst(); final Node<T> copy = (Node<T>)o[0]; final Node<T>[] original_children = (Node<T>[])o[1]; copy.children = (Node<T>[])new Node[original_children.length]; for (int i=0; i<original_children.length; i++) { final Node<T> ochild = original_children[i]; copy.children[i] = newInstance(ochild.x, ochild.y, ochild.la); copy.children[i].setData(ochild.getDataCopy()); copy.children[i].confidence = ochild.confidence; copy.children[i].parent = copy; copy.children[i].tags = ochild.getTagsCopy(); if (null != ml) copy.children[i].la = ml.get(copy.children[i].la.getId()); // replace Layer pointer in the copy if (null != ochild.children) { todo.add(new Object[]{copy.children[i], ochild.children}); } } } return root; } /** Check if this point or the edges to its children are closer to xx,yy than radius, in the 2D plane only. */ final boolean isNear(final float xx, final float yy, final float sqradius) { if (null == children) return sqradius > (Math.pow(xx - x, 2) + Math.pow(yy - y, 2)); // Else, check children: for (int i=0; i<children.length; i++) { if (sqradius > M.distancePointToSegmentSq(xx, yy, 0, // point x, y, 0, // origin of edge (children[i].x - x)/2, (children[i].y - y)/2, 0)) // end of half-edge to child { return true; } } // Check to parent's half segment return null != parent && sqradius > M.distancePointToSegmentSq(xx, yy, 0, // point x, y, 0, // origin of edge (x - parent.x)/2, (y - parent.y)/2, 0); // end of half-edge to parent } public final boolean hasChildren() { return null != children && children.length > 0; } public final int getChildrenCount() { if (null == children) return 0; return children.length; } /** Traverse the tree from this node all the way to the root node, * and count how many nodes apart this node is from the root node: * that is the degree. * Thanks to Johannes Schindelin. */ public final int computeDegree() { int result = 0; for (Node<T> node = this; node != null; node = node.parent) result++; return result; } /** Obtain the (only) list from node a to node b, * including both a (the first element) and b (the last element). * Thanks to Johannes Schindelin. */ public static <I> List<Node<I>> findPath(Node<I> a, Node<I> b) { int degreeA = a.computeDegree(), degreeB = b.computeDegree(); final List<Node<I>> listA = new ArrayList<Node<I>>(), listB = new ArrayList<Node<I>>(); // Traverse upstream the parent chain until finding nodes of the same degree for (; degreeB > degreeA; degreeB--, b = b.parent) listB.add(b); for (; degreeA > degreeB; degreeA--, a = a.parent) listA.add(a); // Traverse upstream the parent chain until finding a common parent node for (; a != b; a = a.parent, b = b.parent) { listA.add(a); listB.add(b); } // Add that common parent node listA.add(a); // add all in reverse for (final ListIterator<Node<I>> it = listB.listIterator(listB.size()); it.hasPrevious(); ) { listA.add(it.previous()); } return listA; } /** Return a map of node vs degree of that node, for the entire subtree (including this node). */ public HashMap<Node<T>,Integer> computeAllDegrees() { final HashMap<Node<T>,Integer> degrees = new HashMap<Node<T>, Integer>(); int degree = 1; ArrayList<Node<T>> next_level = new ArrayList<Node<T>>(); ArrayList<Node<T>> current_level = new ArrayList<Node<T>>(); current_level.add(this); do { for (final Node<T> nd : current_level) { degrees.put(nd, degree); if (null != nd.children) { for (final Node<T> child : nd.children) { next_level.add(child); } } } // rotate lists: current_level.clear(); final ArrayList<Node<T>> tmp = current_level; current_level = next_level; next_level = tmp; degree++; } while (!current_level.isEmpty()); return degrees; } /** Assumes this is NOT a graph with cycles. Non-recursive to avoid stack overflows. */ final void setRoot() { // Works, but can be done in one pass TODO // // Find first the list of nodes from this node to the current root // and then proceed in reverse direction! final LinkedList<Node<T>> path = new LinkedList<Node<T>>(); path.add(this); Node<T> parent = this.parent; while (null != parent) { path.addFirst(parent); parent = parent.parent; } Node<T> newchild = path.removeFirst(); for (final Node<T> nd : path) { // Made nd the parent of newchild (was the opposite) // 1 - Find out the confidence of the edge to the child node: byte conf = MAX_EDGE_CONFIDENCE; for (int i=0; i<newchild.children.length; i++) { if (nd == newchild.children[i]) { conf = newchild.children[i].confidence; break; } } // 2 - Remove the child node from the parent's child list newchild.remove(nd); // 3 - Reverse: add newchild to nd (newchild was parent of nd) newchild.parent = null; nd.add(newchild, conf); // 4 - Prepare next step newchild = nd; } // As root: this.parent = null; // TODO Below, it should work, but it doesn't (?) // It results in all touched nodes not having a parent (all appear as 'S') /* Node child = this; Node parent = this.parent; while (null != parent) { // 1 - Find out the confidence of the edge to the child node: byte conf = MAX_EDGE_CONFIDENCE; for (int i=0; i<parent.children.length; i++) { if (child == parent.children[i]) { conf = parent.children[i].confidence; break; } } // 2 - Remove the child node from the parent's child list parent.remove(child); // 3 - Cache the parent's parent, since it will be overwriten in the next step Node pp = parent.parent; // 4 - Add the parent as a child of the child, with the same edge confidence parent.parent = null; // so it won't be refused child.add(parent, conf); // 5 - prepare next step child = parent; parent = pp; } // Make this node the root node this.parent = null; */ } /** Set the confidence value of this node with its parent. */ synchronized public final boolean setConfidence(final byte conf) { if (conf < 0 || conf > MAX_EDGE_CONFIDENCE) return false; confidence = conf; return true; } /** Adjust the confidence value of this node with its parent. */ final public boolean adjustConfidence(final int inc) { final byte conf = (byte)((confidence&0xff) + inc); if (conf < 0 || conf > MAX_EDGE_CONFIDENCE) return false; confidence = conf; return true; } /** Returns -1 if not a child of this node. */ final byte getConfidence(final Node<T> child) { if (null == children) return -1; for (int i=0; i<children.length; i++) { if (child == children[i]) return children[i].confidence; } return -1; } final int indexOf(final Node<T> child) { if (null == children) return -1; for (int i=0; i<children.length; i++) { if (child == children[i]) return i; } return -1; } synchronized public final Node<T> findPreviousBranchOrRootPoint() { if (null == this.parent) return null; Node<T> parent = this.parent; while (true) { if (1 == parent.children.length) { if (null == parent.parent) return parent; // the root parent = parent.parent; continue; } return parent; } } /** Assumes there aren't any cycles. */ synchronized public final Node<T> findNextBranchOrEndPoint() { Node<T> child = this; while (true) { if (null == child.children || child.children.length > 1) return child; child = child.children[0]; } } public abstract boolean setData(T t); public abstract T getData(); public abstract T getDataCopy(); public abstract Node<T> newInstance(float x, float y, Layer layer); abstract public void paintData(final Graphics2D g, final Rectangle srcRect, final Tree<T> tree, final AffineTransform to_screen, final Color cc, final Layer active_layer); /** Expects Area in local coords. */ public abstract boolean intersects(Area a); /** May return a false positive but never a false negative. * Checks only for itself and towards its parent. */ public boolean isRoughlyInside(final Rectangle localbox) { if (null == parent) { return localbox.contains((int)this.x, (int)this.y); } else { return localbox.intersectsLine(parent.x, parent.y, this.x, this.y); } } /** Returns area in local coords. */ public Area getArea() { return new Area(new Rectangle2D.Float(x, y, 1, 1)); // a "little square" -- sinful! xDDD } /** Returns a list of Patch to link, which lay under the node. Use the given @param aff to transform the Node data before looking up targets. */ public Collection<Displayable> findLinkTargets(final AffineTransform to_world) { float x = this.x, y = this.y; if (null != to_world && !to_world.isIdentity()) { final float[] fp = new float[]{x, y}; to_world.transform(fp, 0, fp, 0, 1); x = fp[0]; y = fp[1]; } return this.la.find(Patch.class, (int)x, (int)y, true); } /** The tags: * null: none * a Tag instance: just one tag * a Tag[] instance: more than one tag, sorted. * * The justification for this seemingly silly storage system is to take the minimal space possible, * while preserving the sortedness of tags. * As expected, the huge storage savings (used to be a TreeSet<Tag>, which has inside a TreeMap, which has a HashMap inside, and so on), * result in heavy computations required to add or remove a Tag, but these operations are rare and thus acceptable. */ Object tags = null; // private to the package /** @return true if the tag wasn't there already. */ @Override synchronized public boolean addTag(final Tag tag) { if (null == this.tags) { // Currently no tags this.tags = tag; return true; } // If not null, there is already at least one tag final Tag[] t2; if (tags instanceof Tag[]) { // Currently more than one tag final Tag[] t1 = (Tag[])tags; for (final Tag t : t1) { if (t.equals(tag)) return false; } t2 = new Tag[t1.length + 1]; System.arraycopy(t1, 0, t2, 0, t1.length); // Add tag as last t2[t2.length -1] = tag; } else { // Currently only one tag if (tag.equals(this.tags)) return false; t2 = new Tag[]{(Tag)this.tags, tag}; } // Sort tags final ArrayList<Tag> al = new ArrayList<Tag>(t2.length); for (final Tag t : t2) al.add(t); Collections.sort(al); this.tags = al.toArray(t2); // reuse t2 array, has the right size return true; } /** @return true if the tag was there. */ @Override synchronized public boolean removeTag(final Tag tag) { if (null == tags) return false; // no tags if (tags instanceof Tag[]) { // Currently more than one tag final Tag[] t1 = (Tag[])this.tags; for (int i=0; i<t1.length; i++) { if (t1[i].equals(tag)) { // remove: if (2 == t1.length) { this.tags = 0 == i ? t1[1] : t1[0]; } else { final Tag[] t2 = new Tag[t1.length -1]; if (0 == i) { System.arraycopy(t1, 1, t2, 0, t2.length); } else if (t1.length -1 == i) { System.arraycopy(t1, 0, t2, 0, t2.length); } else { System.arraycopy(t1, 0, t2, 0, i); System.arraycopy(t1, i+1, t2, i, t2.length - i); } this.tags = t2; } return true; } } return false; } else { // Currently just one tag if (this.tags.equals(tag)) { this.tags = null; } return false; } } protected final void copyProperties(final Node<?> nd) { this.confidence = nd.confidence; this.tags = nd.getTagsCopy(); } synchronized private final Object getTagsCopy() { if (null == this.tags) return null; if (this.tags instanceof Tag) return this.tags; final Tag[] t1 = (Tag[])this.tags; final Tag[] t2 = new Tag[t1.length]; System.arraycopy(t1, 0, t2, 0, t1.length); return t2; } synchronized public boolean hasTag(final Tag t) { if (null == this.tags) return false; return getTags().contains(t); } /** @return a shallow copy of the tags set, if any, or null. */ @Override synchronized public Set<Tag> getTags() { if (null == tags) return null; final TreeSet<Tag> ts = new TreeSet<Tag>(); if (tags instanceof Tag[]) { for (final Tag t : (Tag[])this.tags) ts.add(t); } else { ts.add((Tag)this.tags); } return ts; } /** @return the tags, if any, or null. */ @Override synchronized public Set<Tag> removeAllTags() { final Set<Tag> tags = getTags(); this.tags = null; return tags; } private void paintTags(final Graphics2D g, final int x, final int y, Color background_color) { final int ox = x + 20; int oy = y + 20; Color fontcolor = Color.blue; if (Color.red == background_color || Color.blue == background_color) fontcolor = Color.white; else background_color = Taggable.TAG_BACKGROUND; // so the Color indicated in the parameter background_color is used only as a flag final Stroke stroke = g.getStroke(); g.setStroke(Taggable.DASHED_STROKE); g.setColor(background_color); g.drawLine(x, y, ox, oy); g.setStroke(stroke); final Tag[] tags = this.tags instanceof Tag[] ? (Tag[])this.tags : new Tag[]{(Tag)this.tags}; for (final Tag ob : tags) { final String tag = ob.toString(); final Dimension dim = Utils.getDimensions(tag, g.getFont()); final int arc = (int)(dim.height / 3.0f); final RoundRectangle2D rr = new RoundRectangle2D.Float(ox, oy, dim.width+4, dim.height+2, arc, arc); g.setColor(background_color); g.fill(rr); g.setColor(fontcolor); g.drawString(tag, ox +2, oy +dim.height -1); oy += dim.height + 3; } } public void apply(final mpicbg.models.CoordinateTransform ct, final Area roi) { final double[] fp = new double[]{x, y}; ct.applyInPlace(fp); this.x = (float)fp[0]; this.y = (float)fp[1]; } public void apply(final VectorDataTransform vlocal) { for (final VectorDataTransform.ROITransform rt : vlocal.transforms) { // Apply only the first one that contains the point if (rt.roi.contains(x, y)) { final double[] fp = new double[]{x, y}; rt.ct.applyInPlace(fp); x = (float)fp[0]; y = (float)fp[1]; break; } } } public Point3f asPoint() { return new Point3f(x, y, (float)la.getZ()); } /** Apply @param aff to the data, not to the x,y position. */ protected void transformData(final AffineTransform aff) {} // ==================== Node Iterators /** Stateful abstract Node iterator. */ static public abstract class NodeIterator<I> implements Iterator<Node<I>> { Node<I> next; public NodeIterator(final Node<I> first) { this.next = first; } @Override public boolean hasNext() { return null != next; } @Override public void remove() {} } static public abstract class SubtreeIterator<I> extends NodeIterator<I> { final LinkedList<Node<I>> todo = new LinkedList<Node<I>>(); public SubtreeIterator(final Node<I> first) { super(first); todo.add(first); } @Override public boolean hasNext() { return todo.size() > 0; } } /** For a given starting node, iterates over the complete set of children nodes, recursively and breadth-first. */ static public class BreadthFirstSubtreeIterator<I> extends SubtreeIterator<I> { public BreadthFirstSubtreeIterator(final Node<I> first) { super(first); } @Override public Node<I> next() { if (todo.isEmpty()) { next = null; return null; } next = todo.removeFirst(); if (null != next.children) { for (int i=0; i<next.children.length; i++) todo.add(next.children[i]); } return next; } } static public abstract class FilteredIterator<I> extends SubtreeIterator<I> { public FilteredIterator(final Node<I> first) { super(first); prepareNext(); } public abstract boolean accept(final Node<I> node); private final void prepareNext() { while (todo.size() > 0) { final Node<I> nd = todo.removeFirst(); if (null != nd.children) { for (int i=0; i<nd.children.length; i++) todo.add(nd.children[i]); } if (accept(nd)) { next = nd; return; } } next = null; } @Override public boolean hasNext() { return null != next; } @Override public Node<I> next() { try { return next; } finally { prepareNext(); } //final Node<I> node = next; //prepareNext(); //return node; } } static public class BranchAndEndNodeIterator<I> extends FilteredIterator<I> { public BranchAndEndNodeIterator(final Node <I> first) { super(first); } @Override public boolean accept(final Node<I> node) { return 1 != node.getChildrenCount(); } } static public class BranchNodeIterator<I> extends FilteredIterator<I> { public BranchNodeIterator(final Node <I> first) { super(first); } @Override public boolean accept(final Node<I> node) { return node.getChildrenCount() > 1; } } static public class EndNodeIterator<I> extends FilteredIterator<I> { public EndNodeIterator(final Node <I> first) { super(first); } @Override public boolean accept(final Node<I> node) { return 0 == node.getChildrenCount(); } } /** For a given starting node, iterates all the way to the next end node or branch node, inclusive. */ static public class SlabIterator<I> extends SubtreeIterator<I> { public SlabIterator(final Node<I> first) { super(first); } @Override public Node<I> next() { if (todo.isEmpty()) { next = null; return null; // reached an end node or branch node } final Node<I> next = todo.removeFirst(); if (null == next.children || next.children.length > 1) { this.next = null; return next; // reached an end node or branch node } todo.add(next.children[0]); this.next = next; return next; } } /** Read-only Collection with a very expensive size(). * It is meant for traversing Node subtrees. */ static public class NodeCollection<I> extends AbstractCollection<Node<I>> { final Node<I> first; final Class<?> type; public NodeCollection(final Node<I> first, final Class<?> type) { this.first = first; this.type = type; } @Override public Iterator<Node<I>> iterator() { try { return (Iterator<Node<I>>) type.getConstructor(Node.class).newInstance(first); } catch (final NoSuchMethodException nsme) { IJError.print(nsme); } catch (final InvocationTargetException ite) { IJError.print(ite); } catch (final IllegalAccessException iae) { IJError.print(iae); } catch (final InstantiationException ie) { IJError.print(ie); } return null; } /** Override to avoid calling size(). */ @Override public boolean isEmpty() { return null == first; } /** WARNING: O(n) operation: will traverse the whole collection. */ @Override public int size() { int count = 0; final Iterator<Node<I>> it = iterator(); while (it.hasNext()) { count++; it.next(); } return count; } /** Override to avoid calling size(), which would iterate the whole list just for that. */ @Override public Node<I>[] toArray() { Node<I>[] a = (Node<I>[])new Node[10]; int next = 0; for (final Iterator<Node<I>> it = iterator(); it.hasNext(); ) { if (a.length == next) { final Node[] b = new Node[a.length + 10]; System.arraycopy(a, 0, b, 0, a.length); a = b; } a[next++] = it.next(); } return next < a.length ? Arrays.copyOf(a, next) : a; } @Override public<Y> Y[] toArray(final Y[] a) { final Node<I>[] b = toArray(); if (a.length < b.length) { return (Y[])b; } System.arraycopy(b, 0, a, 0, b.length); if (a.length > b.length) a[b.length] = null; // null-terminate return a; } } // ============= Operations on collections of nodes /** An operation to be applied to a specific Node. */ static public interface Operation<I> { public void apply(final Node<I> nd) throws Exception; } protected final void apply(final Operation<T> op, final Iterator<Node<T>> nodes) throws Exception { while (nodes.hasNext()) op.apply(nodes.next()); } /** Apply @param op to this Node and all its subtree nodes. */ public void applyToSubtree(final Operation<T> op) throws Exception { apply(op, new BreadthFirstSubtreeIterator<T>(this)); /* final Node<?> first = this; apply(op, new Iterable<Node<?>>() { public Iterator<Node<?>> iterator() { return new NodeIterator(first) { public final boolean hasNext() { if (null == next.children) return false; else if (1 == next.children.length) { next = next.children[0]; return true; } return false; } }; } }); */ } /** Apply @param op to this Node and all its subtree nodes until reaching a branch node or end node, inclusive. */ public void applyToSlab(final Operation<T> op) throws Exception { apply(op, new SlabIterator<T>(this)); } public boolean hasSameTags(final Node<?> other) { if (null == this.tags && null == other.tags) return true; final Set<Tag> t1 = getTags(), t2 = other.getTags(); if (null == t1 || null == t2) return false; // at least one is not null t1.removeAll(t2); return t1.isEmpty(); } }