KD-Tree which nodes are visited when finding nearest neighbor - algorithm

Given these points (7,3), (10,5), (9,0), (5,8), (3,2), (8,1), I need to create a balanced KD Tree such that the first level of the KD Tree is split along the x-axis, and when there are two medians we pick the β€œlarger” one as the root of subtree. After building it I need to list the nodes that get visited when trying to find the nearest neighbor of the point (2,4). Here is the tree I built using given points above:
Here is the KD-Tree I've built
Im very confused about finding the nearest neighbor, And i have to list the nodes that get visited when the tree is finding the point (2,4). So far I think it visits (8,1) -> (7,3) -> (5,8). But what comes after that?? Which nodes get visited?

Your k-d tree is correct.
Nearest-neighbor algorithm
The k-d tree nearest-neighbor search traverses the tree by alternating between two phases:
Go-down phase:
Note the distance between your input point and the current node in the tree (the actual distance, not the distance on an axis).
Alternating between x-axis and y-axis, compare the axis-coordinate of your input point with the axis-coordinate of the current node in the tree to determine which sub-tree to go down to.
Repeat Go-down phase until you reached the bottom of the tree, then enter Go-back-up phase.
Go-back-up phase:
Go up one level. If you can't go up, you are done.
If you already did a Go-down phase on both sub-trees of the current node, repeat Go-back-up.
If the actual distance to the best neighbor you have found so far is closer than the axis-distance between your input node and the current node in the tree, repeat Go-back-up.
Otherwise, enter Go-down phase on the sub-tree opposite to the one from where you came.
Your example
Here is a sketch of your k-d tree to make it more clear:
Applying these steps on your example tree and input node (2,4):
Start with Go-down phase at the root node (8,1).
Distance between (8,1) and input node (2,4) is 6.708, so (8,1) is our currently known nearest neighbor. The current axis is x, so we compare 8 and 2 and we see we have to go to the left sub-tree.
Current node is (7,3). Distance between (7,3) and input node (2,4) is 5.099, which is better than the previous best-known distance, so (7,3) becomes our new nearest neighbor. The current axis is y, so we compare 3 and 4 and we see have to go to the right sub-tree.
Current node is (5,8). Distance between (5,8) and input node (2,4) is 5.000, which is smaller than the previous best-known distance, so (5,8) becomes our new nearest neighbor. Current axis is x, but we cannot go down any further, so we enter Go-back-up phase.
We go back up to (7,3). Current axis is y. The y-distance between (7,3) and the input node (2,4) is |3-4| = 1, which is smaller than 5, the distance to the currently known nearest neighbor. Therefore, we have to enter Go-back-down phase on the other sub-tree. You can see it in the picture: The distance between the input point (S) and (5,8) is greater than the distance between (S) and the separation line that goes through (7,3). This means there could be a nearer neighbor on the other side of the separation line.
Current node is (3,2). Distance between (3,2) and input node (2,4) is 2.236 which is better than the previously known best distance, so (3,2) becomes our currently known closest neighbor. The current axis is x, but we cannot go any further, so we enter Go-back-up phase.
We go back up to (7,3). Current axis is y. We visited both sub-trees of this node, so we repeat Go-back-up phase.
We go back up to (8,1). Current axis is x. The x-distance between (8,1) and the input node (2,4) is |8-2| = 6, which is larger than the distance to the currently known closest neighbor, so we repeat Go-back-up phase. You can again see it in the picture: The distance between the input point (S) and the current nearest neighbor (3,2) is smaller than the distance between (S) and the separation line that goes through (8,1). This means that there cannot be a nearer neighbor on the other side of the separation line.
We cannot go up any further, so we are done.
The nodes we visited were: (8,1), (7,3), (5,8), (7,3), (3,2), (7,3), (8,1). The nearest neighbor we found was (3,2) with distance 2.236.

I use this code in a similar issue, of course you should use x and y instead of lat and lon, I hope I could help.
class LocationKDTree {
private static final int K = 3; // 3-d tree
private final Node tree;
public LocationKDTree(#Nonnull final List<Location> locations) {
final List<Node> nodes = new ArrayList<>(locations.size());
for (final Location location : locations) {
nodes.add(new Node(location));
}
tree = buildTree(nodes, 0);
}
#Nullable
public Location findNearest(final double latitude, final double longitude) {
final Node node = findNearest(tree, new Node(latitude, longitude), 0);
return node == null ? null : node.location;
}
private static Node findNearest(final Node current, final Node target, final int depth) {
final int axis = depth % K;
final int direction = getComparator(axis).compare(target, current);
final Node next = (direction < 0) ? current.left : current.right;
final Node other = (direction < 0) ? current.right : current.left;
Node best = (next == null) ? current : findNearest(next, target, depth + 1);
if (current.euclideanDistance(target) < best.euclideanDistance(target)) {
best = current;
}
if (other != null) {
if (current.verticalDistance(target, axis) < best.euclideanDistance(target)) {
final Node possibleBest = findNearest(other, target, depth + 1);
if (possibleBest.euclideanDistance(target) < best.euclideanDistance(target)) {
best = possibleBest;
}
}
}
return best;
}
#Nullable
private static Node buildTree(final List<Node> items, final int depth) {
if (items.isEmpty()) {
return null;
}
Collections.sort(items, getComparator(depth % K));
final int index = items.size() / 2;
final Node root = items.get(index);
root.left = buildTree(items.subList(0, index), depth + 1);
root.right = buildTree(items.subList(index + 1, items.size()), depth + 1);
return root;
}
private static class Node {
Node left;
Node right;
Location location;
final double[] point = new double[K];
Node(final double latitude, final double longitude) {
point[0] = (double) (cos(toRadians(latitude)) * cos(toRadians(longitude)));
point[1] = (double) (cos(toRadians(latitude)) * sin(toRadians(longitude)));
point[2] = (double) (sin(toRadians(latitude)));
}
Node(final Location location) {
this(location.latitude, location.longitude);
this.location = location;
}
double euclideanDistance(final Node that) {
final double x = this.point[0] - that.point[0];
final double y = this.point[1] - that.point[1];
final double z = this.point[2] - that.point[2];
return x * x + y * y + z * z;
}
double verticalDistance(final Node that, final int axis) {
final double d = this.point[axis] - that.point[axis];
return d * d;
}
}
private static Comparator<Node> getComparator(final int i) {
return NodeComparator.values()[i];
}
private static enum NodeComparator implements Comparator<Node> {
x {
#Override
public int compare(final Node a, final Node b) {
return Double.compare(a.point[0], b.point[0]);
}
},
y {
#Override
public int compare(final Node a, final Node b) {
return Double.compare(a.point[1], b.point[1]);
}
},
z {
#Override
public int compare(final Node a, final Node b) {
return Double.compare(a.point[2], b.point[2]);
}
}
}
}
and location class :
class Location {
public double latitude;
public double longitude;
public String name;
}

Related

Is there an algorithm to separate polygons that share an edge?

I have a list of vertices that define a polygon, a list of perimeter edges that connect those vertices and define the outline of the polygon, and a list of inner edges connecting vertices, effectively splitting the polygon. None of the edges intersect each other (they only meet at the start and end points).
I want to partition the bigger polygon into its smaller components by splitting it at the inner edges. Basically I need to know which sets of vertices are part of a polygon that has no intersecting edges.
Basically, this is the information I have:
The vertices [0, 1, 3, 5, 7, 9, 11, 13, 15] define the outside perimeter of my polygon and the blue edge (5, 13) is an inner edge splitting that perimeter into two polygons. (Disregard the horizontal purple lines, they are the result of the trapezoidalization of the polygon to find the edge (5, 13). They have no further meaning)
This is what I want to get to:
One polygon is defined by the vertices [0, 1, 3, 5, 13, 15] and the other is defined by [5, 7, 9, 11, 13].
I need a solution that works for any number of splitting inner edges.
In the end, I would like to be able to partition a polygon like the following:
The sub-polygons are not necessarily convex. That might be of importance for any algorithm depending on it. The red sub-polygon has a concave vertex (13) for example.
My idea initially was to traverse the inside of each sub-polygon in a clockwise or counter-clockwise direction, keeping track of the vertices I encounter and their order. However I am having trouble finding a starting edge/vertex and guaranteeing that the next cw or ccw point is in fact on the inside of that sub-polygon I want to extract.
I tried to google for solutions but this is a field of mathematics I am too unfamiliar with to know what to search for. An idea/algorithm of how to tackle this problem would be much appreciated!
(I don't need any actual code, just an explanation of how to do this or pseudocode would totally suffice)
Now, unfortunately I don't have some code to show as I need a concept to try out first. I don't know how to tackle this problem and thus can't code anything that could accomplish what I need it to do.
EDIT:
This is just one step in what I am trying to do ultimately, which is polygon triangulation. I have read numerous solutions to that problem and wanted to implement it via trapezoidalization to get monotone polygons and ultimately triangulate those. This is basically the last step of (or I guess the next step after) the trapezoidalization, which is never explained in any resources on the topic that i could find.
The end result of the trapezoidalization are the inner edges which define the split into (in my case vertically monotone) polygons. I just need to split the polygons along those edges "datastructurally" so I can work on the subpolygons individually. I hope that helps to clarify things.
The key of the algorithm that you need, is to know how edges can be ordered:
Finding out which is the next edge in (counter)clockwise order
You can calculate the absolute angle, of an edge from node i to node j, with the following formula:
atan2(jy-iy, jx-ix)
See atan2
The relative angle between an edge (i, j) and (j, k) is then given by:
atan2(ky-jy, kx-jx) - atan2(jy-iy, jx-ix)
This expression may yield angles outside of the [-πœ‹, πœ‹] range, so you should map the result back into that range by adding or subtracting 2πœ‹.
So when you have traversed an edge (i, j) and need to choose the next edge (j, k), you can then pick the edge that has the smallest relative angle.
The Algorithm
The partitioning algorithm does not really need to know upfront which edges are internal edges, so I'll assume you just have an edge list. The algorithm could look like this:
Create an adjacency list, so you have a list of neighboring vertices for every vertex.
Add every edge to this adjacency list in both directions, so actually adding two directed edges for each original edge
Pick a directed edge (i, j) from the adjacency list, and remove it from there.
Define a new polygon and add vertex i as its first vertex.
Repeat until you get back to the first vertex in the polygon that's being constructed:
add vertex j to the polygon
find vertex k among the neighbors of vertex j that is not vertex i, and which minimizes the relative angle with the above given formula.
add this angle to a sum
Delete this directed edge from the neighbors of vertex j, so it will never be visited again
let i = j, j = k
If the sum of angles is positive (it will be 2πœ‹) then add the polygon to the list of "positive" polygons, otherwise (it will be -2πœ‹) add it to an alternative list.
Keep repeating from step 2 until there are no more directed edges in the adjacency list.
Finally you'll have two lists of polygons. One list will only have one polygon. This will be the original, outer polygon, and can be ignored. The other list will have the partitioning.
As a demo, here is some code in a runnable JavaScript snippet. It uses one of the examples you pictured in your question (but will consecutive vertex numbering), finds the partitioning according to this algorithm, and shows the result by coloring the polygons that were identified:
function partition(nodes, edges) {
// Create an adjacency list
let adj = [];
for (let i = 0; i < nodes.length; i++) {
adj[i] = []; // initialise the list for each node as an empty one
}
for (let i = 0; i < edges.length; i++) {
let a = edges[i][0]; // Get the two nodes (a, b) that this edge connects
let b = edges[i][1];
adj[a].push(b); // Add as directed edge in both directions
adj[b].push(a);
}
// Traverse the graph to identify polygons, until none are to be found
let polygons = [[], []]; // two lists of polygons, one per "winding" (clockwise or ccw)
let more = true;
while (more) {
more = false;
for (let i = 0; i < nodes.length; i++) {
if (adj[i].length) { // we have unvisited directed edge(s) here
let start = i;
let polygon = [i]; // collect the vertices on a new polygon
let sumAngle = 0;
// Take one neighbor out of this node's neighbor list and follow a path
for (let j = adj[i].pop(), next; j !== start; i = j, j = next) {
polygon.push(j);
// Get coordinates of the current edge's end-points
let ix = nodes[i][0];
let iy = nodes[i][1];
let jx = nodes[j][0];
let jy = nodes[j][1];
let startAngle = Math.atan2(jy-iy, jx-ix);
// In the adjacency list of node j, find the next neighboring vertex in counterclockwise order
// relative to node i where we came from.
let minAngle = 10; // Larger than any normalised angle
for (let neighborIndex = 0; neighborIndex < adj[j].length; neighborIndex++) {
let k = adj[j][neighborIndex];
if (k === i) continue; // ignore the reverse of the edge we came from
let kx = nodes[k][0];
let ky = nodes[k][1];
let relAngle = Math.atan2(ky-jy, kx-jx) - startAngle; // The "magic"
// Normalise the relative angle to the range [-PI, +PI)
if (relAngle < -Math.PI) relAngle += 2*Math.PI;
if (relAngle >= Math.PI) relAngle -= 2*Math.PI;
if (relAngle < minAngle) { // this one comes earlier in counterclockwise order
minAngle = relAngle;
nextNeighborIndex = neighborIndex;
}
}
sumAngle += minAngle; // track the sum of all the angles in the polygon
next = adj[j][nextNeighborIndex];
// delete the chosen directed edge (so it cannot be visited again)
adj[j].splice(nextNeighborIndex, 1);
}
let winding = sumAngle > 0 ? 1 : 0; // sumAngle will be 2*PI or -2*PI. Clockwise or ccw.
polygons[winding].push(polygon);
more = true;
}
}
}
// return the largest list of polygons, so to exclude the whole polygon,
// which will be the only one with a winding that's different from all the others.
return polygons[0].length > polygons[1].length ? polygons[0] : polygons[1];
}
// Sample input:
let nodes = [[59,25],[26,27],[9,59],[3,99],[30,114],[77,116],[89,102],[102,136],[105,154],[146,157],[181,151],[201,125],[194,83],[155,72],[174,47],[182,24],[153,6],[117,2],[89,9],[97,45]];
let internalEdges = [[6, 13], [13, 19], [19, 6]];
// Join outer edges with inner edges to an overall list of edges:
let edges = nodes.map((a, i) => [i, (i+1)%nodes.length]).concat(internalEdges);
// Apply algorithm
let polygons = partition(nodes, edges);
// Report on results
document.querySelector("div").innerHTML =
"input polygon has these points, numbered 0..n<br>" +
JSON.stringify(nodes) + "<br>" +
"resulting polygons, by vertex numbers<br>" +
JSON.stringify(polygons)
// Graphics handling
let io = {
ctx: document.querySelector("canvas").getContext("2d"),
drawEdges(edges) {
for (let [a, b] of edges) {
this.ctx.moveTo(...a);
this.ctx.lineTo(...b);
this.ctx.stroke();
}
},
colorPolygon(polygon, color) {
this.ctx.beginPath();
this.ctx.moveTo(...polygon[0]);
for (let p of polygon.slice(1)) {
this.ctx.lineTo(...p);
}
this.ctx.closePath();
this.ctx.fillStyle = color;
this.ctx.fill();
}
};
// Display original graph
io.drawEdges(edges.map(([a,b]) => [nodes[a], nodes[b]]));
// Color the polygons that the algorithm identified
let colors = ["red", "blue", "silver", "purple", "green", "brown", "orange", "cyan"];
for (let polygon of polygons) {
io.colorPolygon(polygon.map(i => nodes[i]), colors.pop());
}
<canvas width="400" height="180"></canvas>
<div></div>

Calculate area given list of directions

Let's say you're given a list of directions:
up, up, right, down, right, down, left, left
If you follow the directions, you will always return to the starting location. Calculate the area of the shape that you just created.
The shape formed by the directions above would look something like:
___
| |___
|_______|
Clearly, from the picture, you can see that the area is 3.
I tried to use a 2d matrix to trace the directions, but unsure how to get the area from that...
For example, in my 2d array:
O O
O O O
O O O
This is probably not a good way of handling this, any ideas?
Since the polygon you create has axis-aligned edges only, you can calculate the total area from vertical slabs.
Let's say we are given a list of vertices V. I assume we have wrapping in this list, so we can query V.next(v) for every vertex v in V. For the last one, the result is the first.
First, try to find the leftmost and rightmost point, and the vertex where the leftmost point is reached (in linear time).
x = 0 // current x-position
xMin = inf, xMax = -inf // leftmost and rightmost point
leftVertex = null // leftmost vertex
foreach v in V
x = x + (v is left ? -1 : v is right ? 1 : 0)
xMax = max(x, xMax)
if x < xMin
xMin = x
leftVertex = V.next(v)
Now we create a simple data structure: for every vertical slab we keep a max heap (a sorted list is fine as well, but we only need to repetitively fetch the maximum element in the end).
width = xMax - xMin
heaps = new MaxHeap[width]
We start tracing the shape from vertex leftVertex now (the leftmost vertex we found in the first step). We now choose that this vertex has x/y-position (0, 0), just because it is convenient.
x = 0, y = 0
v = leftVertex
do
if v is left
x = x-1 // use left endpoint for index
heaps[x].Add(y) // first dec, then store
if v is right
heaps[x].Add(y) // use left endpoint for index
x = x+1 // first store, then inc
if v is up
y = y+1
if v is down
y = y-1
v = V.next(v)
until v = leftVertex
You can build this structure in O(n log n) time, because adding to a heap costs logarithmic time.
Finally, we need to compute the area from the heap. For a well-formed input, we need to get two contiguous y-values from the heap and subtract them.
area = 0
foreach heap in heaps
while heap not empty
area += heap.PopMax() - heap.PopMax() // each polygon's area
return area
Again, this takes O(n log n) time.
I ported the algorithm to a java implementation (see Ideone). Two sample runs:
public static void main (String[] args) {
// _
// | |_
// |_ _ |
Direction[] input = { Direction.Up, Direction.Up,
Direction.Right, Direction.Down,
Direction.Right, Direction.Down,
Direction.Left, Direction.Left };
System.out.println(computeArea(input));
// _
// |_|_
// |_|
Direction[] input2 = { Direction.Up, Direction.Right,
Direction.Down, Direction.Down,
Direction.Right, Direction.Up,
Direction.Left, Direction.Left };
System.out.println(computeArea(input2));
}
Returns (as expected):
3
2
Assuming some starting point (say, (0,0)) and the y direction is positive upwards:
left adds (-1,0) to the last point.
right adds (+1,0) to the last point.
up adds (0,+1) to the last point.
down adds (0,-1) to the last point.
A sequence of directions would then produce a list of (x,y) vertex co-ordinates from which the area of the resulting (implied closed) polygon can be found from How do I calculate the surface area of a 2d polygon?
EDIT
Here's an implementation and test in Python. The first two functions are from the answer linked above:
def segments(p):
return zip(p, p[1:] + [p[0]])
def area(p):
return 0.5 * abs(sum(x0*y1 - x1*y0
for ((x0, y0), (x1, y1)) in segments(p)))
def mkvertices(pth):
vert = [(0,0)]
for (dx,dy) in pth:
vert.append((vert[-1][0]+dx,vert[-1][1]+dy))
return vert
left = (-1,0)
right = (+1,0)
up = (0,+1)
down = (0,-1)
# _
# | |_
# |__|
print (area(mkvertices([up, up, right, down, right, down, left, left])))
# _
# |_|_
# |_|
print (area(mkvertices([up, right, down, down, right, up, left, left])))
Output:
3.0
0.0
Note that this approach fails for polygons that contain intersecting lines as in the second example.
This can be implemented in place using Shoelace formula for simple polygons.
For each segment (a, b) we have to calculate (b.x - a.x)*(a.y + b.y)/2. The sum over all segments is the signed area of a polygon.
What's more, here we're dealing only with axis aligned segments of length 1. Vertical segments can be ignored because b.x - a.x = 0.
Horizontal segments have a.y + b.y / 2 = a.y = b.y and b.x - a.x = +-1.
So in the end we only have to keep track of y and the area added is always +-y
Here is a sample C++ code:
#include <iostream>
#include <vector>
enum struct Direction
{
Up, Down, Left, Right
};
int area(const std::vector<Direction>& moves)
{
int area = 0;
int y = 0;
for (auto move : moves)
{
switch(move)
{
case Direction::Left:
area += y;
break;
case Direction::Right:
area -= y;
break;
case Direction::Up:
y -= 1;
break;
case Direction::Down:
y += 1;
break;
}
}
return area < 0 ? -area : area;
}
int main()
{
std::vector<Direction> moves{{
Direction::Up,
Direction::Up,
Direction::Right,
Direction::Down,
Direction::Right,
Direction::Down,
Direction::Left,
Direction::Left
}};
std::cout << area(moves);
return 0;
}
I assume there should be some restrictions on the shapes you are drawing (Axis aligned, polygonal graph, closed, non intersecting lines) to be able to calculate the area.
Represent the the shape using segments, each segments consists of two points, each has two coordinates: x and y.
Taking these assumptions into consideration, we can say that any horizontal segment has one parallel segment that has the same x dimensions for its two points but different y dimensions.
The surface area between these two segments equal the hight difference between them.Summing the area for all the horizontal segments gives you the total surface area of the shape.

Maximum projection on x-axis of a binary tree

Given a binary tree in a coordinate plane with its root having coordinates (x,y) . We need to find the maximum projection on x-axis of this tree. That is , basically we need to find the MAXIMUM width of this tree. The tree can be skew as well.
Examples :
1
/ \
2 3
/ / \
4 5 6
Width = 5
1
/ \
2 3
/
4
Width = 4
1
/
2
/
4
Width = 3
My logic was to basically find the left most node and the right most node and subtract their x-coordinates. On going from root to left subtree x becomes x-1 and y becomes y+1 and on going to right subtree x becomes x+1. Upon finding these 2 coordinates xLeft and xRight , we can find the maximum width. But I was having trouble coding it.
Can anyone tell how to go about it ? It is not a homework question , but it was a programming puzzle I read somewhere.
This is a level-order traversal problem. As you traverse the tree in level order, track the width of the largest level. When you are done, the leftmost node and the rightmost node at that level will give you the final projection you are looking for.
EDIT:
mbeckish: The above solution assumes the question was about the largest cross-section. But if that's not the case, level order still works. Except the code must track both minX and maxX during traversal. minX would track the leftmost node and maxX would track the rightmost node. Then the answer would be maxX-minX+1.
This site has a number of well-documented bst traversal code you can modify.
You can solve this by doing a standard tree traversal algorithm while maintaining the x coordinate of the current node. Whenever you go left, you decrement x, and whenever you go right, you decrement x. This is shown here:
void ExtremalNodes(Node* curr, int x, int& maxX, int& minX) {
if (curr == nullptr) return;
maxX = std::max(x, maxX);
minX = std::min(x, minY);
ExtremalNodes(curr->left, x - 1, maxX, minX);
ExtremalNodes(curr->right, x + 1, maxX, minX);
}
int TreeProjection(Node* root) {
if (root == nullptr) return 0;
int maxX = 0;
int minX = 0;
ExtremalNodes(root, 0, maxX, minX);
return maxX - minX + 1;
}
Hope this helps!

Calculating length of objects in binary image - algorithm

I need to calculate length of the object in a binary image (maximum distance between the pixels inside the object). As it is a binary image, so we might consider it a 2D array with values 0 (white) and 1 (black). The thing I need is a clever (and preferably simple) algorithm to perform this operation. Keep in mind there are many objects in the image.
The image to clarify:
Sample input image:
I think the problem is simple if the boundary of an object is convex and no three vertices are on a line (i.e. no vertex can be removed without changing the polygon): Then you can just pick two points at random and use a simple gradient-descent type search to find the longest line:
Start with random vertices A, B
See if the line A' - B is longer than A - B where A' is the point left of A; if so, replace A with A'
See if the line A' - B is longer than A - B where A' is the point right of A; if so, replace A with A'
Do the same for B
repeat until convergence
So I'd suggest finding the convex hull for each seed blob, removing all "superfluos" vertices (to ensure convergence) and running the algorithm above.
Constructing a convex hull is an O(n log n) operation IIRC, where n is the number of boundary pixels. Should be pretty efficient for small objects like these. EDIT: I just remembered that the O(n log n) for the convex hull algorithm was needed to sort the points. If the boundary points are the result of a connected component analysis, they are already sorted. So the whole algorithm should run in O(n) time, where n is the number of boundary points. (It's a lot of work, though, because you might have to write your own convex-hull algorithm or modify one to skip the sort.)
Add: Response to comment
If you don't need 100% accuracy, you could simply fit an ellipse to each blob and calculate the length of the major axis: This can be computed from central moments (IIRC it's simply the square root if the largest eigenvalue of the covariance matrix), so it's an O(n) operation and can efficiently be calculated in a single sweep over the image. It has the additional advantage that it takes all pixels of a blob into account, not just two extremal points, i.e. it is far less affected by noise.
Find the major-axis length of the ellipse that has the same normalized second central moments as the region. In MATLAB you can use regionprops.
A very crude, brute-force approach would be to first identify all the edge pixels (any black pixel in the object adjacent to a non-black pixel) and calculate the distances between all possible pairs of edge pixels. The longest of these distances will give you the length of the object.
If the objects are always shaped like the ones in your sample, you could speed this up by only evaluating the pixels with the highest and lowest x and y values within the object.
I would suggest trying an "reverse" distance transform. In the magical world of mathematical morphology (sorry couldn't resist the alliteration) the distance transform gives you the closest distance of each pixel to its nearest boundary pixel. In your case, you are interested in the farthest distance to a boundary pixel, hence I have cleverly applied a "reverse" prefix.
You can find information on the distance transform here and here. I believe that matlab implements the distance transform as per here. That would lead me to believe that you can find an open source implementation of the distance transform in octave. Furthermore, it would not surprise me in the least if opencv implemented it.
I haven't given it much thought but its intuitive to me that you should be able to reverse the distance transform and calculate it in roughly the same amount of time as the original distance transform.
I think you could consider using a breadth first search algorithm.
The basic idea is that you loop over each row and column in the image, and if you haven't visited the node (a node is a row and column with a colored pixel) yet, then you would run the breadth first search. You would visit each node you possibly could, and keep track of the max and min points for the object.
Here's some C++ sample code (untested):
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
// used to transition from given row, col to each of the
// 8 different directions
int dr[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
int dc[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
// WHITE or COLORED cells
const int WHITE = 0;
const int COLORED = 1;
// number of rows and columns
int nrows = 2000;
int ncols = 2000;
// assume G is the image
int G[2000][2000];
// the "visited array"
bool vis[2000][2000];
// get distance between 2 points
inline double getdist(double x1, double y1, double x2, double y2) {
double d1 = x1 - x2;
double d2 = y1 - y2;
return sqrt(d1*d1+d2*d2);
}
// this function performs the breadth first search
double bfs(int startRow, int startCol) {
queue< int > q;
q.push(startRow);
q.push(startCol);
vector< pair< int, int > > points;
while(!q.empty()) {
int r = q.front();
q.pop();
int c = q.front();
q.pop();
// already visited?
if (vis[r][c])
continue;
points.push_back(make_pair(r,c));
vis[r][c] = true;
// try all eight directions
for(int i = 0; i < 8; ++i) {
int nr = r + dr[i];
int nc = c + dc[i];
if (nr < 0 || nr >= nrows || nc < 0 || nc >= ncols)
continue; // out of bounds
// push next node on queue
q.push(nr);
q.push(nc);
}
}
// the distance is maximum difference between any 2 points encountered in the BFS
double diff = 0;
for(int i = 0; i < (int)points.size(); ++i) {
for(int j = i+1; j < (int)points.size(); ++j) {
diff = max(diff,getdist(points[i].first,points[i].second,points[j].first,points[j].second));
}
}
return diff;
}
int main() {
vector< double > lengths;
memset(vis,false,sizeof vis);
for(int r = 0; r < nrows; ++r) {
for(int c = 0; c < ncols; ++c) {
if (G[r][c] == WHITE)
continue; // we don't care about cells without objects
if (vis[r][c])
continue; // we've already processed this object
// find the length of this object
double len = bfs(r,c);
lengths.push_back(len);
}
}
return 0;
}

nearest neighbor - k-d tree - wikipedia proof

On the wikipedia entry for k-d trees, an algorithm is presented for doing a nearest neighbor search on a k-d tree. What I don't understand is the explanation of step 3.2. How do you know there isn't a closer point just because the difference between the splitting coordinate of the search point and the current node is greater than the difference between the splitting coordinate of the search point and the current best?
Nearest neighbor search Animation of
NN searching with a KD Tree in 2D
The nearest neighbor (NN) algorithm
aims to find the point in the tree
which is nearest to a given input
point. This search can be done
efficiently by using the tree
properties to quickly eliminate large
portions of the search space.
Searching for a nearest neighbor in a
kd-tree proceeds as follows:
Starting with the root node, the algorithm moves down the tree
recursively, in the same way that it
would if the search point were being
inserted (i.e. it goes right or left
depending on whether the point is
greater or less than the current node
in the split dimension).
Once the algorithm reaches a leaf node, it saves that node point as
the "current best"
The algorithm unwinds the recursion of the tree, performing the
following steps at each node:
1. If the current node is closer than the current best, then it
becomes the current best.
2. The algorithm checks whether there could be any points on
the other side of the splitting plane
that are closer to the search point
than the current best. In concept,
this is done by intersecting the
splitting hyperplane with a
hypersphere around the search point
that has a radius equal to the current
nearest distance. Since the
hyperplanes are all axis-aligned this
is implemented as a simple comparison
to see whether the difference between
the splitting coordinate of the search
point and current node is less than
the distance (overall coordinates)
from the search point to the current
best.
1. If the hypersphere crosses the plane, there could be
nearer points on the other side of the
plane, so the algorithm must move down
the other branch of the tree from the
current node looking for closer
points, following the same recursive
process as the entire search.
2. If the hypersphere doesn't intersect the splitting plane,
then the algorithm continues walking
up the tree, and the entire branch on
the other side of that node is
eliminated.
When the algorithm finishes this process for the root node, then the
search is complete.
Generally the algorithm uses squared
distances for comparison to avoid
computing square roots. Additionally,
it can save computation by holding the
squared current best distance in a
variable for comparison.
Look carefully at the 6th frame of the animation on that page.
As the algorithm is going back up the recursion, it is possible that there is a closer point on the other side of the hyperplane that it's on. We've checked one half, but there could be an even closer point on the other half.
Well, it turns out we can sometimes make a simplification. If it's impossible for there to be a point on the other half closer than our current best (closest) point, then we can skip that hyperplane half entirely. This simplification is the one shown on the 6th frame.
Figuring out whether this simplification is possible is done by comparing the distance from the hyperplane to our search location. Because the hyperplane is aligned to the axes, the shortest line from it to any other point will a line along one dimension, so we can compare just the coordinate of the dimension that the hyperplane splits.
If it's farther from the search point to the hyperplane than from the search point to your current closest point, then there's no reason to search past that splitting coordinate.
Even if my explanation doesn't help, the graphic will. Good luck on your project!
Yes, the description of NN (Nearest Neighbour) search in a KD Tree on Wikipedia is a little hard to follow. It doesn't help that a lot of the top Google search results on NN KD Tree searches are just plain wrong!
Here's some C++ code to show you how to get it right:
template <class T, std::size_t N>
void KDTree<T,N>::nearest (
const const KDNode<T,N> &node,
const std::array<T, N> &point, // looking for closest node to this point
const KDPoint<T,N> &closest, // closest node (so far)
double &minDist,
const uint depth) const
{
if (node->isLeaf()) {
const double dist = distance(point, node->leaf->point);
if (dist < minDist) {
minDist = dist;
closest = node->leaf;
}
} else {
const T dim = depth % N;
if (point[dim] < node->splitVal) {
// search left first
nearest(node->left, point, closest, minDist, depth + 1);
if (point[dim] + minDist >= node->splitVal)
nearest(node->right, point, closest, minDist, depth + 1);
} else {
// search right first
nearest(node->right, point, closest, minDist, depth + 1);
if (point[dim] - minDist <= node->splitVal)
nearest(node->left, point, closest, minDist, depth + 1);
}
}
}
API for NN searching on a KD Tree:
// Nearest neighbour
template <class T, std::size_t N>
const KDPoint<T,N> KDTree<T,N>::nearest (const std::array<T, N> &point) const {
const KDPoint<T,N> closest;
double minDist = std::numeric_limits<double>::max();
nearest(root, point, closest, minDist);
return closest;
}
Default distance function:
template <class T, std::size_t N>
double distance (const std::array<T, N> &p1, const std::array<T, N> &p2) {
double d = 0.0;
for (uint i = 0; i < N; ++i) {
d += pow(p1[i] - p2[i], 2.0);
}
return sqrt(d);
}
Edit: some people are asking for help with the data structures too (not just the NN algorithm), so here is what I have used. Depending on your purpose, you might wish to modify the data structures slightly. (Note: but you almost certainly do not want to modify the NN algorithm.)
KDPoint class:
template <class T, std::size_t N>
class KDPoint {
public:
KDPoint<T,N> (std::array<T,N> &&t) : point(std::move(t)) { };
virtual ~KDPoint<T,N> () = default;
std::array<T, N> point;
};
KDNode class:
template <class T, std::size_t N>
class KDNode
{
public:
KDNode () = delete;
KDNode (const KDNode &) = delete;
KDNode & operator = (const KDNode &) = delete;
~KDNode () = default;
// branch node
KDNode (const T split,
std::unique_ptr<const KDNode> &lhs,
std::unique_ptr<const KDNode> &rhs) : splitVal(split), left(std::move(lhs)), right(std::move(rhs)) { };
// leaf node
KDNode (std::shared_ptr<const KDPoint<T,N>> p) : splitVal(0), leaf(p) { };
bool isLeaf (void) const { return static_cast<bool>(leaf); }
// data members
const T splitVal;
const std::unique_ptr<const KDNode<T,N>> left, right;
const std::shared_ptr<const KDPoint<T,N>> leaf;
};
KDTree class: (Note: you'll need to add a member function to build/fill your tree.)
template <class T, std::size_t N>
class KDTree {
public:
KDTree () = delete;
KDTree (const KDTree &) = delete;
KDTree (KDTree &&t) : root(std::move(const_cast<std::unique_ptr<const KDNode<T,N>>&>(t.root))) { };
KDTree & operator = (const KDTree &) = delete;
~KDTree () = default;
const KDPoint<T,N> nearest (const std::array<T, N> &point) const;
// Nearest neighbour search - runs in O(log n)
void nearest (const std::unique_ptr<const KDNode<T,N>> &node,
const std::array<T, N> &point,
std::shared_ptr<const KDPoint<T,N>> &closest,
double &minDist,
const uint depth = 0) const;
// data members
const std::unique_ptr<const KDNode<T,N>> root;
};

Resources