/*
* Copyright 2004-2010 Information & Software Engineering Group (188/1)
* Institute of Software Technology and Interactive Systems
* Vienna University of Technology, Austria
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.ifs.tuwien.ac.at/dm/somtoolbox/license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package at.tuwien.ifs.somtoolbox.visualization;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerListModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import cern.colt.matrix.DoubleMatrix2D;
import cern.colt.matrix.impl.DenseDoubleMatrix2D;
import cern.jet.math.Functions;
import at.tuwien.ifs.somtoolbox.SOMToolboxException;
import at.tuwien.ifs.somtoolbox.data.SOMVisualisationData;
import at.tuwien.ifs.somtoolbox.layers.AdaptiveCoordinatesVirtualLayer;
import at.tuwien.ifs.somtoolbox.layers.GrowingLayer;
import at.tuwien.ifs.somtoolbox.layers.LayerAccessException;
import at.tuwien.ifs.somtoolbox.models.GrowingSOM;
import at.tuwien.ifs.somtoolbox.util.StringUtils;
import at.tuwien.ifs.somtoolbox.util.UiUtils;
/**
* Implementation of the adaptive coordinates method.
*
* @author Timo Kropp (0627880)
* @author Goran Jovanov (0727760)
* @author Rudolf Mayer
* @version $Id: AdaptiveCoordinatesVisualizer.java 3883 2010-11-02 17:13:23Z frank $
*/
public class AdaptiveCoordinatesVisualizer extends AbstractMatrixVisualizer implements BackgroundImageVisualizer {
private int dotSize = 7;
private int fontSize = 12;
private boolean showUnitNames = false;
private boolean showInputNames = false;
private boolean showDots = true;
private boolean showHitHisto = true;
private double selectedThreshold;
public AdaptiveCoordinatesVisualizer() {
NUM_VISUALIZATIONS = 1;
VISUALIZATION_NAMES = new String[] { "Adaptive Coordinates" };
VISUALIZATION_SHORT_NAMES = new String[] { "AdaptCoord" };
VISUALIZATION_DESCRIPTIONS = new String[] { "Adaptive Coordinates" };
setInterpolate(false);
preferredScaleFactor = 2;
neededInputObjects = new String[] { SOMVisualisationData.ADAPTIVE_COORDINATES,
SOMVisualisationData.INPUT_VECTOR };
if (!GraphicsEnvironment.isHeadless()) {
controlPanel = new AdaptiveCoordinatesControlPanel();
}
}
@Override
protected void checkNeededObjectsAvailable(GrowingSOM gsom) throws SOMToolboxException {
super.checkNeededObjectsAvailable(gsom);
// TODO: in the future, we should be able to create the .adaptiveCoord file with training newly..
// if (gsom.getSharedInputObjects().getData(neededInputObjects[0]) == null
// && gsom.getSharedInputObjects().getData(neededInputObjects[1]) == null) {
// throw new SOMToolboxException("You need to specify at least one out of " + neededInputObjects[0] + " or "
// + neededInputObjects[1]);
// }
}
@Override
public BufferedImage createVisualization(int variantIndex, GrowingSOM gsom, int width, int height)
throws SOMToolboxException {
checkNeededObjectsAvailable(gsom);
final GrowingLayer layer = gsom.getLayer();
AdaptiveCoordinatesVirtualLayer virtualLayer = gsom.getSharedInputObjects().getAdaptiveCoordinates();
if (virtualLayer == null) {
// TODO: compute on the fly!
}
if (controlPanel != null) {
((AdaptiveCoordinatesControlPanel) controlPanel).setThresholds(virtualLayer.getThresholds());
} else {
selectedThreshold = virtualLayer.getThresholds()[0];
}
BufferedImage bufferedImage = null;
DoubleMatrix2D matrix = null;
if (showHitHisto) {
matrix = computeHitHistogram(gsom);
matrix.assign(Functions.div(maximumMatrixValue));// normalisation
} else {
matrix = new DenseDoubleMatrix2D(layer.getYSize(), layer.getXSize());
}
bufferedImage = super.createImage(gsom, matrix, width, height, interpolate);
drawPoints(bufferedImage, gsom.getLayer(), virtualLayer, width, height);
return bufferedImage;
}
private void drawPoints(BufferedImage bufferedImage, GrowingLayer layer,
AdaptiveCoordinatesVirtualLayer virtualLayer, int width, int height) throws LayerAccessException {
Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
g.setColor(Color.black);
g.setStroke(new BasicStroke(0, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1f, new float[] { 5f }, 5f));
g.setFont(new Font("Arial", Font.PLAIN, fontSize));
double unitWidth = width / layer.getXSize();
double unitHeight = height / layer.getYSize();
for (int x = 0; x < layer.getXSize(); x++) {
for (int y = 0; y < layer.getYSize(); y++) {
int posX = (int) (unitWidth * virtualLayer.getVirtualUnit(selectedThreshold, x, y).getAXPos()
+ unitWidth / 2 + 0.5);
int posY = (int) (unitHeight * virtualLayer.getVirtualUnit(selectedThreshold, x, y).getAYPos()
+ unitHeight / 2 + 0.5);
if (showDots) {
g.fillOval(posX - (int) (dotSize / 2 + 0.5), posY - (int) (dotSize / 2 + 0.5), dotSize, dotSize);
}
if (showUnitNames) {
g.drawString(layer.getUnit(x, y).toString(), posX + dotSize / 2, posY);
}
if (showInputNames && layer.getUnit(x, y).getMappedInputNames() != null) {
String labels = "";
for (int i = 0; i < layer.getUnit(x, y).getMappedInputNames().length; i++) {
labels = labels + layer.getUnit(x, y).getMappedInputNames()[i] + " ";
}
g.drawString(labels, posX + dotSize / 2, posY + fontSize - 2);
}
}
}
}
protected class AdaptiveCoordinatesControlPanel extends VisualizationControlPanel {
private static final long serialVersionUID = 1L;
private JSpinner spinnerThresholds;
private JLabel labelThresholds = UiUtils.makeLabelWithTooltip("Thresholds",
"Select the thresholds for the Adaptive Coordinates");
public AdaptiveCoordinatesControlPanel() {
super("Adaptive Coordinate Control");
SpinnerListModel listModel = new SpinnerListModel();
spinnerThresholds = new JSpinner(listModel);
((JSpinner.DefaultEditor) spinnerThresholds.getEditor()).getTextField().setEditable(false);
spinnerThresholds.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
selectedThreshold = (Double) spinnerThresholds.getValue();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
JSpinner dotRadiusSpinner = new JSpinner(new SpinnerNumberModel(dotSize, 1, 100.0, 1));
dotRadiusSpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
dotSize = (int) ((Double) ((JSpinner) e.getSource()).getValue()).doubleValue();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
final SpinnerNumberModel fontsizeSpinnerModel = new SpinnerNumberModel(fontSize, 1, 30, 1);
JSpinner fontsizeSpinner = new JSpinner(fontsizeSpinnerModel);
fontsizeSpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
fontSize = fontsizeSpinnerModel.getNumber().intValue();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
// Show Unit Names checkbox
JCheckBox boxShowUnitNames = new JCheckBox("Unit labels", showUnitNames);
boxShowUnitNames.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showUnitNames = ((JCheckBox) e.getSource()).isSelected();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
// Show Unit Labels
JCheckBox boxShowInputNames = new JCheckBox("Input labels", showInputNames);
boxShowInputNames.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showInputNames = ((JCheckBox) e.getSource()).isSelected();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
// Show Dots
JCheckBox boxShowDots = new JCheckBox("Adaptive Coords", showDots);
boxShowDots.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showDots = ((JCheckBox) e.getSource()).isSelected();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
// Show Hit Histogram
JCheckBox boxShowHitHisto = new JCheckBox("Hit Histogram");
boxShowHitHisto.setSelected(showHitHisto);
boxShowHitHisto.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showHitHisto = ((JCheckBox) e.getSource()).isSelected();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
JPanel acPanel = new JPanel(new GridBagLayout());
GridBagConstraints constr = new GridBagConstraints();
constr.fill = GridBagConstraints.HORIZONTAL;
constr.anchor = GridBagConstraints.NORTHWEST;
constr.insets = new Insets(2, 2, 2, 2);
constr.gridy = 0;
acPanel.add(labelThresholds, constr);
acPanel.add(spinnerThresholds, constr);
constr.gridy += 1;
JPanel dotSizePanel = new JPanel();
dotSizePanel.add(UiUtils.makeLabelWithTooltip("Dot size: ",
"The radius of the dots indicating the adaptive coordinates"));
dotSizePanel.add(dotRadiusSpinner);
acPanel.add(dotSizePanel, constr);
JPanel fontSizePanel = new JPanel();
fontSizePanel.add(new JLabel("Font size: "));
fontSizePanel.add(fontsizeSpinner);
acPanel.add(fontSizePanel, constr);
constr.gridy += 1;
acPanel.add(boxShowUnitNames, constr);
acPanel.add(boxShowInputNames, constr);
constr.gridy += 1;
acPanel.add(boxShowDots, constr);
acPanel.add(boxShowHitHisto, constr);
add(acPanel, c);
}
public void setThresholds(double... thresholds) {
SpinnerListModel model = (SpinnerListModel) spinnerThresholds.getModel();
// find out whether we need to update the model
boolean modelEqual = true;
if (model == null || model.getList().size() == 0) { // empty model => replace
modelEqual = false;
} else if (model.getList().size() != thresholds.length) { // different size => replace
modelEqual = false;
} else { // same size => check contents
for (int i = 0; i < model.getList().size(); i++) {
if (!model.getList().get(i).equals(thresholds[i])) {
modelEqual = false;
break;
}
}
}
if (!modelEqual) {
// for some reason, Arrays.asList() didn't return a List that the SpinnerListModel can handle
List<Double> list = new ArrayList<Double>(thresholds.length);
for (double threshold : thresholds) {
list.add(threshold);
}
model.setList(list);
spinnerThresholds.setToolTipText("Select the Adaptive Coordinates threshold ("
+ StringUtils.toString(thresholds, "", "") + ")");
spinnerThresholds.setVisible(model.getList().size() > 0);
labelThresholds.setVisible(spinnerThresholds.isVisible());
selectedThreshold = (Double) model.getValue();
}
}
}
@Override
protected String getCacheKey(GrowingSOM gsom, int index, int width, int height) {
return super.getCacheKey(gsom, index, width, height) + CACHE_KEY_SECTION_SEPARATOR
+ buildCacheKey("threshold:" + selectedThreshold, //
"unitLabels:" + showUnitNames, "inputLabels:" + showInputNames, "fontSize:" + fontSize,//
"showDots:" + showDots, "dotSize:" + dotSize, //
"hitHisto:" + showHitHisto);
}
/**
* Overrides {@link AbstractBackgroundImageVisualizer#needsAdditionalFiles()}, as we need only one of the two
* possible input files to create this visualisation. If the adaptive coordinates file is present, it will be used
* directly, otherwise it can be created from the input vectors.
*/
@Override
public String[] needsAdditionalFiles() {
String[] dataFiles = super.needsAdditionalFiles();
// TODO: in the future, we should be able to create the .adaptiveCoord file with training newly..
// if (dataFiles.length < 2) { // we need only one of the files
// return null;
// } else {
// return dataFiles;
// }
return dataFiles;
}
}