/*
* 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.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.awt.image.BufferedImage;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import cern.colt.function.DoubleFunction;
import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.DoubleMatrix2D;
import cern.jet.math.Functions;
import at.tuwien.ifs.somtoolbox.SOMToolboxException;
import at.tuwien.ifs.somtoolbox.layers.GrowingLayer;
import at.tuwien.ifs.somtoolbox.models.GrowingSOM;
import at.tuwien.ifs.somtoolbox.util.ImageUtils;
import at.tuwien.ifs.somtoolbox.util.UiUtils;
import at.tuwien.ifs.somtoolbox.util.VisualisationUtils;
/**
* @author Rudolf Mayer
* @version $Id: FuzzyColourCodingVisualiser.java 3869 2010-10-21 15:56:09Z mayer $
*/
public class FuzzyColourCodingVisualiser extends AbstractBackgroundImageVisualizer {
protected double T = 1;
protected int r = 1;
protected boolean showUnitNodes = true;
protected boolean showConnectingLines = true;
protected boolean showColourCoding = true;
public FuzzyColourCodingVisualiser() {
NUM_VISUALIZATIONS = 1;
VISUALIZATION_NAMES = new String[] { "Fuzzy Colouring" };
VISUALIZATION_SHORT_NAMES = VISUALIZATION_NAMES;
VISUALIZATION_DESCRIPTIONS = new String[] { "Inplementation of Fuzzy Colouring as described in \""
+ "Johan Himberg. A SOM based cluster visualization and its application for false coloring.\n"
+ " In Proceedings of the IEEE-INNS-ENNS International Joint Conference on Neural Networks (IJCNN 2000), vol. 3, pp. 587-592,\n"
+ " Como, Italy, 2000. " };
if (!GraphicsEnvironment.isHeadless()) {
controlPanel = new FuzzyColouringControlPanel();
}
preferredScaleFactor = 1;
}
@Override
public BufferedImage createVisualization(int variantIndex, GrowingSOM gsom, int width, int height)
throws SOMToolboxException {
GrowingLayer layer = gsom.getLayer();
BufferedImage res = ImageUtils.createEmptyImage(width, height);
Graphics2D g = (Graphics2D) res.getGraphics();
double unitWidth = width / (double) layer.getXSize();
double unitHeight = height / (double) layer.getYSize();
// set up array of unit coordinates
Point2D.Double[][] locations = new Point2D.Double[layer.getXSize()][layer.getYSize()];
for (int i = 0; i < layer.getXSize(); i++) {
for (int j = 0; j < layer.getYSize(); j++) {
locations[i][j] = new Double(i, j);
}
}
// construct a dissimilarity matrix of the model vectors
DoubleMatrix2D unitDistanceMatrix = layer.getUnitDistanceMatrix();
// transform to a similarity matrix - Equation (1) in Himberg 2000.
DoubleMatrix2D similarityMatrix = unitDistanceMatrix.copy();
similarityMatrix.assign(new DoubleFunction() {
@Override
public double apply(double argument) {
return Math.exp(-(argument * argument / T));
}
});
// normalise each row so that it sums up to 1
for (int i = 0; i < similarityMatrix.rows(); i++) {
DoubleMatrix1D row = similarityMatrix.viewRow(i);
final double sum = row.aggregate(Functions.plus, Functions.identity);
row.assign(new DoubleFunction() {
@Override
public double apply(double argument) {
return argument / sum;
}
});
}
// contraction process
// FIXME: check this with the Matlab implementation, it seems that is a bit different to the paper
// http://www.cis.hut.fi/somtoolbox/package/docs2/som_fuzzycolor.html)
for (int k = 0; k < r; k++) {
Double[][] newLocations = new Double[layer.getXSize()][layer.getYSize()];
for (int x = 0; x < layer.getXSize(); x++) {
for (int y = 0; y < layer.getYSize(); y++) {
Double loc = locations[x][y];
Double newLoc = new Double(loc.x, loc.y);
int unitIndex = layer.getUnitIndex(x, y);
for (int x1 = 0; x1 < layer.getXSize(); x1++) {
for (int y1 = 0; y1 < layer.getYSize(); y1++) {
if (x != x1 && y != y1) {
int otherUnitIndex = layer.getUnitIndex(x1, y1);
double similarity = similarityMatrix.getQuick(unitIndex, otherUnitIndex);
// move towards that location
double diffX = locations[x1][y1].x - loc.x;
double diffY = locations[x1][y1].y - loc.y;
newLoc.setLocation(newLoc.x + diffX * similarity, newLoc.y + diffY * similarity);
}
}
}
newLocations[x][y] = newLoc;
}
}
locations = newLocations;
}
// obtain RGB slice according to the (contracted) unit positions, and draw visualisation
Color[][] colours = new Color[locations.length][locations[0].length];
if (showColourCoding) {
double colourZoomX = 255.0 / layer.getXSize();
double colourZoomY = 255.0 / layer.getYSize();
for (int i = 0; i < layer.getXSize(); i++) {
for (int j = 0; j < layer.getYSize(); j++) {
Double loc = locations[i][j];
// colour the SOM unit
colours[i][j] = new Color(
// red is 255 on the top, and 0 on the bottom
(int) Math.round(colourZoomY * (layer.getYSize() - loc.y)),
// green is 255 on the left, and 0 on the right
(int) Math.round(colourZoomX * (layer.getXSize() - loc.x)),
// blue is 0 on the top, and 255 on the bottom
(int) Math.round(colourZoomY * loc.y));
g.setColor(colours[i][j]);
g.fillRect((int) (i * unitWidth), (int) (j * unitHeight), (int) unitWidth, (int) unitHeight);
}
}
}
if (showUnitNodes) {
int markerHeight = (int) (unitHeight / 5);
int markerWidth = (int) (unitWidth / 5);
for (int i = 0; i < layer.getXSize(); i++) {
for (int j = 0; j < layer.getYSize(); j++) {
Double loc = locations[i][j];
// draw the nodes
g.setColor(Color.black);
Point markerPos = getMarkerPos(unitWidth, unitHeight, markerWidth, markerHeight, loc);
VisualisationUtils.drawMarker(g, markerWidth, markerHeight, markerPos);
}
}
}
if (showConnectingLines) {
g.setColor(Color.black);
int lineWidth = (int) Math.round(unitWidth / 20);
int lineHeight = (int) Math.round(unitHeight / 20);
// draw the connections between nodes; can do this only after colouring, as it needs to be on top
for (int i = 0; i < layer.getXSize(); i++) {
for (int j = 0; j < layer.getYSize(); j++) {
// draw the nodes connections to the right
Point start = getLinePos(unitWidth, unitHeight, locations[i][j]);
if (i + 1 < layer.getXSize()) {
Point end = getLinePos(unitWidth, unitHeight, locations[i + 1][j]);
VisualisationUtils.drawThickLine(g, start.x, start.y, end.x, end.y, lineWidth, lineHeight);
}
// draw the nodes connections to the right
if (j + 1 < layer.getYSize()) {
Point end = getLinePos(unitWidth, unitHeight, locations[i][j + 1]);
VisualisationUtils.drawThickLine(g, start.x, start.y, end.x, end.y, lineWidth, lineHeight);
}
}
}
}
return res;
}
private Point getMarkerPos(double unitWidth, double unitHeight, int markerWidth, int markerHeight, Double loc) {
return new Point((int) Math.round(loc.x * unitWidth + (unitWidth - markerWidth) / 2), (int) Math.round(loc.y
* unitHeight + (unitHeight - markerHeight) / 2));
}
private Point getLinePos(double unitWidth, double unitHeight, Double loc) {
return new Point((int) Math.round(loc.x * unitWidth + unitWidth / 2), (int) Math.round(loc.y * unitHeight
+ unitHeight / 2));
}
@Override
protected String getCacheKey(GrowingSOM gsom, int currentVariant, int width, int height) {
return appendToCacheKey(gsom, currentVariant, width, height, "T:" + T, "r:" + r, "colorCoding:"
+ showColourCoding, "showUnits:" + showUnitNodes, "showConnections:" + showConnectingLines);
}
protected class FuzzyColouringControlPanel extends VisualizationControlPanel {
private static final long serialVersionUID = 1L;
public FuzzyColouringControlPanel() {
super("Fuzzy (False) Colouring");
final JSpinner tSpinner = new JSpinner(new SpinnerNumberModel(T, 0.01, 2, 0.01));
tSpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
T = ((SpinnerNumberModel) tSpinner.getModel()).getNumber().doubleValue();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
final JSpinner rSpinner = new JSpinner(new SpinnerNumberModel(r, 1, 30, 1));
rSpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
r = ((SpinnerNumberModel) rSpinner.getModel()).getNumber().intValue();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
final JCheckBox boxShowConnectingLines = new JCheckBox("Connect nodes", showConnectingLines);
boxShowConnectingLines.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showConnectingLines = ((JCheckBox) e.getSource()).isSelected();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
JCheckBox boxShowNodes = new JCheckBox("Plot units", showUnitNodes);
boxShowNodes.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showUnitNodes = ((JCheckBox) e.getSource()).isSelected();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
JCheckBox boxShowColours = new JCheckBox("Colour coding", showColourCoding);
boxShowColours.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showColourCoding = ((JCheckBox) e.getSource()).isSelected();
if (visualizationUpdateListener != null) {
visualizationUpdateListener.updateVisualization();
}
}
});
JPanel colouringPanel = new JPanel(new GridBagLayout());
GridBagConstraints constr = new GridBagConstraints();
constr.fill = GridBagConstraints.HORIZONTAL;
constr.anchor = GridBagConstraints.NORTHEAST;
constr.insets = new Insets(2, 2, 2, 2);
constr.gridy = 0;
colouringPanel.add(UiUtils.makeLabelWithTooltip("T", "Value of contraction parameter T"), constr);
colouringPanel.add(tSpinner, constr);
constr.gridy += 1;
colouringPanel.add(UiUtils.makeLabelWithTooltip("r", "Number of contraction rounds"), constr);
colouringPanel.add(rSpinner, constr);
constr.gridy += 1;
colouringPanel.add(boxShowNodes, constr);
colouringPanel.add(boxShowConnectingLines, constr);
constr.gridy += 1;
colouringPanel.add(boxShowColours, constr);
add(colouringPanel, c);
}
}
}