package fr.unistra.pelican.algorithms.segmentation; import java.awt.Point; import java.util.LinkedList; import fr.unistra.pelican.Algorithm; import fr.unistra.pelican.AlgorithmException; import fr.unistra.pelican.BooleanImage; import fr.unistra.pelican.Image; import fr.unistra.pelican.IntegerImage; import fr.unistra.pelican.algorithms.conversion.BinaryMasksToLabels; import fr.unistra.pelican.algorithms.conversion.ProcessChannels; import fr.unistra.pelican.algorithms.logical.CompareConstant; import fr.unistra.pelican.algorithms.segmentation.flatzones.BooleanConnectedComponentsLabeling; /** * This class is an extension of the marker-based watershed segmentation to * multiple reliefs and markers. * * S. Lefevre, Knowledge from markers in watershed segmentation, IAPR * International Conference on Computer Analysis of Images and Patterns (CAIP), * Vienna, August 2007, Springer-Verlag Lecture Notes in Computer Sciences, * Volume 4673, pages 579-586, doi:10.1007/978-3-540-74272-2_72. * * @author Lefevre */ public class MarkerBasedMultiWatershed extends Algorithm { /** * The input image */ public Image inputImage; /** * The output image */ public Image outputImage; /** * (optional) 4-connexity watershed */ public boolean connexity4 = false; /** * The optionnal mask image */ public Image mask = null; /* * Private attributes */ private final int IGNORE = -1; private final int NULL = 0; private final int GRAY_LEVELS = 256; private boolean cpu = true; private IntegerImage labels = null; private int xdim, ydim; /** * Constructor */ public MarkerBasedMultiWatershed() { super.inputs = "inputImage"; super.outputs = "outputImage"; super.options = "mask,connexity4"; } /** * Extension of the marker-based watershed segmentation to multiple reliefs * and markers * * @param inputImage * The input image * @return The output image */ public static IntegerImage exec(Image inputImage) { return (IntegerImage) new MarkerBasedMultiWatershed().process(inputImage); } /* * (non-Javadoc) * * @see fr.unistra.pelican.Algorithm#launch() */ public void launch() throws AlgorithmException { if (mask == null) { mask = new BooleanImage(inputImage.getXDim(), inputImage.getYDim(), inputImage.getZDim(), inputImage.getTDim(), 1); mask.fill(1); } xdim = inputImage.getXDim(); ydim = inputImage.getYDim(); IntegerImage input = new IntegerImage(inputImage.getXDim(), inputImage .getYDim(), 1, 1, inputImage.getBDim()); IntegerImage output = new IntegerImage(inputImage.getXDim(), inputImage .getYDim(), 1, 1, 2); outputImage = new IntegerImage(inputImage.getXDim(), inputImage.getYDim(), inputImage.getZDim(), inputImage.getTDim(), 1/* inputImage.getBDim() */); for (int z = 0; z < inputImage.getZDim(); z++) // Temporarily disable the B dim as we use it for the different // markers 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++) for (int b = 0; b < inputImage.getBDim(); b++) // That's a nice hack, isn't it? No Byte to Integer // conversion. // Work still have values from 0 to 255. input.setPixelInt(x, y, 0, 0, b, inputImage.getPixelByte(x, y, z, t, b)); HierarchicalQueue queue = new HierarchicalQueue(GRAY_LEVELS); int currentLabel = 1; output.fill(NULL); // Initialise the workout image // initialize output image and fill up the queue with marker // pixels long t1 = 0, t2 = 0; if (cpu) t1 = System.currentTimeMillis(); BooleanImage markers = CompareConstant.exec(input, 0, CompareConstant.EQ); Image classes = BinaryMasksToLabels.exec(markers); labels = BooleanConnectedComponentsLabeling.exec(ProcessChannels.exec(markers, ProcessChannels.MAXIMUM), connexity4 ? BooleanConnectedComponentsLabeling.CONNEXITY4 : BooleanConnectedComponentsLabeling.CONNEXITY8); currentLabel = (Integer) labels.getProperty("nbRegions"); if (cpu) { t2 = System.currentTimeMillis(); System.err.println("Labeling step: " + ((t2 - t1)) + " ms"); t1 = System.currentTimeMillis(); } for (int y = 0; y < ydim; y++) for (int x = 0; x < xdim; x++) { int p = labels.getPixelXYInt(x, y); int c = classes.getPixelXYInt(x, y)-1; if (!mask.getPixelXYBoolean(x, y)) output.setPixelXYBInt(x, y, 0, IGNORE); else if (p != NULL) { output.setPixelXYBInt(x, y, 0, p); output.setPixelXYBInt(x, y, 1, c); if (bord(x, y, p)) queue.add(new Point(x, y), NULL); } } if (cpu) { t2 = System.currentTimeMillis(); System.err.println("Marker step: " + ((t2 - t1)) + " ms"); t1 = System.currentTimeMillis(); } // for (int x = 0; x < inputImage.getXDim(); x++) // for (int y = 0; y < inputImage.getYDim(); y++) // for (int b = 0; b < inputImage.getBDim(); b++) { // int p = input.getPixelXYBInt(x, y, b); // int v = output.getPixelXYBInt(x, y, 0); // if (!mask.getPixelXYBoolean(x, y)) // output.setPixelXYBInt(x, y, 0,IGNORE); // else if (p == NULL && v == NULL) { // marker(input, output, x, y, b, queue, currentLabel); // currentLabel++; // } // } // System.err.println("Number of markers : " + (currentLabel - 1)); // Viewer2D.exec(LabelsToRandomColors.exec(output.getImage4D(0,Image.B))); // Viewer2D.exec(LabelsToRandomColors.exec(output.getImage4D(1,Image.B))); while (!queue.isEmpty()) { Point p = queue.get(); int label = output.getPixelXYBInt(p.x, p.y, 0); int band = output.getPixelXYBInt(p.x, p.y, 1); // get the non labelled 8-neighbours of (x,y) Point[] neighbours = getNonLabelledNeighbours(output, p.x, p.y); for (int i = 0; i < neighbours.length; i++) { // give him the label of p output.setPixelXYBInt(neighbours[i].x, neighbours[i].y, 0, label); output.setPixelXYBInt(neighbours[i].x, neighbours[i].y, 1, band); // get his gray level IN THE APPROPRIATE BAND int val = input.getPixelXYBInt(neighbours[i].x, neighbours[i].y, band); // add him to the appropriate queue queue.add(neighbours[i], val); } } if (cpu) { t2 = System.currentTimeMillis(); System.err.println("Segmentation step: " + ((t2 - t1)) + " ms"); } // Copy the result to the outputImage for (int _x = 0; _x < inputImage.getXDim(); _x++) for (int _y = 0; _y < inputImage.getYDim(); _y++) { outputImage.setPixelInt(_x, _y, z, t, 0, output.getPixelInt(_x, _y, 0, 0, 0)/* * + Integer.MIN_VALUE */); } } return; } private boolean bord(int x, int y, int p) { boolean bord = false; if (x > 0 && mask.getPixelXYBoolean(x - 1, y) && labels.getPixelXYInt(x - 1, y) != p) bord = true; else if (x < xdim - 1 && mask.getPixelXYBoolean(x + 1, y) && labels.getPixelXYInt(x + 1, y) != p) bord = true; else if (y > 0 && mask.getPixelXYBoolean(x, y - 1) && labels.getPixelXYInt(x, y - 1) != p) bord = true; else if (y < ydim - 1 && mask.getPixelXYBoolean(x, y + 1) && labels.getPixelXYInt(x, y + 1) != p) bord = true; // Si 4-connexitᅵ, on a passᅵ tous les tests, c'est un pixel de // bord if (connexity4) return bord; if (x > 0 && y > 0 && mask.getPixelXYBoolean(x - 1, y - 1) && labels.getPixelXYInt(x - 1, y - 1) != p) bord = true; if (x < xdim - 1 && y > 0 && mask.getPixelXYBoolean(x + 1, y - 1) && labels.getPixelXYInt(x + 1, y - 1) != p) bord = true; if (x > 0 && y < ydim - 1 && mask.getPixelXYBoolean(x - 1, y + 1) && labels.getPixelXYInt(x - 1, y + 1) != p) bord = true; if (x < xdim - 1 && y < ydim - 1 && mask.getPixelXYBoolean(x + 1, y + 1) && labels.getPixelXYInt(x + 1, y + 1) != p) bord = true; // on a passᅵ tous les tests en connexitᅵ 8, c'est un pixel de // bord return bord; } private void marker(IntegerImage input, IntegerImage output, int x, int y, int m, HierarchicalQueue queue, int label) { LinkedList<Point> fifo = new LinkedList<Point>(); fifo.add(new Point(x, y)); while (fifo.size() > 0) { Point p = (Point) fifo.removeFirst(); queue.add(p, NULL); output.setPixelXYBInt(p.x, p.y, 0, label); output.setPixelXYBInt(p.x, p.y, 1, m); 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 >= input.getXDim() || j < 0 || j >= input.getYDim()) continue; if (connexity4 && j != 0 && k != 0) continue; if (!mask.getPixelXYBoolean(k, j)) continue; if (!(k == p.x && j == p.y) && input.getPixelXYBInt(k, j, m) == NULL && output.getPixelXYBInt(k, j, 0) == NULL) { int size = fifo.size(); boolean b = false; for (int i = 0; i < size; i++) { Point v = (Point) fifo.get(i); if (v.x == k && v.y == j) b = true; } if (b == false) fifo.add(new Point(k, j)); } } } } } private Point[] getNonLabelledNeighbours(IntegerImage output, int x, int y) { Point[] neighbours = new Point[8]; int cnt = 0; for (int j = y - 1; j <= y + 1; j++) { for (int i = x - 1; i <= x + 1; i++) { if (i < 0 || i >= output.getXDim() || j < 0 || j >= output.getYDim()) continue; if (connexity4 && i != 0 && j != 0) continue; if (!mask.getPixelXYBoolean(i, j)) continue; int z = output.getPixelXYBInt(i, j, 0); if (!(i == x && j == y) && z == NULL) neighbours[cnt++] = new Point(i, j); } } if (cnt < 8) { Point[] tmp = new Point[cnt]; for (int i = 0; i < cnt; i++) tmp[i] = neighbours[i]; neighbours = tmp; } return neighbours; } private class HierarchicalQueue { private LinkedList<Point>[] queue; private int current; HierarchicalQueue(int size) { queue = new LinkedList[size]; for (int i = 0; i < size; i++) queue[i] = new LinkedList<Point>(); current = 0; } void add(Point p, int val) { if (val >= current) queue[val].add(p); else queue[current].add(p); } Point get() { if (queue[current].size() >= 2) return (Point) queue[current].removeFirst(); else if (queue[current].size() == 1) { Point p = (Point) queue[current].removeFirst(); while (current < 255 && queue[current].size() == 0) current++; return p; } else return null; } boolean isEmpty() { int sum = 0; for (int i = current; i < queue.length; i++) sum += queue[i].size(); return (sum == 0); } } }