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