package fr.unistra.pelican.algorithms.segmentation.flatzones; import java.util.ArrayList; import java.util.Arrays; import java.util.Vector; import fr.unistra.pelican.Algorithm; import fr.unistra.pelican.BooleanImage; import fr.unistra.pelican.IntegerImage; /** * This class performs a labeling of a binary image into connected components. * * It uses a fast 2-pass algorithm relying on a correspondance table and offers * two options : the connexity used (either CONNEXITY4 or CONNEXITY8) and the * possiblity to label background pixels * * TODO: add support to integer images (not only boolean images) * * @author Lefevre */ public class BooleanConnectedComponentsLabelingND extends Algorithm { /** * A constant representing the 4-connexity mode */ public static int CONNEXITY4 = 0; /** * A constant representing the 8-connexity mode */ public static int CONNEXITY8 = 1; /** * Input Image */ public BooleanImage input; /** * The type of connexity considered (either CONNEXITY4 or CONNEXITY8) */ public int connexity = CONNEXITY8; /** * Flag to determine if background pixels should also be labeled */ public boolean background = false; /** * Label image */ public IntegerImage output; /** * Number of labels used */ public int countLabels; /* * Private attributes */ private int nbLabels = 0; private Vector<Integer> labels = new Vector<Integer>(); /** * Constructor */ public BooleanConnectedComponentsLabelingND() { super.inputs = "input"; super.options = "connexity,background"; super.outputs = "output,countLabels"; } /** * Performs a labeling of a binary image into connected components. * * @param input * The input image * @param connexity * The type of connexity considered (either CONNEXITY4 or CONNEXITY8) * @param background * Flag to determine if background pixels should also be labeled * @return The label image */ public static IntegerImage exec(BooleanImage input, int connexity, boolean background) { return (IntegerImage) new BooleanConnectedComponentsLabelingND().process(input, connexity, background); } public static IntegerImage exec(BooleanImage input, boolean background) { return (IntegerImage) new BooleanConnectedComponentsLabelingND().process(input, null, background); } public static IntegerImage exec(BooleanImage input, int connexity) { return (IntegerImage) new BooleanConnectedComponentsLabelingND().process(input, connexity); } /** * Performs a labeling of a binary image into connected components. * * @param input * The input image * @return The label image */ public static IntegerImage exec(BooleanImage input) { return (IntegerImage) new BooleanConnectedComponentsLabelingND().process(input); } public void launch() { output = new IntegerImage(input.getXDim(), input.getYDim(), input.getZDim(), input.getTDim(), input.getBDim()); labels.add(0); xDim = input.getXDim(); yDim = input.getYDim(); zDim = input.getZDim(); tDim = input.getTDim(); bDim = input.getBDim(); for (int i = 0; i < input.size(); i++) if (background || input.getPixelBoolean(i)) this.output.setPixelInt(i, Integer.MAX_VALUE); else this.output.setPixelInt(i, 0); if (connexity == CONNEXITY8) { valid = new boolean[3 * 3 * 3 * 3 * 3]; neighbours = new boolean[3 * 3 * 3 * 3 * 3]; } // Premier parcours for (int b = 0; b < bDim; b++) for (int t = 0; t < tDim; t++) for (int z = 0; z < zDim; z++) for (int y = 0; y < yDim; y++) for (int x = 0; x < xDim; x++) if (background || input.getPixelBoolean(x, y, z, t, b)) { if (connexity == CONNEXITY4) output.setPixelInt(x, y, z, t, b, get4Connexity(x, y, z, t, b)); else if (connexity == CONNEXITY8) output.setPixelInt(x, y, z, t, b, get8Connexity(x, y, z, t, b)); } // Simplification de la table d'equivalence ArrayList<Integer> alreadyTreatedLabels = new ArrayList<Integer>(); for (int i = nbLabels; i >= 0; i--) { int j = i; alreadyTreatedLabels.add(j); while (labels.get(j) != j) { j = labels.get(j); // loop treatment if(alreadyTreatedLabels.contains(j)) { for(int label=0;label<alreadyTreatedLabels.size();label++) { labels.set(alreadyTreatedLabels.get(label), i); } j = i; } else { alreadyTreatedLabels.add(j); } } labels.set(i, j); alreadyTreatedLabels.clear(); } // Calcul du nombre de labels et requantification des labels int[] labels2 = new int[nbLabels + 1]; countLabels = 0; for (int i = 0; i < nbLabels + 1; i++) if (i == labels.get(i)) { labels2[i] = countLabels; countLabels++; } output.setProperty("nbRegions", countLabels); // Second parcours for (int b = 0; b < bDim; b++) for (int t = 0; t < tDim; t++) for (int z = 0; z < zDim; z++) for (int y = 0; y < yDim; y++) for (int x = 0; x < xDim; x++) if (background || input.getPixelBoolean(x, y, z, t, b)) output.setPixelInt(x, y, z, t, b, labels2[labels.get(output .getPixelInt(x, y, z, t, b))]); } private int get4Connexity(int x, int y, int z, int t, int b) { boolean current = input.getPixelBoolean(x, y, z, t, b); int min = Integer.MAX_VALUE; boolean val1 = false, val2 = false, val3 = false, val4 = false, val5 = false; if (x - 1 >= 0 && current == input.getPixelBoolean(x - 1, y, z, t, b)) { min = Math.min(min, labels.get(output.getPixelInt(x - 1, y, z, t, b))); val1 = true; } if (y - 1 >= 0 && current == input.getPixelBoolean(x, y - 1, z, t, b)) { min = Math.min(min, labels.get(output.getPixelInt(x, y - 1, z, t, b))); val2 = true; } if (z - 1 >= 0 && current == input.getPixelBoolean(x, y, z - 1, t, b)) { min = Math.min(min, labels.get(output.getPixelInt(x, y, z - 1, t, b))); val3 = true; } if (t - 1 >= 0 && current == input.getPixelBoolean(x, y, z, t - 1, b)) { min = Math.min(min, labels.get(output.getPixelInt(x, y, z, t - 1, b))); val4 = true; } if (b - 1 >= 0 && current == input.getPixelBoolean(x, y, z, t, b - 1)) { min = Math.min(min, labels.get(output.getPixelInt(x, y, z, t, b - 1))); val5 = true; } if (val1 && labels.get(output.getPixelInt(x - 1, y, z, t, b)) != min) setTableMin(output.getPixelInt(x - 1, y, z, t, b), min); if (val2 && labels.get(output.getPixelInt(x, y - 1, z, t, b)) != min) setTableMin(output.getPixelInt(x, y - 1, z, t, b), min); if (val3 && labels.get(output.getPixelInt(x, y, z - 1, t, b)) != min) setTableMin(output.getPixelInt(x, y, z - 1, t, b), min); if (val4 && labels.get(output.getPixelInt(x, y, z, t - 1, b)) != min) setTableMin(output.getPixelInt(x, y, z, t - 1, b), min); if (val5 && labels.get(output.getPixelInt(x, y, z, t, b - 1)) != min) setTableMin(output.getPixelInt(x, y, z, t, b - 1), min); if (min == Integer.MAX_VALUE) { nbLabels++; labels.add(nbLabels); return nbLabels; } else return min; } private boolean neighbours[]; private boolean valid[]; private int xDim, yDim, zDim, tDim, bDim; private int get8Connexity(int x, int y, int z, int t, int b) { boolean current = input.getPixelBoolean(x, y, z, t, b); int min = Integer.MAX_VALUE; int k = 0; boolean stop = false; Arrays.fill(neighbours, false); Arrays.fill(valid, false); for (int bb = -1; bb <= 1; bb++) for (int tt = -1; tt <= 1; tt++) for (int zz = -1; zz <= 1; zz++) for (int yy = -1; yy <= 1; yy++) for (int xx = -1; xx <= 1; xx++) if (!stop) { k++; if (xx == 0 && yy == 0 && zz == 0 && tt == 0 && bb == 0) stop = true; else if (x + xx >= 0 && y + yy >= 0 && z + zz >= 0 && t + tt >= 0 && b + bb >= 0 && x + xx < xDim && y + yy < yDim && z + zz < zDim && t + tt < tDim && b + bb < bDim) { valid[k - 1] = true; if (current == input.getPixelBoolean(x + xx, y + yy, z + zz, t + tt, b + bb)) { min = Math.min(min, labels.get(output.getPixelInt(x + xx, y + yy, z + zz, t + tt, b + bb))); neighbours[k - 1] = true; } } } k = 0; stop = false; for (int bb = -1; bb <= 1; bb++) for (int tt = -1; tt <= 1; tt++) for (int zz = -1; zz <= 1; zz++) for (int yy = -1; yy <= 1; yy++) for (int xx = -1; xx <= 1; xx++) if (!stop) { k++; if (xx == 0 && yy == 0 && zz == 0 && tt == 0 && bb == 0) stop = true; else if (valid[k - 1] && neighbours[k - 1] && labels.get(output.getPixelInt(x + xx, y + yy, z + zz, t + tt, b + bb)) != min) setTableMin(output.getPixelInt(x + xx, y + yy, z + zz, t + tt, b + bb), min); } if (min == Integer.MAX_VALUE) { nbLabels++; labels.add(nbLabels); return nbLabels; } else return min; } // private int get8Connexity(int x, int y, int z, int t, int b) { // boolean current = input.getPixelBoolean(x, y, z, t, b); // int min = Integer.MAX_VALUE; // for (int bb = -1; bb <= 0; bb++) // for (int tt = -1; tt <= 0; tt++) // for (int zz = -1; zz <= 0; zz++) // for (int yy = -1; yy <= 0; yy++) // for (int xx = -1; xx <= 0; xx++) { // if (x + xx >= 0 // && y + yy >= 0 // && z + zz >= 0 // && t + tt >= 0 // && b + bb >= 0 // && xx + yy + zz + tt + bb != 0 // && current == input.getPixelBoolean(x + xx, y + yy, z + zz, t // + tt, b + bb)) { // min = Math.min(min, labels.get(output.getPixelInt(x + xx, y // + yy, z + zz, t + tt, b + bb))); // val[xx + 1][yy + 1][zz + 1][tt + 1][bb + 1] = true; // } else // val[xx + 1][yy + 1][zz + 1][tt + 1][bb + 1] = false; // } // for (int bb = -1; bb <= 0; bb++) // for (int tt = -1; tt <= 0; tt++) // for (int zz = -1; zz <= 0; zz++) // for (int yy = -1; yy <= 0; yy++) // for (int xx = -1; xx <= 0; xx++) // if (val[xx + 1][yy + 1][zz + 1][tt + 1][bb + 1] // && labels.get(output.getPixelInt(x + xx, y + yy, z + zz, // t + tt, b + bb)) != min) // setTableMin(output.getPixelInt(x + xx, y + yy, z + zz, t + tt, // b + bb), min); // if (min == Integer.MAX_VALUE) { // nbLabels++; // labels.add(nbLabels); // return nbLabels; // } else // return min; // } private void setTableMin(int u, int min) { int v = labels.get(u); while (u != v) { labels.set(u, min); u = v; v = labels.get(v); } labels.set(u, min); } }