/*
* 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);
}
}
}