/* * 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.oceantools; import com.bc.ceres.core.ProgressMonitor; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.MetadataElement; import org.esa.snap.core.datamodel.PixelPos; 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.AbstractMetadata; 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; /** * The oil spill clustering and discrimination operator. The pixels detected as oil spill area are first * clustered and then discriminated based on the size of the cluster. */ @OperatorMetadata(alias = "Oil-Spill-Clustering", category = "Radar/SAR Applications/Ocean Applications/Oil Spill Detection", authors = "Jun Lu, Luis Veci", version = "1.0", copyright = "Copyright (C) 2015 by Array Systems Computing Inc.", description = "Remove small clusters from detected area.") public class OilSpillClusteringOp extends Operator { @SourceProduct(alias = "source") private Product sourceProduct; @TargetProduct private Product targetProduct = null; @Parameter(description = "Minimum cluster size", defaultValue = "0.1", label = "Minimum Cluster Size (sq km)") private double minClusterSizeInKm2 = 0.1; private int sourceImageWidth = 0; private int sourceImageHeight = 0; private int minClusterSizeInPixels = 0; private MetadataElement absRoot = null; @Override public void initialize() throws OperatorException { try { absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct); getPixelSpacings(); getSourceImageDimension(); createTargetProduct(); OilSpillDetectionOp.addBitmasks(targetProduct); } catch (Throwable e) { OperatorUtils.catchOperatorException(getId(), e); } } /** * Get the range and azimuth spacings (in meter). * * @throws Exception when metadata is missing or equal to default no data value */ private void getPixelSpacings() throws Exception { final double rangeSpacing = AbstractMetadata.getAttributeDouble(absRoot, AbstractMetadata.range_spacing); final double azimuthSpacing = AbstractMetadata.getAttributeDouble(absRoot, AbstractMetadata.azimuth_spacing); minClusterSizeInPixels = (int) (minClusterSizeInKm2 * Constants.oneMillion / (rangeSpacing * azimuthSpacing)) + 1; } /** * Get source image dimension. */ private void getSourceImageDimension() { sourceImageWidth = sourceProduct.getSceneRasterWidth(); sourceImageHeight = sourceProduct.getSceneRasterHeight(); } /** * Create target product. */ private void createTargetProduct() { 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 { final Band[] bands = sourceProduct.getBands(); final List<String> bandNameList = new ArrayList<>(sourceProduct.getNumBands()); for (Band band : bands) { bandNameList.add(band.getName()); } final String[] sourceBandNames = bandNameList.toArray(new String[bandNameList.size()]); final Band[] sourceBands = OperatorUtils.getSourceBands(sourceProduct, sourceBandNames, false); for (Band srcBand : sourceBands) { final String srcBandName = srcBand.getName(); if (!srcBandName.contains(OilSpillDetectionOp.OILSPILLMASK_NAME)) { final Band targetBand = ProductUtils.copyBand(srcBandName, sourceProduct, targetProduct, true); } else { ProductUtils.copyBand(srcBandName, sourceProduct, targetProduct, 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 { try { final Rectangle targetTileRectangle = targetTile.getRectangle(); final int tx0 = targetTileRectangle.x; final int ty0 = targetTileRectangle.y; final int tw = targetTileRectangle.width; final int th = targetTileRectangle.height; final ProductData trgData = targetTile.getDataBuffer(); //System.out.println("tx0 = " + tx0 + ", ty0 = " + ty0 + ", tw = " + tw + ", th = " + th); final int x0 = Math.max(tx0 - minClusterSizeInPixels, 0); final int y0 = Math.max(ty0 - minClusterSizeInPixels, 0); final int w = Math.min(tw + 2 * minClusterSizeInPixels, sourceImageWidth); final int h = Math.min(th + 2 * minClusterSizeInPixels, sourceImageHeight); final Rectangle sourceTileRectangle = new Rectangle(x0, y0, w, h); //System.out.println("x0 = " + x0 + ", y0 = " + y0 + ", w = " + w + ", h = " + h); final Band sourceBand = sourceProduct.getBand(targetBand.getName()); final Tile sourceTile = getSourceTile(sourceBand, sourceTileRectangle); final ProductData srcData = sourceTile.getDataBuffer(); final int[][] pixelsScaned = new int[h][w]; final TileIndex srcIndex = new TileIndex(sourceTile); final int maxy = ty0 + th; final int maxx = tx0 + tw; for (int ty = ty0; ty < maxy; ty++) { srcIndex.calculateStride(ty); for (int tx = tx0; tx < maxx; tx++) { if (pixelsScaned[ty - y0][tx - x0] == 0 && srcData.getElemIntAt(srcIndex.getIndex(tx)) == 1) { final List<PixelPos> clusterPixels = new ArrayList<>(); clustering(tx, ty, x0, y0, w, h, srcData, sourceTile, pixelsScaned, clusterPixels); if (clusterPixels.size() >= minClusterSizeInPixels) { for (PixelPos pixel : clusterPixels) { final int x = (int) pixel.x; final int y = (int) pixel.y; if (x >= tx0 && x < tx0 + tw && y >= ty0 && y < ty0 + th) { trgData.setElemIntAt(targetTile.getDataBufferIndex(x, y), 1); } } } } } } } catch (Throwable e) { OperatorUtils.catchOperatorException(getId(), e); } } /** * Find pixels detected as target in a 3x3 window centered at a given point. * * @param xc The x coordinate of the given point. * @param yc The y coordinate of the given point. * @param x0 The x coordinate for the upper left corner point of the source rectangle. * @param y0 The y coordinate for the upper left corner point of the source rectangle. * @param w The width of the source rectangle. * @param h The height of the source rectangle. * @param bitMaskData The bit maks band data. * @param bitMaskTile The bit mask band tile. * @param pixelsScaned The binary array indicating which pixel in the tile has been scaned. * @param clusterPixels The list of pixels in the cluster. */ private static void clustering(final int xc, final int yc, final int x0, final int y0, final int w, final int h, final ProductData bitMaskData, final Tile bitMaskTile, final int[][] pixelsScaned, final List<PixelPos> clusterPixels) { final List<PixelPos> seeds = new ArrayList<>(); seeds.add(new PixelPos(xc, yc)); pixelsScaned[yc - y0][xc - x0] = 1; clusterPixels.add(new PixelPos(xc, yc)); while (seeds.size() > 0) { final List<PixelPos> newSeeds = new ArrayList<>(); for (PixelPos pixel : seeds) { searchNeighbourhood(pixel, x0, y0, w, h, bitMaskData, bitMaskTile, pixelsScaned, clusterPixels, newSeeds); } seeds.clear(); seeds.addAll(newSeeds); } } private static void searchNeighbourhood(final PixelPos pixel, final int x0, final int y0, final int w, final int h, final ProductData bitMaskData, final Tile bitMaskTile, final int[][] pixelsScaned, final List<PixelPos> clusterPixels, final List<PixelPos> newSeeds) { final int xc = (int) pixel.x; final int yc = (int) pixel.y; final int[] x = {xc - 1, xc, xc + 1, xc - 1, xc + 1, xc - 1, xc, xc + 1}; final int[] y = {yc - 1, yc - 1, yc - 1, yc, yc, yc + 1, yc + 1, yc + 1}; for (int i = 0; i < 8; i++) { if (x[i] >= x0 && x[i] < x0 + w && y[i] >= y0 && y[i] < y0 + h && pixelsScaned[y[i] - y0][x[i] - x0] == 0 && bitMaskData.getElemIntAt(bitMaskTile.getDataBufferIndex(x[i], y[i])) == 1) { pixelsScaned[y[i] - y0][x[i] - x0] = 1; clusterPixels.add(new PixelPos(x[i], y[i])); newSeeds.add(new PixelPos(x[i], y[i])); } } } /** * Operator SPI. */ public static class Spi extends OperatorSpi { public Spi() { super(OilSpillClusteringOp.class); } } }