diff --git a/Comparison/src/main/java/cz/fidentis/analyst/BatchProcessor.java b/Comparison/src/main/java/cz/fidentis/analyst/BatchProcessor.java index 0de5414dc250900339ad38ded8c12604b67faf91..1adce12f5c10f12798eddbb13e3809b29a6e5299 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/BatchProcessor.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/BatchProcessor.java @@ -7,7 +7,6 @@ import cz.fidentis.analyst.icp.IcpTransformer; import cz.fidentis.analyst.icp.UndersamplingStrategy; import cz.fidentis.analyst.kdtree.KdTree; import cz.fidentis.analyst.mesh.core.MeshFacet; -import cz.fidentis.analyst.symmetry.CurvatureAlg; import cz.fidentis.analyst.symmetry.SymmetryConfig; import cz.fidentis.analyst.symmetry.SymmetryEstimator; import java.io.File; diff --git a/Comparison/src/main/java/cz/fidentis/analyst/icp/RandomStrategy.java b/Comparison/src/main/java/cz/fidentis/analyst/icp/RandomStrategy.java index c7c6046db1cbaec2c5b75453f35f70d674b73bfe..961b880770a4998e736e20c2c8d7181f1f456dd6 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/icp/RandomStrategy.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/icp/RandomStrategy.java @@ -18,7 +18,7 @@ public class RandomStrategy extends UndersamplingStrategy { /** * Constructor for PERCENTAGE undersampling type. * - * @param perc Percentage - a value in (0.0, 1.0> + * @param perc Percentage - a value in (0.0, 1.0>l * @throws IllegalArgumentException if the input parameter is wrong */ public RandomStrategy(double perc) { diff --git a/Comparison/src/main/java/cz/fidentis/analyst/icp/UndersamplingStrategy.java b/Comparison/src/main/java/cz/fidentis/analyst/icp/UndersamplingStrategy.java index 105d9ee8147ee7ca098bd02ab4afdc5803fd1ca9..e0d9b0692bafee98b6ee4335d78ae0ae910d0aec 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/icp/UndersamplingStrategy.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/icp/UndersamplingStrategy.java @@ -38,7 +38,7 @@ public abstract class UndersamplingStrategy { /** * Constructor for PERCENTAGE undersampling type. * - * @param perc Percentage - a value in (0.0, 1.0> + * @param perc Percentage - a value in (0.0, 1.0> * @throws IllegalArgumentException if the input parameter is wrong */ public UndersamplingStrategy(double perc) { diff --git a/GUI/src/main/java/cz/fidentis/analyst/canvas/MouseRotationListener.java b/GUI/src/main/java/cz/fidentis/analyst/canvas/MouseRotationListener.java index 26753fbdd348d09f060a2a9b5eb419d948496df5..a1800faed3997784b4edf99d243c6777cbdb30f9 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/canvas/MouseRotationListener.java +++ b/GUI/src/main/java/cz/fidentis/analyst/canvas/MouseRotationListener.java @@ -67,7 +67,7 @@ public class MouseRotationListener extends MouseAdapter { /** * Actualize mouse movement - * @param evt Mouse position info + * @param e Mouse position info */ @Override public void mouseMoved(MouseEvent e) { diff --git a/GUI/src/main/java/cz/fidentis/analyst/canvas/RotationAnimator.java b/GUI/src/main/java/cz/fidentis/analyst/canvas/RotationAnimator.java index 618d50f314463ea7776b5086cb25ef00ae5e7145..1928b4347dd2d1eca876f47f2d8efe28cc87c559 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/canvas/RotationAnimator.java +++ b/GUI/src/main/java/cz/fidentis/analyst/canvas/RotationAnimator.java @@ -38,7 +38,6 @@ public class RotationAnimator { * Constructor. * * @param glCanvas OpenGL canvas - * @param camera Camera * @throws IllegalArgumentException if some argument is missing */ public RotationAnimator(GLCanvas glCanvas) { 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 d1d4d488e4bb06f67a1cb7e04cdf06a946521f45..d803a9530c3ca85acfa34e8b98510a5cff88ca7e 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java +++ b/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java @@ -1,5 +1,6 @@ package cz.fidentis.analyst.distance; +import com.jogamp.opengl.GL2; import cz.fidentis.analyst.canvas.Canvas; import cz.fidentis.analyst.core.LoadedActionEvent; import cz.fidentis.analyst.core.ControlPanelAction; @@ -11,7 +12,6 @@ import cz.fidentis.analyst.visitors.face.HausdorffDistancePrioritized; import cz.fidentis.analyst.visitors.mesh.HausdorffDistance.Strategy; import java.awt.Color; import java.awt.event.ActionEvent; -import java.util.DoubleSummaryStatistics; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,9 +21,10 @@ import javax.swing.JComboBox; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.JToggleButton; +import javax.vecmath.Point3d; /** - * Action listener for the curvature computation. + * Action listener for computation of the Hausdorff distance. * * @author Radek Oslejsek * @author Daniel Schramm @@ -34,14 +35,22 @@ public class DistanceAction extends ControlPanelAction { * Attributes handling the state */ private HausdorffDistancePrioritized visitor = null; - private final Map<FeaturePointType, Double> featurePointTypes = new HashMap<>(); + 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 boolean weightedFPsShow = true; - private Map<MeshFacet, List<Double>> hausdorffDistance = null; + private Map<MeshFacet, List<Double>> weightedHausdorffDistance = null; + private FeaturePointType hoveredFeaturePoint = null; private final DistancePanel controlPanel; + private final DrawableFeaturePoints weightedFeaturePoints; + + private static final Color WEIGHTED_FEATURE_POINT_DEFAULT_COLOR = Color.WHITE; + private static final Color FEATURE_POINT_HIGHLIGHT_COLOR = Color.MAGENTA; + private static final Color FEATURE_POINT_HOVER_COLOR = Color.CYAN; /** * Constructor. @@ -51,7 +60,91 @@ public class DistanceAction extends ControlPanelAction { */ public DistanceAction(Canvas canvas, JTabbedPane topControlPanel) { super(canvas, topControlPanel); - this.controlPanel = new DistancePanel(this, getSecondaryFeaturePoints().getFeaturePoints()); + + // Calculate weighted Hausdorff distance for all feature points of the secondary face + calculateHausdorffDistance( + getSecondaryFeaturePoints().getFeaturePoints() + .stream() + .collect(Collectors.toMap( + FeaturePoint::getFeaturePointType, + featurePoint -> DrawableFeaturePoints.DEFAULT_SIZE)) + ); + + this.featurePointTypes = visitor.getFeaturePointWeights() + .get(getSecondaryDrawableFace().getHumanFace()) // Get FP weights for the secondary face + .entrySet() + .stream() + .map(fpWeights -> Map.entry( + fpWeights.getKey(), // For each FP type at the secondary face... + fpWeights.getValue() // ... compute average FP weight over all its facets + .values() + .stream() + .mapToDouble(weight -> weight) + .average() + .orElse(Double.NaN))) + .filter(fpWeight -> !Double.isNaN(fpWeight.getValue())) // Filter out feature points with Double.NaN weight + .collect(Collectors.toMap( + Map.Entry::getKey, + fpType -> DrawableFeaturePoints.DEFAULT_SIZE)); + this.controlPanel = new DistancePanel(this, getSecondaryFeaturePoints().getFeaturePoints(), featurePointTypes.keySet()); + this.visitor = null; + + // Add weighted feature points to the scene + this.weightedFeaturePoints = new DrawableFeaturePoints(getSecondaryFeaturePoints().getFeaturePoints()); + weightedFeaturePoints.setRenderMode(GL2.GL_LINE); + weightedFeaturePoints.setColor(WEIGHTED_FEATURE_POINT_DEFAULT_COLOR); + getCanvas().getScene().addOtherDrawable(weightedFeaturePoints); + + // Place control panel to the topControlPanel + topControlPanel.addTab(controlPanel.getName(), controlPanel.getIcon(), controlPanel); + topControlPanel.addChangeListener(e -> { + // If the distance panel is focused... + if (((JTabbedPane) e.getSource()).getSelectedComponent() instanceof DistancePanel) { + // ... display heatmap and feature points relevant to the Hausdorff distance + getCanvas().getScene().setDefaultColors(); + weightedFeaturePoints.resetAllColorsToDefault(); + if (weightedFPsShow) { + weightedFeaturePoints.show(); + } + + boolean secondaryFaceMoved = false; + final List<FeaturePoint> secondaryFPs = getSecondaryFeaturePoints().getFeaturePoints(); + final List<FeaturePoint> weightedFPs = weightedFeaturePoints.getFeaturePoints(); + for (int i = 0; i < secondaryFPs.size(); i++) { + final FeaturePoint faceFP = secondaryFPs.get(i); + final FeaturePoint weightedFP = weightedFPs.get(i); + + // Highlight feature point if selected + if (featurePointTypes.containsKey(faceFP.getFeaturePointType())) { + colorSecondaryFaceFeaturePoint(i, FEATURE_POINT_HIGHLIGHT_COLOR); + } + if (!faceFP.getPosition().equals(weightedFP.getPosition())) { + secondaryFaceMoved = true; + } + } + + // If the positin of secondary face has been changed + if (secondaryFaceMoved) { + visitor = null; // recompute Hausdorff distance + + // Relocate weighted feature points + for (int i = 0; i < secondaryFPs.size(); i++) { + final Point3d faceFpPos = secondaryFPs.get(i).getPosition(); + final Point3d weightedFpPos = weightedFPs.get(i).getPosition(); + + weightedFpPos.x = faceFpPos.x; + weightedFpPos.y = faceFpPos.y; + weightedFpPos.z = faceFpPos.z; + } + } + + updateHausdorffDistanceInformation(); + getSecondaryDrawableFace().setRenderHeatmap(heatmapRender); + } else { + weightedFeaturePoints.hide(); + } + }); + topControlPanel.setSelectedComponent(controlPanel); // Focus Hausdorff distance panel } @Override @@ -59,41 +152,50 @@ public class DistanceAction extends ControlPanelAction { final String action = ae.getActionCommand(); switch (action) { - case DistancePanel.ACTION_COMMAND_SHOW_HIDE_PANEL: - hideShowPanelActionPerformed(ae, this.controlPanel); - break; case DistancePanel.ACTION_COMMAND_SHOW_HIDE_HEATMAP: - if (((JToggleButton) ae.getSource()).isSelected()) { - calculateHausdorffDistance(); - getSecondaryDrawableFace().setRenderHeatmap(true); + heatmapRender = ((JToggleButton) ae.getSource()).isSelected(); + if (heatmapRender) { + updateHausdorffDistanceInformation(); + } + getSecondaryDrawableFace().setRenderHeatmap(heatmapRender); + break; + case DistancePanel.ACTION_COMMAND_SHOW_HIDE_WEIGHTED_FPOINTS: + weightedFPsShow = ((JToggleButton) ae.getSource()).isSelected(); + if (weightedFPsShow) { + weightedFeaturePoints.show(); } else { - getSecondaryDrawableFace().setRenderHeatmap(false); + weightedFeaturePoints.hide(); } break; case DistancePanel.ACTION_COMMAND_SET_DISTANCE_STRATEGY: strategy = (String) ((JComboBox) ae.getSource()).getSelectedItem(); this.visitor = null; // recompute - calculateHausdorffDistance(); + updateHausdorffDistanceInformation(); break; case DistancePanel.ACTION_COMMAND_RELATIVE_ABSOLUTE_DIST: this.relativeDist = ((JToggleButton) ae.getSource()).isSelected(); this.visitor = null; // recompute - calculateHausdorffDistance(); + updateHausdorffDistanceInformation(); break; case DistancePanel.ACTION_COMMAND_FEATURE_POINT_HIGHLIGHT: highlightFeaturePoint((LoadedActionEvent) ae); break; + case DistancePanel.ACTION_COMMAND_FEATURE_POINT_HOVER_IN: + hoverFeaturePoint((LoadedActionEvent) ae, true); + break; + case DistancePanel.ACTION_COMMAND_FEATURE_POINT_HOVER_OUT: + hoverFeaturePoint((LoadedActionEvent) ae, false); + break; case DistancePanel.ACTION_COMMAND_FEATURE_POINT_RESIZE: resizeFeaturePoint((LoadedActionEvent) ae); break; case DistancePanel.ACTION_COMMAND_WEIGHTED_DISTANCE: this.weightedDist = ((JToggleButton) ae.getSource()).isSelected(); - this.hausdorffDistance = null; // recompute only priorities - calculateHausdorffDistance(); + updateHausdorffDistanceInformation(); break; - case DistancePanel.ACTION_COMMAND_WEIGHTED_DIST_RECOMPUTE: + case DistancePanel.ACTION_COMMAND_DISTANCE_RECOMPUTE: this.visitor = null; // recompute - calculateHausdorffDistance(); + updateHausdorffDistanceInformation(); break; default: // to nothing @@ -105,7 +207,30 @@ public class DistanceAction extends ControlPanelAction { * (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}. */ - protected void calculateHausdorffDistance() { + private void updateHausdorffDistanceInformation() { + if (visitor == null) { + calculateHausdorffDistance(featurePointTypes); + + weightedHausdorffDistance = getWeightedDistance(); + + // Update GUI elements that display the calculated Hausdorff distance metrics + setFeaturePointWeigths(); + setHausdorffDistanceStatistics(); + } + + getSecondaryDrawableFace().setHeatMap(weightedDist ? weightedHausdorffDistance : visitor.getDistances()); + } + + /** + * (Re)calculates the Hausdorff distance. + * + * @param featurePoints Types of feature points according to which the Hausdorff + * distances will be prioritized together with + * the radii of priority spheres around the feature + * points of the corresponding type. + * Must not be {@code null}. + */ + private void calculateHausdorffDistance(Map<FeaturePointType, Double> featurePoints) { final Strategy useStrategy; switch (strategy) { case DistancePanel.STRATEGY_POINT_TO_POINT: @@ -118,40 +243,25 @@ public class DistanceAction extends ControlPanelAction { throw new UnsupportedOperationException(strategy); } - if (visitor == null) { - this.visitor = new HausdorffDistancePrioritized(getPrimaryDrawableFace().getModel(), - featurePointTypes, - useStrategy, - relativeDist, - true); - getSecondaryDrawableFace().getHumanFace().accept(visitor); - hausdorffDistance = null; - } - - // Update GUI elements that display the calculated Hausdorff distance - if (hausdorffDistance == null) { - hausdorffDistance = getWeightedDistance(); - setHausdorffDistanceStatistics(); - setFeaturePointWeigths(); - } - getSecondaryDrawableFace().setHeatMap(hausdorffDistance); + this.visitor = new HausdorffDistancePrioritized(getPrimaryDrawableFace().getModel(), + featurePoints, + useStrategy, + relativeDist, + true); + getSecondaryDrawableFace().getHumanFace().accept(visitor); } /** - * Calculates weighted (or regular) Hausdorff distance of the face. + * Calculates weighted Hausdorff distance of the face. * * @return weighted Hausdorff distance */ private Map<MeshFacet, List<Double>> getWeightedDistance() { - if (!weightedDist) { - return visitor.getDistances(); - } - - // Merge the map of distances with the map of priorities final Map<MeshFacet, List<Double>> weightedDistances = new HashMap<>(visitor.getDistances()); - final Map<MeshFacet, List<Double>> mergedPriorities = visitor.getMergedPriorities() .get(getSecondaryDrawableFace().getHumanFace()); + + // Merge the map of distances with the map of priorities for (final Map.Entry<MeshFacet, List<Double>> facetPriorities: mergedPriorities.entrySet()) { weightedDistances.merge( facetPriorities.getKey(), @@ -165,35 +275,12 @@ public class DistanceAction extends ControlPanelAction { return weightedDistances; } - - /** - * Updates the GUI elements of {@link DistancePanel} elements that display - * statistical data about the calculated Hausdorff distance. - */ - private void setHausdorffDistanceStatistics() { - final DoubleSummaryStatistics distanceStats = hausdorffDistance.values() - .stream() - .flatMap(List::stream) - .mapToDouble(distance -> distance) - .summaryStatistics(); - - controlPanel.updateHausdorffDistanceStats( - distanceStats.getAverage(), - distanceStats.getMax(), - distanceStats.getMin() - ); - } /** * Updates the GUI elements of {@link DistancePanel} that display * the weights of feature points used to calculate the weighted Hausdorff distance. */ private void setFeaturePointWeigths() { - if (!weightedDist) { - controlPanel.updateFeaturePointWeights(Map.of()); - return; - } - controlPanel.updateFeaturePointWeights( visitor.getFeaturePointWeights() .get(getSecondaryDrawableFace().getHumanFace()) // Get FP weights for the secondary face @@ -209,9 +296,32 @@ public class DistanceAction extends ControlPanelAction { .average() .orElse(Double.NaN)))); } + + /** + * Updates the GUI elements of {@link DistancePanel} elements that display + * statistical data about the calculated Hausdorff distance. + */ + private void setHausdorffDistanceStatistics() { + controlPanel.updateHausdorffDistanceStats( + visitor.getDistances() + .values() + .stream() + .flatMap(List::stream) + .mapToDouble(distance -> distance) + .summaryStatistics(), + weightedHausdorffDistance + .values() + .stream() + .flatMap(List::stream) + .mapToDouble(distance -> distance) + .summaryStatistics() + ); + } /** - * Changes the colour of the secondary face's feature point at the given index. + * 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 + * for the computation of the weighted Hausdorff distance. * The index is received as the data payload of {@code actionEvent}. * * @param actionEvent Action event with the index of the feature point as its payload data @@ -219,16 +329,60 @@ public class DistanceAction extends ControlPanelAction { private void highlightFeaturePoint(LoadedActionEvent actionEvent) { final int index = (int) actionEvent.getData(); final FeaturePointType fpType = getTypeOfFeaturePoint(index); - final DrawableFeaturePoints secondaryFeaturePoints = getSecondaryFeaturePoints(); if (((JToggleButton) actionEvent.getSource()).isSelected()) { - secondaryFeaturePoints.setColor(index, Color.MAGENTA); - featurePointTypes.put(fpType, secondaryFeaturePoints.getSize(index)); + colorSecondaryFaceFeaturePoint(index, FEATURE_POINT_HIGHLIGHT_COLOR); + featurePointTypes.put(fpType, weightedFeaturePoints.getSize(index)); } else { - secondaryFeaturePoints.resetColorToDefault(index); + resetSecondaryFaceFeaturePointColor(index); featurePointTypes.remove(fpType); } } + + /** + * 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. + * The index is received as the data payload of {@code actionEvent}. + * + * @param actionEvent Action event with the index of the feature point as its payload data + * @param entered {@code true} if the cursor entered the feature point, + * {@code false} if the cursor left the feature point + */ + private void hoverFeaturePoint(LoadedActionEvent actionEvent, boolean entered) { + final int index = (int) actionEvent.getData(); + + if (entered) { // entering a feature point + colorSecondaryFaceFeaturePoint(index, FEATURE_POINT_HOVER_COLOR); + hoveredFeaturePoint = getTypeOfFeaturePoint(index); + } else if (featurePointTypes.containsKey(hoveredFeaturePoint)) { // leaving highlighted FP + colorSecondaryFaceFeaturePoint(index, FEATURE_POINT_HIGHLIGHT_COLOR); + } else { // leaving ordinary FP + resetSecondaryFaceFeaturePointColor(index); + } + } + + /** + * Sets the color of the secondary face's feature point at the given index + * and of its weighted representation. + * + * @param index Index of the feature point + * @param color New color of the feature point + */ + private void colorSecondaryFaceFeaturePoint(int index, Color color) { + getSecondaryFeaturePoints().setColor(index, color); + weightedFeaturePoints.setColor(index, color); + } + + /** + * Resets to default the color of the secondary face's feature point at the given index + * and of its weighted representation. + * + * @param index Index of the feature point + */ + private void resetSecondaryFaceFeaturePointColor(final int index) { + getSecondaryFeaturePoints().resetColorToDefault(index); + weightedFeaturePoints.resetColorToDefault(index); + } /** * Changes the size of the secondary face's feature point at the given index. @@ -240,7 +394,7 @@ public class DistanceAction extends ControlPanelAction { final int index = (int) actionEvent.getData(); final double size = Double.parseDouble(((JTextField) actionEvent.getSource()).getText()); - getSecondaryFeaturePoints().setSize(index, size); + 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 035a8274f3f007166f1eb7cd11ae3ce9ce1a5796..057526561553eb5625a0962da723ff3cf00472fc 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java +++ b/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java @@ -2,22 +2,26 @@ package cz.fidentis.analyst.distance; import cz.fidentis.analyst.core.ControlPanel; import cz.fidentis.analyst.core.ControlPanelBuilder; +import cz.fidentis.analyst.core.LoadedActionEvent; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.feature.FeaturePointType; import cz.fidentis.analyst.scene.DrawableFeaturePoints; import cz.fidentis.analyst.symmetry.SymmetryPanel; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.ArrayList; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +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.BorderFactory; import javax.swing.ImageIcon; -import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JScrollPane; import javax.swing.JTextField; /** @@ -41,9 +45,12 @@ public class DistancePanel extends ControlPanel { 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_RESIZE = "set size of feature point"; - public static final String ACTION_COMMAND_WEIGHTED_DIST_RECOMPUTE = "recompute weighted distance"; + 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"; /* * Configuration of panel-specific GUI elements @@ -51,14 +58,19 @@ 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"; - private final JPanel featurePointsStats; - private final JLabel avgHD, maxHD, minHD; + private final Map<FeaturePointType, JLabel> featurePointStats; + private final JLabel avgHD, avgHDWeight, maxHD, maxHDWeight, minHD, minHDWeight; /** * Constructor. + * * @param action Action listener + * @param featurePoints List of all feature points which can be used to calculate + * the weighted Hausdorff distance + * @param selectedFPs Set of feature point types which are initially selected + * to be used to calculate the weighted Hausdorff distance */ - public DistancePanel(ActionListener action, List<FeaturePoint> featurePoints) { + public DistancePanel(ActionListener action, List<FeaturePoint> featurePoints, Set<FeaturePointType> selectedFPs) { this.setName(NAME); final ControlPanelBuilder builder = new ControlPanelBuilder(this); @@ -82,37 +94,59 @@ public class DistancePanel extends ControlPanel { builder.addGap(); builder.addLine(); - final JCheckBox weightedDistChBx = builder.addCheckBoxOptionLine( - null, - "Weighted Hausdorff distance", - false, - createListener(action, ACTION_COMMAND_WEIGHTED_DISTANCE) - ); - builder.addLine(); - final JPanel featurePointsPanel = new JPanel(); final ControlPanelBuilder fpBuilder = new ControlPanelBuilder(featurePointsPanel); - final List<JCheckBox> fpCheckBoxes = new ArrayList<>(featurePoints.size()); - final List<JTextField> fpSliderInputs = new ArrayList<>(featurePoints.size()); + featurePointStats = new HashMap<>(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( - false, - createListener(action, ACTION_COMMAND_FEATURE_POINT_HIGHLIGHT, i) + selectedFPs.contains(featurePointType), + createListener(action, ACTION_COMMAND_DISTANCE_RECOMPUTE) ); - checkBox.setText(featurePoint.getFeaturePointType().getName()); - fpCheckBoxes.add(checkBox); + checkBox.setText(featurePointType.getName()); + checkBox.addActionListener(createListener(action, ACTION_COMMAND_FEATURE_POINT_HIGHLIGHT, i)); + final int index = i; + checkBox.addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + action.actionPerformed(new LoadedActionEvent( + e.getSource(), + ActionEvent.ACTION_PERFORMED, + ACTION_COMMAND_FEATURE_POINT_HOVER_IN, + index)); + } - final JTextField sliderInput = fpBuilder.addSliderOptionLine( - null, - null, - 100, - createListener(action, ACTION_COMMAND_FEATURE_POINT_RESIZE, i) - ); + @Override + public void mouseExited(MouseEvent e) { + action.actionPerformed(new LoadedActionEvent( + e.getSource(), + ActionEvent.ACTION_PERFORMED, + ACTION_COMMAND_FEATURE_POINT_HOVER_OUT, + index)); + } + }); + + final JTextField sliderInput = fpBuilder.addSliderOptionLine(null, null, 100, null); sliderInput.setText(ControlPanelBuilder.doubleToStringLocale(DrawableFeaturePoints.DEFAULT_SIZE)); - sliderInput.postActionEvent(); - fpSliderInputs.add(sliderInput); + sliderInput.postActionEvent(); // Set correct position of slider + sliderInput.addActionListener(new AbstractAction() { + private final ActionListener listener = createListener(action, ACTION_COMMAND_DISTANCE_RECOMPUTE); + + @Override + public void actionPerformed(ActionEvent ae) { + if (!checkBox.isSelected()) { + return; + } + listener.actionPerformed(ae); // Recompute only if the feature point is selected + } + }); + sliderInput.addActionListener(createListener(action, ACTION_COMMAND_FEATURE_POINT_RESIZE, i)); + + fpBuilder.addGap(); + featurePointStats.put(featurePointType, fpBuilder.addLabelLine(null)); + fpBuilder.addGap(); fpBuilder.addLine(); } @@ -120,39 +154,15 @@ public class DistancePanel extends ControlPanel { .setBorder(BorderFactory.createTitledBorder("Feature points")); builder.addLine(); - final JButton recomputeButton = builder.addButton( - "Recompute", - createListener(action, ACTION_COMMAND_WEIGHTED_DIST_RECOMPUTE) - ); - recomputeButton.setEnabled(false); - recomputeButton.addActionListener((ActionEvent ae) -> - recomputeButton.setEnabled(false) - ); - weightedDistChBx.addActionListener((ActionEvent ae) -> { - if (!weightedDistChBx.isSelected()) { - recomputeButton.setEnabled(false); - } - }); - for (final JCheckBox checkBox: fpCheckBoxes) { - checkBox.addActionListener((ActionEvent ae) -> { - if (weightedDistChBx.isSelected()) { - recomputeButton.setEnabled(true); - } - }); - } - for (int i = 0; i < fpSliderInputs.size(); i++) { - final JTextField sliderInput = fpSliderInputs.get(i); - final JCheckBox sliderCheckBox = fpCheckBoxes.get(i); - sliderInput.addActionListener((ActionEvent ae) -> { - if (weightedDistChBx.isSelected() && sliderCheckBox.isSelected()) { - recomputeButton.setEnabled(true); - } - }); - } - builder.addLine(); - builder.addCaptionLine("Visualization options:"); builder.addLine(); + builder.addCheckBoxOptionLine( + null, + "Weighted feature points", + true, + createListener(action, ACTION_COMMAND_SHOW_HIDE_WEIGHTED_FPOINTS) + ); + builder.addLine(); builder.addCheckBoxOptionLine( null, "Show Hausdorff distance", @@ -161,32 +171,39 @@ public class DistancePanel extends ControlPanel { ); builder.addLine(); + builder.addCheckBoxOptionLine( + null, + "Weighted Hausdorff distance", + false, + createListener(action, ACTION_COMMAND_WEIGHTED_DISTANCE) + ); + builder.addLine(); + builder.addCaptionLine("Hausdorff distance:"); builder.addLine(); + builder.addOptionText(""); + builder.addOptionText("Hausdorff distance"); + builder.addOptionText("Weighted Hasudorff distance"); + builder.addLine(); + builder.addOptionText("Average"); - builder.addGap(); avgHD = builder.addLabelLine(""); builder.addGap(); + builder.addGap(); + avgHDWeight = builder.addLabelLine(""); builder.addLine(); builder.addOptionText("Maximum"); - builder.addGap(); maxHD = builder.addLabelLine(""); builder.addGap(); + builder.addGap(); + maxHDWeight = builder.addLabelLine(""); builder.addLine(); builder.addOptionText("Minimum"); - builder.addGap(); minHD = builder.addLabelLine(""); builder.addGap(); - builder.addLine(); - - featurePointsStats = new JPanel(); - final JScrollPane fpStatsScrollPanel = builder.addScrollPane(featurePointsStats); - fpStatsScrollPanel.setBorder(BorderFactory.createTitledBorder("Feature point weights")); - fpStatsScrollPanel.setVisible(false); - weightedDistChBx.addActionListener((ActionEvent ae) -> { - fpStatsScrollPanel.setVisible(weightedDistChBx.isSelected()); - }); + builder.addGap(); + minHDWeight = builder.addLabelLine(""); builder.addLine(); builder.addVerticalStrut(); @@ -209,14 +226,19 @@ public class DistancePanel extends ControlPanel { /** * Updates GUI elements that display statistical data about the calculated Hausdorff distance. * - * @param averageDist Average distance - * @param maxDist Maximum distance - * @param minDist Minimum distance + * @param hausdorffDistance Statistical data of the ordinary Hausdorff distance + * @param weightedHausdorffDistance Statistical data of the weighted Hausdorff distance */ - public void updateHausdorffDistanceStats(double averageDist, double maxDist, double minDist) { - avgHD.setText(Double.toString(averageDist)); - maxHD.setText(Double.toString(maxDist)); - minHD.setText(Double.toString(minDist)); + public void updateHausdorffDistanceStats(DoubleSummaryStatistics hausdorffDistance, + DoubleSummaryStatistics weightedHausdorffDistance) { + avgHD.setText(Double.toString(hausdorffDistance.getAverage())); + avgHDWeight.setText(Double.toString(weightedHausdorffDistance.getAverage())); + + maxHD.setText(Double.toString(hausdorffDistance.getMax())); + maxHDWeight.setText(Double.toString(weightedHausdorffDistance.getMax())); + + minHD.setText(Double.toString(hausdorffDistance.getMin())); + minHDWeight.setText(Double.toString(weightedHausdorffDistance.getMin())); } /** @@ -226,17 +248,10 @@ public class DistancePanel extends ControlPanel { * @param featurePointWeights Map of feature point types and their weights */ public void updateFeaturePointWeights(Map<FeaturePointType, Double> featurePointWeights) { - featurePointsStats.removeAll(); - - final ControlPanelBuilder fpBuilder = new ControlPanelBuilder(featurePointsStats); - for (final Map.Entry<FeaturePointType, Double> fpStats: featurePointWeights.entrySet()) { - fpBuilder.addOptionText(fpStats.getKey().getName()); - fpBuilder.addGap(); - - fpBuilder.addLabelLine(Double.toString(fpStats.getValue())); - fpBuilder.addGap(); - fpBuilder.addLine(); - } + featurePointStats.forEach((fpType, fpWeightLabel) -> { + final Double fpWeight = featurePointWeights.get(fpType); + fpWeightLabel.setText(fpWeight == null ? "" : Double.toString(fpWeight)); + }); } } diff --git a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationAction.java b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationAction.java index 3044b5613023b9e949ddbb5ae226f5a643e421b4..7cc5713639cd4392010b0f9fa7328a1a15ddbc11 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationAction.java +++ b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationAction.java @@ -29,6 +29,7 @@ import javax.vecmath.Vector3d; * * @author Richard Pajersky * @author Radek Oslejsek + * @author Daniel Schramm */ public class RegistrationAction extends ControlPanelAction { @@ -42,6 +43,7 @@ public class RegistrationAction extends ControlPanelAction { private HausdorffDistance hdVisitor = null; private String strategy = RegistrationPanel.STRATEGY_POINT_TO_POINT; private boolean relativeDist = false; + private boolean heatmapRender = false; /* * Coloring threshold and statistical values of feature point distances: @@ -59,6 +61,21 @@ public class RegistrationAction extends ControlPanelAction { public RegistrationAction(Canvas canvas, JTabbedPane topControlPanel) { super(canvas, topControlPanel); this.controlPanel = new RegistrationPanel(this); + + // Place control panel to the topControlPanel + topControlPanel.addTab(controlPanel.getName(), controlPanel.getIcon(), controlPanel); + topControlPanel.addChangeListener(e -> { + // If the registration panel is focused... + if (((JTabbedPane) e.getSource()).getSelectedComponent() instanceof RegistrationPanel) { + // ... display heatmap and feature points relevant to the registration + getCanvas().getScene().setDefaultColors(); + calculateFeaturePoints(); + setHeatmap(); + getSecondaryDrawableFace().setRenderHeatmap(heatmapRender); + } + }); + topControlPanel.setSelectedComponent(controlPanel); // Focus registration panel + calculateHausdorffDistance(); } @Override @@ -67,19 +84,12 @@ public class RegistrationAction extends ControlPanelAction { String action = ae.getActionCommand(); switch (action) { - case RegistrationPanel.ACTION_COMMAND_SHOW_HIDE_PANEL: - hideShowPanelActionPerformed(ae, this.controlPanel); - if (((JToggleButton) ae.getSource()).isSelected()) { - calculateFeaturePoints(); // color points - calculateHausdorffDistance(); - } else { - this.getCanvas().getScene().setDefaultColors(); - } - break; case RegistrationPanel.ACTION_COMMAND_APPLY_ICP: applyICP(); calculateFeaturePoints(); calculateHausdorffDistance(); + this.hdVisitor = null; // recompute + setHeatmap(); break; case RegistrationPanel.ACTION_COMMAND_SHIFT_X: value = ((Number)(((JFormattedTextField) ae.getSource()).getValue())).doubleValue(); @@ -222,13 +232,12 @@ public class RegistrationAction extends ControlPanelAction { } break; case RegistrationPanel.ACTION_COMMAND_HD_HEATMAP: - if (((JToggleButton) ae.getSource()).isSelected()) { + heatmapRender = ((JToggleButton) ae.getSource()).isSelected(); + if (heatmapRender) { calculateHausdorffDistance(); setHeatmap(); - getSecondaryDrawableFace().setRenderHeatmap(true); - } else { - getSecondaryDrawableFace().setRenderHeatmap(false); } + getSecondaryDrawableFace().setRenderHeatmap(heatmapRender); break; case RegistrationPanel.ACTION_COMMAND_HD_STRATEGY: strategy = (String) ((JComboBox) ae.getSource()).getSelectedItem(); @@ -283,7 +292,7 @@ public class RegistrationAction extends ControlPanelAction { /** * Calculates feature points which are too far away - * and changes their color to red + * and changes their color to green * otherwise set color to default */ private void calculateFeaturePoints() { diff --git a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.java b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.java index 610477e27ff43eebd7aadcd47d0ab0eb4412ec04..f0e8978a33c0f467a9bd2d3f5d1f7030303ece26 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.java +++ b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.java @@ -84,7 +84,7 @@ public class RegistrationPanel extends ControlPanel { /** * Listener which translates executed actions - * into {@link cz.fidentis.analyst.gui.scene.DrawableMesh} + * into {@link cz.fidentis.analyst.scene.DrawableMesh} */ private final ActionListener listener; diff --git a/GUI/src/main/java/cz/fidentis/analyst/scene/Drawable.java b/GUI/src/main/java/cz/fidentis/analyst/scene/Drawable.java index d011aa7b305d3848e8c1539f6ae6d19cbc5eb784..c6dbe3210bbd27e30b1630f2280cd12fe1e256e9 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/scene/Drawable.java +++ b/GUI/src/main/java/cz/fidentis/analyst/scene/Drawable.java @@ -195,7 +195,7 @@ public abstract class Drawable { /** * Sets render mode * @param renderMode Render mode - * @throws IllegalArgumentException if renderMode isn't {@code GL_FILL}, {@code GL_LINE} or {@coed GL_POINT} + * @throws IllegalArgumentException if renderMode isn't {@code GL_FILL}, {@code GL_LINE} or {@code GL_POINT} */ public void setRenderMode(int renderMode) { if (renderMode != GL2.GL_FILL && diff --git a/GUI/src/main/java/cz/fidentis/analyst/scene/DrawableFeaturePoints.java b/GUI/src/main/java/cz/fidentis/analyst/scene/DrawableFeaturePoints.java index d17f2c3936988a3ecc6e1bcad3fac2076849be04..3b9b93bc0aa94e658132cfff0613417203dc06ff 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/scene/DrawableFeaturePoints.java +++ b/GUI/src/main/java/cz/fidentis/analyst/scene/DrawableFeaturePoints.java @@ -36,6 +36,11 @@ public class DrawableFeaturePoints extends Drawable { */ private final Map<Integer, Double> specialSizes = new HashMap<>(); + /** + * feature points rendered differently than the default render mode + */ + private final Map<Integer, Integer> specialRenderModes = new HashMap<>(); + /** * Constructor. * @@ -145,6 +150,65 @@ public class DrawableFeaturePoints extends Drawable { public void resetAllSizesToDefault() { specialSizes.clear(); } + + /** + * Returns render mode of the feature point. + * + * @param index Index of the feature point + * @return The render mode or {@code null} + */ + public Integer getRenderMode(int index) { + if (index < 0 || index >= featurePoints.size()) { + return null; + } + if (specialRenderModes.containsKey(index)) { + return specialRenderModes.get(index); + } else { + return getRenderMode(); + } + } + + /** + * Sets render mode of the feature point. + * The render mode must be {@link GL2#GL_FILL}, {@link GL2#GL_LINE} + * or {@link GL2#GL_POINT}. + * + * @param index Index of the feature point + * @param renderMode New render mode of the feature point + * @throws IllegalArgumentException if renderMode isn't {@code GL_FILL}, + * {@code GL_LINE} or {@code GL_POINT} + */ + public void setRenderMode(int index, int renderMode) { + if (renderMode != GL2.GL_FILL && + renderMode != GL2.GL_LINE && + renderMode != GL2.GL_POINT) { + throw new IllegalArgumentException("renderMode"); + } + if (index < 0 || index >= featurePoints.size()) { + return; + } + if (renderMode == getRenderMode()) { + specialRenderModes.remove(index); + } else { + specialRenderModes.put(index, renderMode); + } + } + + /** + * Removes (possible) special render mode of the feature point. + * + * @param index Index of the feature point + */ + public void resetRenderModeToDefault(int index) { + setRenderMode(index, getRenderMode()); + } + + /** + * Removes all individual render modes of feature points. + */ + public void resetAllRenderModesToDefault() { + specialRenderModes.clear(); + } @Override protected void renderObject(GL2 gl) { @@ -158,12 +222,18 @@ public class DrawableFeaturePoints extends Drawable { for (int i = 0; i < featurePoints.size(); i++) { FeaturePoint fp = featurePoints.get(i); + Color specialColor = specialColors.get(i); if (specialColor != null) { float[] tmpRGB = {specialColor.getRed() / 255f, specialColor.getGreen() / 255f, specialColor.getBlue() / 255f, getTransparency()}; gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, tmpRGB, 0); } + Integer specialRenderMode = specialRenderModes.get(i); + if (specialRenderMode != null) { + gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, specialRenderMode); + } + gl.glPushMatrix(); gl.glTranslated(fp.getX(), fp.getY(), fp.getZ()); GLUquadric center = GLU_CONTEXT.gluNewQuadric(); @@ -177,6 +247,9 @@ public class DrawableFeaturePoints extends Drawable { if (specialColor != null) { gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, rgba, 0); // set default color } + if (specialRenderMode != null) { + gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, getRenderMode()); // set default render mode + } } } diff --git a/GUI/src/main/java/cz/fidentis/analyst/scene/Scene.java b/GUI/src/main/java/cz/fidentis/analyst/scene/Scene.java index ab19624d5e94aab63bc1796d6c4d606ea7a8c86d..7f14cee8e275a139ba89f18b008486b629e2a0ed 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/scene/Scene.java +++ b/GUI/src/main/java/cz/fidentis/analyst/scene/Scene.java @@ -16,6 +16,7 @@ public class Scene { private final List<DrawableFace> drawableFaces = new ArrayList<>(); private final List<DrawableFeaturePoints> drawableFeaturePoints = new ArrayList<>(); private final List<DrawablePlane> drawableSymmetryPlanes = new ArrayList<>(); + private final List<Drawable> otherDrawables = new ArrayList<>(); /** * Constructor for single face analysis. @@ -153,6 +154,20 @@ public class Scene { } } + /** + * Adds new drawable object to the scene. + * + * <p> + * Note: This drawable object is ignored in the {@link setDefaultColors} method. + * The caller must take care of the colors of the object themself. + * </p> + * + * @param otherDrawable Drawable object to be added to the scene + */ + public void addOtherDrawable(Drawable otherDrawable) { + otherDrawables.add(otherDrawable); + } + /** * Returns all drawable objects. * @@ -163,6 +178,7 @@ public class Scene { ret.addAll(this.drawableFaces); ret.addAll(this.drawableFeaturePoints); ret.addAll(this.drawableSymmetryPlanes); + ret.addAll(this.otherDrawables); while (ret.remove(null)) {} return ret; } diff --git a/GUI/src/main/java/cz/fidentis/analyst/toolbar/FaceToFaceToolBar.java b/GUI/src/main/java/cz/fidentis/analyst/toolbar/FaceToFaceToolBar.java index 9df7b46ba231f59ed54401e0ea5db6a08a0f2ef6..337bfc75fc04bfa128f9244e2fe78541f47b9598 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/toolbar/FaceToFaceToolBar.java +++ b/GUI/src/main/java/cz/fidentis/analyst/toolbar/FaceToFaceToolBar.java @@ -1,19 +1,15 @@ package cz.fidentis.analyst.toolbar; import cz.fidentis.analyst.canvas.Canvas; -import cz.fidentis.analyst.core.ControlPanel; import cz.fidentis.analyst.distance.DistanceAction; -import cz.fidentis.analyst.distance.DistancePanel; import cz.fidentis.analyst.registration.RegistrationAction; -import cz.fidentis.analyst.registration.RegistrationPanel; import javax.swing.JTabbedPane; -import javax.swing.JToggleButton; -import org.openide.util.NbBundle; /** * A toolbar extension for the analysis of two faces. * * @author Radek Oslejsek + * @author Daniel Schramm */ public class FaceToFaceToolBar extends RenderingToolBar { @@ -27,28 +23,14 @@ public class FaceToFaceToolBar extends RenderingToolBar { super(canvas); addPrimaryFaceButton(); addSecondaryFaceButton(); - addSeparator(); - addDistanceButton(controlPanel); - addRegistrationButton(controlPanel); - } - - private void addDistanceButton(JTabbedPane controlPanel) { - JToggleButton button = new JToggleButton(); - button.addActionListener(new DistanceAction(getCanvas(), controlPanel)); - button.setActionCommand(ControlPanel.ACTION_COMMAND_SHOW_HIDE_PANEL); - button.setIcon(DistancePanel.getStaticIcon()); - button.setToolTipText(NbBundle.getMessage(RenderingToolBar.class, "FaceToFaceToolBar.distance.text")); - add(button); - button.doClick(); // show - } - - private void addRegistrationButton(JTabbedPane controlPanel) { - JToggleButton button = new JToggleButton(); - button.addActionListener(new RegistrationAction(getCanvas(), controlPanel)); - button.setActionCommand(ControlPanel.ACTION_COMMAND_SHOW_HIDE_PANEL); - button.setIcon(RegistrationPanel.getStaticIcon()); - button.setToolTipText(NbBundle.getMessage(RenderingToolBar.class, "FaceToFaceToolBar.registration.text")); - add(button); - button.doClick(); // show + + // (Re)render scene after all change listeners have been called + // (the first added listener is called last) + controlPanel.addChangeListener(e -> getCanvas().renderScene()); + + // Add distance panel to the control panel + new DistanceAction(getCanvas(), controlPanel); + // Add registration panel to the control panel + new RegistrationAction(getCanvas(), controlPanel); } } diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java index 9176377791fcd07abdc079952050863843ef8d4a..3e0328d96ec801a9583918c2cfc9fdb79aa92671 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java @@ -31,9 +31,7 @@ public class MeshTriangle implements Iterable<MeshPoint> { /** * Creates new triangle * - * @param v1 first vertex - * @param v2 second vertex - * @param v3 third vertex + * @param facet Mesh facet * @param i1 which index is the first vertex stored in the mesh facet * @param i2 which index is the second vertex stored in the mesh facet * @param i3 which index is the third vertex stored in the mesh facet