package cz.cuni.lf1.lge.ThunderSTORM.util;
import java.util.LinkedList;
import java.util.Vector;
/**
* Methods for applying graph algorithms on images.
*/
public class Graph {
/**
* Representation of a connected component.
*
* A connected component consists of nodes, int this particular case
* those are pixels. All the pixels are stored in Point structures.
*
* @see Point
*/
public static class ConnectedComponent {
private Vector<Point> points = new Vector<Point>();
/**
* Calculate centroid of all points stored in the component.
*
* The centroid is calculated simply as mean value of X,Y position
* and the intensity of the centroid is calculated as sum of all the
* nodes in the component.
*
* @return a <strong>new instance</strong> of Point class representing
* the calculated centroid
*/
public Point centroid() {
int npts = points.size();
double[] xarr = new double[npts];
double[] yarr = new double[npts];
double[] valarr = new double[npts];
for(int i = 0, im = npts; i < im; i++) {
Point p = points.elementAt(i);
xarr[i] = p.getX().doubleValue();
yarr[i] = p.getY().doubleValue();
valarr[i] = p.getVal().doubleValue();
}
return new Point(VectorMath.mean(xarr), VectorMath.mean(yarr), VectorMath.sum(valarr));
}
}
/**
* South, North, East, and West pixels are connected.
* <pre>
* {@code
* .|.
* - -
* .|.}
* </pre>
* Note that the dots are here used just for sake of formatting.
*/
public static final int CONNECTIVITY_4 = 4;
/**
* South, North, East, West, SouthWest, SouthEast, NorthWest, and NorthEast pixels are connected.
* <pre>
* {@code
* \|/
* - -
* /|\}
* </pre>
*/
public static final int CONNECTIVITY_8 = 8;
/**
* Get connected components in image.
*
* Take an input {@code image} as an undirected graph where the pixels with value greater than 0 are
* considered to be nodes. The edges between them are created accorging to the specified {@code connectivity} model.
* Then find <a href="http://en.wikipedia.org/wiki/Connected_component_(graph_theory)">connected components</a>
* as defined in graph theory.
*
* @param ip an input image
* @param connectivity one of the connectivity models ({@code CONNECTIVITY_4} or {@code CONNECTIVITY_8})
* @return Vector of ConnectedComponents
* @see ConnectedComponent
*
* @todo This method is much slower than it could be because of too many allocations!
*/
public static Vector<ConnectedComponent> getConnectedComponents(ij.process.ImageProcessor ip, int connectivity) {
assert (ip != null);
assert ((connectivity == CONNECTIVITY_4) || (connectivity == CONNECTIVITY_8));
int[][] map = new int[ip.getWidth()][ip.getHeight()];
for (int x = 0; x < map.length; x++) {
for (int y = 0; y < map[x].length; y++) {
map[x][y] = 0; // member of no component
}
}
Point<Integer> p;
ConnectedComponent c;
Vector<ConnectedComponent> components = new Vector<ConnectedComponent>();
LinkedList<Point<Integer>> queue = new LinkedList<Point<Integer>>();
int counter = 0;
boolean n, s, w, e;
for (int x = 0; x < map.length; x++) {
for (int y = 0; y < map[x].length; y++) {
if (map[x][y] > 0) continue; // already member of another component
if (ip.getPixelValue(x, y) == 0.0f) continue; // disabled pixel
// new component
counter++;
queue.clear();
queue.push(new Point<Integer>(x, y));
c = new ConnectedComponent();
while (!queue.isEmpty()) {
p = queue.pop();
int px = p.getX().intValue();
int py = p.getY().intValue();
if (map[px][py] > 0) continue; // already member of another component
if (ip.getPixelValue(px, py) == 0.0f) continue; // disabled pixel
map[px][py] = counter;
c.points.add(new Point<Float>(p.getX().floatValue(), p.getY().floatValue(), ip.getPixelValue(px, py)));
w = (px > 0); // west
n = (py > 0); // north
e = (px < (map.length - 1)); // east
s = (py < (map[px].length - 1)); // south
if(w) queue.push(new Point<Integer>(px - 1, py)); // west
if(n) queue.push(new Point<Integer>(px, py - 1)); // north
if(e) queue.push(new Point<Integer>(px + 1, py)); // east
if(s) queue.push(new Point<Integer>(px, py + 1)); // south
if(connectivity == CONNECTIVITY_8) {
if(n && w) queue.push(new Point<Integer>(px - 1, py - 1)); // north west
if(n && e) queue.push(new Point<Integer>(px + 1, py - 1)); // north east
if(s && w) queue.push(new Point<Integer>(px - 1, py + 1)); // south west
if(s && e) queue.push(new Point<Integer>(px + 1, py + 1)); // south east
}
}
components.add(c);
}
}
return components;
}
}