/* * 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.Rectangle; import java.awt.image.BufferedImage; import java.util.logging.Level; import cern.colt.matrix.DoubleMatrix2D; import cern.colt.matrix.impl.DenseDoubleMatrix2D; import flanagan.interpolation.BiCubicSplineFast; import at.tuwien.ifs.commons.util.MathUtils; import at.tuwien.ifs.somtoolbox.SOMToolboxException; 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.StdErrProgressWriter; import at.tuwien.ifs.somtoolbox.visualization.contourplot.ContourPlot; /** * @author Thomas Lidy * @author Rudolf Mayer * @version $Id: AbstractMatrixVisualizer.java 3883 2010-11-02 17:13:23Z frank $ */ public abstract class AbstractMatrixVisualizer extends AbstractBackgroundImageVisualizer implements MatrixVisualizer { protected Palette palette = Palettes.getPaletteByName(getPreferredPaletteName()); protected double minimumMatrixValue = -1; protected double maximumMatrixValue = -1; public double getMinimumMatrixValue() { return minimumMatrixValue; } public double getMaximumMatrixValue() { return maximumMatrixValue; } /** * overriding the method in the superclass as we have a different cache key, and to set the min & max matrix values * to -1 */ @Override public BufferedImage getVisualization(int index, GrowingSOM gsom, int width, int height) throws SOMToolboxException { if (controlPanel != null) { // we don't always have this initialised, especially when we just create the // visualisation w/o the viewer controlPanel.updateZDim(gsom.getLayer().getZSize()); } String cacheKey = getCacheKey(gsom, index, width, height); logImageCache(cacheKey); if (cache.get(cacheKey) == null) { minimumMatrixValue = Double.MAX_VALUE; maximumMatrixValue = Double.MIN_VALUE; cache.put(cacheKey, createVisualization(index, gsom, width, height)); } return cache.get(cacheKey); } @Override protected String getCacheKey(GrowingSOM gsom, int index, int width, int height) { return super.getCacheKey(gsom, index, width, height) + CACHE_KEY_SECTION_SEPARATOR + buildCacheKey("palette:" + palette.getShortName(), "reversed:" + palette.isReversed(), "interpolate:" + interpolate, "contour:" + contourMode + (contourMode != ContourMode.None ? "/" + contourInterpolationMode + "/" + numberOfContours : "")); } protected void setInterpolate(boolean interpolate) { this.interpolate = interpolate; if (controlPanel != null) { // we don't always have this initialised, especially when we just create the // visualisation w/o the viewer this.controlPanel.interpolateCheckbox.setSelected(interpolate); } } protected void drawContour(Graphics2D g, DoubleMatrix2D matrix, int width, int height, boolean fill) throws SOMToolboxException { ContourPlot plot = new ContourPlot(matrix.columns(), matrix.rows(), width, height); plot.setFill(fill); plot.setPalette(palette.getColors()); plot.setNumberOfContours(numberOfContours); plot.setLogInterpolation(contourInterpolationMode == ContourInterpolationMode.Log); plot.setZedMatrix(matrix); plot.paint(g); } /** * Creates an image from a matrix of heights. * * @param gsom The GrowingSOM to generate the image for * @param matrix The matrix with the calucalted heights. * @param width the desired width of the image, in pixels * @param height the desired height of the image, in pixels. * @param interpolate indicates whether the image should be interpolated if the widht or height exceeds the matrix * dimensions. * @return the BufferedImage for those settings */ protected BufferedImage createImage(GrowingSOM gsom, DoubleMatrix2D matrix, int width, int height, boolean interpolate) throws SOMToolboxException { /** drawing stuff * */ BufferedImage res = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = (Graphics2D) res.getGraphics(); if (contourMode == ContourMode.Full) { drawContour(g, matrix, width, height, true); return res; } drawBackground(width, height, g); int unitWidth = width / gsom.getLayer().getXSize(); int unitHeight = height / gsom.getLayer().getYSize(); if (interpolate) { BiCubicSplineFast bcs = computeSpline(gsom, matrix, width, height, unitWidth, unitHeight); int elevation = 0; int stepSize = Math.max(5000, height * width / 500); StdErrProgressWriter progress = new StdErrProgressWriter(height * width, "Creating interpolated matrix image, pixel ", stepSize); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // adapted to mnemonic (sparse) SOMs try { if (gsom.getLayer().getUnit((x / unitWidth), (y / unitHeight)) != null) { elevation = (int) Math.round(bcs.interpolate(y, x) * palette.maxColourIndex()); g.setPaint(palette.getColorConstrained(elevation)); g.fill(new Rectangle(x, y, 1, 1)); if (elevation < minimumMatrixValue) { minimumMatrixValue = elevation; } if (elevation > maximumMatrixValue) { maximumMatrixValue = elevation; } } else { // we show an empty (white) unit if this unit is not part of the mnemonic map g.setPaint(Color.WHITE); g.fill(new Rectangle(x, y, 1, 1)); } progress.progress(); } catch (LayerAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** end bicubic spline stuff * */ } else { double factorX = (double) matrix.columns() / (double) gsom.getLayer().getXSize(); double factorY = (double) matrix.rows() / (double) gsom.getLayer().getYSize(); g.setColor(null); int ci = 0; int xOff = 0; int yOff = 0; if (factorX != 1 && factorY != 1) { xOff = (int) Math.round(unitWidth / (factorX * 2)); yOff = (int) Math.round(unitHeight / (factorY * 2)); } for (int y = 0; y < matrix.rows(); y++) { for (int x = 0; x < matrix.columns(); x++) { ci = (int) Math.round(matrix.get(y, x) * palette.maxColourIndex()); Color color = palette.getColor(ci); g.setPaint(color); log.log(Level.FINER, "{0}/{1} => matrix value: {2}, colorIndex: {3}, colour {4}", new Object[] { y, x, matrix.get(y, x), ci, color }); g.fill(new Rectangle(xOff + x * (int) Math.round(unitWidth / factorX), yOff + y * (int) Math.round(unitHeight / factorY), (int) Math.round(unitWidth / factorX), (int) Math.round(unitHeight / factorY))); if (ci < minimumMatrixValue) { minimumMatrixValue = ci; } if (ci > maximumMatrixValue) { maximumMatrixValue = ci; } } } if (factorX != 1 && factorY != 1) { // border ci = (int) Math.round(matrix.get(0, 0) * palette.maxColourIndex()); // top-left g.fill(new Rectangle(0, 0, (int) Math.round(unitWidth / factorX * 2), (int) Math.round(unitHeight / (factorY * 2)))); ci = (int) Math.round(matrix.get(0, matrix.columns() - 1) * palette.maxColourIndex()); // top-right g.fill(new Rectangle(xOff + matrix.columns() * (int) Math.round(unitWidth / factorX), 0, (int) Math.round(unitWidth / (factorX * 2)), (int) Math.round(unitHeight / (factorY * 2)))); ci = (int) Math.round(matrix.get(matrix.rows() - 1, 0) * palette.maxColourIndex()); // bottom-left g.fill(new Rectangle(0, yOff + matrix.rows() * (int) Math.round(unitHeight / factorY), (int) Math.round(unitWidth / (factorX * 2)), (int) Math.round(unitHeight / (factorY * 2)))); ci = (int) Math.round(matrix.get(matrix.rows() - 1, matrix.columns() - 1) * palette.maxColourIndex()); // bottom-right g.fill(new Rectangle(xOff + matrix.columns() * (int) Math.round(unitWidth / factorX), yOff + matrix.rows() * (int) Math.round(unitHeight / factorY), (int) Math.round(unitWidth / (factorX * 2)), (int) Math.round(unitHeight / (factorY * 2)))); for (int x = 0; x < matrix.columns(); x++) { // top border ci = (int) Math.round(matrix.get(0, x) * palette.maxColourIndex()); g.setPaint(palette.getColor(ci)); g.fill(new Rectangle(xOff + x * (int) Math.round(unitWidth / factorX), 0, (int) Math.round(unitWidth / factorX), (int) Math.round(unitHeight / (factorY * 2)))); // bottom border ci = (int) Math.round(matrix.get(matrix.rows() - 1, x) * palette.maxColourIndex()); g.setPaint(palette.getColor(ci)); g.fill(new Rectangle(xOff + x * (int) Math.round(unitWidth / factorX), (yOff + matrix.rows() * (int) Math.round(unitHeight / factorY)), (int) Math.round(unitWidth / factorX), (int) Math.round(unitHeight / (factorY * 2)))); } for (int y = 0; y < matrix.rows(); y++) { // left border ci = (int) Math.round(matrix.get(y, 0) * palette.maxColourIndex()); g.setPaint(palette.getColor(ci)); g.fill(new Rectangle(0, yOff + y * (int) Math.round(unitHeight / factorY), (int) Math.round(unitWidth / (factorX * 2)), (int) Math.round(unitHeight / factorY))); // right border ci = (int) Math.round(matrix.get(y, matrix.columns() - 1) * palette.maxColourIndex()); g.setPaint(palette.getColor(ci)); g.fill(new Rectangle((xOff + matrix.columns() * (int) Math.round(unitWidth / factorX)), yOff + y * (int) Math.round(unitHeight / factorY), (int) Math.round(unitWidth / (factorX * 2)), (int) Math.round(unitHeight / factorY))); } } } if (contourMode == ContourMode.Overlay) { drawContour(g, matrix, width, height, false); } return res; } protected BiCubicSplineFast computeSpline(GrowingSOM gsom, DoubleMatrix2D matrix, int width, int height, int unitWidth, int unitHeight) { DoubleMatrix2D matrixBorders = new DenseDoubleMatrix2D(matrix.rows() + 2, matrix.columns() + 2); matrixBorders.viewPart(1, 1, matrix.rows(), matrix.columns()).assign(matrix); matrixBorders.viewRow(0).assign(matrixBorders.viewRow(1)); matrixBorders.viewRow(matrixBorders.rows() - 1).assign(matrixBorders.viewRow(matrixBorders.rows() - 2)); matrixBorders.viewColumn(0).assign(matrixBorders.viewColumn(1)); matrixBorders.viewColumn(matrixBorders.columns() - 1).assign( matrixBorders.viewColumn(matrixBorders.columns() - 2)); /** start bicubic spline stuff * */ // create support points double factorX = (double) (matrixBorders.columns() - 2) / (double) gsom.getLayer().getXSize(); double factorY = (double) (matrixBorders.rows() - 2) / (double) gsom.getLayer().getYSize(); double[] x1 = new double[matrixBorders.columns()]; x1[0] = 0; for (int x = 0; x < matrixBorders.columns() - 2; x++) { x1[x + 1] = x * unitWidth / factorX + unitWidth / (2 * factorX); } x1[matrixBorders.columns() - 1] = width; double[] x2 = new double[matrixBorders.rows()]; x2[0] = 0; for (int y = 0; y < matrixBorders.rows() - 2; y++) { x2[y + 1] = y * unitHeight / factorY + unitHeight / (2 * factorY); } x2[matrixBorders.rows() - 1] = height; BiCubicSplineFast bcs = new BiCubicSplineFast(x2, x1, matrixBorders.toArray()); return bcs; } protected int constrainWithinPalette(int ci) { return MathUtils.constrainWithin(ci, 0, palette.maxColourIndex()); } @Override public Color[] getPalette() { return palette.getColors(); } @Override public void setPalette(Palette newPalette) { palette = newPalette; } @Override public void reversePalette() { palette.reverse(); } @Override public Palette getCurrentPalette() { return palette; } /** * Default implementation using {@link Palettes#getDefaultPalette()}. Subclasses that want to use a different * palette should overwrite this method. */ @Override public String getPreferredPaletteName() { return Palettes.getDefaultPalette().getName(); } /** Deletes all cached elements that use the {@link Palette} with the given index. */ public void invalidateCache(Palette palette) { for (String key : cache.keySet()) { if (key.contains("palette:" + palette.getShortName())) { cache.remove(key); log.info("Removed cache for: " + key); } } } /** * Computes the hit-histogram from the given {@link GrowingSOM}. Also sets the values of * {@link AbstractMatrixVisualizer#minimumMatrixValue} and {@link AbstractMatrixVisualizer#maximumMatrixValue} */ protected DoubleMatrix2D computeHitHistogram(GrowingSOM gsom) throws LayerAccessException { final GrowingLayer layer = gsom.getLayer(); DoubleMatrix2D matrix = new DenseDoubleMatrix2D(layer.getYSize(), layer.getXSize()); // create matrix from number of hits per unit; also compute min & max of those values for (int x = 0; x < layer.getXSize(); x++) { for (int y = 0; y < layer.getYSize(); y++) { final int numberOfMappedInputs = layer.getUnit(x, y).getNumberOfMappedInputs(); matrix.setQuick(y, x, numberOfMappedInputs); if (numberOfMappedInputs > maximumMatrixValue) { maximumMatrixValue = numberOfMappedInputs; } if (numberOfMappedInputs < minimumMatrixValue) { minimumMatrixValue = numberOfMappedInputs; } } } return matrix; } }