/*
* 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.snap.rcp.actions.window;
import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.core.SubProgressMonitor;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import org.esa.snap.core.datamodel.Band;
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.RGBImageProfile;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.datamodel.Stx;
import org.esa.snap.core.datamodel.VirtualBand;
import org.esa.snap.core.dataop.barithm.BandArithmetic;
import org.esa.snap.core.jexp.ParseException;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.ui.HSVImageProfilePane;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.product.ProductSceneImage;
import org.esa.snap.ui.product.ProductSceneView;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.SwingWorker;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
/**
* This action opens an HSV image view on the currently selected Product.
*/
@ActionID(category = "View", id = "OpenHSVImageViewAction")
@ActionRegistration(
displayName = "#CTL_OpenHSVImageViewAction_MenuText",
popupText = "#CTL_OpenHSVImageViewAction_MenuText",
iconBase = "org/esa/snap/rcp/icons/ImageView.gif",
lazy = true
)
@ActionReferences({
@ActionReference(path = "Menu/Window", position = 115),
@ActionReference(path = "Context/Product/Product", position = 50, separatorAfter = 55),
})
@NbBundle.Messages({
"CTL_OpenHSVImageViewAction_MenuText=Open HSV Image Window",
"CTL_OpenHSVImageViewAction_ShortDescription=Open an HSV image view for the selected product"
})
public class OpenHSVImageViewAction extends AbstractAction implements HelpCtx.Provider {
private static final String HELP_ID = "hsvImageProfile";
private Product product;
private static final String r = "min(round( (floor((6*(h))%6)==0?(v): (floor((6*(h))%6)==1?((1-((s)*((6*(h))%6)-floor((6*(h))%6)))*(v)): (floor((6*(h))%6)==2?((1-(s))*(v)): (floor((6*(h))%6)==3?((1-(s))*(v)): (floor((6*(h))%6)==4?((1-((s)*(1-((6*(h))%6)-floor((6*(h))%6))))*(v)): (floor((6*(h))%6)==5?(v):0)))))) *256), 255)";
private static final String g = "min(round( (floor((6*(h))%6)==0?((1-((s)*(1-((6*(h))%6)-floor((6*(h))%6))))*(v)): (floor((6*(h))%6)==1?(v): (floor((6*(h))%6)==2?(v): (floor((6*(h))%6)==3?((1-((s)*((6*(h))%6)-floor((6*(h))%6)))*(v)): (floor((6*(h))%6)==4?((1-(s))*(v)): (floor((6*(h))%6)==5?((1-(s))*(v)):0)))))) *256), 255)";
private static final String b = "min(round( (floor((6*(h))%6)==0?((1-(s))*(v)): (floor((6*(h))%6)==1?((1-(s))*(v)): (floor((6*(h))%6)==2?((1-((s)*(1-((6*(h))%6)-floor((6*(h))%6))))*(v)): (floor((6*(h))%6)==3?(v): (floor((6*(h))%6)==4?(v): (floor((6*(h))%6)==5?((1-((s)*((6*(h))%6)-floor((6*(h))%6)))*(v)):0)))))) *256), 255)";
public OpenHSVImageViewAction(ProductNode node) {
super(Bundle.CTL_OpenHSVImageViewAction_MenuText());
product = node.getProduct();
putValue(Action.SHORT_DESCRIPTION, Bundle.CTL_OpenHSVImageViewAction_ShortDescription());
}
@Override
public void actionPerformed(final ActionEvent event) {
if (product != null) {
openProductSceneViewHSV(product, HELP_ID);
}
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx(HELP_ID);
}
public void openProductSceneViewHSV(Product hsvProduct, final String helpId) {
final Product[] openedProducts = SnapApp.getDefault().getProductManager().getProducts();
final int[] defaultBandIndices = OpenRGBImageViewAction.getDefaultBandIndices(hsvProduct);
final HSVImageProfilePane profilePane = new HSVImageProfilePane(SnapApp.getDefault().getPreferencesPropertyMap(), hsvProduct,
openedProducts, defaultBandIndices);
final String title = "Select HSV-Image Channels";
final boolean ok = profilePane.showDialog(SnapApp.getDefault().getMainFrame(), title, helpId);
if (!ok) {
return;
}
final String[] hsvExpressions = profilePane.getRgbaExpressions();
nomalizeHSVExpressions(hsvProduct, hsvExpressions);
if (profilePane.getStoreProfileInProduct()) {
RGBImageProfile.storeRgbaExpressions(hsvProduct, hsvExpressions, HSVImageProfilePane.HSV_COMP_NAMES);
}
final String sceneName = OpenRGBImageViewAction.createSceneName(hsvProduct, profilePane.getSelectedProfile(), "HSV");
openProductSceneViewHSV(sceneName, hsvProduct, hsvExpressions);
}
/**
* Creates product scene view using the given HSV expressions.
*/
public void openProductSceneViewHSV(final String name, final Product product, final String[] hsvExpressions) {
final SwingWorker<ProductSceneImage, Object> worker = new ProgressMonitorSwingWorker<ProductSceneImage, Object>(
SnapApp.getDefault().getMainFrame(),
SnapApp.getDefault().getInstanceName() + " - Creating image for '" + name + '\'') {
@Override
protected ProductSceneImage doInBackground(ProgressMonitor pm) throws Exception {
return createProductSceneImageHSV(name, product, hsvExpressions, pm);
}
@Override
protected void done() {
SnapApp.getDefault().getMainFrame().setCursor(Cursor.getDefaultCursor());
String errorMsg = "The HSV image view could not be created.";
try {
final ProductSceneView productSceneView = new ProductSceneView(get());
OpenRGBImageViewAction.openDocumentWindow(productSceneView);
} catch (OutOfMemoryError e) {
Dialogs.showOutOfMemoryError(errorMsg);
return;
} catch (Exception e) {
SnapApp.getDefault().handleError(errorMsg, e);
return;
}
SnapApp.getDefault().setStatusBarMessage("");
}
};
SnapApp.getDefault().setStatusBarMessage("Creating HSV image view..."); /*I18N*/
UIUtils.setRootFrameWaitCursor(SnapApp.getDefault().getMainFrame());
worker.execute();
}
private static ProductSceneImage createProductSceneImageHSV(final String name, final Product product,
final String[] hsvExpressions,
final ProgressMonitor pm) throws Exception {
UIUtils.setRootFrameWaitCursor(SnapApp.getDefault().getMainFrame());
Band[] rgbBands = null;
boolean errorOccured = false;
ProductSceneImage productSceneImage = null;
try {
pm.beginTask("Creating HSV image...", 2);
final String[] rgbaExpressions = convertHSVToRGBExpressions(hsvExpressions);
rgbBands = OpenRGBImageViewAction.allocateRgbBands(product, rgbaExpressions);
productSceneImage = new ProductSceneImage(name,
rgbBands[0],
rgbBands[1],
rgbBands[2],
SnapApp.getDefault().getPreferencesPropertyMap(),
SubProgressMonitor.create(pm, 1));
productSceneImage.initVectorDataCollectionLayer();
productSceneImage.initMaskCollectionLayer();
} catch (Exception e) {
errorOccured = true;
throw e;
} finally {
pm.done();
if (rgbBands != null) {
OpenRGBImageViewAction.releaseRgbBands(rgbBands, errorOccured);
}
}
return productSceneImage;
}
private static void nomalizeHSVExpressions(final Product product, String[] hsvExpressions) {
// normalize
//range = max - min;
//normvalue = min(max(((v- min)/range),0), 1);
boolean modified = product.isModified();
int i = 0;
for (String exp : hsvExpressions) {
if (exp.isEmpty()) continue;
final String checkForNoDataValue = "";//getCheckForNoDataExpression(product, exp);
final Band virtBand = createVirtualBand(product, exp, "tmpVirtBand" + i);
final Stx stx = virtBand.getStx(false, ProgressMonitor.NULL);
if (stx != null) {
final double min = stx.getMinimum();
final double range = stx.getMaximum() - min;
hsvExpressions[i] = checkForNoDataValue + "min(max((((" + exp + ")- " + min + ")/" + range + "), 0), 1)";
}
product.removeBand(virtBand);
++i;
}
product.setModified(modified);
}
private static String getCheckForNoDataExpression(final Product product, final String exp) {
final String[] bandNames = product.getBandNames();
StringBuilder checkForNoData = new StringBuilder("(" + exp + " == NaN");
if (StringUtils.contains(bandNames, exp)) {
double nodatavalue = product.getBand(exp).getNoDataValue();
checkForNoData.append(" or " + exp + " == " + nodatavalue);
}
checkForNoData.append(") ? NaN : ");
return checkForNoData.toString();
}
public static Band createVirtualBand(final Product product, final String expression, final String name) {
int width = product.getSceneRasterWidth();
int height = product.getSceneRasterHeight();
try {
final RasterDataNode[] refRasters = BandArithmetic.getRefRasters(expression, product);
if (refRasters.length > 0) {
width = refRasters[0].getRasterWidth();
height = refRasters[0].getRasterHeight();
}
} catch (ParseException e) {
throw new IllegalArgumentException("Invalid expression: " + expression);
}
final VirtualBand virtBand = new VirtualBand(name,
ProductData.TYPE_FLOAT64,
width,
height,
expression);
virtBand.setNoDataValueUsed(true);
product.addBand(virtBand);
return virtBand;
}
private static String[] convertHSVToRGBExpressions(final String[] hsvExpressions) {
final String h = hsvExpressions[0].isEmpty() ? "0" : hsvExpressions[0];
final String s = hsvExpressions[1].isEmpty() ? "0" : hsvExpressions[1];
final String v = hsvExpressions[2].isEmpty() ? "0" : hsvExpressions[2];
// h,s,v in [0,1]
/* float rr = 0, gg = 0, bb = 0;
float hh = (6 * h) % 6;
int c1 = (int) hh; // floor((6*(h))%6)
float c2 = hh - c1; // ((6*(h))%6)-floor((6*(h))%6)
float x = (1 - s) * v; // ((1-(s))*(v))
float y = (1 - (s * c2)) * v; // ((1-((s)*((6*(h))%6)-floor((6*(h))%6)))*(v))
float z = (1 - (s * (1 - c2))) * v; // ((1-((s)*(1-((6*(h))%6)-floor((6*(h))%6))))*(v))
switch (c1) {
case 0: rr=v; gg=z; bb=x; break;
case 1: rr=y; gg=v; bb=x; break;
case 2: rr=x; gg=v; bb=z; break;
case 3: rr=x; gg=y; bb=v; break;
case 4: rr=z; gg=x; bb=v; break;
case 5: rr=v; gg=x; bb=y; break;
}
int N = 256;
int r = Math.min(Math.round(rr*N),N-1);
int g = Math.min(Math.round(gg*N),N-1);
int b = Math.min(Math.round(bb*N),N-1);
*/
final String[] rgbExpressions = new String[3];
rgbExpressions[0] = r.replace("(h)", '(' + h + ')').replace("(s)", '(' + s + ')').replace("(v)", '(' + v + ')');
rgbExpressions[1] = g.replace("(h)", '(' + h + ')').replace("(s)", '(' + s + ')').replace("(v)", '(' + v + ')');
rgbExpressions[2] = b.replace("(h)", '(' + h + ')').replace("(s)", '(' + s + ')').replace("(v)", '(' + v + ')');
return rgbExpressions;
}
}