/*
* 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.morpher;
import static org.jgrasstools.gears.libs.modules.JGTConstants.doubleNovalue;
import static org.jgrasstools.gears.libs.modules.JGTConstants.isNovalue;
import static org.jgrasstools.gears.libs.modules.Variables.CLOSE;
import static org.jgrasstools.gears.libs.modules.Variables.DILATE;
import static org.jgrasstools.gears.libs.modules.Variables.ERODE;
import static org.jgrasstools.gears.libs.modules.Variables.LINEENDINGS;
import static org.jgrasstools.gears.libs.modules.Variables.OPEN;
import static org.jgrasstools.gears.libs.modules.Variables.PRUNE;
import static org.jgrasstools.gears.libs.modules.Variables.SKELETONIZE1;
import static org.jgrasstools.gears.libs.modules.Variables.*;
import static org.jgrasstools.gears.libs.modules.Variables.SKELETONIZE2VAR;
import static org.jgrasstools.gears.libs.modules.Variables.SKELETONIZE3;
import java.awt.image.WritableRaster;
import javax.media.jai.iterator.RandomIterFactory;
import javax.media.jai.iterator.WritableRandomIter;
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 oms3.annotations.UI;
import org.geotools.coverage.grid.GridCoverage2D;
import org.jgrasstools.gears.libs.exceptions.ModelsIllegalargumentException;
import org.jgrasstools.gears.libs.modules.GridNode;
import org.jgrasstools.gears.libs.modules.JGTConstants;
import org.jgrasstools.gears.libs.modules.JGTModel;
import org.jgrasstools.gears.libs.monitor.IJGTProgressMonitor;
import org.jgrasstools.gears.modules.utils.BinaryFast;
import org.jgrasstools.gears.utils.RegionMap;
import org.jgrasstools.gears.utils.coverage.CoverageUtilities;
@Description("Mophologic binary operations")
@Author(name = "Simon Horne, Andrea Antonello", contact = "http://homepages.inf.ed.ac.uk/rbf/HIPR2/, www.hydrologis.com")
@Keywords("Dilation, Erosion, Skeletonize, Open, Close, Raster")
@Label(JGTConstants.RASTERPROCESSING)
@Name("omsmorpher")
@Status(Status.DRAFT)
@License("http://www.gnu.org/licenses/gpl-3.0.html")
public class OmsMorpher extends JGTModel {
@Description("The map to morph.")
@In
public GridCoverage2D inMap = null;
@Description("A kernel to use instead of the default.")
@In
public int[] pKernel = null;
@Description("Process in binary mode.")
@In
public boolean doBinary = true;
@Description("The operation type to perform (dilate, erode, skeletonize(1,2,2var,3), prune, open, close, lineendings, linejunctions)")
@UI("combo:" + DILATE + "," + ERODE + "," + SKELETONIZE1 + "," + SKELETONIZE2 + "," + SKELETONIZE2VAR + "," + SKELETONIZE3
+ "," + PRUNE + "," + OPEN + "," + CLOSE + "," + LINEENDINGS + "," + LINEJUNCTIONS)
@In
public String pMode = DILATE;
@Description("Number of iterations (used in case of pruning).")
@In
public int pIterations = 3;
@Description("The resulting map.")
@Out
public GridCoverage2D outMap = null;
@Execute
public void process() throws Exception {
if (!concatOr(outMap == null, doReset)) {
return;
}
if (pKernel == null) {
pKernel = MorpherHelp.DEFAULT3X3KERNEL;
}
RegionMap regionMap = CoverageUtilities.getRegionParamsFromGridCoverage(inMap);
WritableRaster inWR = CoverageUtilities.renderedImage2WritableRaster(inMap.getRenderedImage(), false);
WritableRaster[] wrHolder = new WritableRaster[1];
outMap = CoverageUtilities.createCoverageFromTemplate(inMap, doubleNovalue, wrHolder);
WritableRaster outWR = wrHolder[0];
if (pMode.equals(SKELETONIZE1)) {
skeletonize(inWR, regionMap, outWR, MorpherHelp.SKELETON1_KERNEL);
} else if (pMode.equals(SKELETONIZE2)) {
skeletonize(inWR, regionMap, outWR, MorpherHelp.SKELETON2_KERNEL);
} else if (pMode.equals(SKELETONIZE2VAR)) {
skeletonize(inWR, regionMap, outWR, MorpherHelp.SKELETON2VARIANT_KERNEL);
} else if (pMode.equals(SKELETONIZE3)) {
skeletonize(inWR, regionMap, outWR, MorpherHelp.SKELETON3_KERNEL);
} else if (pMode.equals(LINEENDINGS)) {
lineendings(inWR, regionMap, outWR, MorpherHelp.LINEEND_KERNEL);
} else if (pMode.equals(LINEJUNCTIONS)) {
lineendings(inWR, regionMap, outWR, MorpherHelp.LINEJUNCTIONS_KERNEL);
} else if (pMode.equals(PRUNE)) {
if (pIterations == 0) {
throw new ModelsIllegalargumentException("Number of iterations has to be > 0.", this, pm);
}
prune(inWR, regionMap, outWR, MorpherHelp.DEFAULT_PRUNE_KERNEL, pIterations);
} else {
if (pMode.equals(DILATE)) {
dilate(inWR, regionMap, outWR, pKernel, doBinary, pm);
} else if (pMode.equals(ERODE)) {
erode(inWR, regionMap, outWR, pKernel, pm);
} else if (pMode.equals(OPEN)) {
open(inWR, regionMap, outWR, pKernel, doBinary, pm);
} else if (pMode.equals(CLOSE)) {
close(inWR, regionMap, outWR, pKernel, doBinary, pm);
} else {
throw new ModelsIllegalargumentException("Could not recognize mode.", this, pm);
}
}
}
/**
* Morphologically dilates an input raster by a given kernel.
*
* @param inWR the input raster.
* @param regionMap the {@link RegionMap}.
* @param outWR the raster to modify.
* @param kernelArray the kernel to use.
* @param binary if <code>true</code>, binary mode is used.
* @param pm
*/
public static void dilate( WritableRaster inWR, RegionMap regionMap, WritableRaster outWR, int[] kernelArray, boolean binary,
IJGTProgressMonitor pm ) {
int cols = regionMap.getCols();
int rows = regionMap.getRows();
double xres = regionMap.getXres();
double yres = regionMap.getYres();
WritableRandomIter inIter = RandomIterFactory.createWritable(inWR, null);
WritableRandomIter outIter = RandomIterFactory.createWritable(outWR, null);
int[][] kernel = MorpherHelp.getSquareKernelMatrix(kernelArray);
pm.beginTask("Perform dilation...", cols);
for( int c = 0; c < cols; c++ ) {
for( int r = 0; r < rows; r++ ) {
GridNode node = new GridNode(inIter, cols, rows, xres, yres, c, r);
if (!node.isValid()) {
double[][] nodeNeighbours = node.getWindow(kernel.length, false);
// apply the kernel
boolean set = false;
boolean doBreak = false;
double max = Double.NEGATIVE_INFINITY;
for( int kr = 0; kr < kernel.length; kr++ ) {
for( int kc = 0; kc < kernel[0].length; kc++ ) {
if (kernel[kr][kc] == 1) {
// valid kernel value
if (!isNovalue(nodeNeighbours[kr][kc]) && nodeNeighbours[kr][kc] > max) {
max = nodeNeighbours[kr][kc];
set = true;
if (binary) {
break;
}
}
}
}
if (doBreak) {
break;
}
}
if (set) {
node.setValueInMap(outIter, max);
} else {
node.setValueInMap(outIter, JGTConstants.doubleNovalue);
}
continue;
} else {
node.setValueInMap(outIter, node.elevation);
}
}
pm.worked(1);
}
pm.done();
}
/**
* Morphologically erodes an input raster by a given kernel.
*
* @param inWR the input raster.
* @param regionMap the {@link RegionMap}.
* @param outWR the raster to modify.
* @param kernelArray the kernel to use.
*/
public static void erode( WritableRaster inWR, RegionMap regionMap, WritableRaster outWR, int[] kernelArray,
IJGTProgressMonitor pm ) {
int cols = regionMap.getCols();
int rows = regionMap.getRows();
double xres = regionMap.getXres();
double yres = regionMap.getYres();
WritableRandomIter inIter = RandomIterFactory.createWritable(inWR, null);
WritableRandomIter outIter = RandomIterFactory.createWritable(outWR, null);
int[][] kernel = MorpherHelp.getSquareKernelMatrix(kernelArray);
pm.beginTask("Perform erosion...", cols);
for( int c = 0; c < cols; c++ ) {
for( int r = 0; r < rows; r++ ) {
GridNode node = new GridNode(inIter, cols, rows, xres, yres, c, r);
if (node.isValid()) {
double[][] nodeNeighbours = node.getWindow(kernel.length, false);
// apply the kernel
boolean set = false;
double min = Double.POSITIVE_INFINITY;
for( int kr = 0; kr < kernel.length; kr++ ) {
for( int kc = 0; kc < kernel[0].length; kc++ ) {
if (kernel[kr][kc] == 1) {
// valid kernel value
if (isNovalue(nodeNeighbours[kr][kc])) {
// novalue is the absolute min
min = doubleNovalue;
set = true;
break;
} else if (nodeNeighbours[kr][kc] < min) {
min = nodeNeighbours[kr][kc];
set = true;
}
}
}
if (isNovalue(min)) {
break;
}
}
if (set) {
node.setValueInMap(outIter, min);
} else {
node.setValueInMap(outIter, node.elevation);
}
continue;
}
}
pm.worked(1);
}
pm.done();
}
/**
* Morphologically opens an input raster by a given kernel.
*
* @param inWR the input raster.
* @param regionMap the {@link RegionMap}.
* @param outWR the raster to modify.
* @param kernelArray the kernel to use.
* @param binary if <code>true</code>, binary mode is used.
*/
public static void open( WritableRaster inWR, RegionMap regionMap, WritableRaster outWR, int[] kernelArray, boolean binary,
IJGTProgressMonitor pm ) {
erode(inWR, regionMap, outWR, kernelArray, pm);
inWR.setDataElements(0, 0, outWR);
clearRaster(regionMap, outWR);
dilate(inWR, regionMap, outWR, kernelArray, binary, pm);
}
/**
* Morphologically closes an input raster by a given kernel.
*
* @param inWR the input raster.
* @param regionMap the {@link RegionMap}.
* @param outWR the raster to modify.
* @param kernelArray the kernel to use.
* @param binary if <code>true</code>, binary mode is used.
*/
public static void close( WritableRaster inWR, RegionMap regionMap, WritableRaster outWR, int[] kernelArray, boolean binary,
IJGTProgressMonitor pm ) {
dilate(inWR, regionMap, outWR, kernelArray, binary, pm);
inWR.setDataElements(0, 0, outWR);
clearRaster(regionMap, outWR);
erode(inWR, regionMap, outWR, kernelArray, pm);
}
public static void skeletonize( WritableRaster inWR, RegionMap regionMap, WritableRaster outWR, int[][] kernels ) {
int cols = regionMap.getCols();
int rows = regionMap.getRows();
WritableRandomIter inIter = RandomIterFactory.createWritable(inWR, null);
WritableRandomIter outIter = RandomIterFactory.createWritable(outWR, null);
BinaryFast binaryData = toBinaryFast(cols, rows, inIter);
new Thin().processSkeleton(binaryData, kernels);
fromBinaryFast(cols, rows, outIter, binaryData);
}
public static void prune( WritableRaster inWR, RegionMap regionMap, WritableRaster outWR, int[][] kernels, int iterations ) {
int cols = regionMap.getCols();
int rows = regionMap.getRows();
WritableRandomIter inIter = RandomIterFactory.createWritable(inWR, null);
WritableRandomIter outIter = RandomIterFactory.createWritable(outWR, null);
BinaryFast binaryData = toBinaryFast(cols, rows, inIter);
new Thin().processPruning(binaryData, iterations, kernels);
fromBinaryFast(cols, rows, outIter, binaryData);
}
public static void lineendings( WritableRaster inWR, RegionMap regionMap, WritableRaster outWR, int[][] kernels ) {
int cols = regionMap.getCols();
int rows = regionMap.getRows();
WritableRandomIter inIter = RandomIterFactory.createWritable(inWR, null);
WritableRandomIter outIter = RandomIterFactory.createWritable(outWR, null);
BinaryFast binaryData = toBinaryFast(cols, rows, inIter);
new Thin().processLineendings(binaryData, kernels);
int[] values = binaryData.getValues();
int index = 0;
for( int y = 0; y < rows; y++ ) {
for( int x = 0; x < cols; x++ ) {
double value = (double) values[index];
if (value == 0) {
value = doubleNovalue;
}
double origValue = inIter.getSampleDouble(x, y, 0);
if (!isNovalue(origValue) && isNovalue(value)) {
outIter.setSample(x, y, 0, 1);
}
index++;
}
}
}
private static void clearRaster( RegionMap regionMap, WritableRaster outWR ) {
// clear raster
for( int c = 0; c < regionMap.getCols(); c++ ) {
for( int r = 0; r < regionMap.getRows(); r++ ) {
outWR.setSample(c, r, 0, doubleNovalue);
}
}
}
public static void fromBinaryFast( int cols, int rows, WritableRandomIter outIter, BinaryFast binaryData ) {
int[] values = binaryData.getValues();
int index = 0;
for( int y = 0; y < rows; y++ ) {
for( int x = 0; x < cols; x++ ) {
double value = (double) values[index];
if (value == 0) {
value = doubleNovalue;
}
outIter.setSample(x, y, 0, value);
index++;
}
}
}
public static BinaryFast toBinaryFast( int cols, int rows, WritableRandomIter inIter ) {
int[][] data = new int[cols][rows];
for( int c = 0; c < cols; c++ ) {
for( int r = 0; r < rows; r++ ) {
double value = inIter.getSampleDouble(c, r, 0);
data[c][r] = BinaryFast.BACKGROUND;
if (isNovalue(value)) {
continue;
} else {
data[c][r] = BinaryFast.FOREGROUND;
}
}
}
BinaryFast binaryData = new BinaryFast(data);
return binaryData;
}
}