Commit 052d263d authored by Jan Smid's avatar Jan Smid
Browse files

[multi_face_heatmap] Moves multi face heatmap to batch processing

parent 340188d0
Loading
Loading
Loading
Loading
+1 −68
Original line number Diff line number Diff line
@@ -318,34 +318,23 @@ public class ProjectPanel extends javax.swing.JPanel {
        if (sel == 0) {
            singleAnalysisButton.setEnabled(false);
            pairAnalysisButton.setEnabled(false);
            if (multiComparisonButton != null)
                multiComparisonButton.setEnabled(false);
            batchAnalysisButton.setEnabled(false);
        } else if (sel == 1) {
            singleAnalysisButton.setEnabled(true);
            pairAnalysisButton.setEnabled(false);
            if (multiComparisonButton != null)
                multiComparisonButton.setEnabled(false);
            batchAnalysisButton.setEnabled(false);
        } else if (sel == 2) {
            singleAnalysisButton.setEnabled(true);
            pairAnalysisButton.setEnabled(true);
            if (multiComparisonButton != null)
                multiComparisonButton.setEnabled(false);
            batchAnalysisButton.setEnabled(false);
        } else if (sel > 2) {
            singleAnalysisButton.setEnabled(true);
            pairAnalysisButton.setEnabled(false);
            if (multiComparisonButton != null)
                multiComparisonButton.setEnabled(true);
            batchAnalysisButton.setEnabled(true);
        }

        singleAnalysisButton.setFont(singleAnalysisButton.isEnabled() ? fontOn : fontOff);
        pairAnalysisButton.setFont(pairAnalysisButton.isEnabled() ? fontOn : fontOff);
        if (multiComparisonButton != null) {
            multiComparisonButton.setFont(multiComparisonButton.isEnabled() ? fontOn : fontOff);
        }
        batchAnalysisButton.setFont(batchAnalysisButton.isEnabled() ? fontOn : fontOff);
    }
    
@@ -356,26 +345,6 @@ public class ProjectPanel extends javax.swing.JPanel {
                .collect(Collectors.toList());
    }

    private int showTemplateSelectionDialog(List<FaceReference> faces) {
        String[] names = faces.stream().map(FaceReference::getName).toArray(String[]::new);
        JList<String> list = new JList<>(names);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setVisibleRowCount(Math.min(10, names.length));
        list.setSelectedIndex(0);
        JScrollPane scrollPane = new JScrollPane(list);
        scrollPane.setPreferredSize(new Dimension(250, Math.min(300, names.length * 20)));
        int result = JOptionPane.showConfirmDialog(
                this,
                scrollPane,
                "Select template face",
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.PLAIN_MESSAGE);
        if (result == JOptionPane.OK_OPTION) {
            return list.getSelectedIndex();
        }
        return -1;
    }
    
    private void initListeners() {
        addButton.addActionListener((ActionEvent e) -> { 
            addFacesToProject();
@@ -448,32 +417,6 @@ public class ProjectPanel extends javax.swing.JPanel {
            openTaskTab(task);
        });

        
        //table.addTableModelListener((TableModelEvent e) -> {
        //});

        multiComparisonButton.addActionListener((ActionEvent e) -> {
            List<FaceReference> selected = getSelectedFaces();
            if (selected.size() < 3) {
                JOptionPane.showMessageDialog(this, "Select at least 3 faces for multi comparison.");
                return;
            }
            int choice = showTemplateSelectionDialog(selected);
            if (choice < 0) {
                return;
            }

            List<FaceReference> ordered = new ArrayList<>(selected.size());
            ordered.add(selected.get(choice));
            for (int i = 0; i < selected.size(); i++) {
                if (i == choice) {
                    continue;
                }
                ordered.add(selected.get(i));
            }
            Task task = taskService.createTask(ordered, TaskType.MULTI_COMPARISON);
            openTaskTab(task);
        });
        projectTable1.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
            if (!e.getValueIsAdjusting()) {
                actionListeners.forEach(action -> action.actionPerformed(new ActionEvent(
@@ -523,7 +466,6 @@ public class ProjectPanel extends javax.swing.JPanel {
        jSeparator1 = new javax.swing.JToolBar.Separator();
        singleAnalysisButton = new javax.swing.JButton();
        pairAnalysisButton = new javax.swing.JButton();
        multiComparisonButton = new javax.swing.JButton();
        batchAnalysisButton = new javax.swing.JButton();

        jScrollPane1.setViewportView(projectTable1);
@@ -578,14 +520,6 @@ public class ProjectPanel extends javax.swing.JPanel {
        pairAnalysisButton.setFocusable(false);
        jToolBar1.add(pairAnalysisButton);

        multiComparisonButton = new javax.swing.JButton();
        multiComparisonButton.setFont(new java.awt.Font("Ubuntu", 1, 15)); // NOI18N
        multiComparisonButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/analysis24.png"))); // NOI18N
        multiComparisonButton.setText("multi comparison");
        multiComparisonButton.setToolTipText("Compare 3+ faces");
        multiComparisonButton.setFocusable(false);
        jToolBar1.add(multiComparisonButton);

        batchAnalysisButton.setFont(new java.awt.Font("Ubuntu", 1, 15)); // NOI18N
        batchAnalysisButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/analysis24.png"))); // NOI18N
        batchAnalysisButton.setText("batch processing");
@@ -633,7 +567,6 @@ public class ProjectPanel extends javax.swing.JPanel {
    private javax.swing.JToolBar.Separator jSeparator1;
    private javax.swing.JToolBar.Separator jSeparator2;
    private javax.swing.JToolBar jToolBar1;
    private javax.swing.JButton multiComparisonButton;
    private javax.swing.JButton pairAnalysisButton;
    private cz.fidentis.analyst.gui.project.table.ProjectTable projectTable1;
    private javax.swing.JButton removeButton;
+8 −13
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ import cz.fidentis.analyst.gui.project.ProjectPanel;
import cz.fidentis.analyst.gui.app.SwingTopComponent;
import cz.fidentis.analyst.gui.task.batch.distance.BatchDistanceAction;
import cz.fidentis.analyst.gui.task.batch.distanceheatmap.BatchDistanceHeatmapAction;
import cz.fidentis.analyst.gui.task.batch.visualization.BatchVisualizationAction;
import cz.fidentis.analyst.gui.task.batch.registration.BatchRegistrationAction;
import cz.fidentis.analyst.gui.task.curvature.CurvatureAction;
import cz.fidentis.analyst.gui.task.distance.DistanceAction;
@@ -21,7 +22,6 @@ import cz.fidentis.analyst.gui.task.masks.InteractiveMaskAction;
import cz.fidentis.analyst.gui.task.registration.RegistrationAction;
import cz.fidentis.analyst.gui.task.symmetry.CuttingPlanesAction;
import cz.fidentis.analyst.gui.task.symmetry.SymmetryAction;
import cz.fidentis.analyst.gui.task.multicomparison.MultiComparisonAction;
import cz.fidentis.analyst.project.Task;
import cz.fidentis.analyst.project.TaskService;
import cz.fidentis.analyst.project.TaskType;
@@ -30,7 +30,6 @@ import cz.fidentis.analyst.rendering.Scene;
import cz.fidentis.analyst.toolbar.BatchToolbar;
import cz.fidentis.analyst.toolbar.DoubleFaceToolbar;
import cz.fidentis.analyst.toolbar.SingleFaceToolbar;
import cz.fidentis.analyst.toolbar.MultiComparisonToolbar;

import javax.swing.*;
import java.awt.event.ActionEvent;
@@ -214,8 +213,7 @@ public class TaskWindow extends JComponent {

    private void initCanvas() {
        if (task.getType() == TaskType.SINGLE_FACE_ANALYSIS
                || task.getType() == TaskType.PAIR_COMPARISON
                || task.getType() == TaskType.MULTI_COMPARISON) { // initialize primary face
                || task.getType() == TaskType.PAIR_COMPARISON) { // initialize primary face
            Scene scene = canvas.getScene();
            int index = scene.getFreeSlotForFace();
            FaceReference primary = taskService.getPrimaryFace(task);
@@ -246,9 +244,6 @@ public class TaskWindow extends JComponent {
            case PAIR_COMPARISON:
                canvas.addToolBox(new DoubleFaceToolbar(humanFacesEventBusService, canvas, task));
                break;
            case MULTI_COMPARISON:
                canvas.addToolBox(new MultiComparisonToolbar(humanFacesEventBusService, canvas, task));
                break;
            case BATCH_PROCESSING:
                canvas.addToolBox(new BatchToolbar(humanFacesEventBusService, canvas, task));
                break;
@@ -306,17 +301,17 @@ public class TaskWindow extends JComponent {
                new FaceOverviewAction(humanFacesEventBusService, getCanvas(), task, controlPanel, 1);
                //new InteractiveMaskAction(getCanvas(), faces, controlPanel);
                break;
            case MULTI_COMPARISON:
                new MultiComparisonAction(faceService, humanFacesEventBusService, canvas, task, controlPanel).popup();
                break;
            case BATCH_PROCESSING: // batch mode
                new BatchRegistrationAction(canvas, task, controlPanel);
                BatchDistanceAction distanceAction = new BatchDistanceAction(canvas, task, controlPanel);
                BatchDistanceHeatmapAction heatmapAction = new BatchDistanceHeatmapAction(projectPanel, taskService, canvas, task, controlPanel);
                BatchDistanceHeatmapAction clusteringAction = new BatchDistanceHeatmapAction(projectPanel, taskService, canvas, task, controlPanel);
                BatchVisualizationAction visualizationAction = new BatchVisualizationAction(faceService, humanFacesEventBusService, canvas, task, controlPanel);
                //new BatchCuttingPlanesAction(canvas, batchFacesProxy, controlPanel);

                distanceAction.addRegistrationListener(heatmapAction);
                distanceAction.addDistanceListener(heatmapAction);
                distanceAction.addRegistrationListener(clusteringAction);
                distanceAction.addDistanceListener(clusteringAction);
                distanceAction.addRegistrationListener(visualizationAction);
                distanceAction.addDistanceListener(visualizationAction);

                distanceAction.showFace();
                break;
+1 −1
Original line number Diff line number Diff line
@@ -53,7 +53,7 @@ public class BatchDistanceHeatmapPanel extends ControlPanel implements BatchCont
    public static final String DIVISIVE_STRATEGY = "Divisive strategy";

    public static final String ICON = "data_visualization_icon.png";
    public static final String NAME = "Visualization";
    public static final String NAME = "Clustering";

    public static final String HELP_URL = "https://gitlab.fi.muni.cz/grp-fidentis/analyst2/-/wikis/batch-visualization";
    public static final int BOX_GLUE = 5;
+2 −0
Original line number Diff line number Diff line
@@ -77,6 +77,8 @@ public abstract class BatchRegistrationTask extends SwingWorker<MeshModel, Store
            if (isCancelled()) {
                return;
            }
            // Persist the registered face
            faceService.updateHumanFace(f);
            if (faceSceneSlot == -1) {
                faceSceneSlot = canvas.getScene().getFreeSlotForFace();
            }
+297 −0
Original line number Diff line number Diff line
package cz.fidentis.analyst.gui.task.batch.visualization;

import cz.fidentis.analyst.canvas.Canvas;
import cz.fidentis.analyst.data.face.FaceReference;
import cz.fidentis.analyst.data.face.FaceService;
import cz.fidentis.analyst.data.face.HumanFace;
import cz.fidentis.analyst.data.face.HumanFaceEvent;
import cz.fidentis.analyst.data.face.HumanFaceListener;
import cz.fidentis.analyst.data.face.HumanFacesEventBusService;
import cz.fidentis.analyst.data.face.StoredHumanFace;
import cz.fidentis.analyst.data.shapes.HeatMap3D;
import cz.fidentis.analyst.drawables.DrawableFace;
import cz.fidentis.analyst.engines.distance.MeshDistanceConfig;
import cz.fidentis.analyst.gui.elements.ProgressDialog;
import cz.fidentis.analyst.gui.task.ControlPanelAction;
import cz.fidentis.analyst.gui.task.batch.distance.BatchDistanceListener;
import cz.fidentis.analyst.gui.task.multicomparison.computation.VariationMetric;
import cz.fidentis.analyst.gui.task.multicomparison.task.VariationComputationTask;
import cz.fidentis.analyst.project.Task;
import cz.fidentis.analyst.rendering.Scene;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;

/**
 * Action for batch visualization panel with overlay and heatmap.
 *
 * @author Jan Smid
 */
public class BatchVisualizationAction
        extends ControlPanelAction<BatchVisualizationPanel>
        implements BatchDistanceListener, HumanFaceListener {

    private static final Logger LOGGER =
            LoggerFactory.getLogger(BatchVisualizationAction.class);

    private final FaceService faceService;
    private final HumanFacesEventBusService humanFacesEventBusService;

    private StoredHumanFace templateFace;
    private HeatMap3D cachedHeatmap;
    private VariationMetric cachedMetric;
    private VariationComputationTask variationTask;
    private boolean distanceComputed = false;

    /**
     * Constructor.
     *
     * @param faceService face service
     * @param humanFacesEventBusService event bus service
     * @param canvas the rendering canvas
     * @param task the batch task
     * @param topControlPane the control panel container
     */
    public BatchVisualizationAction(
            final FaceService faceService,
            final HumanFacesEventBusService humanFacesEventBusService,
            final Canvas canvas,
            final Task task,
            final JTabbedPane topControlPane) {
        super(canvas, task, topControlPane);
        this.faceService = faceService;
        this.humanFacesEventBusService = humanFacesEventBusService;
        setControlPanel(new BatchVisualizationPanel(this));
        setShowHideCode(this::showVisualization, this::hideVisualization);
    }

    @Override
    public void acceptEvent(final HumanFaceEvent event) {
        // Nothing to do
    }

    @Override
    public void registrationCompleted(final StoredHumanFace face) {
        this.templateFace = face;
        invalidateCache();
    }

    @Override
    public void symmetryCompleted(final double[][] distances) {
        if (distances != null) {
            distanceComputed = true;
            getControlPanel().setHeatmapAvailable(true);
        }
    }

    @Override
    public void actionPerformed(final ActionEvent e) {
        String command = e.getActionCommand();

        if (BatchVisualizationPanel.ACTION_MODE_CHANGED.equals(command)) {
            handleModeChanged();
        } else if (BatchVisualizationPanel.ACTION_METRIC_CHANGED.equals(command)) {
            handleMetricChanged();
        }
    }

    private void handleModeChanged() {
        if (getControlPanel().isHeatmapSelected() && canShowHeatmap()) {
            showHeatmap();
        } else {
            showOverlay();
        }
    }

    private void handleMetricChanged() {
        if (getControlPanel().isHeatmapSelected()) {
            invalidateCache();
            showHeatmap();
        }
    }

    private void showVisualization() {
        SwingUtilities.invokeLater(() -> {
            if (getControlPanel().isHeatmapSelected() && canShowHeatmap()) {
                showHeatmap();
            } else {
                showOverlay();
            }
        });
    }

    private boolean canShowHeatmap() {
        return distanceComputed && templateFace != null;
    }

    private void hideVisualization() {
        Scene scene = getCanvas().getScene();
        for (Integer slot : scene.getFaceSlots()) {
            scene.showDrawableFace(slot, false);
        }
    }

    private void showOverlay() {
        clearHeatmap();
        loadAllFaces();
        renderScene();
    }

    private void showHeatmap() {
        if (!distanceComputed || templateFace == null) {
            return;
        }

        VariationMetric metric = getControlPanel().getSelectedMetric();
        if (cachedHeatmap != null && metric == cachedMetric) {
            applyHeatmap(cachedHeatmap);
            return;
        }

        computeHeatmap(metric);
    }

    private void computeHeatmap(final VariationMetric metric) {
        HumanFace template = templateFace.getHumanFace();
        if (template == null) {
            return;
        }

        ProgressDialog<HeatMap3D, Void> progressDialog =
                new ProgressDialog<>(getControlPanel(),
                        "Computing " + metric.getDisplayName());

        variationTask = new VariationComputationTask(
                faceService,
                template,
                templateFace.getId(),
                new ArrayList<>(getTask().getFaces()),
                progressDialog,
                MeshDistanceConfig.Method.POINT_TO_POINT_NEAREST_NEIGHBORS,
                metric);

        progressDialog.setOnCancel(() -> variationTask.cancel(true));

        variationTask.addPropertyChangeListener(evt -> {
            if ("progress".equals(evt.getPropertyName())) {
                Object value = evt.getNewValue();
                if (value instanceof Integer progress) {
                    progressDialog.setValue(progress);
                }
            } else if ("state".equals(evt.getPropertyName())) {
                if (SwingWorker.StateValue.DONE.equals(evt.getNewValue())) {
                    handleComputationComplete(metric);
                }
            }
        });

        progressDialog.runTask(variationTask);
    }

    private void handleComputationComplete(final VariationMetric metric) {
        VariationComputationTask task = variationTask;
        variationTask = null;

        if (task == null) {
            return;
        }

        try {
            HeatMap3D heatmap = task.get();
            if (heatmap != null) {
                cachedHeatmap = heatmap;
                cachedMetric = metric;
                applyHeatmap(heatmap);
            }
        } catch (CancellationException ex) {
            LOGGER.info("Heatmap computation cancelled");
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        } catch (ExecutionException ex) {
            LOGGER.error("Heatmap computation failed", ex.getCause());
        }
    }

    private void applyHeatmap(final HeatMap3D heatmap) {
        Scene scene = getCanvas().getScene();
        scene.clearScene();

        if (templateFace == null) {
            return;
        }

        int slot = scene.getFreeSlotForFace();
        if (slot == -1) {
            return;
        }

        scene.setHumanFace(slot, templateFace);
        scene.setFaceAsPrimary(slot);

        DrawableFace drawable = scene.getDrawableFace(slot);
        if (drawable != null) {
            drawable.setHeatMap(heatmap);
            drawable.show(true);
        }

        humanFacesEventBusService.registerListener(templateFace.getId(), getCanvas());

        getCanvas().getCamera().zoomToFit(scene);
        renderScene();
    }

    private void clearHeatmap() {
        Scene scene = getCanvas().getScene();
        for (Integer slot : scene.getFaceSlots()) {
            DrawableFace face = scene.getDrawableFace(slot);
            if (face != null) {
                face.setHeatMap(null);
            }
        }
    }

    private void loadAllFaces() {
        Scene scene = getCanvas().getScene();
        scene.clearScene();

        int primarySlot = -1;
        int secondarySlot = -1;

        for (FaceReference reference : getTask().getFaces()) {
            int slot = scene.getFreeSlotForFace();
            if (slot == -1) {
                break;
            }
            StoredHumanFace storedFace = faceService.getFaceByReference(reference);
            scene.setHumanFace(slot, storedFace);
            if (primarySlot == -1) {
                primarySlot = slot;
            } else if (secondarySlot == -1) {
                secondarySlot = slot;
            }
            humanFacesEventBusService.registerListener(reference.getId(), getCanvas());
        }

        if (primarySlot != -1) {
            scene.setFaceAsPrimary(primarySlot);
        }
        if (secondarySlot != -1) {
            scene.setFaceAsSecondary(secondarySlot);
        }

        scene.setDefaultColors();
        getCanvas().getCamera().zoomToFit(scene);
    }

    private void invalidateCache() {
        cachedHeatmap = null;
        cachedMetric = null;
    }
}
Loading