Newer
Older
package cz.fidentis.analyst.distance;
import com.jogamp.opengl.GL2;

Radek Ošlejšek
committed
import cz.fidentis.analyst.Logger;
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;
import cz.fidentis.analyst.scene.DrawableFeaturePoints;
import cz.fidentis.analyst.visitors.face.HausdorffDistancePrioritized;
import cz.fidentis.analyst.visitors.mesh.HausdorffDistance.Strategy;
import java.awt.event.ActionEvent;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.swing.JComboBox;
import javax.swing.JTabbedPane;
import javax.swing.JToggleButton;
import javax.vecmath.Point3d;
* Action listener for computation of the Hausdorff distance.

Daniel Schramm
committed
* @author Daniel Schramm
public class DistanceAction extends ControlPanelAction {
/*
* Attributes handling the state
*/
private HausdorffDistancePrioritized visitor = null;
private final Map<FeaturePointType, Double> featurePointTypes;
private String strategy = DistancePanel.STRATEGY_POINT_TO_POINT;
private boolean relativeDist = false;
private String heatmapDisplayed = DistancePanel.HEATMAP_HAUSDORFF_DISTANCE;
private boolean weightedFPsShow = true;
private FeaturePointType hoveredFeaturePoint = null;
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.
*
* @param canvas OpenGL canvas
* @param topControlPanel Top component for placing control panels
public DistanceAction(Canvas canvas, JTabbedPane topControlPanel) {
super(canvas, topControlPanel);
// 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(Double::doubleValue)
.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 (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(isHeatmapDisplayed());
getSecondaryDrawableFace().setRenderHeatmap(false);
weightedFeaturePoints.hide();
getSecondaryDrawableFace().clearHeatMapSaturation();
}
});
topControlPanel.setSelectedComponent(controlPanel); // Focus Hausdorff distance panel
}
@Override
public void actionPerformed(ActionEvent ae) {
final String action = ae.getActionCommand();
case DistancePanel.ACTION_COMMAND_SET_DISPLAYED_HEATMAP:
heatmapDisplayed = (String) ((JComboBox) ae.getSource()).getSelectedItem();
updateHausdorffDistanceInformation();
getSecondaryDrawableFace().setRenderHeatmap(isHeatmapDisplayed());
case DistancePanel.ACTION_COMMAND_SHOW_HIDE_WEIGHTED_FPOINTS:
weightedFPsShow = ((JToggleButton) ae.getSource()).isSelected();
if (weightedFPsShow) {
weightedFeaturePoints.show();
} else {
weightedFeaturePoints.hide();
}
break;
case DistancePanel.ACTION_COMMAND_SET_DISTANCE_STRATEGY:
strategy = (String) ((JComboBox) ae.getSource()).getSelectedItem();
recompute();
case DistancePanel.ACTION_COMMAND_RELATIVE_ABSOLUTE_DIST:
this.relativeDist = ((JToggleButton) ae.getSource()).isSelected();
recompute();
case DistancePanel.ACTION_COMMAND_FEATURE_POINT_HIGHLIGHT:
highlightFeaturePoint((LoadedActionEvent) ae);
recompute();
case DistancePanel.ACTION_COMMAND_FEATURE_POINT_SELECT_ALL:
highlightAllFeaturePoints(true);
recompute();
break;
case DistancePanel.ACTION_COMMAND_FEATURE_POINT_SELECT_NONE:
highlightAllFeaturePoints(false);
recompute();
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_DISTANCE_RECOMPUTE:
recompute();

Daniel Schramm
committed
break;
/**
* 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}.
*/
private void updateHausdorffDistanceInformation() {
if (visitor == null) {
calculateHausdorffDistance(featurePointTypes);
// Update GUI elements that display the calculated Hausdorff distance metrics
setFeaturePointWeigths();
setHausdorffDistanceStatistics();
}
switch (heatmapDisplayed) {
case DistancePanel.HEATMAP_HAUSDORFF_DISTANCE:
getSecondaryDrawableFace().clearHeatMapSaturation();
break;
case DistancePanel.HEATMAP_WEIGHTED_HAUSDORFF_DISTANCE:
getSecondaryDrawableFace().setHeatMapSaturation(
visitor.getMergedPriorities()
.get(getSecondaryDrawableFace().getHumanFace())
);
break;
case DistancePanel.HEATMAP_HIDE:
return;
default:
throw new UnsupportedOperationException(heatmapDisplayed);
}
getSecondaryDrawableFace().setHeatMap(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:
useStrategy = Strategy.POINT_TO_POINT;
break;
case DistancePanel.STRATEGY_POINT_TO_TRIANGLE:
useStrategy = Strategy.POINT_TO_TRIANGLE_APPROXIMATE;
break;
default:
throw new UnsupportedOperationException(strategy);
}

Radek Ošlejšek
committed
Logger out = Logger.measureTime();
this.visitor = new HausdorffDistancePrioritized(getPrimaryDrawableFace().getModel(),
featurePoints,
useStrategy,
relativeDist,
true);
getSecondaryDrawableFace().getHumanFace().accept(visitor);
out.printDuration("Computation of Hausdorff distance for models with "
+ getPrimaryDrawableFace().getHumanFace().getMeshModel().getNumVertices()
+ "/"
+ getSecondaryDrawableFace().getHumanFace().getMeshModel().getNumVertices()
+ " vertices"
);
}
/**
* Updates the GUI elements of {@link DistancePanel} that display
* the weights of feature points used to calculate the weighted Hausdorff distance.
*/
private void setFeaturePointWeigths() {
controlPanel.updateFeaturePointWeights(
visitor.getFeaturePointWeights()
.get(getSecondaryDrawableFace().getHumanFace()) // Get FP weights for the secondary face
.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey, // For each FP type at the secondary face...
weights -> weights.getValue() // ... compute average FP weight over all its facets
.values()
.stream()
.mapToDouble(Double::doubleValue)

Daniel Schramm
committed
/**
* 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(Double::doubleValue)

Daniel Schramm
committed
.summaryStatistics(),

Daniel Schramm
committed
.values()
.stream()
.flatMap(List::stream)
.mapToDouble(Double::doubleValue)

Daniel Schramm
committed
.summaryStatistics()
);
}
/**
* Calculates weighted Hausdorff distance of the face.
*
* @return weighted Hausdorff distance
*/
private Map<MeshFacet, List<Double>> getWeightedDistance() {
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(),
facetPriorities.getValue(),
(distancesList, prioritiesList) ->
IntStream.range(0, distancesList.size())
.mapToDouble(i -> distancesList.get(i) * prioritiesList.get(i))
.boxed()
.collect(Collectors.toList()));
}
return weightedDistances;
}
/**
* 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
* 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
*/
private void highlightFeaturePoint(LoadedActionEvent actionEvent) {
final int index = (int) actionEvent.getData();
final FeaturePointType fpType = getTypeOfFeaturePoint(index);
if (((JToggleButton) actionEvent.getSource()).isSelected()) {
colorSecondaryFaceFeaturePoint(index, FEATURE_POINT_HIGHLIGHT_COLOR);
featurePointTypes.put(fpType, weightedFeaturePoints.getSize(index));
resetSecondaryFaceFeaturePointColor(index);
featurePointTypes.remove(fpType);
}
}
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/**
* 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();
}
}
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/**
* 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.
* 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
*/
private void resizeFeaturePoint(LoadedActionEvent actionEvent) {
final int index = (int) actionEvent.getData();
final double size = ControlPanelBuilder.parseLocaleDouble((JTextField) actionEvent.getSource());
weightedFeaturePoints.setSize(index, size);
featurePointTypes.replace(getTypeOfFeaturePoint(index), size);
}
/**
* Returns type of the feature point at the given index in the secondary face.
*
* @param index Index of the feature point
* @return Type of the feature point or {@code null}
*/
private FeaturePointType getTypeOfFeaturePoint(int index) {
final List<FeaturePoint> featurePoints = getSecondaryFeaturePoints().getFeaturePoints();
if (index < 0 || index >= featurePoints.size()) {
return null;
}
return featurePoints.get(index)