package fr.unistra.pelican.algorithms.segmentation; import java.awt.Point; import java.util.ArrayList; import fr.unistra.pelican.Algorithm; import fr.unistra.pelican.AlgorithmException; import fr.unistra.pelican.Image; import fr.unistra.pelican.IntegerImage; /** * This class performs a watershed segmentation using the Soille algorithm (with * hierarchical queues) * * It works by default on Byte resolution. The maximum number of created segment * is 2^31-1. It return an IntegerImage, the first segment as label * Integer.MIN_VALUE. * * @author Aptoula, Derivaux, Weber */ public class Watershed extends Algorithm { /** * The input image */ public Image inputImage; /** * The resolution considered, 8 by default */ public int resolution = 8; /** * The output image */ public Image outputImage; /** * A constant to represent watershed lines */ public static final int WSHED = 0; /* * Private attributes */ private static final int INIT = -1; private static final int MASK = -2; private final Point fictitious = new Point(-1, -1); /** * Constructor */ public Watershed() { super.inputs = "inputImage"; super.options = "resolution"; super.outputs = "outputImage"; } /** * Performs a watershed segmentation using the Soille algorithm (with * hierarchical queues) * * @param inputImage * The input image * @return The output image */ public static IntegerImage exec(Image inputImage) { return (IntegerImage) new Watershed().process(inputImage); } /* * (non-Javadoc) * * @see fr.unistra.pelican.Algorithm#launch() */ public void launch() throws AlgorithmException { IntegerImage work = new IntegerImage(inputImage.getXDim(), inputImage .getYDim(), 1, 1, 1); IntegerImage dist = new IntegerImage(inputImage.getXDim(), inputImage .getYDim(), 1, 1, 1); IntegerImage workOut = new IntegerImage(inputImage.getXDim(), inputImage.getYDim(), 1, 1, 1); outputImage = new IntegerImage(inputImage, false); for (int z = 0; z < inputImage.getZDim(); z++) for (int b = 0; b < inputImage.getBDim(); b++) for (int t = 0; t < inputImage.getTDim(); t++) { // Create a working Image. for (int x = 0; x < inputImage.getXDim(); x++) for (int y = 0; y < inputImage.getYDim(); y++) // That's a nice hack, isn't it? No Byte to Integer // conversion. // Work still have values from 0 to 255. work.setPixelInt(x, y, 0, 0, 0, inputImage .getPixelByte(x, y, z, t, b)); // Initialise the workout image workOut.fill(INIT); dist.fill(0); int currentLabel = WSHED; Fifo fifo = new Fifo(); Point p; // pixel value distribution, ArrayList<Point>[] distro = calculateDistro(work); // start flooding for (int i = 0; i < 256; i++) { // geodesic SKIZ of level i - 1 inside level i int size = distro[i].size(); for (int j = 0; j < size; j++) { p = distro[i].get(j); workOut.setPixelXYInt(p.x, p.y, MASK); if (areThereLabelledNeighbours(workOut, p.x, p.y) == true) { dist.setPixelXYInt(p.x, p.y, 1); fifo.add(p); } } int curDist = 1; fifo.add(fictitious); do { p = fifo.retrieve(); if (p.x == -1 && p.y == -1) { if (fifo.isEmpty() == true) break; else { fifo.add(fictitious); curDist++; p = fifo.retrieve(); } } // labelling p by inspecting its neighbours for (int j = p.y - 1; j <= p.y + 1; j++) { for (int k = p.x - 1; k <= p.x + 1; k++) { if (k < 0 || k >= inputImage.getXDim() || j < 0 || j >= inputImage.getYDim()) continue; // if the pixel is // already labelled if (!(j == p.y && k == p.x) && dist.getPixelXYInt(k, j) < curDist && workOut.getPixelXYInt(k, j) > WSHED) { if (workOut.getPixelXYInt(k, j) > 0) { if (workOut.getPixelXYInt(p.x, p.y) == MASK || workOut.getPixelXYInt( p.x, p.y) == WSHED) workOut.setPixelXYInt(p.x, p.y, workOut.getPixelXYInt( k, j)); else if (workOut.getPixelXYInt(p.x, p.y) != workOut .getPixelXYInt(k, j)) workOut.setPixelXYInt(p.x, p.y, WSHED); } else if (workOut.getPixelXYInt(p.x, p.y) == MASK) workOut.setPixelXYInt(p.x, p.y, WSHED); // if the neighbour is a plateau pixel } else if (workOut.getPixelXYInt(k, j) == MASK && dist.getPixelXYInt(k, j) == 0) { dist.setPixelXYInt(k, j, curDist + 1); fifo.add(new Point(k, j)); } } } } while (true); // check for new minima size = distro[i].size(); // detect and process new minima at level i for (int j = 0; j < size; j++) { p = distro[i].get(j); // reset distance to 0 dist.setPixelXYInt(p.x, p.y, 0); // if p is inside a new minimum if (workOut.getPixelXYInt(p.x, p.y) == MASK) { // create a new label currentLabel++; fifo.add(p); workOut.setPixelXYInt(p.x, p.y, currentLabel); while (fifo.isEmpty() == false) { Point q = fifo.retrieve(); // for every pixel in the 8-neighbourhood of // q for (int l = q.y - 1; l <= q.y + 1; l++) { for (int k = q.x - 1; k <= q.x + 1; k++) { if (k < 0 || k >= inputImage .getXDim() || l < 0 || l >= inputImage .getYDim()) continue; if (!(k == q.x && l == q.y) && workOut.getPixelXYInt(k, l) == MASK) { fifo.add(new Point(k, l)); workOut.setPixelXYInt(k, l, currentLabel); } } } } } } // Copy the result to the outputImage for (int _x = 0; _x < inputImage.getXDim(); _x++) for (int _y = 0; _y < inputImage.getYDim(); _y++) { // That's a nice hack, isn't it? No Integer to // Byte // conversion. // Values are inside [0,255] if the algo is // correct. outputImage.setPixelInt(_x, _y, z, t, b, workOut.getPixelInt(_x, _y, 0, 0, 0)/* * + * Integer.MIN_VALUE */); } } } return; } @SuppressWarnings("unchecked") private ArrayList<Point>[] calculateDistro(IntegerImage img) { ArrayList<Point>[] distro = (ArrayList<Point>[])new ArrayList[256]; for (int i = 0; i < 256; i++) distro[i] = new ArrayList<Point>(); for (int x = 0; x < img.getXDim(); x++) { for (int y = 0; y < img.getYDim(); y++) distro[img.getPixelXYInt(x, y)].add(new Point(x, y)); } return distro; } private boolean areThereLabelledNeighbours(IntegerImage img, int x, int y) throws AlgorithmException { for (int j = y - 1; j <= y + 1; j++) { if (j >= img.getYDim() || j < 0) continue; for (int i = x - 1; i <= x + 1; i++) { if (i >= img.getXDim() || i < 0) continue; // try{ if (!(i == x && j == y) && img.getPixelXYInt(i, j) >= WSHED) return true; // }catch(java.lang.ArrayIndexOutOfBoundsException ex){ // throw new AlgorithmException( // "ArrayIndexOutOfBoundsException in // areThereLabelledNeighbours" // +" with i=" + i + " j=" + j); // } } } return false; } private class Fifo { private ArrayList<Object> v; Fifo() { v = new ArrayList<Object>(); } void add(Object o) { v.add(o); } Point retrieve() { Object o = v.remove(0); return (Point) o; } boolean isEmpty() { return (v.size() == 0); } int size() { return v.size(); } } }