/*
* 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.calibration.gpf.support.CalibrationFactory;
import org.esa.s1tbx.calibration.gpf.support.Calibrator;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.core.datamodel.VirtualBand;
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.engine_utilities.datamodel.AbstractMetadata;
import org.esa.snap.engine_utilities.gpf.InputProductValidator;
import org.esa.snap.engine_utilities.gpf.OperatorUtils;
import org.esa.snap.engine_utilities.gpf.StackUtils;
import java.io.File;
/**
* Calibration for all data products.
*
* @todo automatically search aux file in local repository using time period
*/
@OperatorMetadata(alias = "Calibration",
category = "Radar/Radiometric",
authors = "Jun Lu, Luis Veci",
copyright = "Copyright (C) 2014 by Array Systems Computing Inc.",
version = "1.0",
description = "Calibration of products")
public class CalibrationOp 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 Band")
private String[] sourceBandNames;
@Parameter(valueSet = {LATEST_AUX, PRODUCT_AUX, EXTERNAL_AUX}, description = "The auxiliary file",
defaultValue = LATEST_AUX, label = "Auxiliary File")
private String auxFile = LATEST_AUX;
@Parameter(description = "The antenna elevation pattern gain auxiliary data file.", label = "External Aux File")
private File externalAuxFile = null;
@Parameter(description = "Output image in complex", defaultValue = "false", label = "Save in complex")
private Boolean outputImageInComplex = false;
@Parameter(description = "Output image scale", defaultValue = "false", label = "Scale in dB")
private Boolean outputImageScaleInDb = false;
@Parameter(description = "Create gamma0 virtual band", defaultValue = "false", label = "Create gamma0 virtual band")
private Boolean createGammaBand = false;
@Parameter(description = "Create beta0 virtual band", defaultValue = "false", label = "Create beta0 virtual band")
private Boolean createBetaBand = false;
// for Sentinel-1 mission only
@Parameter(description = "The list of polarisations", label = "Polarisations")
private String[] selectedPolarisations;
@Parameter(description = "Output sigma0 band", defaultValue = "true", label = "Output sigma0 band")
private Boolean outputSigmaBand = true;
@Parameter(description = "Output gamma0 band", defaultValue = "false", label = "Output gamma0 band")
private Boolean outputGammaBand = false;
@Parameter(description = "Output beta0 band", defaultValue = "false", label = "Output beta0 band")
private Boolean outputBetaBand = false;
// @Parameter(description = "Output DN band", defaultValue = "false", label = "Output DN band")
private Boolean outputDNBand = false;
private Calibrator calibrator = null;
public static final String PRODUCT_AUX = "Product Auxiliary File";
public static final String LATEST_AUX = "Latest Auxiliary File";
public static final String EXTERNAL_AUX = "External Auxiliary File";
/**
* Default constructor. The graph processing framework
* requires that an operator has a default constructor.
*/
public CalibrationOp() {
}
/**
* 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.checkIfSARProduct();
if (StackUtils.isCoregisteredStack(sourceProduct)) {
throw new OperatorException("Cannot apply calibration to coregistered product.");
}
calibrator = CalibrationFactory.createCalibrator(sourceProduct);
calibrator.setAuxFileFlag(auxFile);
calibrator.setExternalAuxFile(externalAuxFile);
calibrator.setOutputImageInComplex(outputImageInComplex);
calibrator.setOutputImageIndB(outputImageScaleInDb);
if (calibrator instanceof Sentinel1Calibrator) {
Sentinel1Calibrator cal = (Sentinel1Calibrator) calibrator;
cal.setUserSelections(sourceProduct,
selectedPolarisations, outputSigmaBand, outputGammaBand, outputBetaBand, outputDNBand);
}
targetProduct = calibrator.createTargetProduct(sourceProduct, sourceBandNames);
calibrator.initialize(this, sourceProduct, targetProduct, false, true);
if (createGammaBand) {
createGammaVirtualBand(targetProduct, outputImageScaleInDb);
}
if (createBetaBand) {
createBetaVirtualBand(targetProduct, outputImageScaleInDb);
}
updateTargetProductMetadata();
} catch (Throwable e) {
OperatorUtils.catchOperatorException(getId(), e);
}
}
/**
* Update the metadata in the target product.
*/
private void updateTargetProductMetadata() {
final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(targetProduct);
absRoot.getAttribute(AbstractMetadata.abs_calibration_flag).getData().setElemBoolean(true);
if(!outputImageInComplex) {
absRoot.setAttributeString(AbstractMetadata.SAMPLE_TYPE, "DETECTED");
}
}
/**
* 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 {
try {
calibrator.computeTile(targetBand, targetTile, pm);
} catch (Throwable e) {
OperatorUtils.catchOperatorException(getId(), e);
}
}
/**
* Create Gamma image as a virtual band.
*
* @param trgProduct The target product
* @param outputImageScaleInDb flag if output is in dB
*/
public static void createGammaVirtualBand(final Product trgProduct, final boolean outputImageScaleInDb) {
int count = 1;
final Band[] bands = trgProduct.getBands();
for (Band trgBand : bands) {
final String unit = trgBand.getUnit();
if (trgBand instanceof VirtualBand || (unit != null && unit.contains("phase"))) {
continue;
}
final String trgBandName = trgBand.getName();
final String expression;
if (outputImageScaleInDb) {
expression = "(pow(10," + trgBandName + "/10.0)" + " / cos(incident_angle * PI/180.0)) "
+ "==0 ? 0 : 10 * log10(abs("
+ "(pow(10," + trgBandName + "/10.0)" + " / cos(incident_angle * PI/180.0))"
+ "))";
} else {
expression = trgBandName + " / cos(incident_angle * PI/180.0)";
}
String gammeBandName = "Gamma0";
if (bands.length > 1) {
if (trgBandName.contains("_HH"))
gammeBandName += "_HH";
else if (trgBandName.contains("_VV"))
gammeBandName += "_VV";
else if (trgBandName.contains("_HV"))
gammeBandName += "_HV";
else if (trgBandName.contains("_VH"))
gammeBandName += "_VH";
}
if (outputImageScaleInDb) {
gammeBandName += "_dB";
}
while (trgProduct.getBand(gammeBandName) != null) {
gammeBandName += "_" + ++count;
}
final VirtualBand band = new VirtualBand(gammeBandName,
ProductData.TYPE_FLOAT32,
trgBand.getRasterWidth(),
trgBand.getRasterHeight(),
expression);
band.setUnit(unit);
band.setDescription("Gamma0 image");
trgProduct.addBand(band);
}
}
/**
* Create Beta image as a virtual band.
*
* @param trgProduct The target product
* @param outputImageScaleInDb flag if output is in dB
*/
public static void createBetaVirtualBand(final Product trgProduct, final boolean outputImageScaleInDb) {
int count = 1;
final Band[] bands = trgProduct.getBands();
for (Band trgBand : bands) {
final String unit = trgBand.getUnit();
if (trgBand instanceof VirtualBand || (unit != null && unit.contains("phase"))) {
continue;
}
final String trgBandName = trgBand.getName();
final String expression;
if (outputImageScaleInDb) {
expression = "(pow(10," + trgBandName + "/10.0)" + " / sin(incident_angle * PI/180.0)) "
+ "==0 ? 0 : 10 * log10(abs("
+ "(pow(10," + trgBandName + "/10.0)" + " / sin(incident_angle * PI/180.0))"
+ "))";
} else {
expression = trgBandName + " / sin(incident_angle * PI/180.0)";
}
String betaBandName = "Beta0";
if (bands.length > 1) {
if (trgBandName.contains("_HH"))
betaBandName += "_HH";
else if (trgBandName.contains("_VV"))
betaBandName += "_VV";
else if (trgBandName.contains("_HV"))
betaBandName += "_HV";
else if (trgBandName.contains("_VH"))
betaBandName += "_VH";
}
if (outputImageScaleInDb) {
betaBandName += "_dB";
}
while (trgProduct.getBand(betaBandName) != null) {
betaBandName += "_" + ++count;
}
final VirtualBand band = new VirtualBand(betaBandName,
ProductData.TYPE_FLOAT32,
trgBand.getRasterWidth(),
trgBand.getRasterHeight(),
expression);
band.setUnit(unit);
band.setDescription("Beta0 image");
trgProduct.addBand(band);
}
}
/**
* 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(CalibrationOp.class);
}
}
}