/*
* This file is part of JGrasstools (http://www.jgrasstools.org)
*
* JGrasstools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jgrasstools.gears.modules.r.labeler;
import static org.jgrasstools.gears.libs.modules.JGTConstants.isNovalue;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.util.HashMap;
import javax.media.jai.iterator.RandomIter;
import javax.media.jai.iterator.RandomIterFactory;
import oms3.annotations.Author;
import oms3.annotations.Description;
import oms3.annotations.Execute;
import oms3.annotations.In;
import oms3.annotations.Keywords;
import oms3.annotations.Label;
import oms3.annotations.License;
import oms3.annotations.Name;
import oms3.annotations.Out;
import oms3.annotations.Status;
import org.geotools.coverage.grid.GridCoverage2D;
import org.jgrasstools.gears.libs.modules.JGTConstants;
import org.jgrasstools.gears.libs.modules.JGTModel;
import org.jgrasstools.gears.modules.utils.BinaryFast;
import org.jgrasstools.gears.utils.coverage.CoverageUtilities;
@Description("Connected components labeling operation")
@Author(name = "Simon Horne, Andrea Antonello", contact = "http://homepages.inf.ed.ac.uk/rbf/HIPR2/, www.hydrologis.com")
@Keywords("Labeling, Raster")
@Label(JGTConstants.RASTERPROCESSING)
@Name("omslabeler")
@Status(Status.DRAFT)
@License("http://www.gnu.org/licenses/gpl-3.0.html")
public class OmsLabeler extends JGTModel {
@Description("The map to label.")
@In
public GridCoverage2D inMap = null;
@Description("The resulting map.")
@Out
public GridCoverage2D outMap = null;
@Execute
public void process() throws Exception {
if (!concatOr(outMap == null, doReset)) {
return;
}
final RenderedImage renderedImage = inMap.getRenderedImage();
int width = renderedImage.getWidth();
int height = renderedImage.getHeight();
int[] data = new int[width * height];
RandomIter iter = RandomIterFactory.create(renderedImage, null);
int index = 0;
for( int r = 0; r < height; r++ ) {
for( int c = 0; c < width; c++ ) {
double value = iter.getSampleDouble(c, r, 0);
if (isNovalue(value)) {
data[index] = BinaryFast.BACKGROUND;
} else {
data[index] = BinaryFast.FOREGROUND;
}
index++;
}
}
int[] labelsArray = doLabel(data, width, height);
WritableRaster dataWR = CoverageUtilities.createWritableRasterFromArray(width, height, labelsArray);
HashMap<String, Double> regionMap = CoverageUtilities.getRegionParamsFromGridCoverage(inMap);
outMap = CoverageUtilities.buildCoverage("labeled", dataWR, regionMap, inMap.getCoordinateReferenceSystem()); //$NON-NLS-1$
}
/**
* Applies the Labeling algorithm plus offset and scaling
*
* <p>The input image is expected to be 8-bit mono 0=black everything else=white
*
* @param data The input pixel array
* @param width width of the destination image in pixels
* @param height height of the destination image in pixels
* @return A pixel array containing the labelled image
*/
private int[] doLabel( int[] data, int width, int height ) {
int nextlabel = 1;
int nbs[] = new int[4];
int nbls[] = new int[4];
// Get size of image and make 1d_arrays
int d_w = width;
int d_h = height;
int[] dest_1d = new int[d_w * d_h];
// the most labels there can be is 1/2 of the points
// in checkerboard
int[] labels = new int[d_w * d_h / 2];
int result = 0;
// labelsValid = false; // only set to true once we've complete the task
// initialise labels
for( int i = 0; i < labels.length; i++ )
labels[i] = i;
int count;
// now Label the image
for( int i = 0; i < data.length; i++ ) {
int src1rgb = data[i] & 0x000000ff;
if (src1rgb == 0) {
result = 0; // nothing here
} else {
// The 4 visited neighbours
nbs[0] = getNeighbours(data, i, -1, 0, d_w, d_h);
nbs[1] = getNeighbours(data, i, 0, -1, d_w, d_h);
nbs[2] = getNeighbours(data, i, -1, -1, d_w, d_h);
nbs[3] = getNeighbours(data, i, 1, -1, d_w, d_h);
// Their corresponding labels
nbls[0] = getNeighbourd(dest_1d, i, -1, 0, d_w, d_h);
nbls[1] = getNeighbourd(dest_1d, i, 0, -1, d_w, d_h);
nbls[2] = getNeighbourd(dest_1d, i, -1, -1, d_w, d_h);
nbls[3] = getNeighbourd(dest_1d, i, 1, -1, d_w, d_h);
// label the point
if ((nbs[0] == nbs[1]) && (nbs[1] == nbs[2]) && (nbs[2] == nbs[3]) && (nbs[0] == 0)) {
// all neighbours are 0 so gives this point a new label
result = nextlabel;
nextlabel++;
} else { // one or more neighbours have already got labels
count = 0;
int found = -1;
for( int j = 0; j < 4; j++ ) {
if (nbs[j] != 0) {
count += 1;
found = j;
}
}
if (count == 1) {
// only one neighbour has a label, so assign the same label to this.
result = nbls[found];
} else {
// more than 1 neighbour has a label
result = nbls[found];
// Equivalence the connected points
for( int j = 0; j < 4; j++ ) {
if ((nbls[j] != 0) && (nbls[j] != result)) {
associate(nbls[j], result, labels);
}
}
}
}
}
dest_1d[i] = result;
}
// reduce labels ie 76=23=22=3 -> 76=3
// done in reverse order to preserve sorting
for( int i = labels.length - 1; i > 0; i-- ) {
labels[i] = reduce(i, labels);
}
/*now labels will look something like 1=1 2=2 3=2 4=2 5=5.. 76=5 77=5
this needs to be condensed down again, so that there is no wasted
space eg in the above, the labels 3 and 4 are not used instead it jumps
to 5.
*/
int condensed[] = new int[nextlabel]; // cant be more than nextlabel labels
count = 0;
for( int i = 0; i < nextlabel; i++ ) {
if (i == labels[i])
condensed[i] = count++;
}
// Record the number of labels
int numberOfLabels = count - 1;
// now run back through our preliminary results, replacing the raw label
// with the reduced and condensed one, and do the scaling and offsets too
// Now generate an array of colours which will be used to label the image
int[] labelColors = new int[numberOfLabels + 1];
// Variable used to check if the color generated is acceptable
// boolean acceptColor = false;
for( int i = 0; i < labelColors.length; i++ ) {
// acceptColor = false;
// while( !acceptColor ) {
// double tmp = Math.random();
// labelColors[i] = (int) (tmp * 16777215);
// if (((labelColors[i] & 0x000000ff) < 200) && (((labelColors[i] & 0x0000ff00) >> 8) <
// 64) && (((labelColors[i] & 0x00ff0000) >> 16) < 64)) {
// // Color to be rejected so don't set acceptColor
// } else {
// acceptColor = true;
// }
// }
// if (i == 0)
// labelColors[i] = 0;
labelColors[i] = i;
}
for( int i = 0; i < data.length; i++ ) {
result = condensed[labels[dest_1d[i]]];
// result = (int) ( scale * (float) result + oset );
// truncate if necessary
// if( result > 255 ) result = 255;
// if( result < 0 ) result = 0;
// produce grayscale
// dest_1d[i] = 0xff000000 | (result + (result << 16) + (result << 8));
dest_1d[i] = labelColors[result];// + 0xff000000;
}
// labelsValid = true; // only set to true now we've complete the task
return dest_1d;
}
/**
* getNeighbours will get the pixel value of i's neighbour that's ox and oy
* away from i, if the point is outside the image, then 0 is returned.
* This version gets from source image.
* @param d_w
* @param d_h
*/
private int getNeighbours( int[] src1d, int i, int ox, int oy, int d_w, int d_h ) {
int x, y, result;
x = (i % d_w) + ox; // d_w and d_h are assumed to be set to the
y = (i / d_w) + oy; // width and height of scr1d
if ((x < 0) || (x >= d_w) || (y < 0) || (y >= d_h)) {
result = 0;
} else {
result = src1d[y * d_w + x] & 0x000000ff;
}
return result;
}
/**
* getNeighbourd will get the pixel value of i's neighbour that's ox and oy
* away from i, if the point is outside the image, then 0 is returned.
* This version gets from destination image.
* @param d_w
* @param d_h
*/
private int getNeighbourd( int[] src1d, int i, int ox, int oy, int d_w, int d_h ) {
int x, y, result;
x = (i % d_w) + ox; // d_w and d_h are assumed to be set to the
y = (i / d_w) + oy; // width and height of scr1d
if ((x < 0) || (x >= d_w) || (y < 0) || (y >= d_h)) {
result = 0;
} else {
result = src1d[y * d_w + x];
}
return result;
}
/**
* Associate(equivalence) a with b.
* a should be less than b to give some ordering (sorting)
* if b is already associated with some other value, then propagate
* down the list.
* @param labels
*/
private void associate( int a, int b, int[] labels ) {
if (a > b) {
associate(b, a, labels);
return;
}
if ((a == b) || (labels[b] == a))
return;
if (labels[b] == b) {
labels[b] = a;
} else {
associate(labels[b], a, labels);
if (labels[b] > a) { // ***rbf new
labels[b] = a;
}
}
}
/**
* Reduces the number of labels.
* @param labels
*/
private int reduce( int a, int[] labels ) {
if (labels[a] == a) {
return a;
} else {
return reduce(labels[a], labels);
}
}
// /**
// *getColours
// *@return the number of unique, non zero colours. -1 if not valid
// */
// private int getColours() {
//
// if (labelsValid) {
//
// return numberOfLabels;
// } else {
// return -1;
// }
// }
//
// /**
// * Returns the number of labels.
// */
// private int getNumberOfLabels() {
// return numberOfLabels;
// }
}