/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2015 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.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.collections.CollectionUtils;
import org.jhotdraw.draw.Figure;
import org.openmicroscopy.shoola.agents.events.measurement.SelectPlane;
import org.openmicroscopy.shoola.agents.measurement.IconManager;
import org.openmicroscopy.shoola.agents.measurement.MeasurementAgent;
import org.openmicroscopy.shoola.util.file.ExcelWriter;
import org.openmicroscopy.shoola.util.image.geom.Factory;
import org.openmicroscopy.shoola.util.roi.figures.MeasureBezierFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureEllipseFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureLineConnectionFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureLineFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasurePointFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureRectangleFigure;
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.AnnotationKeys;
import org.openmicroscopy.shoola.util.roi.model.annotation.MeasurementAttributes;
import org.openmicroscopy.shoola.util.roi.model.util.Coord3D;
import org.openmicroscopy.shoola.util.ui.ColorListRenderer;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.filechooser.FileChooser;
import org.openmicroscopy.shoola.util.ui.slider.OneKnobSlider;
import org.openmicroscopy.shoola.agents.measurement.util.ChannelSummaryModel;
import org.openmicroscopy.shoola.agents.measurement.util.ChannelSummaryTable;
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.AnalysisStatsWrapper.StatsType;
import org.openmicroscopy.shoola.env.config.Registry;
import omero.log.Logger;
import org.openmicroscopy.shoola.env.rnd.roi.ROIShapeStatsSimple;
import org.openmicroscopy.shoola.env.ui.UserNotifier;
import omero.gateway.model.ChannelData;
/**
* Displays statistics computed on the pixels intensity value of a given
* ROI shape.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0
* @since OME3.0
*/
class IntensityView
extends JPanel
implements ActionListener, TabPaneInterface, ChangeListener,
PropertyChangeListener
{
/** Index to identify tab */
public final static int INDEX = MeasurementViewerUI.INTENSITY_INDEX;
/** Width of the text field components. */
public final static int TEXT_WIDTH = 100;
/** Height of the text field components. */
public final static int TEXT_HEIGHT = 26;
/** Width of label components. */
public final static int LABEL_WIDTH = 60;
/** Height of label components. */
public final static int LABEL_HEIGHT = 26;
/** The initial size of the intensity table dialog. */
private Dimension intensityTableSize = new Dimension(300, 300);
/** The default preview of the sheet displaying the channel's name.*/
private static final String CHANNEL_SHEET = "Channel name ";
/** 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 View";
/** The save button action command. */
private static final int SAVE_ACTION = 0;
/** The show table action button. */
private static final int SHOW_TABLE_ACTION = 1;
/** The channel selection action command. */
private static final int CHANNEL_SELECTION = 2;
/** Reference to the model. */
private MeasurementViewerModel model;
/** Reference to the view. */
private MeasurementViewerUI view;
/**
* SelectChannelsForm the form to select the channels to output to the
* file.
*/
private ChannelSelectionForm channelsSelectionForm;
/** The map of <ROIShape, ROIStats> .*/
private Map ROIStats;
/** Table for summary of measurement values. */
private ChannelSummaryTable channelSummaryTable;
/** Model for summary of measurement values. */
private ChannelSummaryModel channelSummaryModel;
/** The channel selected in the Combo box. */
private int selectedChannel = -1;
/** The name of the channel selected in the Combo box. */
private String selectedChannelName;
/** Select to choose the channel to show values for . */
private JComboBox channelSelection;
/** The save button. */
private JButton saveButton;
/** The slider controlling the movement of the analysis through Z. */
private OneKnobSlider zSlider;
/** The slider controlling the movement of the analysis through T. */
private OneKnobSlider tSlider;
/** 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 minimum, 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 LinkedHashMap<String, Integer>();
/** The map of the shape stats to coord. */
private TreeMap<Coord3D, Map<StatsType, Map>> shapeStatsList;
/** Map of the pixel intensity values to coordinates. */
private TreeMap<Coord3D, Map<Integer, ROIShapeStatsSimple>> pixelStats;
/** Map of the minimum channel intensity values to coordinates. */
private TreeMap<Coord3D, Map<Integer, Double>> minStats;
/** Map of the max channel intensity values to coordinates. */
private TreeMap<Coord3D, Map<Integer, Double>> maxStats;
/** Map of the mean channel intensity values to coordinates. */
private TreeMap<Coord3D, Map<Integer, Double>> meanStats;
/** Map of the std dev channel intensity values to coordinates. */
private TreeMap<Coord3D, Map<Integer, Double>> stdDevStats;
/** Map of the sum channel intensity values to coordinates. */
private TreeMap<Coord3D, Map<Integer, Double>> sumStats;
/** Map of the coordinates to a shape. */
private TreeMap<Coord3D, ROIShape> shapeMap;
/** The current coordinates of the ROI being depicted in the slider. */
private Coord3D coord;
/** Button for the calling of the intensity table. */
private JButton showIntensityTable;
/** Current ROIShape. */
private ROIShape shape;
/** Dialog showing the intensity values for the selected channel. */
private IntensityValuesDialog intensityDialog;
/** Table Model. */
private IntensityModel tableModel;
/** Reference to the controller */
private MeasurementViewerControl controller;
/** Indicates the selected plane.*/
private void formatPlane()
{
if (!zSlider.isVisible() && !tSlider.isVisible()) {
view.setPlaneStatus("");
return;
}
StringBuffer buffer = new StringBuffer();
if (zSlider.isVisible())
buffer.append("Z="+zSlider.getValue()+" ");
if (tSlider.isVisible())
buffer.append("T="+tSlider.getValue());
view.setPlaneStatus(buffer.toString());
}
/** The slider has changed value and the mouse button released. */
private void handleSliderReleased()
{
if (zSlider == null || tSlider == null || coord == null ||
state != State.READY)
return;
int newZ = zSlider.getValue() - 1;
int newT = tSlider.getValue() - 1;
Coord3D thisCoord = new Coord3D(newZ, newT);
if (coord.equals(thisCoord))
return;
if (checkPlane(newZ, newT)) {
state = State.ANALYSING;
SelectPlane evt = new SelectPlane(model.getPixelsID(),
zSlider.getValue() - 1, tSlider.getValue() - 1);
MeasurementAgent.getRegistry().getEventBus().post(evt);
}
}
/**
* Controls if the specified coordinates are valid.
* Returns <code>true</code> if the passed values are in the correct ranges,
* <code>false</code> otherwise.
*
* @param z The z coordinate. Must be in the range <code>[0, sizeZ)</code>.
* @param t The t coordinate. Must be in the range <code>[0, sizeT)</code>.
* @return See above.
*/
private boolean checkPlane(int z, int t)
{
if (z < 0 || model.getNumZSections() <= z) return false;
if (t < 0 || model.getNumTimePoints() <= t) return false;
return true;
}
/** Initializes the table model.*/
private void initTableModel()
{
Double summaryData[][] = new Double[1][1];
List<String> rowNames = new ArrayList<String>();
List<String> columnNames = new ArrayList<String>();
rowNames.add("");
columnNames.add("");
channelSummaryModel = new ChannelSummaryModel(rowNames, columnNames,
summaryData);
}
/** Initializes the component composing the display. */
private void initComponents()
{
Double data[][] = new Double[1][1];
tableModel = new IntensityModel(data);
initTableModel();
channelSummaryTable = new ChannelSummaryTable(channelSummaryModel);
showIntensityTable = new JButton("Intensity Values...");
showIntensityTable.setEnabled(false);
showIntensityTable.addActionListener(this);
showIntensityTable.setActionCommand(""+SHOW_TABLE_ACTION);
channelSelection = new JComboBox();
channelSelection.setEnabled(false);
channelSelection.setVisible(false);
channelSelection.addActionListener(this);
channelSelection.setActionCommand(""+CHANNEL_SELECTION);
saveButton = new JButton("Export to Excel...");
saveButton.addActionListener(this);
saveButton.setActionCommand(""+SAVE_ACTION);
saveButton.setEnabled(false);
state = State.READY;
zSlider = new OneKnobSlider();
zSlider.setOrientation(JSlider.VERTICAL);
zSlider.setPaintTicks(false);
zSlider.setPaintLabels(false);
zSlider.setMajorTickSpacing(1);
zSlider.setShowArrows(true);
zSlider.setVisible(false);
zSlider.setEndLabel("Z");
zSlider.setShowEndLabel(true);
tSlider = new OneKnobSlider();
tSlider.setPaintTicks(false);
tSlider.setPaintLabels(false);
tSlider.setMajorTickSpacing(1);
tSlider.setSnapToTicks(true);
tSlider.setShowArrows(true);
tSlider.setVisible(false);
tSlider.setEndLabel("T");
tSlider.setShowEndLabel(true);
zSlider.addPropertyChangeListener(this);
tSlider.addPropertyChangeListener(this);
zSlider.addChangeListener(this);
tSlider.addChangeListener(this);
}
/** Builds and lays out the UI. */
private void buildGUI()
{
JPanel scrollPanel = new JPanel();
JPanel containerPanel = new JPanel();
containerPanel.setLayout(new BoxLayout(containerPanel,
BoxLayout.X_AXIS));
JPanel tPanel = tablePanel();
containerPanel.add(zSlider);
containerPanel.add(tPanel);
JPanel cPanel = new JPanel();
cPanel.setLayout(new BoxLayout(cPanel, BoxLayout.Y_AXIS));
cPanel.add(containerPanel);
cPanel.add(tSlider);
JPanel buttonPanel = createButtonPanel();
scrollPanel.setLayout(new BoxLayout(scrollPanel, BoxLayout.X_AXIS));
scrollPanel.add(cPanel);
scrollPanel.add(buttonPanel);
scrollPanel.add(Box.createGlue());
this.setLayout(new BorderLayout());
this.add(scrollPanel, BorderLayout.CENTER);
intensityDialog = new IntensityValuesDialog(view, tableModel,
channelSelection);
}
/**
* Create the button panel to the right of the summary table.
* @return see above.
*/
private JPanel createButtonPanel()
{
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(Box.createRigidArea(new Dimension(0,10)));
panel.add(Box.createRigidArea(new Dimension(0,10)));
JPanel intensityPanel =
UIUtilities.buildComponentPanel(showIntensityTable);
UIUtilities.setDefaultSize(intensityPanel, new Dimension(175, 32));
panel.add(intensityPanel);
panel.add(Box.createRigidArea(new Dimension(0,10)));
JPanel savePanel = UIUtilities.buildComponentPanel(saveButton);
UIUtilities.setDefaultSize(savePanel, new Dimension(175, 32));
panel.add(savePanel);
panel.add(Box.createVerticalGlue());
return panel;
}
/**
* Create the table panel which holds all the intensities for the selected
* channel in the table.
* @return See Above.
*/
private JPanel tablePanel()
{
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
JScrollPane scrollPane = new JScrollPane(channelSummaryTable);
scrollPane.setVerticalScrollBar(scrollPane.createVerticalScrollBar());
scrollPane.setHorizontalScrollBar(
scrollPane.createHorizontalScrollBar());
panel.add(scrollPane);
return panel;
}
/** Clears the maps just in case the data is not being reassigned. */
private void clearMaps()
{
if (shapeStatsList != null)
shapeStatsList.clear();
shapeStatsList = null;
if (pixelStats != null)
pixelStats.clear();
pixelStats = null;
if (shapeMap != null)
shapeMap.clear();
shapeMap = null;
if (maxStats != null)
maxStats.clear();
maxStats = null;
if (meanStats != null)
meanStats.clear();
meanStats = null;
if (minStats != null)
minStats.clear();
minStats = null;
if (sumStats != null)
sumStats.clear();
sumStats = null;
if (stdDevStats != null)
stdDevStats.clear();
stdDevStats = null;
}
/** Clears the combo box. */
private void clearAllValues()
{
channelSelection.removeAllItems();
}
/** Creates the combobox holding the channel list. */
private void createComboBox()
{
Object[][] channelCols = new Object[channelName.size()][2];
List<ChannelData> metadata = model.getMetadata();
Iterator<ChannelData> i = metadata.iterator();
ChannelData channelData;
int channel;
int index = 0;
while (i.hasNext()) {
channelData = i.next();
channel = channelData.getIndex();
if (channelName.containsKey(channel))
{
channelCols[index] = new Object[]{ channelColour.get(channel),
channelName.get(channel) };
index++;
}
}
//if (channelCols.length == 0)
//return;
channelSelection.setModel(new DefaultComboBoxModel(channelCols));
channelSelection.setRenderer(new ColorListRenderer());
if (selectedChannelName != null)
if (nameMap.containsKey(selectedChannelName))
selectedChannel = nameMap.get(selectedChannelName);
else
selectedChannel = 0;
else
selectedChannel = 0;
if (selectedChannel >= channelSelection.getItemCount()
|| selectedChannel < 0)
return;
channelSelection.setSelectedIndex(selectedChannel);
channelSelection.setEnabled(true);
}
/**
* Populates the table and fields with the data.
*
* @param coord the coordinate of the shape being analysed.
* @param channel the channel to be analysed.
*/
private void populateData(Coord3D coord, int channel)
{
channelSummaryTable.setVisible(true);
interpretResults(coord, channel);
populateChannelSummaryTable(coord);
}
/**
* Populates the pixels value per channel.
*
* @param channel The selected channel.
*/
private void populateChannel()
{
Object[] nameColour = (Object[]) channelSelection.getSelectedItem();
String string = (String) nameColour[1];
if (!nameMap.containsKey(string))
return;
selectedChannelName = string;
int channel = nameMap.get(string);
if (channel < 0) return;
ROIShapeStatsSimple pixels = pixelStats.get(coord).get(channel);
if (pixels == null) return;
Iterator<Point> pixelIterator = pixels.getPoints().iterator();
double minX, maxX, minY, maxY;
if (!pixelIterator.hasNext()) return;
Point point = pixelIterator.next();
minX = (point.getX());
maxX = (point.getX());
minY = (point.getY());
maxY = (point.getY());
while (pixelIterator.hasNext())
{
point = pixelIterator.next();
minX = Math.min(minX, point.getX());
maxX = Math.max(maxX, point.getX());
minY = Math.min(minY, point.getY());
maxY = Math.max(maxY, point.getY());
}
int sizeX, sizeY;
sizeX = (int) (maxX-minX)+1;
sizeY = (int) ((maxY-minY)+1);
Double[][] data = new Double[sizeX][sizeY];
int x, y;
for(int i=0; i<pixels.getPointsCount(); i++)
{
point = pixels.getPoints().get(i);
x = (int) (point.getX()-minX);
y = (int) (point.getY()-minY);
if (x >= sizeX || y >= sizeY)
continue;
data[x][y] = pixels.getValues()[i];;
}
tableModel = new IntensityModel(data);
intensityDialog.setModel(tableModel);
}
/**
* Populate the summary table with the list of values for the ROI at
* coord.
* @param coord see above.
*/
private void populateChannelSummaryTable(Coord3D coord)
{
List<String> statNames = new ArrayList<String>();
List<String> channelNames = new ArrayList<String>();
ROIFigure fig = shape.getFigure();
int count = 0;
statNames.add("Min");
statNames.add("Max");
statNames.add("Sum");
statNames.add("Mean");
statNames.add("Std Dev.");
statNames.add("NumPixels");
if (areaFigure(fig))
addAreaStats(statNames);
else if (lineFigure(fig))
addLineStats(statNames);
else if (pointFigure(fig))
addPointStats(statNames);
Iterator<Integer> channelIterator = channelName.keySet().iterator();
while (channelIterator.hasNext())
channelNames.add(channelName.get(channelIterator.next()));
Double data[][] = new Double[channelName.size()][statNames.size()];
channelIterator = channelName.keySet().iterator();
int channel;
count = 0;
while (channelIterator.hasNext())
{
channel = channelIterator.next();
populateSummaryColumn(fig, data, channel, count);
count++;
}
channelSummaryModel = new ChannelSummaryModel(statNames, channelNames,
data);
channelSummaryTable.setModel(channelSummaryModel);
}
/**
* Populates the data for use in the summary table for the figure fig,
* and for channel.
*
* @param fig see above.
* @param data see above.
* @param channel see above.
* @param count the column the data is being placed in.
*/
private void populateSummaryColumn(ROIFigure fig, Double data[][],
int channel, int count)
{
data[count][0] = channelMin.get(channel);
data[count][1] = channelMax.get(channel);
data[count][2] = channelSum.get(channel);
data[count][3] = channelMean.get(channel);
data[count][4] = channelStdDev.get(channel);
data[count][5] = (double) fig.getSize();
if (areaFigure(fig))
addValuesForAreaFigure(fig, data, channel, count);
else if (lineFigure(fig))
addValuesForLineFigure(fig, data, channel, count);
else if (pointFigure(fig))
addValuesForPointFigure(fig, data, channel, count);
}
/**
* Add stats in the column for area figures.
* @param fig the figure where the stats come from.
* @param data the data being populated.
* @param count the column in the table being populated.
*/
private void addValuesForAreaFigure(ROIFigure fig, Double data[][],
int channel, int count)
{
data[count][6] = AnnotationKeys.AREA.get(fig.getROIShape()).getValue();
data[count][7] = fig.getBounds().getX();
data[count][8] = fig.getBounds().getY();
data[count][9] = AnnotationKeys.WIDTH.get(fig.getROIShape()).getValue();
data[count][10] = AnnotationKeys.HEIGHT.get(fig.getROIShape()).getValue();
data[count][11] = AnnotationKeys.CENTREX.get(fig.getROIShape()).getValue();
data[count][12] = AnnotationKeys.CENTREY.get(fig.getROIShape()).getValue();
}
/**
* Add stats in the column for line figures.
* @param fig the figure where the stats come from.
* @param data the data being populated.
* @param channel the channel where the stats come from/.
* @param count the column in the table being populated.
*/
private void addValuesForLineFigure(ROIFigure fig, Double data[][],
int channel, int count)
{
data[count][6] = AnnotationKeys.STARTPOINTX.get(shape).getValue();
data[count][7] = AnnotationKeys.STARTPOINTY.get(shape).getValue();
data[count][8] = AnnotationKeys.ENDPOINTX.get(shape).getValue();
data[count][9] = AnnotationKeys.ENDPOINTY.get(shape).getValue();
data[count][10] = AnnotationKeys.CENTREX.get(shape).getValue();
data[count][11] = AnnotationKeys.CENTREY.get(shape).getValue();
}
/**
* Add stats in the column for point figures.
* @param fig the figure where the stats come from.
* @param data the data being populated.
* @param channel the channel where the stats come from/.
* @param count the column in the table being populated.
*/
private void addValuesForPointFigure(ROIFigure fig, Double data[][],
int channel, int count)
{
data[count][6] = AnnotationKeys.CENTREX.get(shape).getValue();
data[count][7] = AnnotationKeys.CENTREY.get(shape).getValue();
}
/**
* Sets the rows describing the stats being displayed in the summary table.
* @param statNames The list of stats being displayed in the summary table.
*/
private void addAreaStats(List<String> statNames)
{
statNames.add("Area");
statNames.add("X Coord");
statNames.add("Y Coord");
statNames.add("Width");
statNames.add("Height");
statNames.add("X Center");
statNames.add("Y Center");
}
/**
* Sets the rows describing the stats being displayed in the summary table.
*
* @param statNames The list of stats being displayed in the summary table.
*/
private void addLineStats(List<String> statNames)
{
statNames.add("X1 Coord");
statNames.add("Y1 Coord");
statNames.add("X2 Coord");
statNames.add("Y2 Coord");
statNames.add("X Center");
statNames.add("Y Center");
}
/**
* Sets the rows describing the stats being displayed in the summary table.
*
* @param statNames The list of stats being displayed in the summary table.
*/
private void addPointStats(List<String> statNames)
{
statNames.add("X Center");
statNames.add("Y Center");
}
/**
* Called by the display analysis results method to build the results into
* datastructures used by the intensityView.
* @param coord
* @param channel
*/
private void interpretResults(Coord3D coord, int channel)
{
channelMin = minStats.get(coord);
channelMax = maxStats.get(coord);
channelMean = meanStats.get(coord);
channelStdDev = stdDevStats.get(coord);
channelSum = sumStats.get(coord);
shape = shapeMap.get(coord);
}
/**
* Returns <code>true</code> if the passed figure is an area figure,
* <code>false</code> otherwise.
*
* @param fig The param.
* @return See above.
*/
private boolean areaFigure(ROIFigure fig)
{
if (fig instanceof MeasureEllipseFigure ||
fig instanceof MeasureRectangleFigure)
return true;
if (fig instanceof MeasureBezierFigure)
{
MeasureBezierFigure bFig = (MeasureBezierFigure) fig;
if (bFig.isClosed())
return true;
}
return false;
}
/**
* Returns <code>true</code> if the passed figure is a line figure,
* <code>false</code> otherwise.
*
* @param fig The param.
* @return See above.
*/
private boolean lineFigure(ROIFigure fig)
{
if (fig instanceof MeasureLineFigure ||
fig instanceof MeasureLineConnectionFigure)
return true;
if (fig instanceof MeasureBezierFigure)
{
MeasureBezierFigure bFig = (MeasureBezierFigure)fig;
if (!bFig.isClosed())
return true;
}
return false;
}
/**
* Returns <code>true</code> if the passed figure is a Point figure,
* <code>false</code> otherwise.
*
* @param fig The param.
* @return See above.
*/
private boolean pointFigure(ROIFigure fig)
{
return (fig instanceof MeasurePointFigure);
}
/** Save the results to an Excel File. */
private void saveResults()
{
channelsSelectionForm = new ChannelSelectionForm(channelName);
FileChooser chooser = view.createSaveToExcelChooser();
chooser.addComponentToControls(channelsSelectionForm);
if (chooser.showDialog() != JFileChooser.APPROVE_OPTION) return;
File file = chooser.getFormattedSelectedFile();
List<Integer> channels = channelsSelectionForm.getUserSelection();
if (channels == null || channels.size() == 0) {
UserNotifier un = MeasurementAgent.getRegistry().getUserNotifier();
un.notifyInfo("Save Results", " Please select at least a channel.");
view.setStatus("No Channel selected to output.");
return;
}
ExcelWriter writer = null;
try
{
writer = new ExcelWriter(file.getAbsolutePath());
writer.openFile();
writer.createSheet("Channel Summary");
Iterator<Coord3D> coordMapIterator = shapeMap.keySet().iterator();
Coord3D currentCoord;
int n = channels.size();
Integer channel;
if (channelSummarySelected(channels))
outputSummary(writer, shapeMap);
BufferedImage originalImage = model.getRenderedImage();
if(originalImage != null)
{
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.getDrawingView().print(image.getGraphics());
model.setAttributes(MeasurementAttributes.SHOWID, false);
try {
writer.addImageToWorkbook("ThumbnailImage", image);
} catch (Exception e) {
Logger logger = MeasurementAgent.getRegistry().getLogger();
logger.error(this, "Cannot write Image: "+e.toString());
}
int col = writer.getMaxColumn(0);
writer.writeImage(0, col+1, 256, 256, "ThumbnailImage");
}
String name;
String sheet;
if (channelSummarySelected(channels) && channels.size() != 1)
while (coordMapIterator.hasNext())
{
currentCoord = coordMapIterator.next();
for (int i = 0 ; i < n ; i++)
{
channel = channels.get(i);
if (channel == ChannelSelectionForm.SUMMARYVALUE)
continue;
if (!nameMap.containsKey(channelName.get(channel)))
continue;
int rowIndex = 0;
name = channelName.get(channel);
sheet = CHANNEL_SHEET+name;
//First check if the sheet already exists.
if (writer.setCurrentSheet(sheet) == null)
writer.createSheet(sheet);
writeHeader(writer, rowIndex, currentCoord);
channel = nameMap.get(name);
writeData(writer, rowIndex, currentCoord,
channel.intValue());
}
}
writer.close();
} catch (Exception e)
{
Logger logger = MeasurementAgent.getRegistry().getLogger();
logger.error(this, "Cannot save ROI results: "+e.toString());
UserNotifier un = MeasurementAgent.getRegistry().getUserNotifier();
String message = "An error occurred while trying to" +
" save the data.\nPlease try again.";
if (e instanceof NumberFormatException) {
message = "We only support the British/American style of " +
"representing numbers,\nusing a decimal point rather " +
"than a comma.";
}
un.notifyInfo("Save Results", message);
//delete the file
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.");
}
/**
* Create summary table with horizontal columns.
*
* @param writer The Excel writer.
* @param rowIndex The selected row.
*/
private void printSummaryHeader(ExcelWriter writer, int rowIndex)
{
writer.writeElement(rowIndex, 0, "channel");
writer.writeElement(rowIndex, 1, "zsection");
writer.writeElement(rowIndex, 2, "time");
for (int y = 0 ; y < channelSummaryTable.getRowCount() ; y++)
writer.writeElement(rowIndex, 3+y,
channelSummaryTable.getValueAt(y, 0));
}
/**
* Outputs the summary information from the shape map.
*
* @param writer The Excel writer.
* @param shapeMap see above.
* @throws IOException
*/
private void outputSummary(ExcelWriter writer, TreeMap<Coord3D,
ROIShape> shapeMap)
{
int rowIndex = 0;
printSummaryHeader(writer, rowIndex);
rowIndex++;
Coord3D start = shapeMap.firstKey();
Coord3D end = shapeMap.lastKey();
Coord3D coord;
List<Integer> channels = new ArrayList<Integer>(channelName.keySet());
Set<Coord3D> keys;
Iterator<Coord3D> i;
for (Integer c : channels) {
keys = shapeMap.keySet();
i = keys.iterator();
while (i.hasNext()) {
coord = (Coord3D) i.next();
populateData(coord, c);
outputSummaryRow(writer, rowIndex, c, coord.getZSection(),
coord.getTimePoint());
rowIndex++;
}
}
}
/**
* Adds the any remaining fields (min, max, mean, stdDev) to the file being
* saved.
*
* @param writer The Excel writer.
* @param rowIndex The selected row.
* @param channel The channel to output.
* @param z z-section to output.
* @param t timepoint to output.
*/
private void outputSummaryRow(ExcelWriter writer, int rowIndex,
Integer channel, int z, int t)
{
String name = channelName.get(channel);
writer.writeElement(rowIndex, 0, name);
writer.writeElement(rowIndex, 1, ""+(z+1));
writer.writeElement(rowIndex, 2, ""+(t+1));
int col;
String v;
for (int y = 0 ; y < channelSummaryTable.getRowCount() ; y++)
{
col = getColumn(name);
if (col == -1)
continue;
v = (String) channelSummaryTable.getValueAt(y, col);
if (v != null) {
if (v.contains(".") && v.contains(",")) {
v = v.replace(".", "");
v = v.replace(",", ".");
}
writer.writeElement(rowIndex, 3+y, new Double(v));
}
}
}
/**
* Returns the column for the name equal to the string.
*
* @param name see above.
* @return see above.
*/
private int getColumn(String name)
{
for (int i = 0 ; i < channelSummaryModel.getColumnCount(); i++)
if (channelSummaryModel.getColumnName(i).equals(name))
return i;
return -1;
}
/**
* Returns <code>true</code> if the user has selected the summary channel,
* <code>false</code> otherwise.
*
* @param selection see above.
* @return see above.
*/
private boolean channelSummarySelected(List<Integer> selection)
{
for (Integer value: selection)
if (value == ChannelSelectionForm.SUMMARYVALUE)
return true;
return false;
}
/**
* Writes the header information for the file, image, projects, dataset.
*
* @param writer The Excel writer.
* @param rowIndex The selected row.
* @param currentCoord The coord of the shape being written.
* @throws IOException Thrown if the data cannot be written.
*/
private void writeHeader(ExcelWriter writer, int rowIndex,
Coord3D currentCoord)
{
writer.writeElement(rowIndex, 0 , "Image ");
writer.writeElement(rowIndex, 1 , model.getImageName());
rowIndex++;
writer.writeElement(rowIndex, 0 , "Z ");
writer.writeElement(rowIndex, 1 , (currentCoord.getZSection()+1));
rowIndex++;
writer.writeElement(rowIndex, 0 , "T ");
writer.writeElement(rowIndex, 1 , (currentCoord.getTimePoint()+1));
rowIndex++;
}
/**
* Writes the channel intensities and stats to the files.
*
* @param writer The Excel writer.
* @param rowIndex The selected row.
* @param coord The specified coordinate.
* @param channel The channel to output.
*/
private void writeData(ExcelWriter writer, int rowIndex, Coord3D coord,
int channel)
{
populateData(coord, channel);
writer.writeTableToSheet(rowIndex, 0, tableModel);
}
/** Shows the intensity results dialog. */
private void showIntensityResults()
{
populateChannel();
UIUtilities.setLocationRelativeToAndSizeToWindow(this, intensityDialog,
intensityTableSize);
}
/**
* 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>.
* @param controller Reference to the Controller. Mustn't be <code>null</code>.
*/
IntensityView(MeasurementViewerUI view, MeasurementViewerModel model, MeasurementViewerControl controller)
{
if (view == null)
throw new IllegalArgumentException("No view.");
if (model == null)
throw new IllegalArgumentException("No model.");
this.view = view;
this.model = model;
this.controller = controller;
selectedChannelName = "";
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);
}
/** Invokes when ROI are removed. */
void onFigureRemoved()
{
channelSelection.setEnabled(false);
showIntensityTable.setEnabled(false);
saveButton.setEnabled(false);
channelSelection.setVisible(false);
channelSummaryTable.setVisible(false);
}
/**
* 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();
if (ROIStats == null || ROIStats.size() == 0)
return;
state = State.ANALYSING;
clearMaps();
shapeStatsList = new TreeMap<Coord3D, Map<StatsType, Map>>(new Coord3D());
pixelStats =
new TreeMap<Coord3D, Map<Integer, ROIShapeStatsSimple>>(new Coord3D());
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 LinkedHashMap<String, Integer>();
int minZ = Integer.MAX_VALUE, maxZ = Integer.MIN_VALUE;
int minT = Integer.MAX_VALUE, maxT = Integer.MIN_VALUE;
int cT = model.getDefaultT();
int cZ = model.getDefaultZ();
clearAllValues();
Coord3D c3D;
Map<StatsType, Map> shapeStats;
ChannelData channelData;
int channel;
Iterator<ChannelData> i;
List<ChannelData> metadata = model.getMetadata();
Set<Figure> statsMissingFigures = new HashSet<Figure>();
boolean hasData = false;
while (j.hasNext())
{
entry = (Entry) j.next();
shape = (ROIShape) entry.getKey();
c3D = shape.getCoord3D();
if (c3D.getZSection() == cZ) {
minT = Math.min(minT, c3D.getTimePoint());
maxT = Math.max(maxT, c3D.getTimePoint());
}
if (c3D.getTimePoint() == cT) {
minZ = Math.min(minZ, c3D.getZSection());
maxZ = Math.max(maxZ, c3D.getZSection());
}
shapeMap.put(c3D, shape);
if (shape.getFigure() instanceof MeasureTextFigure)
{
state = State.READY;
return;
}
shapeStats = AnalysisStatsWrapper.convertStats(
(Map) entry.getValue());
if (shapeStats != null) {
shapeStatsList.put(c3D, shapeStats);
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));
pixelStats.put(c3D, shapeStats.get(StatsType.PIXELDATA));
}
if (cT == c3D.getTimePoint() && cZ == c3D.getZSection()) {
if (shapeStats != null)
// data for current plane is there, can be displayed
hasData = true;
else
// data is missing for current plane, analysis has to be
// kicked off for the specific figure
statsMissingFigures.add(shape.getFigure());
}
/* really inefficient but hey.... quick hack just now till refactor */
channelName.clear();
nameMap.clear();
channelColour.clear();
i = metadata.iterator();
List<String> names = new ArrayList<String>();
String name;
while (i.hasNext()) {
channelData = i.next();
channel = channelData.getIndex();
if (model.isChannelActive(channel))
{
name = channelData.getChannelLabeling();
if (names.contains(name)) name += " "+channel;
channelName.put(channel, name);
nameMap.put(channelName.get(channel), channel);
channelColour.put(channel,
(Color) model.getActiveChannels().get(channel));
}
}
}
if (!hasData) {
if (!statsMissingFigures.isEmpty())
controller.analyseFigures(statsMissingFigures);
else
state = State.READY;
channelSummaryTable.setVisible(false);
clearMaps();
return;
}
if (channelName.size() != channelColour.size() || nameMap.size() == 0)
{
createComboBox();
channelSelection.setVisible(channelSelection.getItemCount() > 0);
List<String> names = channelSummaryModel.getRowNames();
List<String> channelNames = new ArrayList<String>();
Double data[][] = new Double[channelName.size()][names.size()];
channelSummaryModel = new ChannelSummaryModel(names, channelNames,
data);
channelSummaryTable.setModel(channelSummaryModel);
if (intensityDialog != null) intensityDialog.setVisible(false);
state = State.READY;
showIntensityTable.setEnabled(channelSelection.isVisible());
saveButton.setEnabled(tableModel.getRowCount() > 0);
return;
}
maxZ = maxZ+1;
minZ = minZ+1;
maxT = maxT+1;
minT = minT+1;
createComboBox();
channelSelection.setVisible(channelSelection.getItemCount() > 0);
showIntensityTable.setEnabled(channelSelection.isVisible());
Object[] nameColour = (Object[]) channelSelection.getSelectedItem();
String string = (String) nameColour[1];
selectedChannel = nameMap.get(string);
zSlider.removeChangeListener(this);
tSlider.removeChangeListener(this);
zSlider.setMaximum(maxZ);
zSlider.setMinimum(minZ);
tSlider.setMaximum(maxT);
tSlider.setMinimum(minT);
zSlider.setVisible((maxZ != minZ));
tSlider.setVisible((maxT != minT));
tSlider.setValue(model.getCurrentView().getTimePoint()+1);
zSlider.setValue(model.getCurrentView().getZSection()+1);
zSlider.addChangeListener(this);
tSlider.addChangeListener(this);
coord = new Coord3D(zSlider.getValue()-1, tSlider.getValue()-1);
shape = shapeMap.get(coord);
populateData(coord, selectedChannel);
formatPlane();
saveButton.setEnabled(tableModel.getRowCount() > 0);
state = State.READY;
}
/** Invokes when figures are selected. */
void onFigureSelected()
{
Set<Figure> selectedFigures =
view.getDrawingView().getSelectedFigures();
if (CollectionUtils.isEmpty(selectedFigures)) {
boolean row = tableModel.getRowCount() > 1;
showIntensityTable.setEnabled(row);
channelSelection.setEnabled(row);
saveButton.setEnabled(row);
} else {
boolean size = channelSelection.getModel().getSize() > 0;
channelSelection.setEnabled(size);
showIntensityTable.setEnabled(size);
saveButton.setEnabled(size);
}
}
/**
* Indicates any on-going analysis.
*
* @param analyse Passes <code>true</code> when analyzing,
* <code>false</code> otherwise.
*/
void onAnalysed(boolean analyse)
{
if (!analyse) {
onFigureSelected();
} else {
showIntensityTable.setEnabled(false);
saveButton.setEnabled(false);
}
zSlider.setEnabled(!analyse);
tSlider.setEnabled(!analyse);
if(!analyse)
state = State.READY;
}
/**
* Implemented as specified by the I/F {@link TabPaneInterface}
* @see TabPaneInterface#getIndex()
*/
public int getIndex() {return INDEX; }
/**
* Reacts to the controls.
* @see ActionListener#actionPerformed(ActionEvent)
*/
public void actionPerformed(ActionEvent e)
{
if (state == State.ANALYSING)
return;
int index = Integer.parseInt(e.getActionCommand());
switch (index) {
case CHANNEL_SELECTION:
populateChannel();
break;
case SAVE_ACTION:
saveResults();
break;
case SHOW_TABLE_ACTION:
showIntensityResults();
}
}
/**
* Reacts to slider moves.
* @see ChangeListener#stateChanged(ChangeEvent)
*/
public void stateChanged(ChangeEvent evt)
{
Object src = evt.getSource();
if (src == zSlider || src == tSlider) {
formatPlane();
handleSliderReleased();
}
}
/**
* Listens to property fired by {@link #zSlider} or {@link #tSlider}.
* @see ChangeListener#stateChanged(ChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt)
{
String name = evt.getPropertyName();
if (OneKnobSlider.ONE_KNOB_RELEASED_PROPERTY.equals(name)) {
handleSliderReleased();
}
}
}