package org.esa.snap.rcp.bandmaths; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.binding.PropertyDescriptor; import com.bc.ceres.binding.ValueSet; import com.bc.ceres.core.Assert; import com.bc.ceres.swing.binding.BindingContext; import com.bc.ceres.swing.binding.PropertyEditor; import com.bc.ceres.swing.binding.PropertyEditorRegistry; 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.ProductNodeGroup; import org.esa.snap.core.datamodel.VirtualBand; import org.esa.snap.core.dataop.barithm.BandArithmetic; import org.esa.snap.core.dataop.barithm.StandardUncertaintyGenerator; import org.esa.snap.core.jexp.ParseException; import org.esa.snap.core.util.ProductUtils; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.actions.window.OpenImageViewAction; import org.esa.snap.rcp.nodes.UndoableProductNodeInsertion; import org.esa.snap.rcp.util.Dialogs; import org.esa.snap.ui.GridBagUtils; import org.esa.snap.ui.ModalDialog; import org.openide.awt.UndoRedo; import org.openide.util.NbBundle; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.Color; import java.awt.GridBagConstraints; import java.beans.PropertyChangeListener; /** * @author Norman Fomferra */ @NbBundle.Messages({ "CTL_PropagateUncertaintyDialog_Title=Propagate Uncertainty", }) public class PropagateUncertaintyDialog extends ModalDialog { private static final String PROPERTY_NAME_BAND_NAME = "bandName"; private static final String PROPERTY_NAME_ORDER = "order"; private static final String PROPERTY_NAME_RELATION = "relation"; private static final String ERROR_PREFIX = "Error: "; private static Color OK_MSG_COLOR = new Color(0, 128, 0); private static Color WARN_MSG_OLOR = new Color(128, 0, 0); private final BindingContext bindingContext; private VirtualBand sourceBand; @SuppressWarnings("UnusedDeclaration") private String bandName; @SuppressWarnings("UnusedDeclaration") private int order; @SuppressWarnings("UnusedDeclaration") private String relation; private JTextArea sourceExprArea; private JTextArea targetExprArea; private JLabel expressionIsCompatibleLabel; public PropagateUncertaintyDialog(VirtualBand virtualBand) { super(SnapApp.getDefault().getMainFrame(), Bundle.CTL_PropagateUncertaintyDialog_Title(), ID_OK_CANCEL_HELP, "propagateUncertainty"); Assert.notNull(virtualBand, "virtualBand"); this.sourceBand = virtualBand; bindingContext = createBindingContext(); bandName = virtualBand.getName() + "_unc"; order = 1; initUI(); } @Override protected void onOK() { String uncertaintyExpression = targetExprArea.getText(); Product targetProduct = sourceBand.getProduct(); int width = sourceBand.getRasterWidth(); int height = sourceBand.getRasterHeight(); ProductNodeGroup<Band> bandGroup = targetProduct.getBandGroup(); VirtualBand uncertaintyBand = new VirtualBand(getBandName(), ProductData.TYPE_FLOAT32, width, height, uncertaintyExpression); uncertaintyBand.setDescription("Uncertainty propagated from band " + sourceBand.getName() + " = " + sourceBand.getExpression()); uncertaintyBand.setUnit(sourceBand.getUnit()); uncertaintyBand.setNoDataValue(Double.NaN); uncertaintyBand.setNoDataValueUsed(true); uncertaintyBand.setValidPixelExpression(sourceBand.getValidPixelExpression()); ProductUtils.copySpectralBandProperties(sourceBand, uncertaintyBand); bandGroup.add(uncertaintyBand); sourceBand.addAncillaryVariable(uncertaintyBand, relation); UndoRedo.Manager undoManager = SnapApp.getDefault().getUndoManager(targetProduct); if (undoManager != null) { undoManager.addEdit(new UndoableProductNodeInsertion<>(bandGroup, uncertaintyBand)); } hide(); uncertaintyBand.setModified(true); if (SnapApp.getDefault().getPreferences().getBoolean(BandMathsDialog.PREF_KEY_AUTO_SHOW_NEW_BANDS, true)) { OpenImageViewAction.openImageView(uncertaintyBand); } } private String generateUncertaintyExpression() throws ParseException, UnsupportedOperationException { StandardUncertaintyGenerator propagator = new StandardUncertaintyGenerator(order, false); return propagator.generateUncertainty(sourceBand.getProduct(), relation, sourceBand.getExpression()); } @Override protected boolean verifyUserInput() { String uncertaintyExpression = targetExprArea.getText(); if (uncertaintyExpression == null || uncertaintyExpression.trim().isEmpty()) { Dialogs.showError("Uncertainty expression is empty."); return false; } if (uncertaintyExpression.startsWith(ERROR_PREFIX)) { Dialogs.showError(uncertaintyExpression.substring(ERROR_PREFIX.length())); return false; } if (sourceBand.getProduct().containsBand(getBandName())) { Dialogs.showError("A raster with name '" + getBandName() + "' already exists."); return false; } return super.verifyUserInput(); } private void initUI() { JComponent[] components; final JPanel panel = GridBagUtils.createPanel(); int line = 0; GridBagConstraints gbc = new GridBagConstraints(); sourceExprArea = new JTextArea(3, 40); sourceExprArea.setEditable(false); sourceExprArea.setEnabled(false); sourceExprArea.setText(sourceBand.getExpression()); targetExprArea = new JTextArea(6, 40); targetExprArea.setEditable(true); expressionIsCompatibleLabel = new JLabel(); updateTargetExprArea(); targetExprArea.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { validateTargExpression(); } @Override public void removeUpdate(DocumentEvent e) { validateTargExpression(); } @Override public void changedUpdate(DocumentEvent e) { validateTargExpression(); } }); final JComboBox<String> comboBox = new JComboBox<>(new String[]{"uncertainty", "standard_deviation", "error"}); comboBox.setEditable(true); bindingContext.bind(PROPERTY_NAME_RELATION, comboBox); gbc.gridy = ++line; components = createComponents(PROPERTY_NAME_BAND_NAME); GridBagUtils.addToPanel(panel, components[1], gbc, "weightx=0, insets.right=3, insets.top=3, gridwidth=1, fill=HORIZONTAL, anchor=WEST"); GridBagUtils.addToPanel(panel, components[0], gbc, "weightx=1, insets.right=0, insets.top=3, gridwidth=2, fill=HORIZONTAL, anchor=WEST"); gbc.gridy = ++line; components = createComponents(PROPERTY_NAME_ORDER); GridBagUtils.addToPanel(panel, components[1], gbc, "weightx=0, insets.right=3, insets.top=3, gridwidth=1, fill=HORIZONTAL, anchor=WEST"); GridBagUtils.addToPanel(panel, components[0], gbc, "weightx=1, insets.right=0, insets.top=3, gridwidth=2, fill=HORIZONTAL, anchor=WEST"); gbc.gridy = ++line; PropertyDescriptor relationDescriptor = bindingContext.getPropertySet().getDescriptor(PROPERTY_NAME_RELATION); GridBagUtils.addToPanel(panel, new JLabel(relationDescriptor.getDisplayName() + ":"), gbc, "weightx=0, insets.right=3, insets.top=3, gridwidth=1, fill=HORIZONTAL, anchor=WEST"); GridBagUtils.addToPanel(panel, comboBox, gbc, "weightx=1, insets.right=0, insets.top=3, gridwidth=2, fill=HORIZONTAL, anchor=WEST"); gbc.gridy = ++line; GridBagUtils.addToPanel(panel, new JLabel("Source expression:"), gbc, "weightx=1, insets.top=8, gridwidth=3, fill=HORIZONTAL, anchor=WEST"); gbc.gridy = ++line; GridBagUtils.addToPanel(panel, new JScrollPane(sourceExprArea), gbc, "weightx=1, insets.top=3, gridwidth=3, fill=HORIZONTAL, anchor=WEST"); gbc.gridy = ++line; GridBagUtils.addToPanel(panel, new JLabel("Uncertainty expression:"), gbc, "weightx=1, insets.top=3, gridwidth=3, fill=HORIZONTAL, anchor=WEST"); gbc.gridy = ++line; GridBagUtils.addToPanel(panel, new JScrollPane(targetExprArea), gbc, "weightx=1, weighty=1, insets.top=3, gridwidth=3, fill=BOTH, anchor=WEST"); gbc.gridy = ++line; GridBagUtils.addToPanel(panel, expressionIsCompatibleLabel, gbc, "weightx=0, weighty=1, insets.top=3, gridwidth=3, fill=NONE, anchor=EAST"); setContent(panel); } private void validateTargExpression() { final String targExpression = targetExprArea.getText(); final Product product = sourceBand.getProduct(); try { if (!BandArithmetic.areRastersEqualInSize(product, sourceExprArea.getText(), targExpression)) { expressionIsCompatibleLabel.setText("Referenced rasters must be of the same size"); expressionIsCompatibleLabel.setForeground(WARN_MSG_OLOR); } else { expressionIsCompatibleLabel.setText("Ok, no errors"); expressionIsCompatibleLabel.setForeground(OK_MSG_COLOR); } } catch (ParseException e) { expressionIsCompatibleLabel.setText("Expression is invalid"); expressionIsCompatibleLabel.setForeground(WARN_MSG_OLOR); } } private void updateTargetExprArea() { try { String uncertaintyExpression = generateUncertaintyExpression(); targetExprArea.setText(uncertaintyExpression); validateTargExpression(); } catch (ParseException | UnsupportedOperationException e) { targetExprArea.setText(ERROR_PREFIX + e.getMessage()); } } private JComponent[] createComponents(String propertyName) { PropertyDescriptor descriptor = bindingContext.getPropertySet().getDescriptor(propertyName); PropertyEditor propertyEditor = PropertyEditorRegistry.getInstance().findPropertyEditor(descriptor); return propertyEditor.createComponents(descriptor, bindingContext); } private BindingContext createBindingContext() { final PropertyContainer container = PropertyContainer.createObjectBacked(this); final BindingContext context = new BindingContext(container); PropertyDescriptor descriptor; descriptor = container.getDescriptor(PROPERTY_NAME_BAND_NAME); descriptor.setDisplayName("Uncertainty band name"); descriptor.setDescription("The name for the new uncertainty band."); descriptor.setNotEmpty(true); descriptor.setValidator(new ProductNodeNameValidator(sourceBand.getProduct())); descriptor.setDefaultValue(getDefaultBandName(sourceBand.getName() + "_unc")); descriptor = container.getDescriptor(PROPERTY_NAME_ORDER); descriptor.setDisplayName("Order of Taylor polynomial"); descriptor.setDescription("The number of Taylor series expansion terms used for the Standard Combined Uncertainty (GUM 1995)."); descriptor.setDefaultValue(1); descriptor.setValueSet(new ValueSet(new Integer[]{1, 2, 3})); descriptor = container.getDescriptor(PROPERTY_NAME_RELATION); descriptor.setDisplayName("Relation name of ancillary bands"); descriptor.setDescription("Relation name of ancillary variables that represent uncertainties (NetCDF-U 'rel' attribute)."); descriptor.setDefaultValue("uncertainty"); descriptor.setNotNull(true); descriptor.setNotEmpty(true); container.setDefaultValues(); PropertyChangeListener targetExprUpdater = evt -> { updateTargetExprArea(); }; context.addPropertyChangeListener(PROPERTY_NAME_ORDER, targetExprUpdater); context.addPropertyChangeListener(PROPERTY_NAME_RELATION, targetExprUpdater); return context; } private String getDefaultBandName(String nameBase) { String defaultName = nameBase; Product product = sourceBand.getProduct(); int i = 0; while (product.getRasterDataNode(defaultName) != null) { defaultName = nameBase + (++i); } return defaultName; } private String getBandName() { return bandName.trim(); } }