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();
}
}