/* * 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.insar.gpf; 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.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.gpf.OperatorUtils; import org.esa.snap.engine_utilities.gpf.StackUtils; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * Averaging multi-temporal images */ @OperatorMetadata(alias = "Stack-Averaging", category = "Radar/Coregistration/Stack Tools", authors = "Jun Lu, Luis Veci", version = "1.0", copyright = "Copyright (C) 2014 by Array Systems Computing Inc.", description = "Averaging multi-temporal images") public class StackAveragingOp extends Operator { @SourceProduct private Product sourceProduct; @TargetProduct private Product targetProduct; @Parameter(valueSet = {"Mean Average", "Minimum", "Maximum", "Standard Deviation", "Coefficient of Variation"}, defaultValue = "Mean Average", label = "Statistic") private String statistic = "Mean Average"; private BandInfo[] nameGroups; /** * 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 { targetProduct = new Product(sourceProduct.getName(), sourceProduct.getProductType(), sourceProduct.getSceneRasterWidth(), sourceProduct.getSceneRasterHeight()); ProductUtils.copyProductNodes(sourceProduct, targetProduct); nameGroups = getBandGroupNames(); for (BandInfo bandInfo : nameGroups) { if (bandInfo.isVirtual) { // add virtual intensity bands addOriginalVirtualBands(bandInfo.name); } else { final String name_prefix = bandInfo.name; final Band[] sourceBands = getSourceBands(name_prefix); final String unit = sourceBands[0].getUnit(); final double nodatavalue = sourceBands[0].getNoDataValue(); switch (statistic) { case "Mean Average": addVirtualBand("average", name_prefix, mean(sourceBands), unit, nodatavalue); break; case "Minimum": addVirtualBand("min", name_prefix, min(sourceBands), unit, nodatavalue); break; case "Maximum": addVirtualBand("max", name_prefix, max(sourceBands), unit, nodatavalue); break; case "Standard Deviation": addVirtualBand("stddev", name_prefix, stddev(sourceBands), unit, nodatavalue); break; case "Coefficient of Variation": addVirtualBand("coefVar", name_prefix, coefVar(sourceBands), unit, nodatavalue); break; } } } updateMetadata(targetProduct); } catch (Throwable e) { OperatorUtils.catchOperatorException(getId(), e); } } @Override public void dispose() { for (BandInfo bandInfo : nameGroups) { final Band srcBand = sourceProduct.getBand(bandInfo.name); if (srcBand != null) { sourceProduct.removeBand(srcBand); } } sourceProduct.setModified(false); } private static void updateMetadata(final Product targetProduct) { final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(targetProduct); AbstractMetadata.setAttribute(absRoot, AbstractMetadata.coregistered_stack, 0); } private BandInfo[] getBandGroupNames() { final Band[] bands = sourceProduct.getBands(); final Set<String> nameSet = new LinkedHashSet<>(); final List<BandInfo> bandGroup = new ArrayList<>(); for (Band band : bands) { final String name = StackUtils.getBandNameWithoutDate(band.getName()); if (!nameSet.contains(name)) { nameSet.add(name); bandGroup.add(new BandInfo(band, name)); } } return bandGroup.toArray(new BandInfo[bandGroup.size()]); } private Band[] getSourceBands(final String name_prefix) { final Band[] bands = sourceProduct.getBands(); final List<Band> bandList = new ArrayList<>(); for (Band band : bands) { if (!(band instanceof VirtualBand) && band.getName().startsWith(name_prefix)) { bandList.add(band); } } return bandList.toArray(new Band[bandList.size()]); } private void addVirtualBand(final String operation, final String name_prefix, final String expression, final String unit, final double nodatavalue) { final VirtualBand virtBand = new VirtualBand(name_prefix, ProductData.TYPE_FLOAT32, sourceProduct.getSceneRasterWidth(), sourceProduct.getSceneRasterHeight(), expression); virtBand.setUnit(unit); virtBand.setDescription(name_prefix + ' ' + operation + ' ' + unit); virtBand.setNoDataValueUsed(true); virtBand.setNoDataValue(nodatavalue); final Band srcBand = sourceProduct.getBand(virtBand.getName()); if (srcBand != null) { sourceProduct.removeBand(srcBand); } sourceProduct.addBand(virtBand); ProductUtils.copyBand(name_prefix, sourceProduct, targetProduct, true); } private void addOriginalVirtualBands(final String trgBandName) { final Band[] srcBands = sourceProduct.getBands(); Band virtSrcBand = null; for (Band band : srcBands) { if (band.getName().startsWith(trgBandName) && band instanceof VirtualBand) { virtSrcBand = band; break; } } if (virtSrcBand == null) return; final VirtualBand srcBand = (VirtualBand) virtSrcBand; String expression = srcBand.getExpression(); for (Band b : srcBands) { final String bName = b.getName(); if (expression.contains(bName) && !nameGroupContains(bName)) { final String newName = StackUtils.getBandNameWithoutDate(bName); expression = expression.replaceAll(bName, newName); } } final VirtualBand virtBand = new VirtualBand(trgBandName, srcBand.getDataType(), srcBand.getRasterWidth(), srcBand.getRasterHeight(), expression); virtBand.setUnit(srcBand.getUnit()); virtBand.setDescription(srcBand.getDescription()); virtBand.setNoDataValue(srcBand.getNoDataValue()); virtBand.setNoDataValueUsed(srcBand.isNoDataValueUsed()); targetProduct.addBand(virtBand); } private boolean nameGroupContains(final String name) { for(BandInfo b : nameGroups) { if(name.equals(b.name)) return true; } return false; } private static String mean(final Band[] sourceBands) { final StringBuilder expression = new StringBuilder("( "); int cnt = 0; for (Band band : sourceBands) { if (cnt > 0) expression.append(" + "); expression.append(band.getName()); ++cnt; } expression.append(") / "); expression.append(sourceBands.length); return expression.toString(); } private static String min(final Band[] sourceBands) { final StringBuilder expression = new StringBuilder("min( "); int cnt = 0; for (Band band : sourceBands) { if (cnt > 0) { expression.append(", "); if (cnt < sourceBands.length - 1) expression.append("min( "); } expression.append(band.getName()); ++cnt; } for (int i = 0; i < sourceBands.length - 1; ++i) { expression.append(')'); } return expression.toString(); } private static String max(final Band[] sourceBands) { final StringBuilder expression = new StringBuilder("max( "); int cnt = 0; for (Band band : sourceBands) { if (cnt > 0) { expression.append(", "); if (cnt < sourceBands.length - 1) expression.append("max( "); } expression.append(band.getName()); ++cnt; } for (int i = 0; i < sourceBands.length - 1; ++i) { expression.append(')'); } return expression.toString(); } private static String mean2(final Band[] sourceBands) { final StringBuilder expression = new StringBuilder("( "); int cnt = 0; for (Band band : sourceBands) { if (cnt > 0) expression.append(" + "); expression.append("sq("); expression.append(band.getName()); expression.append(')'); ++cnt; } expression.append(") / "); expression.append(sourceBands.length); return expression.toString(); } private static String mean4(final Band[] sourceBands) { final StringBuilder expression = new StringBuilder("( "); int cnt = 0; for (Band band : sourceBands) { if (cnt > 0) expression.append(" + "); expression.append("pow("); expression.append(band.getName()); expression.append(", 4)"); ++cnt; } expression.append(") / "); expression.append(sourceBands.length); return expression.toString(); } private static String stddev(final Band[] sourceBands) { return "sqrt( " + mean2(sourceBands) + " - " + "sq(" + mean(sourceBands) + "))"; } private static String coefVar(final Band[] sourceBands) { final String m2 = mean2(sourceBands); return "sqrt( " + mean4(sourceBands) + " - " + "sq(" + m2 + ")) / " + m2; } private static class BandInfo { final String name; final boolean isVirtual; public BandInfo(final Band band, final String name) { this.name = name; this.isVirtual = band instanceof VirtualBand; } } /** * 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(StackAveragingOp.class); } } }