/*
* 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.fex.gpf;
import com.bc.ceres.core.ProgressMonitor;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.Mask;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductData;
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.Unit;
import org.esa.snap.engine_utilities.eo.Constants;
import org.esa.snap.engine_utilities.gpf.OperatorUtils;
import org.esa.snap.engine_utilities.gpf.TileIndex;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* The change detection operator.
* <p/>
* The operator performs change detection by computing the ratio of log ratio of given image pair.
* It is assumed that the input product is a stack of two co-registered images.
* <p/>
*/
@OperatorMetadata(alias = "Change-Detection",
category = "Radar/SAR Applications",
authors = "Jun Lu, Luis Veci",
version = "1.0",
copyright = "Copyright (C) 2015 by Array Systems Computing Inc.",
description = "Change Detection.")
public class ChangeDetectionOp extends Operator {
@SourceProduct(alias = "source")
private Product sourceProduct;
@TargetProduct
private Product targetProduct = null;
@Parameter(description = "The list of source bands.", alias = "sourceBands",
rasterDataNodeType = Band.class, label = "Source Bands")
private String[] sourceBandNames = null;
@Parameter(description = "Mask upper threshold", defaultValue = "2.0", label = "Mask upper threshold")
private float maskUpperThreshold = 2.0f;
@Parameter(description = "Mask lower threshold", defaultValue = "-2.0", label = "Mask lower threshold")
private float maskLowerThreshold = -2.0f;
@Parameter(description = "Include source bands", defaultValue = "false", label = "Include source bands")
private boolean includeSourceBands = false;
@Parameter(description = "Output Log Ratio", defaultValue = "false", label = "Output Log Ratio")
private boolean outputLogRatio = false;
private int sourceImageWidth;
private int sourceImageHeight;
private String ratioBandName;
private static String RATIO_BAND_NAME = "ratio";
private static String LOG_RATIO_BAND_NAME = "log_ratio";
private static final String MASK_NAME = "_change";
@Override
public void initialize() throws OperatorException {
try {
sourceImageWidth = sourceProduct.getSceneRasterWidth();
sourceImageHeight = sourceProduct.getSceneRasterHeight();
createTargetProduct();
} catch (Throwable e) {
OperatorUtils.catchOperatorException(getId(), e);
}
}
/**
* Create target product.
*
* @throws Exception The exception.
*/
private void createTargetProduct() throws Exception {
targetProduct = new Product(sourceProduct.getName(),
sourceProduct.getProductType(),
sourceImageWidth,
sourceImageHeight);
ProductUtils.copyProductNodes(sourceProduct, targetProduct);
addSelectedBands();
}
/**
* Add the user selected bands to target product.
*
* @throws OperatorException The exceptions.
*/
private void addSelectedBands() throws OperatorException {
if (sourceBandNames == null || sourceBandNames.length == 0) { // if user did not select any band
final Band[] bands = sourceProduct.getBands();
final List<String> bandNameList = new ArrayList<>(sourceProduct.getNumBands());
for (Band band : bands) {
if (band.getUnit() != null && band.getUnit().contains(Unit.INTENSITY)) {
bandNameList.add(band.getName());
if(bandNameList.size() == 2)
break;
}
}
if(bandNameList.size() < 2) {
bandNameList.clear();
for (Band band : bands) {
if (band.getUnit() != null && band.getUnit().contains(Unit.AMPLITUDE)) {
bandNameList.add(band.getName());
if(bandNameList.size() == 2)
break;
}
}
}
if(bandNameList.size() < 2) {
bandNameList.clear();
for (Band band : bands) {
bandNameList.add(band.getName());
if(bandNameList.size() == 2)
break;
}
}
sourceBandNames = bandNameList.toArray(new String[bandNameList.size()]);
}
if (sourceBandNames.length != 2) {
throw new OperatorException("Please select two source bands");
}
if(includeSourceBands) {
for(String srcBandName : sourceBandNames) {
ProductUtils.copyBand(srcBandName, sourceProduct, targetProduct, true);
}
}
ratioBandName = RATIO_BAND_NAME;
if (outputLogRatio) {
ratioBandName = LOG_RATIO_BAND_NAME;
}
final Band targetRatioBand = new Band(ratioBandName,
ProductData.TYPE_FLOAT32,
sourceImageWidth,
sourceImageHeight);
targetRatioBand.setNoDataValue(0);
targetRatioBand.setNoDataValueUsed(true);
if (outputLogRatio) {
targetRatioBand.setUnit("log_ratio");
} else {
targetRatioBand.setUnit("ratio");
}
targetProduct.addBand(targetRatioBand);
//create Mask
String expression = targetRatioBand.getName() + " > "+ maskUpperThreshold+" ? 1 : " + targetRatioBand.getName() + " < "+maskLowerThreshold+" ? -1 : 0";
final Mask mask = new Mask(targetRatioBand.getName() + MASK_NAME,
targetRatioBand.getRasterWidth(),
targetRatioBand.getRasterHeight(),
Mask.BandMathsType.INSTANCE);
mask.setDescription("Change");
mask.getImageConfig().setValue("color", Color.RED);
mask.getImageConfig().setValue("transparency", 0.7);
mask.getImageConfig().setValue("expression", expression);
mask.setNoDataValue(0);
mask.setNoDataValueUsed(true);
targetProduct.getMaskGroup().add(mask);
}
/**
* Called by the framework in order to compute the stack of tiles for the given target bands.
* <p>The default implementation throws a runtime exception with the message "not implemented".</p>
*
* @param targetTiles The current tiles to be computed for each target band.
* @param targetRectangle The area in pixel coordinates to be computed (same for all rasters in <code>targetRasters</code>).
* @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 rasters.
*/
@Override
public void computeTileStack(Map<Band, Tile> targetTiles, Rectangle targetRectangle, ProgressMonitor pm) throws OperatorException {
try {
final int tx0 = targetRectangle.x;
final int ty0 = targetRectangle.y;
final int tw = targetRectangle.width;
final int th = targetRectangle.height;
//System.out.println("tx0 = " + tx0 + ", ty0 = " + ty0 + ", tw = " + tw + ", th = " + th);
final Band nominatorBand = sourceProduct.getBand(sourceBandNames[0]);
final Band denominatorBand = sourceProduct.getBand(sourceBandNames[1]);
final Tile nominatorTile = getSourceTile(nominatorBand, targetRectangle);
final Tile denominatorTile = getSourceTile(denominatorBand, targetRectangle);
final ProductData nominatorData = nominatorTile.getDataBuffer();
final ProductData denominatorData = denominatorTile.getDataBuffer();
final Double noDataValueN = nominatorBand.getNoDataValue();
final Double noDataValueD = denominatorBand.getNoDataValue();
final Band targetRatioBand = targetProduct.getBand(ratioBandName);
final Tile targetRatioTile = targetTiles.get(targetRatioBand);
final ProductData ratioData = targetRatioTile.getDataBuffer();
final TileIndex trgIndex = new TileIndex(targetTiles.get(targetTiles.keySet().iterator().next()));
final TileIndex srcIndex = new TileIndex(nominatorTile);
final int maxy = ty0 + th;
final int maxx = tx0 + tw;
double vRatio;
for (int ty = ty0; ty < maxy; ty++) {
trgIndex.calculateStride(ty);
srcIndex.calculateStride(ty);
for (int tx = tx0; tx < maxx; tx++) {
final int trgIdx = trgIndex.getIndex(tx);
final int srcIdx = srcIndex.getIndex(tx);
final double vN = nominatorData.getElemDoubleAt(srcIdx);
final double vD = denominatorData.getElemDoubleAt(srcIdx);
if (noDataValueN.equals(vN) || noDataValueD.equals(vD) || vN <= 0.0 || vD <= 0.0) {
ratioData.setElemFloatAt(trgIdx, 0.0f);
continue;
}
vRatio = vN / vD;
if (outputLogRatio) {
vRatio = Math.log(Math.max(vRatio, Constants.EPS));
}
ratioData.setElemFloatAt(trgIdx, (float) vRatio);
}
}
} catch (Throwable e) {
OperatorUtils.catchOperatorException(getId(), e);
}
}
/**
* Operator SPI.
*/
public static class Spi extends OperatorSpi {
public Spi() {
super(ChangeDetectionOp.class);
}
}
}