/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * 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.snap.rcp.actions.raster; import com.bc.ceres.core.Assert; import com.bc.ceres.core.ProgressMonitor; import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.ColorPaletteDef; import org.esa.snap.core.datamodel.GeoPos; import org.esa.snap.core.datamodel.ImageInfo; 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.datamodel.ProductNode; import org.esa.snap.core.datamodel.VirtualBand; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.preferences.general.UiBehaviorController; import org.esa.snap.rcp.util.Dialogs; import org.esa.snap.ui.UIUtils; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionRegistration; import org.openide.awt.UndoRedo; import org.openide.util.ContextAwareAction; import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.WeakListeners; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import java.awt.Color; import java.awt.Frame; import java.awt.event.ActionEvent; import java.io.IOException; import java.util.concurrent.ExecutionException; @ActionID( category = "Tools", id = "CreateGeoCodingDisplacementBandsAction" ) @ActionRegistration( displayName = "#CTL_CreateGeoCodingDisplacementBandsActionText", popupText = "#CTL_CreateGeoCodingDisplacementBandsActionText" ) @ActionReference(path = "Menu/Raster", position = 30 ) @NbBundle.Messages({ "CTL_CreateGeoCodingDisplacementBandsActionText=Geo-Coding Displacement Bands...", "CTL_CreateGeoCodingDisplacementBandsDialogTitle=Geo-Coding Displacement Bands", "CTL_CreateGeoCodingDisplacementBandsDescription=<html>Computes actual pixel position minus pixel position computed from inverse\n" + " geo-coding<br/>\n" + " and adds displacements as new bands (test for geo-coding accuracy)." }) /** * An action that lets users add a number of bands that can be used to assess the performance/accuracy * of the current geo-coding. */ public class CreateGeoCodingDisplacementBandsAction extends AbstractAction implements ContextAwareAction, LookupListener { private final Lookup lookup; public static final float[][] OFFSETS = new float[][]{ {0.00f, 0.00f}, {0.25f, 0.25f}, {0.50f, 0.50f}, {0.75f, 0.75f}, {0.25f, 0.75f}, {0.75f, 0.25f}, }; public CreateGeoCodingDisplacementBandsAction() { this(Utilities.actionsGlobalContext()); } public CreateGeoCodingDisplacementBandsAction(Lookup lookup) { super(Bundle.CTL_CreateGeoCodingDisplacementBandsActionText()); this.lookup = lookup; Lookup.Result<ProductNode> lkpContext = lookup.lookupResult(ProductNode.class); lkpContext.addLookupListener(WeakListeners.create(LookupListener.class, this, lkpContext)); setEnableState(); putValue(Action.SHORT_DESCRIPTION, Bundle.CTL_CreateGeoCodingDisplacementBandsDescription()); } @Override public Action createContextAwareInstance(Lookup actionContext) { return new CreateGeoCodingDisplacementBandsAction(actionContext); } @Override public void resultChanged(LookupEvent ev) { setEnableState(); } @Override public void actionPerformed(ActionEvent event) { createXYDisplacementBands(lookup.lookup(ProductNode.class).getProduct()); } private void setEnableState() { ProductNode productNode = lookup.lookup(ProductNode.class); boolean state = false; if (productNode != null) { Product product = productNode.getProduct(); if (product != null && !product.isMultiSize()) { state = product.getSceneGeoCoding() != null && product.getSceneGeoCoding().canGetGeoPos() && product.getSceneGeoCoding().canGetPixelPos(); } } setEnabled(state); } private void createXYDisplacementBands(Product product) { final SnapApp snapApp = SnapApp.getDefault(); final Frame mainFrame = snapApp.getMainFrame(); String dialogTitle = Bundle.CTL_CreateGeoCodingDisplacementBandsDialogTitle(); final ProgressMonitorSwingWorker swingWorker = new ProgressMonitorSwingWorker<Band[], Object>(mainFrame, dialogTitle) { @Override protected Band[] doInBackground(ProgressMonitor pm) throws Exception { final Band[] xyDisplacementBands = createXYDisplacementBands(product, pm); UndoRedo.Manager undoManager = SnapApp.getDefault().getUndoManager(product); if (undoManager != null) { undoManager.addEdit(new UndoableDisplacementBandsCreation(product, xyDisplacementBands)); } return xyDisplacementBands; } @Override public void done() { if (snapApp.getPreferences().getBoolean(UiBehaviorController.PREFERENCE_KEY_AUTO_SHOW_NEW_BANDS, true)) { try { Band[] bands = get(); if (bands == null) { return; } for (Band band : bands) { Band oldBand = product.getBand(band.getName()); if (oldBand != null) { product.removeBand(oldBand); } product.addBand(band); } } catch (Exception e) { Throwable cause = e; if (e instanceof ExecutionException) { cause = e.getCause(); } String msg = "An internal error occurred:\n" + e.getMessage(); if (cause instanceof IOException) { msg = "An I/O error occurred:\n" + e.getMessage(); } Dialogs.showError(dialogTitle, msg); } finally { UIUtils.setRootFrameDefaultCursor(mainFrame); } } } }; swingWorker.execute(); } private static Band[] createXYDisplacementBands(final Product product, ProgressMonitor pm) { final int width = product.getSceneRasterWidth(); final int height = product.getSceneRasterHeight(); ImageInfo blueToRedGrad = new ImageInfo(new ColorPaletteDef(new ColorPaletteDef.Point[]{ new ColorPaletteDef.Point(-1.0, Color.BLUE), new ColorPaletteDef.Point(0.0, Color.WHITE), new ColorPaletteDef.Point(1.0, Color.RED), })); ImageInfo amplGrad = new ImageInfo(new ColorPaletteDef(new ColorPaletteDef.Point[]{ new ColorPaletteDef.Point(0.0, Color.WHITE), new ColorPaletteDef.Point(1.0, Color.RED), })); ImageInfo phaseGrad = new ImageInfo(new ColorPaletteDef(new ColorPaletteDef.Point[]{ new ColorPaletteDef.Point(-Math.PI, Color.WHITE), new ColorPaletteDef.Point(0.0, Color.BLUE), new ColorPaletteDef.Point(+Math.PI, Color.WHITE), })); final Band bandX = new Band("gc_displ_x", ProductData.TYPE_FLOAT64, width, height); configureBand(bandX, blueToRedGrad.clone(), "pixels", "Geo-coding X-displacement"); final Band bandY = new Band("gc_displ_y", ProductData.TYPE_FLOAT64, width, height); configureBand(bandY, blueToRedGrad.clone(), "pixels", "Geo-coding Y-displacement"); final Band bandAmpl = new VirtualBand("gc_displ_ampl", ProductData.TYPE_FLOAT64, width, height, "ampl(gc_displ_x, gc_displ_y)"); configureBand(bandAmpl, amplGrad.clone(), "pixels", "Geo-coding displacement amplitude"); final Band bandPhase = new VirtualBand("gc_displ_phase", ProductData.TYPE_FLOAT64, width, height, "phase(gc_displ_x, gc_displ_y)"); configureBand(bandPhase, phaseGrad.clone(), "radians", "Geo-coding displacement phase"); final double[] dataX = new double[width * height]; final double[] dataY = new double[width * height]; bandX.setRasterData(ProductData.createInstance(dataX)); bandY.setRasterData(ProductData.createInstance(dataY)); pm.beginTask("Computing geo-coding displacements for product '" + product.getName() + "'...", height); try { final GeoPos geoPos = new GeoPos(); final PixelPos pixelPos1 = new PixelPos(); final PixelPos pixelPos2 = new PixelPos(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { double maxX = 0; double maxY = 0; double valueX = 0; double valueY = 0; for (float[] offset : OFFSETS) { pixelPos1.setLocation(x + offset[0], y + offset[1]); product.getSceneGeoCoding().getGeoPos(pixelPos1, geoPos); product.getSceneGeoCoding().getPixelPos(geoPos, pixelPos2); double dx = pixelPos2.x - pixelPos1.x; double dy = pixelPos2.y - pixelPos1.y; if (Math.abs(dx) > maxX) { maxX = Math.abs(dx); valueX = dx; } if (Math.abs(dy) > maxY) { maxY = Math.abs(dy); valueY = dy; } } dataX[y * width + x] = valueX; dataY[y * width + x] = valueY; } if (pm.isCanceled()) { return null; } pm.worked(1); } } finally { pm.done(); } return new Band[]{bandX, bandY, bandAmpl, bandPhase}; } private static void configureBand(Band band05X, ImageInfo imageInfo, String unit, String description) { band05X.setUnit(unit); band05X.setDescription(description); band05X.setImageInfo(imageInfo); band05X.setNoDataValue(Double.NaN); band05X.setNoDataValueUsed(true); } private static class UndoableDisplacementBandsCreation extends AbstractUndoableEdit { private Band[] displacementBands; private Product product; public UndoableDisplacementBandsCreation(Product product, Band[] displacementBands) { Assert.notNull(product, "product"); Assert.notNull(displacementBands, "displacementBands"); this.product = product; this.displacementBands = displacementBands; } @Override public String getPresentationName() { return Bundle.CTL_CreateGeoCodingDisplacementBandsDialogTitle(); } @Override public void undo() throws CannotUndoException { super.undo(); for (Band displacementBand : displacementBands) { if (product.containsBand(displacementBand.getName())) { product.removeBand(displacementBand); } } } @Override public void redo() throws CannotRedoException { super.redo(); for (Band displacementBand : displacementBands) { if (!product.containsBand(displacementBand.getName())) { product.addBand(displacementBand); product.fireProductNodeChanged(displacementBand.getName()); } } } @Override public void die() { product = null; displacementBands = null; } } }