diff --git a/GUI/src/main/java/cz/fidentis/analyst/core/ControlPanelBuilder.java b/GUI/src/main/java/cz/fidentis/analyst/core/ControlPanelBuilder.java index 56a92405bbe223515e824557a3c99b2f36935620..6227837d1def1f36f56a49d61b97f825818263f3 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/core/ControlPanelBuilder.java +++ b/GUI/src/main/java/cz/fidentis/analyst/core/ControlPanelBuilder.java @@ -7,6 +7,9 @@ import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.NumberFormat; @@ -280,7 +283,10 @@ public class ControlPanelBuilder { * @return This new GUI object */ public JButton addButton(String caption, ActionListener action) { - return addButtonCustom(caption, action, new GridBagConstraints()); + final GridBagConstraints c = new GridBagConstraints(); + c.anchor = GridBagConstraints.LINE_START; + + return addButtonCustom(caption, action, c); } /** @@ -462,6 +468,39 @@ public class ControlPanelBuilder { } inputField.postActionEvent(); // invoke textField action listener }); + slider.addMouseListener( + new MouseAdapter() { + @Override + public void mouseExited(MouseEvent e) { + e.setSource(inputField); + inputField.dispatchEvent(e); + } + + @Override + public void mouseEntered(MouseEvent e) { + e.setSource(inputField); + inputField.dispatchEvent(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + e.setSource(inputField); + inputField.dispatchEvent(e); + } + + @Override + public void mousePressed(MouseEvent e) { + e.setSource(inputField); + inputField.dispatchEvent(e); + } + + @Override + public void mouseClicked(MouseEvent e) { + e.setSource(inputField); + inputField.dispatchEvent(e); + } + } + ); inputField.addActionListener((ActionEvent ae) -> { if (max == -1) { // percents in [0,1] diff --git a/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java b/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java index d803a9530c3ca85acfa34e8b98510a5cff88ca7e..a4ec60f28e493f4d41aa070506c088fa4418c2bb 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java +++ b/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java @@ -4,6 +4,7 @@ import com.jogamp.opengl.GL2; import cz.fidentis.analyst.canvas.Canvas; import cz.fidentis.analyst.core.LoadedActionEvent; import cz.fidentis.analyst.core.ControlPanelAction; +import cz.fidentis.analyst.core.ControlPanelBuilder; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.feature.FeaturePointType; import cz.fidentis.analyst.mesh.core.MeshFacet; @@ -38,8 +39,7 @@ public class DistanceAction extends ControlPanelAction { private final Map<FeaturePointType, Double> featurePointTypes; private String strategy = DistancePanel.STRATEGY_POINT_TO_POINT; private boolean relativeDist = false; - private boolean weightedDist = false; - private boolean heatmapRender = false; + private String heatmapDisplayed = DistancePanel.HEATMAP_HAUSDORFF_DISTANCE; private boolean weightedFPsShow = true; private Map<MeshFacet, List<Double>> weightedHausdorffDistance = null; @@ -79,7 +79,7 @@ public class DistanceAction extends ControlPanelAction { fpWeights.getValue() // ... compute average FP weight over all its facets .values() .stream() - .mapToDouble(weight -> weight) + .mapToDouble(Double::doubleValue) .average() .orElse(Double.NaN))) .filter(fpWeight -> !Double.isNaN(fpWeight.getValue())) // Filter out feature points with Double.NaN weight @@ -123,7 +123,7 @@ public class DistanceAction extends ControlPanelAction { } } - // If the positin of secondary face has been changed + // If the position of secondary face has been changed if (secondaryFaceMoved) { visitor = null; // recompute Hausdorff distance @@ -139,7 +139,7 @@ public class DistanceAction extends ControlPanelAction { } updateHausdorffDistanceInformation(); - getSecondaryDrawableFace().setRenderHeatmap(heatmapRender); + getSecondaryDrawableFace().setRenderHeatmap(isHeatmapDisplayed()); } else { weightedFeaturePoints.hide(); } @@ -152,12 +152,10 @@ public class DistanceAction extends ControlPanelAction { final String action = ae.getActionCommand(); switch (action) { - case DistancePanel.ACTION_COMMAND_SHOW_HIDE_HEATMAP: - heatmapRender = ((JToggleButton) ae.getSource()).isSelected(); - if (heatmapRender) { - updateHausdorffDistanceInformation(); - } - getSecondaryDrawableFace().setRenderHeatmap(heatmapRender); + case DistancePanel.ACTION_COMMAND_SET_DISPLAYED_HEATMAP: + heatmapDisplayed = (String) ((JComboBox) ae.getSource()).getSelectedItem(); + updateHausdorffDistanceInformation(); + getSecondaryDrawableFace().setRenderHeatmap(isHeatmapDisplayed()); break; case DistancePanel.ACTION_COMMAND_SHOW_HIDE_WEIGHTED_FPOINTS: weightedFPsShow = ((JToggleButton) ae.getSource()).isSelected(); @@ -169,16 +167,23 @@ public class DistanceAction extends ControlPanelAction { break; case DistancePanel.ACTION_COMMAND_SET_DISTANCE_STRATEGY: strategy = (String) ((JComboBox) ae.getSource()).getSelectedItem(); - this.visitor = null; // recompute - updateHausdorffDistanceInformation(); + recompute(); break; case DistancePanel.ACTION_COMMAND_RELATIVE_ABSOLUTE_DIST: this.relativeDist = ((JToggleButton) ae.getSource()).isSelected(); - this.visitor = null; // recompute - updateHausdorffDistanceInformation(); + recompute(); break; case DistancePanel.ACTION_COMMAND_FEATURE_POINT_HIGHLIGHT: highlightFeaturePoint((LoadedActionEvent) ae); + recompute(); + break; + case DistancePanel.ACTION_COMMAND_FEATURE_POINT_SELECT_ALL: + highlightAllFeaturePoints(true); + recompute(); + break; + case DistancePanel.ACTION_COMMAND_FEATURE_POINT_SELECT_NONE: + highlightAllFeaturePoints(false); + recompute(); break; case DistancePanel.ACTION_COMMAND_FEATURE_POINT_HOVER_IN: hoverFeaturePoint((LoadedActionEvent) ae, true); @@ -189,13 +194,8 @@ public class DistanceAction extends ControlPanelAction { case DistancePanel.ACTION_COMMAND_FEATURE_POINT_RESIZE: resizeFeaturePoint((LoadedActionEvent) ae); break; - case DistancePanel.ACTION_COMMAND_WEIGHTED_DISTANCE: - this.weightedDist = ((JToggleButton) ae.getSource()).isSelected(); - updateHausdorffDistanceInformation(); - break; case DistancePanel.ACTION_COMMAND_DISTANCE_RECOMPUTE: - this.visitor = null; // recompute - updateHausdorffDistanceInformation(); + recompute(); break; default: // to nothing @@ -203,6 +203,15 @@ public class DistanceAction extends ControlPanelAction { renderScene(); } + /** + * Recalculates the Hausdorff distance and updates all GUI elements + * of {@link DistancePanel}. + */ + private void recompute() { + this.visitor = null; + updateHausdorffDistanceInformation(); + } + /** * (Re)calculates the Hausdorff distance and updates the heat map of the secondary face * as well as values of all appropriate GUI elements of {@link DistancePanel}. @@ -218,7 +227,20 @@ public class DistanceAction extends ControlPanelAction { setHausdorffDistanceStatistics(); } - getSecondaryDrawableFace().setHeatMap(weightedDist ? weightedHausdorffDistance : visitor.getDistances()); + final Map<MeshFacet, List<Double>> heatmap; + switch (heatmapDisplayed) { + case DistancePanel.HEATMAP_HAUSDORFF_DISTANCE: + heatmap = visitor.getDistances(); + break; + case DistancePanel.HEATMAP_WEIGHTED_HAUSDORFF_DISTANCE: + heatmap = weightedHausdorffDistance; + break; + case DistancePanel.HEATMAP_HIDE: + return; + default: + throw new UnsupportedOperationException(heatmapDisplayed); + } + getSecondaryDrawableFace().setHeatMap(heatmap); } /** @@ -292,7 +314,7 @@ public class DistanceAction extends ControlPanelAction { weights -> weights.getValue() // ... compute average FP weight over all its facets .values() .stream() - .mapToDouble(weight -> weight) + .mapToDouble(Double::doubleValue) .average() .orElse(Double.NaN)))); } @@ -307,17 +329,26 @@ public class DistanceAction extends ControlPanelAction { .values() .stream() .flatMap(List::stream) - .mapToDouble(distance -> distance) + .mapToDouble(Double::doubleValue) .summaryStatistics(), weightedHausdorffDistance .values() .stream() .flatMap(List::stream) - .mapToDouble(distance -> distance) + .mapToDouble(Double::doubleValue) .summaryStatistics() ); } + /** + * Returns {@code true} if the heatmap is displayed and {@code false} otherwise. + * + * @return {@code true} if the heatmap is displayed, {@code false} otherwise + */ + private boolean isHeatmapDisplayed() { + return !DistancePanel.HEATMAP_HIDE.equals(heatmapDisplayed); + } + /** * Changes the color of the secondary face's feature point at the given index * and of its weighted representation when the feature point is (de)selected @@ -339,6 +370,32 @@ public class DistanceAction extends ControlPanelAction { } } + /** + * Changes the color of all secondary face's feature points and of their + * weighted representations. + * All feature points are also (de)selected for the computation + * of the weighted Hausdorff distance. + * + * @param highlighted {@code true} if the feature points are to be highlighted, + * {@code false} otherwise + */ + private void highlightAllFeaturePoints(boolean highlighted) { + if (highlighted) { + final List<FeaturePoint> featurePoints = getSecondaryFeaturePoints().getFeaturePoints(); + for (int i = 0; i < featurePoints.size(); i++) { + colorSecondaryFaceFeaturePoint(i, FEATURE_POINT_HIGHLIGHT_COLOR); + featurePointTypes.put( + featurePoints.get(i).getFeaturePointType(), + weightedFeaturePoints.getSize(i) + ); + } + } else { + getSecondaryFeaturePoints().resetAllColorsToDefault(); + weightedFeaturePoints.resetAllColorsToDefault(); + featurePointTypes.clear(); + } + } + /** * Changes the color of the secondary face's feature point at the given index * and of its weighted representation when the cursor hovers over the feature point's name. @@ -392,7 +449,7 @@ public class DistanceAction extends ControlPanelAction { */ private void resizeFeaturePoint(LoadedActionEvent actionEvent) { final int index = (int) actionEvent.getData(); - final double size = Double.parseDouble(((JTextField) actionEvent.getSource()).getText()); + final double size = ControlPanelBuilder.parseLocaleDouble((JTextField) actionEvent.getSource()); weightedFeaturePoints.setSize(index, size); featurePointTypes.replace(getTypeOfFeaturePoint(index), size); diff --git a/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java b/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java index 057526561553eb5625a0962da723ff3cf00472fc..a04b4273a17a57e1bc5cd7681eb9c48bef99dfc1 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java +++ b/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java @@ -9,20 +9,24 @@ import cz.fidentis.analyst.scene.DrawableFeaturePoints; import cz.fidentis.analyst.symmetry.SymmetryPanel; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.ArrayList; import java.util.DoubleSummaryStatistics; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; +import javax.swing.KeyStroke; /** * Control panel for Hausdorff distance. @@ -41,13 +45,14 @@ public class DistancePanel extends ControlPanel { /* * External actions */ - public static final String ACTION_COMMAND_SHOW_HIDE_HEATMAP = "show-hide heatmap"; + public static final String ACTION_COMMAND_SET_DISPLAYED_HEATMAP = "set displayed heatmap"; public static final String ACTION_COMMAND_SET_DISTANCE_STRATEGY = "set strategy"; public static final String ACTION_COMMAND_RELATIVE_ABSOLUTE_DIST = "switch abosulte-relative distance"; - public static final String ACTION_COMMAND_WEIGHTED_DISTANCE = "switch weighted distance on/off"; public static final String ACTION_COMMAND_FEATURE_POINT_HOVER_IN = "highlight hovered FP"; public static final String ACTION_COMMAND_FEATURE_POINT_HOVER_OUT = "set default color of hovered FP"; public static final String ACTION_COMMAND_FEATURE_POINT_HIGHLIGHT = "highlight feature point with color"; + public static final String ACTION_COMMAND_FEATURE_POINT_SELECT_ALL = "select all feature points"; + public static final String ACTION_COMMAND_FEATURE_POINT_SELECT_NONE = "deselect all feature points"; public static final String ACTION_COMMAND_FEATURE_POINT_RESIZE = "set size of feature point"; public static final String ACTION_COMMAND_DISTANCE_RECOMPUTE = "recompute the Hausdorff distance"; public static final String ACTION_COMMAND_SHOW_HIDE_WEIGHTED_FPOINTS = "show-hide weighted feature points"; @@ -57,8 +62,12 @@ public class DistancePanel extends ControlPanel { */ public static final String STRATEGY_POINT_TO_POINT = "Point to point"; public static final String STRATEGY_POINT_TO_TRIANGLE = "Point to triangle"; + public static final String HEATMAP_HAUSDORFF_DISTANCE = "Hausdorff distance"; + public static final String HEATMAP_WEIGHTED_HAUSDORFF_DISTANCE = "Weighted Hausdorff distance"; + public static final String HEATMAP_HIDE = "None"; private final Map<FeaturePointType, JLabel> featurePointStats; + private final List<JCheckBox> featurePointCheckBoxes; private final JLabel avgHD, avgHDWeight, maxHD, maxHDWeight, minHD, minHDWeight; /** @@ -97,16 +106,16 @@ public class DistancePanel extends ControlPanel { final JPanel featurePointsPanel = new JPanel(); final ControlPanelBuilder fpBuilder = new ControlPanelBuilder(featurePointsPanel); featurePointStats = new HashMap<>(featurePoints.size()); + featurePointCheckBoxes = new ArrayList<>(featurePoints.size()); for (int i = 0; i < featurePoints.size(); i++) { final FeaturePoint featurePoint = featurePoints.get(i); final FeaturePointType featurePointType = featurePoint.getFeaturePointType(); final JCheckBox checkBox = fpBuilder.addCheckBox( selectedFPs.contains(featurePointType), - createListener(action, ACTION_COMMAND_DISTANCE_RECOMPUTE) + createListener(action, ACTION_COMMAND_FEATURE_POINT_HIGHLIGHT, i) ); checkBox.setText(featurePointType.getName()); - checkBox.addActionListener(createListener(action, ACTION_COMMAND_FEATURE_POINT_HIGHLIGHT, i)); final int index = i; checkBox.addMouseListener(new MouseAdapter() { @Override @@ -127,22 +136,58 @@ public class DistancePanel extends ControlPanel { index)); } }); + featurePointCheckBoxes.add(checkBox); final JTextField sliderInput = fpBuilder.addSliderOptionLine(null, null, 100, null); sliderInput.setText(ControlPanelBuilder.doubleToStringLocale(DrawableFeaturePoints.DEFAULT_SIZE)); sliderInput.postActionEvent(); // Set correct position of slider - sliderInput.addActionListener(new AbstractAction() { - private final ActionListener listener = createListener(action, ACTION_COMMAND_DISTANCE_RECOMPUTE); + sliderInput.addActionListener(createListener(action, ACTION_COMMAND_FEATURE_POINT_RESIZE, i)); + // Modify listener of the ENTER key press + final Object enterKeyAction = sliderInput.getInputMap() + .get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); + final Action originalEnterAction = sliderInput.getActionMap() + .get(enterKeyAction); + sliderInput.getActionMap().put( + enterKeyAction, + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + originalEnterAction.actionPerformed(e); + if (!checkBox.isSelected()) { + return; // Recompute only if the feature point is selected + } + + action.actionPerformed(new ActionEvent( + e.getSource(), + ActionEvent.ACTION_PERFORMED, + ACTION_COMMAND_DISTANCE_RECOMPUTE + )); + } + } + ); + // Add mouse listener of the slider + sliderInput.addMouseListener(new MouseAdapter() { + private double oldValue = DrawableFeaturePoints.DEFAULT_SIZE; + + @Override + public void mousePressed(MouseEvent e) { + oldValue = ControlPanelBuilder.parseLocaleDouble(sliderInput); + } @Override - public void actionPerformed(ActionEvent ae) { - if (!checkBox.isSelected()) { - return; + public void mouseReleased(MouseEvent e) { + if (!checkBox.isSelected() + || oldValue == ControlPanelBuilder.parseLocaleDouble(sliderInput)) { + return; // Recompute only if the feature point is selected and value changed } - listener.actionPerformed(ae); // Recompute only if the feature point is selected + + action.actionPerformed(new ActionEvent( + e.getSource(), + ActionEvent.ACTION_PERFORMED, + ACTION_COMMAND_DISTANCE_RECOMPUTE) + ); } }); - sliderInput.addActionListener(createListener(action, ACTION_COMMAND_FEATURE_POINT_RESIZE, i)); fpBuilder.addGap(); featurePointStats.put(featurePointType, fpBuilder.addLabelLine(null)); @@ -154,6 +199,24 @@ public class DistancePanel extends ControlPanel { .setBorder(BorderFactory.createTitledBorder("Feature points")); builder.addLine(); + builder.addButton("Select all", (ActionEvent ae) -> { + selectAllFeaturePoints(true); + action.actionPerformed(new ActionEvent( + ae.getSource(), + ActionEvent.ACTION_PERFORMED, + ACTION_COMMAND_FEATURE_POINT_SELECT_ALL) + ); + }); + builder.addButton("Deselect all", (ActionEvent ae) -> { + selectAllFeaturePoints(false); + action.actionPerformed(new ActionEvent( + ae.getSource(), + ActionEvent.ACTION_PERFORMED, + ACTION_COMMAND_FEATURE_POINT_SELECT_NONE) + ); + }); + builder.addLine(); + builder.addCaptionLine("Visualization options:"); builder.addLine(); builder.addCheckBoxOptionLine( @@ -163,20 +226,17 @@ public class DistancePanel extends ControlPanel { createListener(action, ACTION_COMMAND_SHOW_HIDE_WEIGHTED_FPOINTS) ); builder.addLine(); - builder.addCheckBoxOptionLine( - null, - "Show Hausdorff distance", - false, - createListener(action, ACTION_COMMAND_SHOW_HIDE_HEATMAP) - ); - builder.addLine(); - builder.addCheckBoxOptionLine( - null, - "Weighted Hausdorff distance", - false, - createListener(action, ACTION_COMMAND_WEIGHTED_DISTANCE) + builder.addOptionText("Heatmap"); + builder.addComboBox( + List.of( + HEATMAP_HAUSDORFF_DISTANCE, + HEATMAP_WEIGHTED_HAUSDORFF_DISTANCE, + HEATMAP_HIDE + ), + createListener(action, ACTION_COMMAND_SET_DISPLAYED_HEATMAP) ); + builder.addGap(); builder.addLine(); builder.addCaptionLine("Hausdorff distance:"); @@ -223,6 +283,18 @@ public class DistancePanel extends ControlPanel { return new ImageIcon(SymmetryPanel.class.getClassLoader().getResource("/" + ICON)); } + /** + * (De)selects all feature points for the computation of the weighted Hausdorff distance. + * + * @param selected {@code true} if all feature point checkboxes are to be selected, + * {@code false} otherwise + */ + private void selectAllFeaturePoints(boolean selected) { + featurePointCheckBoxes.forEach(fpCheckBox -> + fpCheckBox.setSelected(selected) + ); + } + /** * Updates GUI elements that display statistical data about the calculated Hausdorff distance. *