/*
* Copyright (C) 2014 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.calibration.gpf;
import com.bc.ceres.core.ProgressMonitor;
import org.esa.s1tbx.calibration.gpf.calibrators.Sentinel1Calibrator;
import org.esa.s1tbx.insar.gpf.support.Sentinel1Utils;
import org.esa.snap.core.datamodel.*;
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.engine_utilities.datamodel.AbstractMetadata;
import org.esa.snap.engine_utilities.datamodel.Unit;
import org.esa.snap.engine_utilities.gpf.InputProductValidator;
import org.esa.snap.engine_utilities.gpf.OperatorUtils;
import org.esa.snap.engine_utilities.gpf.TileIndex;
import org.esa.snap.engine_utilities.util.Maths;
import java.awt.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* Apply thermal noise correction to Sentinel-1 Level-1 products.
*/
@OperatorMetadata(alias = "ThermalNoiseRemoval",
category = "Radar/Radiometric",
authors = "Cecilia Wong, Jun Lu, Luis Veci",
copyright = "Copyright (C) 2014 by Array Systems Computing Inc.",
version = "1.0",
description = "Removes thermal noise from products")
public final class Sentinel1RemoveThermalNoiseOp extends Operator {
@SourceProduct
private Product sourceProduct;
@TargetProduct
private Product targetProduct;
@Parameter(description = "The list of polarisations", label = "Polarisations")
private String[] selectedPolarisations;
@Parameter(description = "Remove thermal noise", defaultValue = "true", label = "Remove Thermal Noise")
private Boolean removeThermalNoise = true;
@Parameter(description = "Re-introduce thermal noise", defaultValue = "false", label = "Re-Introduce Thermal Noise")
private Boolean reIntroduceThermalNoise = false;
private MetadataElement absRoot = null;
private MetadataElement origMetadataRoot = null;
private boolean thermalNoiseCorrectionPerformed = false;
private boolean absoluteCalibrationPerformed = false;
private boolean isComplex = false;
private boolean inputSigmaBand = false;
private boolean inputBetaBand = false;
private boolean inputGammaBand = false;
private boolean inputDNBand = false;
private boolean isTOPSARSLC = false;
private String productType = null;
private int numOfSubSwath = 1;
private ThermalNoiseInfo[] noise = null;
private Sentinel1Calibrator.CalibrationInfo[] calibration = null;
private java.util.List<String> selectedPolList = null;
private final HashMap<String, String[]> targetBandNameToSourceBandName = new HashMap<>(2);
/**
* Default constructor. The graph processing framework
* requires that an operator has a default constructor.
*/
public Sentinel1RemoveThermalNoiseOp() {
}
/**
* Initializes this operator and sets the one and only target product.
* <p>The target product can be either defined by a field of type {@link Product} annotated with the
* {@link TargetProduct TargetProduct} annotation or
* by calling {@link #setTargetProduct} method.</p>
* <p>The framework calls this method after it has created this operator.
* Any client code that must be performed before computation of tile data
* should be placed here.</p>
*
* @throws OperatorException If an error occurs during operator initialisation.
* @see #getTargetProduct()
*/
@Override
public void initialize() throws OperatorException {
try {
final InputProductValidator validator = new InputProductValidator(sourceProduct);
validator.checkIfSentinel1Product();
validator.checkAcquisitionMode(new String[] {"IW","EW","SM"});
validator.checkProductType(new String[] {"SLC","GRD"});
absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct);
origMetadataRoot = AbstractMetadata.getOriginalProductMetadata(sourceProduct);
getProductType();
getAcquisitionMode();
getThermalNoiseCorrectionFlag();
setSelectedPolarisations();
noise = getThermalNoiseVectors(origMetadataRoot, selectedPolList, numOfSubSwath);
getSampleType();
getCalibrationFlag();
if (absoluteCalibrationPerformed) {
getCalibrationVectors();
}
createTargetProduct();
updateTargetProductMetadata();
} catch (Throwable e) {
OperatorUtils.catchOperatorException(getId(), e);
}
}
/**
* Get product type from abstracted metadata.
*/
private void getProductType() {
productType = absRoot.getAttributeString(AbstractMetadata.PRODUCT_TYPE);
String mode = absRoot.getAttributeString(AbstractMetadata.ACQUISITION_MODE);
isTOPSARSLC = productType.contains("SLC") && (mode.contains("IW") || mode.contains("EW"));
}
/**
* Get acquisition mode from abstracted metadata.
*/
private void getAcquisitionMode() {
final String acquisitionMode = absRoot.getAttributeString(AbstractMetadata.ACQUISITION_MODE);
if (productType.equals("SLC")) {
if (acquisitionMode.equals("IW")) {
numOfSubSwath = 3;
} else if (acquisitionMode.equals("EW")) {
numOfSubSwath = 5;
}
}
}
/**
* Get thermal noise correction flag from the original product metadata.
*/
private void getThermalNoiseCorrectionFlag() {
final MetadataElement annotationElem = origMetadataRoot.getElement("annotation");
final MetadataElement[] annotationDataSetListElem = annotationElem.getElements();
final MetadataElement productElem = annotationDataSetListElem[0].getElement("product");
final MetadataElement imageAnnotationElem = productElem.getElement("imageAnnotation");
final MetadataElement processingInformationElem = imageAnnotationElem.getElement("processingInformation");
thermalNoiseCorrectionPerformed = Boolean.parseBoolean(
processingInformationElem.getAttribute("thermalNoiseCorrectionPerformed").getData().getElemString());
if (removeThermalNoise && thermalNoiseCorrectionPerformed) {
throw new OperatorException("Thermal noise correction has already been performed for the product");
}
if (reIntroduceThermalNoise && !thermalNoiseCorrectionPerformed) {
throw new OperatorException("Thermal noise correction has never been performed for the product");
}
}
/**
* Get thermal noise vectors from the original product metadata.
*/
public static ThermalNoiseInfo[] getThermalNoiseVectors(final MetadataElement origMetadataRoot,
final List<String> selectedPolList,
final int numOfSubSwath) throws IOException {
final ThermalNoiseInfo[] noise = new ThermalNoiseInfo[numOfSubSwath * selectedPolList.size()];
if(origMetadataRoot == null) {
throw new IOException("Unable to find original product metadata");
}
final MetadataElement noiseElem = origMetadataRoot.getElement("noise");
if(noiseElem == null) {
throw new IOException("Unable to find noise element in original product metadata");
}
final MetadataElement[] noiseDataSetListElem = noiseElem.getElements();
int dataSetIndex = 0;
for (MetadataElement dataSetListElem : noiseDataSetListElem) {
final MetadataElement noiElem = dataSetListElem.getElement("noise");
final MetadataElement adsHeaderElem = noiElem.getElement("adsHeader");
final String pol = adsHeaderElem.getAttributeString("polarisation");
if (!selectedPolList.contains(pol)) {
continue;
}
final MetadataElement noiseVectorListElem = noiElem.getElement("noiseVectorList");
final String subSwath = adsHeaderElem.getAttributeString("swath");
noise[dataSetIndex] = new ThermalNoiseInfo(pol, subSwath,
Sentinel1Utils.getTime(adsHeaderElem, "startTime").getMJD(),
Sentinel1Utils.getTime(adsHeaderElem, "stopTime").getMJD(),
Sentinel1Calibrator.getNumOfLines(origMetadataRoot, pol, subSwath),
Integer.parseInt(noiseVectorListElem.getAttributeString("count")),
Sentinel1Utils.getNoiseVector(noiseVectorListElem));
dataSetIndex++;
}
return noise;
}
private void getSampleType() {
final String sampleType = absRoot.getAttributeString(AbstractMetadata.SAMPLE_TYPE);
if (sampleType.equals("COMPLEX")) {
isComplex = true;
}
}
/**
* Get absolute calibration flag from the abstracted metadata.
*/
private void getCalibrationFlag() {
absoluteCalibrationPerformed =
absRoot.getAttribute(AbstractMetadata.abs_calibration_flag).getData().getElemBoolean();
if (absoluteCalibrationPerformed) {
if (isComplex) {
// Currently the calibrated complex product can only be sigma0, this should be changed later if
// complex beta0 and gamma0 are available
inputSigmaBand = true;
} else {
final String[] sourceBandNames = sourceProduct.getBandNames();
for (String bandName : sourceBandNames) {
if (bandName.contains("Sigma0")) {
inputSigmaBand = true;
} else if (bandName.contains("Gamma0")) {
inputGammaBand = true;
} else if (bandName.contains("Beta0")) {
inputBetaBand = true;
} else if (bandName.contains("DN")) {
inputDNBand = true;
}
}
if (!inputSigmaBand && !inputGammaBand && !inputBetaBand && !inputDNBand) {
throw new OperatorException("For calibrated product, Sigma0 or Gamma0 or Beta0 or DN band is expected");
}
}
}
}
/**
* Get calibration vectors from the original product metadata.
*/
private void getCalibrationVectors() throws IOException {
calibration = Sentinel1Calibrator.getCalibrationVectors(
sourceProduct,
selectedPolList,
inputSigmaBand,
inputBetaBand,
inputGammaBand,
inputDNBand);
}
/**
* Set user selected polarisations.
*/
private void setSelectedPolarisations() {
String[] selectedPols = selectedPolarisations;
if (selectedPols == null || selectedPols.length == 0) {
selectedPols = Sentinel1Utils.getProductPolarizations(absRoot);
}
selectedPolList = Arrays.asList(selectedPols);
}
/**
* Create a target product for output.
*/
private void createTargetProduct() {
targetProduct = new Product(sourceProduct.getName(),
sourceProduct.getProductType(),
sourceProduct.getSceneRasterWidth(),
sourceProduct.getSceneRasterHeight());
addSelectedBands();
ProductUtils.copyProductNodes(sourceProduct, targetProduct);
}
/**
* Add user selected bands to target product.
*/
private void addSelectedBands() {
final Band[] sourceBands = sourceProduct.getBands();
for (int i = 0; i < sourceBands.length; i++) {
final Band srcBand = sourceBands[i];
if (srcBand instanceof VirtualBand) {
continue;
}
final String unit = srcBand.getUnit();
if (unit == null) {
throw new OperatorException("band " + srcBand.getName() + " requires a unit");
}
if (!unit.contains(Unit.REAL) && !unit.contains(Unit.AMPLITUDE) && !unit.contains(Unit.INTENSITY)) {
continue;
}
String[] srcBandNames;
if (unit.contains(Unit.REAL)) { // SLC
if (i + 1 >= sourceBands.length) {
throw new OperatorException("Real and imaginary bands are not in pairs");
}
final String nextUnit = sourceBands[i + 1].getUnit();
if (nextUnit == null || !nextUnit.contains(Unit.IMAGINARY)) {
throw new OperatorException("Real and imaginary bands are not in pairs");
}
srcBandNames = new String[2];
srcBandNames[0] = srcBand.getName();
srcBandNames[1] = sourceBands[i + 1].getName();
++i;
} else { // GRD
srcBandNames = new String[1];
srcBandNames[0] = srcBand.getName();
}
final String pol = srcBandNames[0].substring(srcBandNames[0].lastIndexOf("_") + 1);
if (!selectedPolList.contains(pol)) {
continue;
}
final String targetBandName = createTargetBandName(srcBandNames[0]);
if (targetProduct.getBand(targetBandName) == null) {
targetBandNameToSourceBandName.put(targetBandName, srcBandNames);
final Band targetBand = new Band(
targetBandName,
ProductData.TYPE_FLOAT32,
srcBand.getRasterWidth(),
srcBand.getRasterHeight());
targetBand.setUnit(Unit.INTENSITY);
targetBand.setDescription(srcBand.getDescription());
targetBand.setNoDataValue(srcBand.getNoDataValue());
targetBand.setNoDataValueUsed(srcBand.isNoDataValueUsed());
targetProduct.addBand(targetBand);
}
}
}
/**
* Create target band name for given source bane name.
*
* @param sourceBandName Source band name string.
* @return Target band name string.
*/
private String createTargetBandName(final String sourceBandName) {
final String pol = sourceBandName.substring(sourceBandName.indexOf('_'));
if (absoluteCalibrationPerformed) {
if (isComplex) {
return "Sigma0" + pol;
} else {
return sourceBandName;
}
}
return "Intensity" + pol;
}
/**
* Update target product metadata.
*/
private void updateTargetProductMetadata() {
final MetadataElement abs = AbstractMetadata.getAbstractedMetadata(targetProduct);
final String[] targetBandNames = targetProduct.getBandNames();
Sentinel1Utils.updateBandNames(abs, selectedPolList, targetBandNames);
final MetadataElement origMetadataRoot = AbstractMetadata.getOriginalProductMetadata(targetProduct);
final MetadataElement annotationElem = origMetadataRoot.getElement("annotation");
final MetadataElement[] annotationDataSetListElem = annotationElem.getElements();
for (MetadataElement elem : annotationDataSetListElem) {
final MetadataElement productElem = elem.getElement("product");
final MetadataElement imageAnnotationElem = productElem.getElement("imageAnnotation");
final MetadataElement processingInformationElem = imageAnnotationElem.getElement("processingInformation");
if (removeThermalNoise) {
processingInformationElem.getAttribute("thermalNoiseCorrectionPerformed").getData().setElems("true");
}
if (reIntroduceThermalNoise) {
processingInformationElem.getAttribute("thermalNoiseCorrectionPerformed").getData().setElems("false");
}
}
}
/**
* Called by the framework in order to compute a tile for the given target band.
* <p>The default implementation throws a runtime exception with the message "not implemented".</p>
*
* @param targetBand The target band.
* @param targetTile The current tile associated with the target band to be computed.
* @param pm A progress monitor which should be used to determine computation cancelation requests.
* @throws OperatorException If an error occurs during computation of the target raster.
*/
@Override
public void computeTile(Band targetBand, Tile targetTile, ProgressMonitor pm) throws OperatorException {
final Rectangle targetTileRectangle = targetTile.getRectangle();
final int x0 = targetTileRectangle.x;
final int y0 = targetTileRectangle.y;
final int w = targetTileRectangle.width;
final int h = targetTileRectangle.height;
//System.out.println("x0 = " + x0 + ", y0 = " + y0 + ", w = " + w + ", h = " + h + ", target band = " + targetBand.getName());
try {
final String targetBandName = targetBand.getName();
final ThermalNoiseInfo noiseInfo = getNoiseInfo(targetBandName);
Tile sourceRaster1 = null;
ProductData srcData1 = null;
ProductData srcData2 = null;
Band sourceBand1 = null;
final String[] srcBandNames = targetBandNameToSourceBandName.get(targetBand.getName());
if (srcBandNames.length == 1) {
sourceBand1 = sourceProduct.getBand(srcBandNames[0]);
sourceRaster1 = getSourceTile(sourceBand1, targetTileRectangle);
srcData1 = sourceRaster1.getDataBuffer();
} else {
sourceBand1 = sourceProduct.getBand(srcBandNames[0]);
final Band sourceBand2 = sourceProduct.getBand(srcBandNames[1]);
sourceRaster1 = getSourceTile(sourceBand1, targetTileRectangle);
final Tile sourceRaster2 = getSourceTile(sourceBand2, targetTileRectangle);
srcData1 = sourceRaster1.getDataBuffer();
srcData2 = sourceRaster2.getDataBuffer();
}
final Unit.UnitType bandUnit = Unit.getUnitType(sourceBand1);
final ProductData trgData = targetTile.getDataBuffer();
final TileIndex srcIndex = new TileIndex(sourceRaster1);
final TileIndex tgtIndex = new TileIndex(targetTile);
final int maxY = y0 + h;
final int maxX = x0 + w;
final boolean complexData = bandUnit == Unit.UnitType.REAL || bandUnit == Unit.UnitType.IMAGINARY;
Sentinel1Calibrator.CalibrationInfo calInfo = null;
Sentinel1Calibrator.CALTYPE calType = null;
if (absoluteCalibrationPerformed) {
calInfo = getCalInfo(targetBandName);
calType = Sentinel1Calibrator.getCalibrationType(targetBandName);
}
double dn, dn2, i, q;
int srcIdx, tgtIdx;
for (int y = y0; y < maxY; ++y) {
srcIndex.calculateStride(y);
tgtIndex.calculateStride(y);
final double[] lut = new double[w];
if (absoluteCalibrationPerformed) {
final int calVecIdx = calInfo.getCalibrationVectorIndex(y);
final Sentinel1Utils.CalibrationVector vec0 = calInfo.getCalibrationVector(calVecIdx);
final Sentinel1Utils.CalibrationVector vec1 = calInfo.getCalibrationVector(calVecIdx + 1);
final float[] vec0LUT = Sentinel1Calibrator.getVector(calType, vec0);
final float[] vec1LUT = Sentinel1Calibrator.getVector(calType, vec1);
final Sentinel1Utils.CalibrationVector calVec = calInfo.calibrationVectorList[calVecIdx];
final int pixelIdx0 = calVec.getPixelIndex(x0);
computeTileScaledNoiseLUT(y, x0, w, noiseInfo, calInfo, vec0.timeMJD, vec1.timeMJD,
vec0LUT, vec1LUT, vec0.pixels, pixelIdx0, lut);
} else {
computeTileNoiseLUT(y, x0, w, noiseInfo, lut);
}
for (int x = x0; x < maxX; ++x) {
final int xx = x - x0;
srcIdx = srcIndex.getIndex(x);
tgtIdx = tgtIndex.getIndex(x);
if (bandUnit == Unit.UnitType.AMPLITUDE) {
dn = srcData1.getElemDoubleAt(srcIdx);
dn2 = dn * dn;
} else if (complexData) {
i = srcData1.getElemDoubleAt(srcIdx);
q = srcData2.getElemDoubleAt(srcIdx);
dn2 = i * i + q * q;
} else if (bandUnit == Unit.UnitType.INTENSITY) {
dn2 = srcData1.getElemDoubleAt(srcIdx);
} else {
throw new OperatorException("Unhandled unit");
}
double value = dn2 - lut[xx];
if(value < 0) {
value = dn2; // small intensity value; if too small, calibration will make it nodatavalue
}
trgData.setElemDoubleAt(tgtIdx, value);
}
}
} catch (Throwable e) {
throw new OperatorException(e.getMessage());
}
}
/**
* Get thermal noise information for given target band.
*
* @param targetBandName Target band name.
* @return The ThermalNoiseInfo object.
*/
private ThermalNoiseInfo getNoiseInfo(final String targetBandName) throws OperatorException {
for (ThermalNoiseInfo noiseInfo : noise) {
if (isTOPSARSLC) {
if (targetBandName.contains(noiseInfo.polarization) && targetBandName.contains(noiseInfo.subSwath)) {
return noiseInfo;
}
} else {
if (targetBandName.contains(noiseInfo.polarization)) {
return noiseInfo;
}
}
}
throw new OperatorException("NoiseInfo not found for "+targetBandName);
}
/**
* Get calibration information for given target band.
*
* @param targetBandName Target band name.
* @return The CalibrationInfo object.
*/
private Sentinel1Calibrator.CalibrationInfo getCalInfo(final String targetBandName) {
for (Sentinel1Calibrator.CalibrationInfo cal : calibration) {
final String pol = cal.polarization;
final String ss = cal.subSwath;
if (isTOPSARSLC) {
if (targetBandName.contains(pol) && targetBandName.contains(ss)) {
return cal;
}
} else {
if (targetBandName.contains(pol)) {
return cal;
}
}
}
return null;
}
/**
* Compute scaled noise LUTs for the given range line.
*
* @param y Index of the given range line.
* @param x0 X coordinate of the upper left corner pixel of the given tile.
* @param w Tile width.
* @param noiseInfo Object of ThermalNoiseInfo class.
* @param calInfo Object of CalibrationInfo class.
* @param lut The scaled noise LUT.
*/
private void computeTileScaledNoiseLUT(final int y, final int x0, final int w,
final ThermalNoiseInfo noiseInfo,
final Sentinel1Calibrator.CalibrationInfo calInfo,
final double azT0, final double azT1,
final float[] vec0LUT, final float[] vec1LUT,
final int[] vec0Pixels, final int pixelIdx0,
final double[] lut) {
final double[] noiseLut = new double[w];
computeTileNoiseLUT(y, x0, w, noiseInfo, noiseLut);
final double[] calLut = new double[w];
computeTileCalibrationLUTs(y, x0, w, calInfo, azT0, azT1,
vec0LUT, vec1LUT, vec0Pixels, pixelIdx0, calLut);
if (removeThermalNoise) {
for (int i = 0; i < w; i++) {
lut[i] = noiseLut[i] / (calLut[i]*calLut[i]);
}
} else { // reIntroduceThermalNoise
for (int i = 0; i < w; i++) {
lut[i] = -noiseLut[i] / (calLut[i]*calLut[i]);
}
}
}
/**
* Compute calibration LUTs for the given range line.
*
* @param y Index of the given range line.
* @param x0 X coordinate of the upper left corner pixel of the given tile.
* @param w Tile width.
* @param calInfo Object of CalibrationInfo class.
* @param lut LUT for calibration.
*/
public static void computeTileCalibrationLUTs(final int y, final int x0, final int w,
final Sentinel1Calibrator.CalibrationInfo calInfo,
final double azT0, final double azT1,
final float[] vec0LUT, final float[] vec1LUT,
final int[] vec0Pixels, int pixelIdx0,
final double[] lut) {
final double azTime = calInfo.firstLineTime + y * calInfo.lineTimeInterval;
double muX, muY = (azTime - azT0) / (azT1 - azT0);
int pixelIdx = pixelIdx0;
final int maxX = x0 + w;
for (int x = x0; x < maxX; x++) {
if (x > vec0Pixels[pixelIdx + 1]) {
pixelIdx++;
}
muX = (double) (x - vec0Pixels[pixelIdx]) / (double) (vec0Pixels[pixelIdx + 1] - vec0Pixels[pixelIdx]);
lut[x - x0] = Maths.interpolationBiLinear(
vec0LUT[pixelIdx], vec0LUT[pixelIdx + 1], vec1LUT[pixelIdx], vec1LUT[pixelIdx + 1], muX, muY);
}
}
/**
* Compute noise LUTs for the given range line.
*
* @param y Index of the given range line.
* @param x0 X coordinate of the upper left corner pixel of the given tile.
* @param w Tile width.
* @param noiseInfo Object of ThermalNoiseInfo class.
* @param lut The noise LUT.
*/
private static void computeTileNoiseLUT(final int y, final int x0, final int w,
final ThermalNoiseInfo noiseInfo, final double[] lut) {
try {
final int noiseVecIdx = getNoiseVectorIndex(y, noiseInfo);
final Sentinel1Utils.NoiseVector noiseVector0 = noiseInfo.noiseVectorList[noiseVecIdx];
final Sentinel1Utils.NoiseVector noiseVector1 = noiseInfo.noiseVectorList[noiseVecIdx + 1];
final double azTime = noiseInfo.firstLineTime + y * noiseInfo.lineTimeInterval;
final double azT0 = noiseVector0.timeMJD;
final double azT1 = noiseVector1.timeMJD;
final double muY = (azTime - azT0) / (azT1 - azT0);
int pixelIdx0 = getPixelIndex(x0, noiseVector0);
int pixelIdx1 = getPixelIndex(x0, noiseVector1);
final int maxLength0 = noiseVector0.pixels.length - 2;
final int maxLength1 = noiseVector1.pixels.length - 2;
final int maxX = x0 + w;
for (int x = x0; x < maxX; x++) {
if (x > noiseVector0.pixels[pixelIdx0 + 1] && pixelIdx0 < maxLength0) {
pixelIdx0++;
}
final int x00 = noiseVector0.pixels[pixelIdx0];
final int x01 = noiseVector0.pixels[pixelIdx0 + 1];
final double muX0 = (double) (x - x00) / (double) (x01 - x00);
final double noise0 = Maths.interpolationLinear(
noiseVector0.noiseLUT[pixelIdx0], noiseVector0.noiseLUT[pixelIdx0 + 1], muX0);
if (x > noiseVector1.pixels[pixelIdx1 + 1] && pixelIdx1 < maxLength1) {
pixelIdx1++;
}
final int x10 = noiseVector1.pixels[pixelIdx1];
final int x11 = noiseVector1.pixels[pixelIdx1 + 1];
final double muX1 = (double) (x - x10) / (double) (x11 - x10);
final double noise1 = Maths.interpolationLinear(
noiseVector1.noiseLUT[pixelIdx1], noiseVector1.noiseLUT[pixelIdx1 + 1], muX1);
lut[x - x0] = Maths.interpolationLinear(noise0, noise1, muY);
}
} catch (Throwable e) {
OperatorUtils.catchOperatorException("computeTileNoiseLUT", e);
}
}
/**
* Get index of the noise vector in the list for a given line.
*
* @param y Line coordinate.
* @param noiseInfo Object of ThermalNoiseInfo class.
* @return The noise vector index.
*/
private static int getNoiseVectorIndex(final int y, final ThermalNoiseInfo noiseInfo) {
for (int i = 1; i < noiseInfo.count; i++) {
if (y < noiseInfo.noiseVectorList[i].line) {
return i - 1;
}
}
return noiseInfo.count - 2;
}
/**
* Get pixel index in a given noise vector for a given pixel.
*
* @param x Pixel coordinate.
* @param noiseVector Noise vector.
* @return The pixel index.
*/
private static int getPixelIndex(final int x, final Sentinel1Utils.NoiseVector noiseVector) {
for (int i = 0; i < noiseVector.pixels.length; i++) {
if (x < noiseVector.pixels[i]) {
return i - 1;
}
}
return noiseVector.pixels.length - 2;
}
public static class ThermalNoiseInfo {
public String polarization;
public String subSwath;
public double firstLineTime;
public double lastLineTime;
public int numOfLines;
public int count; // number of noiseVector records within the list
public Sentinel1Utils.NoiseVector[] noiseVectorList;
final double lineTimeInterval;
ThermalNoiseInfo(final String pol, final String subSwath, final double firstLineTime, final double lastLineTime,
final int numOfLines, final int count, final Sentinel1Utils.NoiseVector[] noiseVectorList) {
this.polarization = pol;
this.subSwath = subSwath;
this.firstLineTime = firstLineTime;
this.lastLineTime = lastLineTime;
this.numOfLines = numOfLines;
this.count = count;
this.noiseVectorList = noiseVectorList;
lineTimeInterval = (lastLineTime - firstLineTime) / (numOfLines - 1);
}
}
/**
* 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(Sentinel1RemoveThermalNoiseOp.class);
}
}
}