org.openmicroscopy.shoola.agents.measurement.view.IntensityResultsView.java Source code

Java tutorial

Introduction

Here is the source code for org.openmicroscopy.shoola.agents.measurement.view.IntensityResultsView.java

Source

/*
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2014 University of Dundee. All rights reserved.
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */
package org.openmicroscopy.shoola.agents.measurement.view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import java.util.Map.Entry;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;

import org.apache.commons.collections.CollectionUtils;

import org.jhotdraw.draw.Figure;

import org.openmicroscopy.shoola.agents.measurement.IconManager;
import org.openmicroscopy.shoola.agents.measurement.MeasurementAgent;
import org.openmicroscopy.shoola.agents.measurement.util.TabPaneInterface;
import org.openmicroscopy.shoola.agents.measurement.util.model.AnalysisStatsWrapper;
import org.openmicroscopy.shoola.agents.measurement.util.model.AnnotationDescription;
import org.openmicroscopy.shoola.agents.measurement.util.model.AnalysisStatsWrapper.StatsType;
import org.openmicroscopy.shoola.env.config.Registry;
import omero.log.Logger;
import org.openmicroscopy.shoola.env.rnd.roi.ROIShapeStats;
import org.openmicroscopy.shoola.env.ui.UserNotifier;
import org.openmicroscopy.shoola.util.file.ExcelWriter;
import org.openmicroscopy.shoola.util.image.geom.Factory;
import org.openmicroscopy.shoola.util.roi.figures.MeasureTextFigure;
import org.openmicroscopy.shoola.util.roi.figures.ROIFigure;
import org.openmicroscopy.shoola.util.roi.model.ROIShape;
import org.openmicroscopy.shoola.util.roi.model.annotation.MeasurementAttributes;
import org.openmicroscopy.shoola.util.roi.model.util.Coord3D;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.filechooser.FileChooser;

import omero.gateway.model.ChannelData;

/** 
 * Displays the intensity results.
 *
 * @author Jean-Marie Burel     
 *    <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 *    <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since OME3.0
 */
class IntensityResultsView extends JPanel implements ActionListener, TabPaneInterface {

    /** Index to identify tab */
    public final static int INDEX = MeasurementViewerUI.INTENSITYRESULTVIEW_INDEX;

    /** The add button name. */
    private final static String ADD_NAME = "Add";

    /** Tooltip for the add button. */
    private final static String ADD_DESCRIPTION = "Add Intensities for " + "the selected ROIs to results table.";

    /** The addAll button name. */
    private final static String ADDALL_NAME = "Add Selected";

    /** Tooltip for the add button. */
    private final static String ADDALL_DESCRIPTION = "Add Intensities for "
            + "all the shapes of the selected ROIs to results table.";

    /** The remove button name. */
    private final static String REMOVE_NAME = "Remove";

    /** Tooltip for the remove button. */
    private final static String REMOVE_DESCRIPTION = "Remove Results in " + "selected row from table.";

    /** The remove button name. */
    private final static String REMOVE_ALL_NAME = "Remove All";

    /** Tooltip for the remove button. */
    private final static String REMOVE_ALL_DESCRIPTION = "Remove all the Results from table.";

    /** The save button name. */
    private final static String SAVE_NAME = "Export to Excel...";

    /** Tooltip for the save button. */
    private final static String SAVE_DESCRIPTION = "Save Intensities " + "to Excel File.";

    /** Reference to the view. */
    private MeasurementViewerUI view;

    /** The results table. */
    private JTable results;

    /** The results model for the results table. */
    private ResultsTableModel resultsModel;

    /** The remove button. */
    private JButton removeButton;

    /** The save button. */
    private JButton saveButton;

    /** The add button. */
    private JButton addButton;

    /** The addAll button. */
    private JButton addAllButton;

    /** The add button. */
    private JButton removeAllButton;

    /** The state of the Intensity View. */
    static enum State {
        /** Analysing data. */
        ANALYSING,
        /** Ready to analyse. */
        READY
    }

    /** 
     * Intensity view state, if Analysing we should not all the user to 
     * change combobox or save. 
     */
    private State state = State.READY;

    /** The name of the panel. */
    private static final String NAME = "Intensity Results View";

    /** Action command id indicating to remove the selected rows. */
    private static final int REMOVE = 0;

    /** Action command id indicating to add new row. */
    private static final int ADD = 1;

    /** Action command id indicating to add new row. */
    private static final int ADDALL = 4;

    /** Action command id indicating to remove all the rows. */
    private static final int REMOVE_ALL = 2;

    /** Action command id indicating to save the results. */
    private static final int SAVE = 3;

    /** Reference to the model. */
    private MeasurementViewerModel model;

    /** The map of <ROIShape, ROIStats> .*/
    private Map<ROIShape, Map<Integer, ROIShapeStats>> ROIStats;

    /** list of the channel names. */
    private Map<Integer, String> channelName = new TreeMap<Integer, String>();

    /** List of the channel colours. */
    private Map<Integer, Color> channelColour = new TreeMap<Integer, Color>();

    /** Map of the channel sums, for each selected channel. */
    private Map<Integer, Double> channelSum = new TreeMap<Integer, Double>();

    /** Map of the channel mins, for each selected channel. */
    private Map<Integer, Double> channelMin = new TreeMap<Integer, Double>();

    /** Map of the channel Max, for each selected channel. */
    private Map<Integer, Double> channelMax = new TreeMap<Integer, Double>();

    /** Map of the channel Mean, for each selected channel. */
    private Map<Integer, Double> channelMean = new TreeMap<Integer, Double>();

    /** Map of the channel std. dev., for each selected channel. */
    private Map<Integer, Double> channelStdDev = new TreeMap<Integer, Double>();

    /** Map of the channel name to channel number .*/
    private Map<String, Integer> nameMap = new HashMap<String, Integer>();

    /** Map of the min channel intensity values to coord. */
    private Map<Coord3D, Map<Integer, Double>> minStats;

    /** Map of the max channel intensity values to coord. */
    private Map<Coord3D, Map<Integer, Double>> maxStats;

    /** Map of the mean channel intensity values to coord. */
    private Map<Coord3D, Map<Integer, Double>> meanStats;

    /** Map of the std dev channel intensity values to coord. */
    private Map<Coord3D, Map<Integer, Double>> stdDevStats;

    /** Map of the sum channel intensity values to coord. */
    private Map<Coord3D, Map<Integer, Double>> sumStats;

    /** Map of the coordinate to a shape. */
    private Map<Coord3D, ROIShape> shapeMap;

    /** The current coordinate of the ROI being depicted in the slider. */
    private Coord3D coord;

    /** Current ROIShape. */
    private ROIShape shape;

    /**
     * Wraps an ROI shape such that its string representation shows its ID,
     * or <q>--</q> if it a client-side shape. This is used for the shape IDs
     * in the first column of the results model table so that it is easy to
     * query any row to discover to which ROI shape it pertains.
     */
    private static class ShapeAsID {
        private final ROIShape shape;
        private final String id;

        /**
         * Construct a new ShapeAsID instance.
         * @param shape the ROI shape that the instance is to wrap, may not be null
         */
        private ShapeAsID(ROIShape shape) {
            if (shape == null)
                throw new IllegalArgumentException(new NullPointerException());

            this.shape = shape;

            /* how the shape ID should display in the results model table */
            if (shape.getROI().isClientSide())
                this.id = "--";
            else
                this.id = Long.toString(shape.getROIShapeID());
        }

        /* a string suited for the ID column of the results model table */
        @Override
        public String toString() {
            return this.id;
        }

        /**
         * @return the wrapped ROI shape
         */
        private ROIShape getShape() {
            return this.shape;
        }
    }

    /**
     * Implemented as specified by the I/F {@link TabPaneInterface}
     * @see TabPaneInterface#getIndex()
     */
    public int getIndex() {
        return INDEX;
    }

    /** Initializes the component composing the display. */
    private void initComponents() {
        state = State.READY;
        removeButton = new JButton(REMOVE_NAME);
        removeButton.setToolTipText(UIUtilities.formatToolTipText(REMOVE_DESCRIPTION));
        removeButton.setActionCommand("" + REMOVE);
        removeButton.addActionListener(this);
        saveButton = new JButton(SAVE_NAME);
        saveButton.setToolTipText(UIUtilities.formatToolTipText(SAVE_DESCRIPTION));
        saveButton.setActionCommand("" + SAVE);
        saveButton.addActionListener(this);
        setButtonsEnabled(false);
        addButton = new JButton(ADD_NAME);
        addButton.setToolTipText(UIUtilities.formatToolTipText(ADD_DESCRIPTION));
        addButton.setActionCommand("" + ADD);
        addButton.addActionListener(this);
        addAllButton = new JButton(ADDALL_NAME);
        addAllButton.setToolTipText(UIUtilities.formatToolTipText(ADDALL_DESCRIPTION));
        addAllButton.setActionCommand("" + ADDALL);
        addAllButton.addActionListener(this);
        removeAllButton = new JButton(REMOVE_ALL_NAME);
        removeAllButton.setToolTipText(UIUtilities.formatToolTipText(REMOVE_ALL_DESCRIPTION));
        removeAllButton.setActionCommand("" + REMOVE_ALL);
        removeAllButton.addActionListener(this);
        addAllButton.setEnabled(false);
        removeAllButton.setEnabled(false);
        removeButton.setEnabled(false);
    }

    /**
     * Sets the <code>enabled</code> flag of the {@link #saveButton}.
     * 
     * @param enabled The value to set.
     */
    private void setButtonsEnabled(boolean enabled) {
        saveButton.setEnabled(enabled);
    }

    /** Builds and lays out the UI. */
    private void buildGUI() {
        resultsModel = new ResultsTableModel();
        resultsModel.addColumn(AnnotationDescription.ROIID_STRING);
        resultsModel.addColumn("Z");
        resultsModel.addColumn("T");
        resultsModel.addColumn("Channel");
        resultsModel.addColumn("Text");
        resultsModel.addColumn("Min");
        resultsModel.addColumn("Max");
        resultsModel.addColumn("Sum");
        resultsModel.addColumn("Mean");
        resultsModel.addColumn("stdDev");
        results = new JTable(resultsModel);
        results.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                onFigureSelected();
            }
        });
        JPanel centrePanel = new JPanel();
        centrePanel.setLayout(new BorderLayout());
        JScrollPane scrollPane = new JScrollPane(results);
        centrePanel.add(scrollPane, BorderLayout.CENTER);
        JPanel bottomPanel = new JPanel();
        bottomPanel.setLayout(new FlowLayout());
        //bottomPanel.add(addButton);
        bottomPanel.add(addAllButton);
        bottomPanel.add(removeButton);
        bottomPanel.add(removeAllButton);
        bottomPanel.add(saveButton);
        JPanel containerPanel = new JPanel();
        containerPanel.setLayout(new BorderLayout());
        containerPanel.add(centrePanel, BorderLayout.CENTER);
        containerPanel.add(bottomPanel, BorderLayout.SOUTH);

        setLayout(new BorderLayout());
        add(containerPanel, BorderLayout.CENTER);
    }

    /**
     * Returns <code>true</code> if the shape is already displayed for
     * the specified channel, <code>false</code> otherwise.
     *
     * @param shape The shape to add.
     * @param channelName The name of channel to display.
     */
    private boolean isValueDisplayed(ROIShape shape, String channelName) {
        long id = shape.getID();
        int z = shape.getCoord3D().getZSection();
        int t = shape.getCoord3D().getTimePoint();
        for (int i = 0; i < resultsModel.getRowCount(); i++) {
            ROIShape s = ((ShapeAsID) resultsModel.getValueAt(i, 0)).getShape();
            if (s.getID() == id && s.getCoord3D().getZSection() == z && s.getCoord3D().getTimePoint() == t) {
                //check channel
                String v = (String) resultsModel.getValueAt(i, 3);
                if (v.equals(channelName))
                    return true;
            }
        }
        return false;
    }

    /** 
     * Populates the table with the data. 
     * 
     * @param shape The analyzed shape. 
     */
    private void getResults(ROIShape shape) {
        Vector<Vector<Object>> rows = new Vector<Vector<Object>>();

        final ShapeAsID shapeID = new ShapeAsID(shape);
        Iterator<String> channelIterator = channelName.values().iterator();
        channelMin = minStats.get(coord);
        channelMax = maxStats.get(coord);
        channelMean = meanStats.get(coord);
        channelStdDev = stdDevStats.get(coord);
        channelSum = sumStats.get(coord);
        String cName;
        int channel;
        Vector<Object> rowData;
        while (channelIterator.hasNext()) {
            cName = channelIterator.next();
            channel = nameMap.get(cName);
            if (!isValueDisplayed(shape, cName)) {
                rowData = new Vector<Object>();
                rowData.add(shapeID);
                rowData.add(shape.getCoord3D().getZSection() + 1);
                rowData.add(shape.getCoord3D().getTimePoint() + 1);
                rowData.add(cName);
                rowData.add(MeasurementAttributes.TEXT.get(shape.getFigure()));
                rowData.add(channelMin.get(channel));
                rowData.add(channelMax.get(channel));
                rowData.add(channelSum.get(channel));
                rowData.add(channelMean.get(channel));
                rowData.add(channelStdDev.get(channel));
                rows.add(rowData);
            }
        }
        for (Vector<Object> data : rows) {
            resultsModel.addRow(data);
        }
        results.repaint();
    }

    /** Saves the results of the table to an Excel file. */
    private void saveResults() {
        FileChooser chooser = view.createSaveToExcelChooser();

        if (chooser.showDialog() != JFileChooser.APPROVE_OPTION)
            return;
        File file = chooser.getFormattedSelectedFile();
        ExcelWriter writer = null;
        try {
            String filename = file.getAbsolutePath();
            writer = new ExcelWriter(filename);
            writer.openFile();
            writer.createSheet("Intensity Results");
            writer.writeTableToSheet(0, 0, resultsModel);
            BufferedImage originalImage = model.getRenderedImage();
            BufferedImage image = Factory.copyBufferedImage(originalImage);
            // Add the ROI for the current plane to the image.
            //TODO: Need to check that.
            model.setAttributes(MeasurementAttributes.SHOWID, true);
            model.setAttributes(MeasurementAttributes.SHOWID, false);
            try {
                if (image != null) {
                    model.getDrawingView().print(image.getGraphics());
                    writer.addImageToWorkbook("ThumbnailImage", image);
                    int col = writer.getMaxColumn(0);
                    writer.writeImage(0, col + 1, 256, 256, "ThumbnailImage");
                }
            } catch (Exception e) {
                Logger logger = MeasurementAgent.getRegistry().getLogger();
                logger.error(this, "Cannot write Image: " + e.toString());
            }
            writer.close();

        } catch (IOException e) {
            Logger logger = MeasurementAgent.getRegistry().getLogger();
            logger.error(this, "Cannot save ROI results: " + e.toString());

            UserNotifier un = MeasurementAgent.getRegistry().getUserNotifier();
            un.notifyInfo("Save Results",
                    "An error occurred while trying to" + " save the data.\nPlease try again.");
            file.delete();
            try {
                writer.close();
            } catch (Exception e2) {
                //ignore: cannot close the writer.
            }
            return;
        }
        Registry reg = MeasurementAgent.getRegistry();
        UserNotifier un = reg.getUserNotifier();
        un.notifyInfo("Save ROI results", "The ROI results have been " + "successfully saved.");
    }

    /** Removes the selected results from the table. */
    private void removeResults() {
        int[] rows = results.getSelectedRows();
        for (int i = 0; i < rows.length; i++) {
            resultsModel.removeRow(rows[i] - i);
        }
        results.clearSelection();
        setButtonsEnabled(results.getRowCount() > 0);
    }

    /**
     * @return the ROI shapes currently in the results table rows, never null
     */
    private Set<ROIShape> getShapesInRows() {
        final Set<ROIShape> shapes = new HashSet<ROIShape>();
        for (int i = 0; i < resultsModel.getRowCount(); i++)
            shapes.add(((ShapeAsID) resultsModel.getValueAt(i, 0)).getShape());
        return shapes;
    }

    /**
     * Check to see if the selected figure contains textFigure
     * @param selectedFigures see above.
     * @return see above.
     */
    private boolean validFigures(Set<Figure> selectedFigures) {
        if (CollectionUtils.isEmpty(selectedFigures))
            return false;
        for (Figure figure : selectedFigures)
            if (figure instanceof MeasureTextFigure)
                return false;
        return true;
    }

    /**
     * Adds the statistics from the selected ROI to the table.
     */
    private void addResults() {
        Set<Figure> selectedFigures = view.getDrawingView().getSelectedFigures();
        if (!validFigures(selectedFigures))
            return;
        if (CollectionUtils.isEmpty(selectedFigures) || state == State.ANALYSING)
            return;
        state = State.ANALYSING;
        List<ROIShape> shapeList = new ArrayList<ROIShape>();
        final Set<ROIShape> alreadyInTable = getShapesInRows();
        Iterator<Figure> iterator = selectedFigures.iterator();
        ROIFigure fig;
        Map<ROIShape, Map<Integer, ROIShapeStats>> analysisResults = model.getAnalysisResults();
        if (analysisResults == null)
            analysisResults = Collections.emptyMap();
        ROIShape shape;
        while (iterator.hasNext()) {
            fig = (ROIFigure) iterator.next();
            shape = fig.getROIShape();
            if (alreadyInTable.contains(shape))
                removeShape(shape);
            final Map<Integer, ROIShapeStats> shapeStatsByChannel = analysisResults.get(shape);
            if (shapeStatsByChannel == null || shapeStatsByChannel.isEmpty())
                /* if empty, they were probably wiped out by AnalysisStatsWrapper.convertStats */
                shapeList.add(shape);
        }
        if (shapeList.size() > 0) {
            view.calculateStats(shapeList);
            onFigureSelected();
        }
        state = State.READY;
    }

    /**
     * Removes the shape from the table.
     * 
     * @param shapeID The identifier of the shape.
     */
    private void removeShape(ROIShape shape) {
        List<Integer> indexes = new ArrayList<Integer>();
        for (int i = 0; i < resultsModel.getRowCount(); i++)
            if (((ShapeAsID) resultsModel.getValueAt(i, 0)).getShape().equals(shape))
                indexes.add(i);
        Iterator<Integer> j = indexes.iterator();
        while (j.hasNext()) {
            resultsModel.removeRow(j.next());
        }
    }

    /**
     * Sets the enabled flag of the various buttons.
     * 
     * @param value The value to set.
     */
    private void setControls(boolean value) {
        addAllButton.setEnabled(value);
        removeAllButton.setEnabled(value);
        removeButton.setEnabled(value);
        saveButton.setEnabled(value);
    }

    /**
     * Adds the statistics from the ROIShapes of the select ROI to the table.
     */
    private void addAllResults() {
        Set<Figure> selectedFigures = view.getDrawingView().getSelectedFigures();
        if (CollectionUtils.isEmpty(selectedFigures) || state == State.ANALYSING)
            return;
        state = State.ANALYSING;
        setControls(false);
        List<ROIShape> shapeList = new ArrayList<ROIShape>();
        final Set<ROIShape> alreadyInTable = getShapesInRows();

        Iterator<Figure> i = selectedFigures.iterator();
        ROIFigure fig;
        TreeMap<Coord3D, ROIShape> treeMap;
        Iterator<Coord3D> j;
        ROIShape shape;
        Map<ROIShape, Map<Integer, ROIShapeStats>> analysisResults = model.getAnalysisResults();
        if (analysisResults == null)
            analysisResults = Collections.emptyMap();
        while (i.hasNext()) {
            fig = (ROIFigure) i.next();
            if (!(fig instanceof MeasureTextFigure)) {
                treeMap = fig.getROI().getShapes();
                j = treeMap.keySet().iterator();
                while (j.hasNext()) {
                    shape = treeMap.get(j.next());
                    if (!alreadyInTable.contains(shape)) {
                        final Map<Integer, ROIShapeStats> shapeStatsByChannel = analysisResults.get(shape);
                        if (shapeStatsByChannel == null || shapeStatsByChannel.isEmpty())
                            /* if empty, they were probably wiped out by AnalysisStatsWrapper.convertStats */
                            shapeList.add(shape);
                    }
                }
            }
        }
        if (shapeList.isEmpty()) {
            if (!analysisResults.isEmpty()) {
                displayAnalysisResults();
            }
        } else {
            view.calculateStats(shapeList);
        }
    }

    /**
     * Creates a new instance.
     * 
     * @param view       Reference to the View. Mustn't be <code>null</code>.
     * @param model       Reference to the Model. Mustn't be <code>null</code>.
     */
    IntensityResultsView(MeasurementViewerUI view, MeasurementViewerModel model) {
        if (view == null)
            throw new IllegalArgumentException("No view.");
        if (model == null)
            throw new IllegalArgumentException("No model.");
        this.view = view;
        this.model = model;
        initComponents();
        buildGUI();
    }

    /**
     * Returns the name of the component.
     * 
     * @return See above.
     */
    String getComponentName() {
        return NAME;
    }

    /**
     * Returns the icon of the component.
     * 
     * @return See above.
     */
    Icon getComponentIcon() {
        IconManager icons = IconManager.getInstance();
        return icons.getIcon(IconManager.INTENSITYVIEW);
    }

    /**
     * Get the analysis results from the model and convert to the 
     * necessary array. data types using the ROIStats wrapper then
     * create the appropriate table data and summary statistics.  
     */
    void displayAnalysisResults() {
        this.ROIStats = model.getAnalysisResults();
        onFigureSelected();
        if (ROIStats == null || ROIStats.size() == 0) {
            state = State.READY;
            return;
        }
        shapeMap = new TreeMap<Coord3D, ROIShape>(new Coord3D());
        minStats = new TreeMap<Coord3D, Map<Integer, Double>>(new Coord3D());
        maxStats = new TreeMap<Coord3D, Map<Integer, Double>>(new Coord3D());
        meanStats = new TreeMap<Coord3D, Map<Integer, Double>>(new Coord3D());
        sumStats = new TreeMap<Coord3D, Map<Integer, Double>>(new Coord3D());
        stdDevStats = new TreeMap<Coord3D, Map<Integer, Double>>(new Coord3D());

        Entry entry;
        Iterator j = ROIStats.entrySet().iterator();
        channelName = new TreeMap<Integer, String>();
        nameMap = new HashMap<String, Integer>();
        Map<StatsType, Map> shapeStats;
        Coord3D c3D;
        ChannelData channelData;
        int channel;
        List<ChannelData> metadata = model.getMetadata();
        Iterator<ChannelData> i;
        while (j.hasNext()) {
            entry = (Entry) j.next();
            shape = (ROIShape) entry.getKey();
            //shapeMap.put(shape.getCoord3D(), shape);
            if (shape.getFigure() instanceof MeasureTextFigure) {
                state = State.READY;
                return;
            }
            c3D = shape.getCoord3D();
            shapeStats = AnalysisStatsWrapper.convertStats((Map) entry.getValue());
            if (shapeStats != null) {
                minStats.put(c3D, shapeStats.get(StatsType.MIN));
                maxStats.put(c3D, shapeStats.get(StatsType.MAX));
                meanStats.put(c3D, shapeStats.get(StatsType.MEAN));
                sumStats.put(c3D, shapeStats.get(StatsType.SUM));
                stdDevStats.put(c3D, shapeStats.get(StatsType.STDDEV));
            }
            channelName.clear();
            nameMap.clear();
            channelColour.clear();

            i = metadata.iterator();
            while (i.hasNext()) {
                channelData = i.next();
                channel = channelData.getIndex();
                if (model.isChannelActive(channel)) {
                    channelName.put(channel, channelData.getChannelLabeling());
                    nameMap.put(channelName.get(channel), channel);
                    channelColour.put(channel, (Color) model.getActiveChannels().get(channel));
                }
            }

            if (channelName.size() == 0 || nameMap.size() == 0 || channelColour.size() == 0) {
                state = State.READY;
                return;
            }

            coord = c3D;
            getResults(shape);
        }

        setButtonsEnabled(true);
        onFigureSelected();
        state = State.READY;
    }

    /** Invokes when figures are selected. */
    void onFigureSelected() {
        Set<Figure> selectedFigures = view.getDrawingView().getSelectedFigures();
        boolean valid = validFigures(selectedFigures);
        addButton.setEnabled(valid);
        addAllButton.setEnabled(valid);
        if (results != null) {
            int count = results.getRowCount();
            int[] rows = results.getSelectedRows();
            removeButton.setEnabled(rows != null && rows.length > 0);
            removeAllButton.setEnabled(count > 0);
        } else {
            removeButton.setEnabled(false);
            removeAllButton.setEnabled(false);
        }
    }

    /** Removes the results from the table. */
    void removeAllResults() {
        int count = results.getRowCount();
        for (int i = count - 1; i >= 0; i--)
            resultsModel.removeRow(i);
        model.setAnalysisResults(null);
        setButtonsEnabled(false);
        onFigureSelected();
    }

    /**
     * Listens to the controls.
     * @see ActionListener#actionPerformed(ActionEvent)
     * 
     */
    public void actionPerformed(ActionEvent e) {
        int index = Integer.parseInt(e.getActionCommand());
        switch (index) {
        case ADD:
            addResults();
            break;
        case ADDALL:
            addAllResults();
            break;
        case SAVE:
            saveResults();
            break;
        case REMOVE:
            removeResults();
            break;
        case REMOVE_ALL:
            removeAllResults();
        }
    }

    /** 
     * The table model for the results table, only overridden to make it read 
     * only.
     */
    class ResultsTableModel extends DefaultTableModel {

        /**
         * Overridden to make sure that the cell cannot be edited.
         * @see DefaultTableModel#isCellEditable(int, int)
         */
        public boolean isCellEditable(int row, int col) {
            return false;
        }
    }

}