/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.image.jai; // J2SE dependencies import java.awt.Rectangle; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.util.Map; // JAI dependencies import javax.media.jai.ImageLayout; import javax.media.jai.UntiledOpImage; import javax.media.jai.iterator.RandomIter; import javax.media.jai.iterator.RandomIterFactory; /** * Effectue un seuillage par hysteresis sur une image. * L'opération de seuillage s'effectue de la manière suivante: * <p> * On dispose d'un seuil haut, <var>sh</var>, et d'un seuil bas, <var>sb</var>. * Si la valeur d'un pixel est supérieur à <var>sh</var>, on la conserve, elle * nous interesse. Si cette valeur est inférieure à <var>sb</var>, on la supprime. * Si elle est entre les deux on dit le pixel indeterminé et on ne le conserve que * s'il est proche d'un pixel dont la valeur est au dessus de <var>sh</var>, ou * proche d'un pixel indéterminé que l'on a précédement trouvé proche d'un pixel * de valeur supérieure à <var>sh</var>. Cette recherche se fait de manière itérative, * jusqu'à ce que le point indéterminé n'est plus de voisins satisfaisants. * * @since 2.1 * @source $URL$ * @version $Id$ * @author Lionel Flahaut (2ie Technologie, IRD) * @author Martin Desruisseaux */ public class Hysteresis extends UntiledOpImage { /** * The low threshold value, inclusive. */ private final double low; /** * The high threshold value, inclusive. */ private final double high; /** * The value to give to filtered pixel. */ private final double padValue; /** * Constructs a new Hysterisis filter for the given image. * * @param source The source image. * @param layout The image layout. * @param map The rendering hints and image properties. * @param low The low threshold value, inclusive. * @param high The high threshold value, inclusive. * @param padValue The value to give to filtered pixel. */ protected Hysteresis(final RenderedImage source, final ImageLayout layout, final Map map, final double low, final double high, final double padValue) { super(source, map, layout); this.low = low; this.high = high; this.padValue = padValue; } /** * Computes the whole image. */ 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(); } }