001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2003, 2ie Technologie
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.image.jai;
018:
019: // J2SE dependencies
020: import java.awt.Rectangle;
021: import java.awt.image.RenderedImage;
022: import java.awt.image.WritableRaster;
023: import java.util.Map;
024:
025: // JAI dependencies
026: import javax.media.jai.AreaOpImage;
027: import javax.media.jai.ImageLayout;
028: import javax.media.jai.PlanarImage;
029: import javax.media.jai.iterator.RandomIter;
030: import javax.media.jai.iterator.RandomIterFactory;
031:
032: // Geotools dependencies
033: import org.geotools.resources.XMath;
034:
035: /**
036: * Replaces {@link Double#NaN} values by the weighted average of neighbors values.
037: * This operation use a box of {@code size}×{@code size} pixels centered on
038: * each {@code NaN} value. The weighted average is then computed, ignoring all
039: * {@code NaN} values. If the number of valid values is greater than
040: * {@code validityThreshold}, then the center {@code NaN} is replaced
041: * by the computed average. Otherwise, the {@code NaN} value is left unchanged.
042: *
043: * @since 2.1
044: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/image/jai/NodataFilter.java $
045: * @version $Id: NodataFilter.java 20970 2006-08-11 07:53:22Z jgarnett $
046: * @author Lionel Flahaut
047: * @author Martin Desruisseaux
048: */
049: public class NodataFilter extends AreaOpImage {
050: /**
051: * Shared instance of {@link #distances} for the common case where {@code padding==1}.
052: */
053: private static double[] sharedDistances;
054:
055: /**
056: * Pre-computed distances. Used in order to avoid a huge amount of calls to
057: * {@link Math#sqrt} in {@link #computeRect}.
058: */
059: private final double[] distances;
060:
061: /**
062: * The minimal number of valid neighbors required in order to consider the average as valid.
063: */
064: private final int validityThreshold;
065:
066: /**
067: * Constructs a new operation.
068: *
069: * @param source The source image.
070: * @param layout The image layout.
071: * @param map The image properties and rendering hints.
072: * @param padding The number of pixel above, below, to the left and to the right of central
073: * {@code NaN} pixel. The full box size is {@code padding}×2+1.
074: * @param validityThreshold The minimal number of valid neighbors required in order to consider
075: * the average as valid.
076: */
077: protected NodataFilter(final RenderedImage source,
078: final ImageLayout layout, final Map map, final int padding,
079: final int validityThreshold) {
080: super (source, layout, map, false, null, padding, padding,
081: padding, padding);
082: this .validityThreshold = validityThreshold;
083: /*
084: * Computes the array of distances once for ever. For the special case where the padding
085: * equals 1, we will try to reuse the same array for all NodataFilter instances.
086: */
087: if (padding == 1 && sharedDistances != null) {
088: distances = sharedDistances;
089: } else {
090: distances = new double[(leftPadding + rightPadding + 1)
091: * (topPadding + bottomPadding + 1)];
092: int index = 0;
093: for (int dy = -topPadding; dy <= bottomPadding; dy++) {
094: for (int dx = -leftPadding; dx <= rightPadding; dx++) {
095: distances[index++] = Math.sqrt(dx * dx + dy * dy);
096: }
097: }
098: assert index == distances.length;
099: if (padding == 1) {
100: sharedDistances = distances;
101: }
102: }
103: }
104:
105: /**
106: * Computes a rectangle of outputs.
107: */
108: protected void computeRect(final PlanarImage[] sources,
109: final WritableRaster dest, final Rectangle destRect) {
110: assert sources.length == 1;
111: final PlanarImage source = sources[0];
112: Rectangle sourceRect = mapDestRect(destRect, 0);
113: sourceRect = sourceRect.intersection(source.getBounds());
114: final RandomIter iter = RandomIterFactory.create(source,
115: sourceRect);
116: final int minX = destRect.x; // Minimum inclusive
117: final int minY = destRect.y; // Minimum inclusive
118: final int maxX = destRect.width + minX; // Maximum exclusive
119: final int maxY = destRect.height + minY; // Maximum exclusive
120: final int hPad = leftPadding + rightPadding + 1; // Horizontal padding
121: for (int band = source.getNumBands(); --band >= 0;) {
122: for (int y = minY; y < maxY; y++) {
123: final int minScanY = Math.max(minY, y - topPadding); // Inclusive
124: final int maxScanY = Math.min(maxY, y + bottomPadding
125: + 1); // Exclusive
126: final int minScanI = (minScanY - (y - topPadding))
127: * hPad;
128: assert minScanI >= 0 && minScanI <= distances.length : minScanI;
129: for (int x = minX; x < maxX; x++) {
130: final double current = iter.getSampleDouble(x, y,
131: band);
132: if (!Double.isNaN(current)) {
133: /*
134: * Pixel is already valid: no operation here.
135: */
136: dest.setSample(x, y, band, current);
137: continue;
138: }
139: /*
140: * Computes the average and set the value if the amount of
141: * valid pixels is at least equals to the threshold amount.
142: */
143: int count = 0; // Number of valid values.
144: double sumValue = 0; // Weighted sum of values.
145: double sumDistance = 0; // Sum of distances of valid values.
146: final int minScanX = Math
147: .max(minX, x - leftPadding); // Inclusive
148: final int maxScanX = Math.min(maxX, x
149: + rightPadding + 1); // Exclusive
150: final int lineOffset = hPad - (maxScanX - minScanX);
151: int index = minScanI
152: + (minScanX - (x - leftPadding));
153: for (int sy = minScanY; sy < maxScanY; sy++) {
154: for (int sx = minScanX; sx < maxScanX; sx++) {
155: final double scan = iter.getSampleDouble(
156: sx, sy, band);
157: if (!Double.isNaN(scan)) {
158: final double distance = distances[index];
159: assert (Math.abs(distance
160: - XMath.hypot(sx - x, sy - y)) < 1E-6)
161: && (distance > 0) : distance;
162: sumValue += distance * scan;
163: sumDistance += distance;
164: count++;
165: }
166: index++;
167: }
168: index += lineOffset;
169: }
170: dest.setSample(x, y, band,
171: (count >= validityThreshold) ? sumValue
172: / sumDistance : current);
173: }
174: }
175: }
176: iter.done();
177: }
178: }
|