/* * Copyright (C) 2015 by Array Systems Computing Inc. http://www.array.ca * * This program 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.esa.s1tbx.utilities.gpf; import com.bc.ceres.core.ProgressMonitor; import org.apache.commons.math3.util.FastMath; import org.esa.snap.core.dataio.ProductReader; import org.esa.snap.core.dataio.ProductSubsetBuilder; import org.esa.snap.core.dataio.ProductSubsetDef; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.CrsGeoCoding; import org.esa.snap.core.datamodel.GeoCoding; import org.esa.snap.core.datamodel.MetadataElement; import org.esa.snap.core.datamodel.PixelPos; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductData; import org.esa.snap.core.datamodel.TiePointGrid; import org.esa.snap.core.gpf.Operator; import org.esa.snap.core.gpf.OperatorException; import org.esa.snap.core.gpf.OperatorSpi; import org.esa.snap.core.gpf.Tile; import org.esa.snap.core.gpf.annotations.OperatorMetadata; import org.esa.snap.core.gpf.annotations.Parameter; import org.esa.snap.core.gpf.annotations.SourceProduct; import org.esa.snap.core.gpf.annotations.TargetProduct; import org.esa.snap.core.util.ProductUtils; import org.esa.snap.core.util.ResourceInstaller; import org.esa.snap.engine_utilities.datamodel.AbstractMetadata; import org.esa.snap.engine_utilities.datamodel.Unit; import org.esa.snap.engine_utilities.gpf.FilterWindow; import org.esa.snap.engine_utilities.gpf.OperatorUtils; import org.esa.snap.engine_utilities.util.ResourceUtils; import java.awt.Rectangle; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; /** * This operator down samples a real or complex image using sub-sampling method or kernel filtering method. * <p> * With sub-sampling method, the image is down sampled with user specified sub-sampling rates in both range * and azimuth directions. For complex image, the i and q bands in the image are down sampled separately, * and the down sampled image is still a complex image. * <p> * With kernel filtering method, the image is down sampled with a kernel moving across the image with a * step-size determined by the size of the required output image. The kernel can be selected from pre- * defined shapes or defined by the user. For complex image, intensity image is computed from the i and * q bands before kernel filtering is applied. The down sampled image is always real image. The user can * determine the output image size by specifying the output image size, or the pixel spacings, or the * down sampling ratios. * <p> * The parameters used by the operator are as the follows: * <p> * Source Band: All bands (real or virtual) of the source product. * Under-Sampling Method: Sub-Sampling method or Kernel Filtering method * <p> * For Sub-Sampling method, the following parameters are used: * <p> * Sub-Sampling in X: User provided sub-sampling rate in range. * Sub-Sampling in Y: User provided sub-sampling rate in azimuth. * <p> * For Kernel Filtering method, the following parameters are used: * <p> * Filter Type: The kernel filter type. * Filter Size: The kernel filter size. * Kernel File: The user defined kernel. * Output Image Rows: The row size of the down sampled image. * Output Image Columns: The column size of the down sampled image. * Width Ratio: The ratio of the down sampled image width and the source image width. * Height Ratio: The ratio of the down sampled image height and the source image height. * Range Spacing: The range pixel spacing of the down sampled image. * Azimuth Spacing: The azimuth pixel spacing of the down sampled image. */ @OperatorMetadata(alias = "Undersample", category = "Radar/SAR Utilities/Resampling", authors = "Jun Lu, Luis Veci", version = "1.0", copyright = "Copyright (C) 2015 by Array Systems Computing Inc.", description = "Undersample the datset") public class UndersamplingOp extends Operator { @SourceProduct(alias = "source") private Product sourceProduct; @TargetProduct private Product targetProduct; @Parameter(description = "The list of source bands.", alias = "sourceBands", rasterDataNodeType = Band.class, label = "Source Bands") private String[] sourceBandNames; @Parameter(valueSet = {SUB_SAMPLING, KERNEL_FILTERING}, defaultValue = KERNEL_FILTERING, label = "Under-Sampling Method") private String method = KERNEL_FILTERING; // @Parameter(valueSet = {SUMMARY, EDGE_DETECT, EDGE_ENHANCEMENT, LOW_PASS, HIGH_PASS, HORIZONTAL, VERTICAL, USER_DEFINED}, // defaultValue = LOW_PASS, label="Filter Type") private String filterType = LOW_PASS; @Parameter(valueSet = {FilterWindow.SIZE_3x3, FilterWindow.SIZE_5x5, FilterWindow.SIZE_7x7}, defaultValue = FilterWindow.SIZE_3x3, label = "Filter Size") private String filterSize = FilterWindow.SIZE_3x3; @Parameter(defaultValue = "2", label = "Sub-Sampling in X") private int subSamplingX = 2; @Parameter(defaultValue = "2", label = "Sub-Sampling in Y") private int subSamplingY = 2; @Parameter(valueSet = {IMAGE_SIZE, RATIO, PIXEL_SPACING}, defaultValue = RATIO, label = "Output Image By:") private String outputImageBy = RATIO; @Parameter(description = "The row dimension of the output image", defaultValue = "1000", label = "Output Image Rows") private int targetImageHeight = 1000; @Parameter(description = "The col dimension of the output image", defaultValue = "1000", label = "Output Image Columns") private int targetImageWidth = 1000; @Parameter(description = "The width ratio of the output/input images", defaultValue = "0.5", label = "Width Ratio") private float widthRatio = 0.5f; @Parameter(description = "The height ratio of the output/input images", defaultValue = "0.5", label = "Height Ratio") private float heightRatio = 0.5f; @Parameter(description = "The range pixel spacing", defaultValue = "12.5", label = "Range Spacing") private float rangeSpacing = 12.5f; @Parameter(description = "The azimuth pixel spacing", defaultValue = "12.5", label = "Azimuth Spacing") private float azimuthSpacing = 12.5f; private ProductReader subsetReader = null; private MetadataElement absRoot = null; private int filterWidth; private int filterHeight; private int sourceImageWidth; private int sourceImageHeight; private double stepRange; // step size in range direction for moving window filtering private double stepAzimuth; // step size in azimuth direction for moving window filtering private float srcRangeSpacing; // range pixel spacing of source image private float srcAzimuthSpacing; // azimuth pixel spacing of source image private float[][] kernel; // kernel for filtering private final HashMap<String, String[]> targetBandNameToSourceBandName = new HashMap<>(); public static final String SUB_SAMPLING = "Sub-Sampling"; public static final String KERNEL_FILTERING = "LowPass Filtering"; public static final String SUMMARY = "Summary"; public static final String EDGE_DETECT = "Edge Detect"; public static final String EDGE_ENHANCEMENT = "Edge Enhancement"; public static final String LOW_PASS = "Low Pass"; public static final String HIGH_PASS = "High Pass"; public static final String HORIZONTAL = "Horizontal"; public static final String VERTICAL = "Vertical"; private static final String USER_DEFINED = "User Defined"; public static final String IMAGE_SIZE = "Image Size"; public static final String RATIO = "Ratio"; public static final String PIXEL_SPACING = "Pixel Spacing"; private static final String PRODUCT_SUFFIX = "_Udr"; @Override public void initialize() throws OperatorException { GeoCoding sourceGeoCoding = sourceProduct.getSceneGeoCoding(); if (sourceGeoCoding instanceof CrsGeoCoding) { throw new OperatorException("Undersampling is not intended for map projected products"); } sourceImageWidth = sourceProduct.getSceneRasterWidth(); sourceImageHeight = sourceProduct.getSceneRasterHeight(); absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct); switch (method) { case SUB_SAMPLING: initializeForSubSampling(); break; case KERNEL_FILTERING: initializeForKernelFiltering(); break; default: throw new OperatorException("Unknown undersampling method: " + method); } } /** * Initialization for sub-sampling. * * @throws OperatorException The exceptions. */ private void initializeForSubSampling() throws OperatorException { try { if (sourceBandNames == null || sourceBandNames.length == 0) { final Band[] bands = sourceProduct.getBands(); final List<String> bandNameList = new ArrayList<>(sourceProduct.getNumBands()); for (Band band : bands) { bandNameList.add(band.getName()); } sourceBandNames = bandNameList.toArray(new String[bandNameList.size()]); } for (int i = 0; i < sourceBandNames.length; i++) { final String unit1 = sourceProduct.getBand(sourceBandNames[i]).getUnit(); if (unit1 != null && unit1.contains(Unit.REAL)) { if (i + 1 < sourceBandNames.length && sourceProduct.getBand(sourceBandNames[i + 1]).getUnit().contains(Unit.IMAGINARY)) { i++; } else { throw new OperatorException("Real and imaginary bands should be selected in pairs"); } } } subsetReader = new ProductSubsetBuilder(); final ProductSubsetDef subsetDef = new ProductSubsetDef(); subsetDef.addNodeNames(sourceProduct.getTiePointGridNames()); subsetDef.addNodeNames(sourceBandNames); subsetDef.setSubSampling(subSamplingX, subSamplingY); subsetDef.setIgnoreMetadata(false); subsetDef.setTreatVirtualBandsAsRealBands(true); targetProduct = subsetReader.readProductNodes(sourceProduct, subsetDef); ProductUtils.copyMetadata(sourceProduct, targetProduct); ProductUtils.copyFlagCodings(sourceProduct, targetProduct); ProductUtils.copyGeoCoding(sourceProduct, targetProduct); targetProduct.setStartTime(sourceProduct.getStartTime()); targetProduct.setEndTime(sourceProduct.getEndTime()); updateTargetProductMetadata(subSamplingX, subSamplingY); } catch (Throwable e) { OperatorUtils.catchOperatorException(getId(), e); } } private void updateTargetProductMetadata(int subSamplingX, int subSamplingY) { getSrcImagePixelSpacings(); targetImageWidth = (sourceImageWidth - 1) / subSamplingX + 1; targetImageHeight = (sourceImageHeight - 1) / subSamplingY + 1; rangeSpacing = srcRangeSpacing * sourceImageWidth / targetImageWidth; azimuthSpacing = srcAzimuthSpacing * sourceImageHeight / targetImageHeight; final MetadataElement absTgt = AbstractMetadata.getAbstractedMetadata(targetProduct); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.azimuth_spacing, azimuthSpacing); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.range_spacing, rangeSpacing); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.num_samples_per_line, targetImageWidth); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.num_output_lines, targetImageHeight); final float oldLineTimeInterval = (float) absTgt.getAttributeDouble(AbstractMetadata.line_time_interval); AbstractMetadata.setAttribute( absTgt, AbstractMetadata.line_time_interval, oldLineTimeInterval * (float) subSamplingY); } /** * Initialization for kernel filtering. * * @throws OperatorException The exceptions. */ private void initializeForKernelFiltering() throws OperatorException { try { filterWidth = FilterWindow.parseWindowSize(filterSize); filterHeight = filterWidth; getSrcImagePixelSpacings(); computeTargetImageSizeAndPixelSpacings(); computeRangeAzimuthStepSizes(); getKernelFile(); createTargetProduct(); } catch (Exception e) { throw new OperatorException(e.getMessage()); } } /** * Get the range and azimuth spacings (in meter). */ void getSrcImagePixelSpacings() { srcRangeSpacing = (float) absRoot.getAttributeDouble(AbstractMetadata.range_spacing); //System.out.println("Range spacing is " + srcRangeSpacing); srcAzimuthSpacing = (float) absRoot.getAttributeDouble(AbstractMetadata.azimuth_spacing); //System.out.println("Azimuth spacing is " + srcAzimuthSpacing); } /** * Compute target image size and range/azimuth spacings. * * @throws OperatorException The exceptions. */ private void computeTargetImageSizeAndPixelSpacings() throws OperatorException { switch (outputImageBy) { case IMAGE_SIZE: if (targetImageHeight <= 0 || targetImageHeight >= sourceImageHeight || targetImageWidth <= 0 || targetImageWidth >= sourceImageWidth) { throw new OperatorException("Output image size must be positive and smaller than the source image size"); } rangeSpacing = srcRangeSpacing * sourceImageWidth / targetImageWidth; azimuthSpacing = srcAzimuthSpacing * sourceImageHeight / targetImageHeight; break; case RATIO: if (widthRatio <= 0 || widthRatio > 1 || heightRatio <= 0 || heightRatio > 1) { throw new OperatorException("The width or height ratio must be within range (0, 1)"); } targetImageHeight = (int) (heightRatio * sourceImageHeight + 0.5f); targetImageWidth = (int) (widthRatio * sourceImageWidth + 0.5f); rangeSpacing = srcRangeSpacing / widthRatio; azimuthSpacing = srcAzimuthSpacing / heightRatio; break; case PIXEL_SPACING: if (rangeSpacing <= srcRangeSpacing || azimuthSpacing <= srcAzimuthSpacing) { throw new OperatorException("The azimuth or range spacing must be greater than the source spacing"); } targetImageHeight = (int) (srcRangeSpacing / rangeSpacing * sourceImageHeight + 0.5); targetImageWidth = (int) (srcAzimuthSpacing / azimuthSpacing * sourceImageWidth + 0.5); break; default: throw new OperatorException("Please specify output image size, or row and column ratios, or pixel spacings"); } } /** * Compute range and azimuth step size for kernel filtering. */ private void computeRangeAzimuthStepSizes() { stepAzimuth = (double) (sourceImageHeight - filterHeight) / (double) (targetImageHeight - 1); stepRange = (double) (sourceImageWidth - filterWidth) / (double) (targetImageWidth - 1); } /** * Read pre-defined or user defined kernel file. */ private void getKernelFile() throws IOException { String fileName = ""; if (filterType.equals(USER_DEFINED)) { // user defined kernel file filterHeight = kernel.length; filterWidth = kernel[0].length; } else { // pre-defined kernel file with user specified filter diemnsion switch (filterType) { case SUMMARY: fileName = "sum_" + filterHeight + '_' + filterWidth + ".ker"; break; case EDGE_DETECT: fileName = "edd_" + filterHeight + '_' + filterWidth + ".ker"; break; case EDGE_ENHANCEMENT: fileName = "ede_" + filterHeight + '_' + filterWidth + ".ker"; break; case LOW_PASS: fileName = "lop_" + filterHeight + '_' + filterWidth + ".ker"; break; case HIGH_PASS: fileName = "hip_" + filterHeight + '_' + filterWidth + ".ker"; break; case HORIZONTAL: fileName = "hor_" + filterHeight + '_' + filterWidth + ".ker"; break; case VERTICAL: fileName = "ver_" + filterHeight + '_' + filterWidth + ".ker"; break; default: throw new OperatorException("Incorrect filter type: " + filterType); } kernel = readFile(getResFile(fileName), fileName); if (filterHeight != kernel.length || filterWidth != kernel[0].length) { throw new OperatorException("Kernel size does not match given filter size"); } } } private InputStream getResFile(String fileName) throws IOException { final Path moduleBasePath = ResourceInstaller.findModuleCodeBasePath(this.getClass()); final Path kernelPath = moduleBasePath.resolve("org/esa/s1tbx/utilities/kernels/" + fileName); return ResourceUtils.getResourceAsStream(kernelPath.toString(), this.getClass()); } /** * Read data from kernel file and save them in a 2D array. * * @param fileName The kernel file name * @return array The 2D array holding kernel data */ public static float[][] readFile(final InputStream stream, final String fileName) { // get reader final BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); // read data from file and save them in 2-D array String line = ""; StringTokenizer st; float[][] array; int rowIdx = 0; try { // get the 1st line if ((line = reader.readLine()) == null) { throw new OperatorException("Empty file: " + fileName); } st = new StringTokenizer(line); if (st.countTokens() != 2) { throw new OperatorException("Incorrect file format: " + fileName); } final int numRows = Integer.parseInt(st.nextToken()); final int numCols = Integer.parseInt(st.nextToken()); array = new float[numRows][numCols]; // get the rest numRows lines while ((line = reader.readLine()) != null) { st = new StringTokenizer(line); if (st.countTokens() != numCols) { throw new OperatorException("Incorrect file format: " + fileName); } for (int j = 0; j < numCols; j++) { array[rowIdx][j] = Float.parseFloat(st.nextToken()); } rowIdx++; } if (numRows != rowIdx) { throw new OperatorException("Incorrect number of lines in file: " + fileName); } reader.close(); stream.close(); } catch (IOException e) { throw new OperatorException(e); } return array; } /** * Create target product. */ private void createTargetProduct() { targetProduct = new Product(sourceProduct.getName() + PRODUCT_SUFFIX, sourceProduct.getProductType(), targetImageWidth, targetImageHeight); OperatorUtils.addSelectedBands( sourceProduct, sourceBandNames, targetProduct, targetBandNameToSourceBandName, false, true); ProductUtils.copyMetadata(sourceProduct, targetProduct); ProductUtils.copyFlagCodings(sourceProduct, targetProduct); targetProduct.setStartTime(sourceProduct.getStartTime()); targetProduct.setEndTime(sourceProduct.getEndTime()); addGeoCoding(); updateTargetProductMetadata(); } private void addGeoCoding() { final TiePointGrid lat = OperatorUtils.getLatitude(sourceProduct); final TiePointGrid lon = OperatorUtils.getLongitude(sourceProduct); final TiePointGrid incidenceAngle = OperatorUtils.getIncidenceAngle(sourceProduct); final TiePointGrid slantRgTime = OperatorUtils.getSlantRangeTime(sourceProduct); if (lat == null || lon == null || incidenceAngle == null || slantRgTime == null) { // for unit test ProductUtils.copyTiePointGrids(sourceProduct, targetProduct); ProductUtils.copyGeoCoding(sourceProduct, targetProduct); return; } final int gridWidth = 11; final int gridHeight = 11; final float subSamplingX = targetImageWidth / (gridWidth - 1.0f); final float subSamplingY = targetImageHeight / (gridHeight - 1.0f); final PixelPos[] newTiePointPos = new PixelPos[gridWidth * gridHeight]; int k = 0; for (int j = 0; j < gridHeight; j++) { final float ty = Math.min(j * subSamplingY, targetImageHeight - 1); final float y = (int) (ty * stepAzimuth + 0.5) + (int) (filterHeight * 0.5); for (int i = 0; i < gridWidth; i++) { final float tx = Math.min(i * subSamplingX, targetImageWidth - 1); final float x = (int) (tx * stepRange + 0.5) + (int) (filterWidth * 0.5); newTiePointPos[k] = new PixelPos(); newTiePointPos[k].x = x; newTiePointPos[k].y = y; k++; } } OperatorUtils.createNewTiePointGridsAndGeoCoding( sourceProduct, targetProduct, gridWidth, gridHeight, subSamplingX, subSamplingY, newTiePointPos); } /** * Update metadata in the target product. */ private void updateTargetProductMetadata() { final MetadataElement absTgt = AbstractMetadata.getAbstractedMetadata(targetProduct); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.azimuth_spacing, azimuthSpacing); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.range_spacing, rangeSpacing); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.num_samples_per_line, targetImageWidth); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.num_output_lines, targetImageHeight); final float oldLineTimeInterval = (float) absTgt.getAttributeDouble(AbstractMetadata.line_time_interval); AbstractMetadata.setAttribute(absTgt, AbstractMetadata.line_time_interval, oldLineTimeInterval * (float) stepAzimuth); final String oldFirstLineTime = absTgt.getAttributeString(AbstractMetadata.first_line_time); final int idx = oldFirstLineTime.lastIndexOf(':') + 1; final String oldSecondsStr = oldFirstLineTime.substring(idx); final double oldSeconds = Double.parseDouble(oldSecondsStr); final double newSeconds = oldSeconds + oldLineTimeInterval * (filterHeight - 1) / 2.0; final String newFirstLineTime = String.valueOf(oldFirstLineTime.subSequence(0, idx)) + newSeconds + "000000"; AbstractMetadata.setAttribute(absTgt, AbstractMetadata.first_line_time, AbstractMetadata.parseUTC(newFirstLineTime.substring(0, 27))); // AbstractMetadata.parseUTC(newFirstLineTime.substring(0,27), "dd-MMM-yyyy HH:mm:ss")); } @Override public void computeTile(Band targetBand, Tile targetTile, ProgressMonitor pm) throws OperatorException { try { switch (method) { case SUB_SAMPLING: computeTileUsingSubSampling(targetBand, targetTile, pm); break; case KERNEL_FILTERING: computeTileUsingKernelFiltering(targetBand, targetTile); break; default: throw new OperatorException("Unknown undersampling method: " + method); } } catch (Throwable e) { OperatorUtils.catchOperatorException(getId(), e); } finally { pm.done(); } } private void computeTileUsingSubSampling(Band targetBand, Tile targetTile, ProgressMonitor pm) { final ProductData destBuffer = targetTile.getRawSamples(); final Rectangle rectangle = targetTile.getRectangle(); try { subsetReader.readBandRasterData(targetBand, rectangle.x, rectangle.y, rectangle.width, rectangle.height, destBuffer, pm); targetTile.setRawSamples(destBuffer); } catch (IOException e) { throw new OperatorException(e); } } private void computeTileUsingKernelFiltering(Band targetBand, Tile targetTile) { final Rectangle targetTileRectangle = targetTile.getRectangle(); final int tx0 = targetTileRectangle.x; final int ty0 = targetTileRectangle.y; final int tw = targetTileRectangle.width; final int th = targetTileRectangle.height; //System.out.println("tx0 = " + tx0 + ", ty0 = " + ty0 + ", tw = " + tw + ", th = " + th); final int x0 = (int) (tx0 * stepRange + 0.5f); final int y0 = (int) (ty0 * stepAzimuth + 0.5f); final int w = (int) ((tx0 + tw - 1) * stepRange + 0.5f) + filterWidth - (int) (tx0 * stepRange + 0.5f); final int h = (int) ((ty0 + th - 1) * stepAzimuth + 0.5f) + filterHeight - (int) (ty0 * stepAzimuth + 0.5f); final Rectangle sourceTileRectangle = new Rectangle(x0, y0, w, h); //System.out.println("x0 = " + x0 + ", y0 = " + y0 + ", w = " + w + ", h = " + h); Tile sourceRaster1 = null; Tile sourceRaster2 = null; Band sourceBand1; final String[] srcBandNames = targetBandNameToSourceBandName.get(targetBand.getName()); if (srcBandNames.length == 1) { sourceBand1 = sourceProduct.getBand(srcBandNames[0]); sourceRaster1 = getSourceTile(sourceBand1, sourceTileRectangle); if (sourceRaster1 == null) { throw new OperatorException("Cannot get source tile"); } } else { sourceBand1 = sourceProduct.getBand(srcBandNames[0]); final Band sourceBand2 = sourceProduct.getBand(srcBandNames[1]); sourceRaster1 = getSourceTile(sourceBand1, sourceTileRectangle); sourceRaster2 = getSourceTile(sourceBand2, sourceTileRectangle); if (sourceRaster1 == null || sourceRaster2 == null) { throw new OperatorException("Cannot get source tile"); } } final Unit.UnitType bandUnitType = Unit.getUnitType(sourceBand1); final ProductData trgData = targetTile.getDataBuffer(); double filteredValue; final int maxy = ty0 + th; final int maxx = tx0 + tw; for (int ty = ty0; ty < maxy; ty++) { for (int tx = tx0; tx < maxx; tx++) { filteredValue = getFilteredValue(tx, ty, sourceRaster1, sourceRaster2, bandUnitType); trgData.setElemDoubleAt(targetTile.getDataBufferIndex(tx, ty), filteredValue); } } } private double getFilteredValue(int tx, int ty, Tile sourceRaster1, Tile sourceRaster2, Unit.UnitType bandUnitType) { final int x0 = (int) (tx * stepRange + 0.5); final int y0 = (int) (ty * stepAzimuth + 0.5); final int maxY = y0 + filterHeight; final int maxX = x0 + filterWidth; final ProductData srcData1 = sourceRaster1.getDataBuffer(); ProductData srcData2 = null; if (sourceRaster2 != null) srcData2 = sourceRaster2.getDataBuffer(); float numPixels = filterWidth * filterHeight; double filteredValue = 0.0; for (int y = y0; y < maxY; y++) { for (int x = x0; x < maxX; x++) { final int index = sourceRaster1.getDataBufferIndex(x, y); final float weight = kernel[maxY - 1 - y][maxX - 1 - x] / numPixels; if (bandUnitType == Unit.UnitType.INTENSITY_DB || bandUnitType == Unit.UnitType.AMPLITUDE_DB) { final double dn = srcData1.getElemDoubleAt(index); filteredValue += FastMath.pow(10, dn / 10.0) * weight; // dB to linear } else if (sourceRaster2 == null) { filteredValue += srcData1.getElemDoubleAt(index) * weight; } else { // COMPLEX final double i = srcData1.getElemDoubleAt(index); final double q = srcData2.getElemDoubleAt(index); filteredValue += (i * i + q * q) * weight; } } } if (bandUnitType == Unit.UnitType.INTENSITY_DB || bandUnitType == Unit.UnitType.AMPLITUDE_DB) { filteredValue = 10.0 * Math.log10(filteredValue); // linear to dB } return filteredValue; } /** * Set undersampling method. The function is for unit test only. * * @param samplingMethod The undersampling method. */ public void setUndersamplingMethod(String samplingMethod) { method = samplingMethod; } /** * Set sub-sampling rate for both x and y. The function is for unit test only. * * @param subSamplingRateX The sub-sampling rate for x. * @param subSamplingRateY The sub-sampling rate for y. */ public void setSubSamplingRate(int subSamplingRateX, int subSamplingRateY) { subSamplingX = subSamplingRateX; subSamplingY = subSamplingRateY; } /** * Set filter type. The function is for unit test only. * * @param type The filter type. */ public void setFilterType(String type) { filterType = type; } /** * Set filter size. The function is for unit test only. * * @param size The filter size. */ public void setFilterSize(String size) { filterSize = size; } /** * Set the output image dimension. The function is for unit test only. * * @param numRows The number of rows. * @param numCols The number of columns. */ public void setOutputImageSize(int numRows, int numCols) { targetImageHeight = numRows; targetImageWidth = numCols; } /** * Set the output image method. The function is for unit test only. * * @param method The output image method. */ public void setOutputImageBy(String method) { outputImageBy = method; } /** * The SPI is used to register this operator in the graph processing framework * via the SPI configuration file * {@code META-INF/services/org.esa.snap.core.gpf.OperatorSpi}. * This class may also serve as a factory for new operator instances. * * @see OperatorSpi#createOperator() * @see OperatorSpi#createOperator(java.util.Map, java.util.Map) */ public static class Spi extends OperatorSpi { public Spi() { super(UndersamplingOp.class); } } }