/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2003-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. */ package org.geotoolkit.image.jai; import java.util.Map; import java.util.Vector; import java.awt.Rectangle; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import javax.media.jai.ImageLayout; import javax.media.jai.UntiledOpImage; import javax.media.jai.iterator.RandomIter; import javax.media.jai.iterator.RandomIterFactory; /** * Applies a hysteresis threshold on an image. This operation is defined by an upper threshold, * <var>high</var>, and a lower threshold, <var>low</var>. If a pixel value is equals or higher * than <var>high</var>, it is keep unchanged. If a pixel value is lower than <var>low</var>, * it is replaced by the pad value. If a pixel value is between <var>low</var> and <var>high</var>, * then this pixel is called "indeterminate". Its value is keep unchanged only if this pixel * is either a neighbor of a pixel having a value equals or higher than <var>high</var>, or a * neighbor of an other indeterminate pixel which has been determined close to a pixel having * a value equals or higher than <var>high</var> in a previous iteration. This search is performed * in an iterative manner until there is no more indeterminate pixels having satisfying neighbor. * * @author Lionel Flahaut (IRD) * @author Martin Desruisseaux (IRD) * @version 3.00 * * @since 2.1 * @module */ public class Hysteresis extends UntiledOpImage { /** * The name of this operation in the JAI registry. * This is {@value}. */ public static final String OPERATION_NAME = "org.geotoolkit.Hysteresis"; /** * The lower threshold value, inclusive. Pixels having a value * lower than this value will be set to the {@link #padValue}. */ protected final double low; /** * The upper threshold value, inclusive. Pixels having a value * equals or higher than this value will be keep unchanged. */ protected final double high; /** * The value to give to filtered pixel. */ protected final double padValue; /** * Constructs a new Hysterisis filter for the given image. While this constructor is public, * it should usually not be invoked directly. You should use {@linkplain javax.media.jai.JAI} * factory methods instead. * * @param source The source image. * @param layout The image layout. * @param configuration The image properties and rendering hints. * @param low The lower threshold value, inclusive. * @param high The upper threshold value, inclusive. * @param padValue The value to give to filtered pixel. */ public Hysteresis(final RenderedImage source, final ImageLayout layout, final Map<?,?> configuration, final double low, final double high, final double padValue) { super(source, configuration, layout); this.low = low; this.high = high; this.padValue = padValue; } /** * Returns the source images. */ @Override @SuppressWarnings("unchecked") public Vector<RenderedImage> getSources() { return super.getSources(); } /** * Computes a rectangle of outputs. * * @param sources The source images. Should be an array of length 1. * @param dest The raster to be filled in. * @param destRect The region within the raster to be filled. */ @Override protected void computeImage(final Raster[] sources, final WritableRaster dest, final Rectangle destRect) { assert sources.length == 1; final Raster source = sources[0]; Rectangle sourceRect = mapDestRect(destRect, 0); sourceRect = sourceRect.intersection(source.getBounds()); final RandomIter iter = RandomIterFactory.create(source, sourceRect); final int minX = destRect.x; // Minimum inclusive final int minY = destRect.y; // Minimum inclusive final int maxX = destRect.width + minX; // Maximum exclusive final int maxY = destRect.height + minY; // Maximum exclusive final int w = width -1; final int h = height-1; final boolean[] accepted = new boolean[destRect.width * destRect.height]; final boolean[] rejected = new boolean[destRect.width * destRect.height]; for (int band=source.getNumBands(); --band>=0;) { /* * Find immediately all accepted values (above the high threshold) and rejected values * (below the low threshold). NaN values are both accepted and rejected ("accepted" * since they are going to be copied in the destination image, and "rejected" since * they do not cause the acceptation of neighbor values). */ int index = 0; for (int y=minY; y<maxY; y++) { for (int x=minX; x<maxX; x++) { final double current = iter.getSampleDouble(x, y, band); accepted[index] = !(current < high); // Accept NaN values rejected[index] = !(current >= low); // Accept NaN values index++; } } assert index == accepted.length; /* * Complete the mask of "accepted" values. Unknow values (those which are neither * accepted or rejected) are tested for their proximity with an accepted value. * This loop will be run until there is no change. */ int sign = +1; boolean changed; do { changed = false; final int stop; if (sign >= 0) { index = 0; stop = accepted.length; } else { index = accepted.length-1; stop = -1; } while (index != stop) { if (!accepted[index] && !rejected[index]) { int check; final int y = index / width; final int x = index % width; if ((x!=0 && ((accepted[check=index-1 ] && !rejected[check]) || (y!=0 && accepted[check=index-1-width] && !rejected[check]) || (y!=h && accepted[check=index-1+width] && !rejected[check]))) || (x!=w && ((accepted[check=index+1 ] && !rejected[check]) || (y!=0 && accepted[check=index+1-width] && !rejected[check]) || (y!=h && accepted[check=index+1+width] && !rejected[check]))) || (y!=0 && ((accepted[check=index -width] && !rejected[check]))) || (y!=w && ((accepted[check=index +width] && !rejected[check])))) { accepted[index] = true; changed = true; } } index += sign; } sign = -sign; } while (changed); /* * Copy all accepted values in the destination raster. * Other values are replaced by NaN. */ index = 0; for (int y=minY; y<maxY; y++) { for (int x=minX; x<maxX; x++) { dest.setSample(x, y, band, accepted[index++] ? iter.getSampleDouble(x, y, band) : padValue); } } assert index == accepted.length; } iter.done(); } }