/* * 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.AlphaComposite; import java.awt.Color; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.logging.Logger; import javax.swing.ImageIcon; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.ToolTipManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.jfree.util.PaintList; import cern.colt.matrix.DoubleMatrix2D; import cern.colt.matrix.impl.DenseDoubleMatrix2D; import flanagan.interpolation.BiCubicSplineFast; import at.tuwien.ifs.commons.gui.controls.TitledCollapsiblePanel; import at.tuwien.ifs.somtoolbox.SOMToolboxException; import at.tuwien.ifs.somtoolbox.apps.viewer.MapPNode; import at.tuwien.ifs.somtoolbox.data.SOMVisualisationData; import at.tuwien.ifs.somtoolbox.layers.LayerAccessException; import at.tuwien.ifs.somtoolbox.layers.Unit; import at.tuwien.ifs.somtoolbox.models.GrowingSOM; import at.tuwien.ifs.somtoolbox.util.GridBagConstraintsIFS; import at.tuwien.ifs.somtoolbox.util.ProgressListener; import at.tuwien.ifs.somtoolbox.util.ProgressListenerFactory; import at.tuwien.ifs.somtoolbox.util.VectorTools; import at.tuwien.ifs.somtoolbox.visualization.thematicmap.RegionManager; /** * This visualisation colours the map according to the distribution of classes assigned to the data items. Helper * methods are to be found in the package {@link at.tuwien.ifs.somtoolbox.visualization.thematicmap}. * * @author Taha Abdel Aziz * @author Florian Guggenberger * @author Ewald Peiszer * @author Andrei Grecu * @author Rudolf Mayer * @version $Id: ThematicClassMapVisualizer.java 3883 2010-11-02 17:13:23Z frank $ */ public class ThematicClassMapVisualizer extends AbstractBackgroundImageVisualizer implements BackgroundImageVisualizer { public static final String CLASSMAP_SHORT_NAME = "ClassMap"; private static final int METHOD_ALPHA = 1; private static final int METHOD_BLACK = 3; private static final int METHOD_WHITE = 2; private static final int METHOD_NO_COMBINATION = 0; private static final int METHOD_HSV_MODEL = 5; private int zoom = MapPNode.DEFAULT_UNIT_WIDTH / preferredScaleFactor; double min_visible_class = 0; double gamma = 1; double contrast = 1; double gain = 1; double method = 0; boolean voronoi = true; boolean chessBoard = false; protected Hashtable<String, RegionManager> regionCache = new Hashtable<String, RegionManager>(); private double minimumMatrixValue = 255; private double maximumMatrixValue = -1; private double HSVRANGE = 255; public ThematicClassMapVisualizer() { NUM_VISUALIZATIONS = 1; VISUALIZATION_NAMES = new String[] { "Thematic Class Map" }; VISUALIZATION_SHORT_NAMES = new String[] { CLASSMAP_SHORT_NAME }; VISUALIZATION_DESCRIPTIONS = new String[] { "This visualisation colours the map thematically by the class membership of the units. This visualisation is only available if a class information file is loaded.\n" + "Implementation as described in \"R. Mayer, A. A. Taha, and A. Rauber. Visualising Class Distribution on Self-Organising Maps.\n" + "Proceedings of the International Conference on Artificial Neural Networks (ICANN'07),\n" + "pp 359-368, LNCS 4669, Porto, Portugal, September 9-13, 2007, Springer Verlag.\"" }; neededInputObjects = new String[] { SOMVisualisationData.CLASS_INFO }; // don't initialise the control panel if we have no graphics environment (e.g. in server applications) if (!GraphicsEnvironment.isHeadless()) { try { controlPanel = new ClassVisControlPanel(this); } catch (Throwable e) { Logger.getLogger("at.tuwien.ifs.somtoolbox").severe( "Caught runtime exception/error during graphics init: " + e.getMessage() + "\n Headless environment? " + GraphicsEnvironment.isHeadless()); } } } @Override protected String getCacheKey(GrowingSOM gsom, int currentVariant, int width, int height) { return super.getCacheKey(gsom, currentVariant, width, height) + CACHE_KEY_SECTION_SEPARATOR + buildCacheKey("voronoi:" + voronoi, "chess:" + chessBoard, "minClass:" + min_visible_class) + CACHE_KEY_SECTION_SEPARATOR + buildCacheKey("weight:" + method, "gamma:" + gamma, "contrast:" + contrast, "gain:" + gain); } public void setInitialParams(boolean chessBoard, boolean voronoi, double minVisibleClass) { ClassVisControlPanel classVisControlPanel = (ClassVisControlPanel) controlPanel; classVisControlPanel.minSizeSpinner.setValue(new Double(minVisibleClass * 100)); classVisControlPanel.chessBoardCheckbox.setSelected(chessBoard); classVisControlPanel.voronoiCheckbox.setSelected(voronoi); update(minVisibleClass, 0, 0, 0, METHOD_NO_COMBINATION, voronoi, chessBoard); } private int clamp(int a, int min, int max) { if (a < min) { return min; } if (a > max) { return max; } return a; } @Override public BufferedImage createVisualization(int index, GrowingSOM gsom, int width, int height) throws SOMToolboxException { return createVisualization(index, gsom, width, height, -1); } public BufferedImage createVisualization(int index, GrowingSOM gsom, int width, int height, int ClassID) throws SOMToolboxException { checkNeededObjectsAvailable(gsom); BufferedImage res = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = res.createGraphics(); drawBackground(width, height, g); g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); g.setColor(Color.BLACK); // DistanceMetric metric = gsom.getLayer().getMetric(); int xSize = gsom.getLayer().getXSize(); int ySize = gsom.getLayer().getYSize(); // key for the cache, need to consider width and height as region sizes depend on this // this is specifically needed for the server-side application String key = min_visible_class + "_" + width + "_" + height; if (regionCache.get(key) == null) { try { PaintList paintList = inputObjects.getClassInfo().getPaintList(); // System.out.println("MapPNode.DEFAULT_UNIT_WIDTH: " + MapPNode.DEFAULT_UNIT_WIDTH); // System.out.println("preferredScaleFactor: " + preferredScaleFactor); // System.out.println("zoom: " + zoom); RegionManager regionManager = new RegionManager(inputObjects.getClassInfo(), paintList, width, height, min_visible_class, zoom); ProgressListener progressWriter = ProgressListenerFactory.getInstance().createProgressListener( gsom.getLayer().getNumberOfNotEmptyUnits(), "Creating region ", 10); // int h = 0; for (int j = 0; j < ySize; j++) { for (int i = 0; i < xSize; i++) { Unit unit = gsom.getLayer().getUnit(i, j); if (unit != null && unit.getNumberOfMappedInputs() != 0) { // SOMRegion r = regionManager.addNewRegion(unit); regionManager.addNewRegion(unit); progressWriter.progress(); } } } regionManager.build(); regionCache.put(key, regionManager); } catch (LayerAccessException e) { e.printStackTrace(); return null; } } RegionManager regionManager = regionCache.get(key); regionManager.resetResolvingState(); if (ClassID != -1) { // special mode for showing only one class if regionManager.fillSingleRegion(g, chessBoard, ClassID); } else { regionManager.fillRegions(g, chessBoard); } if (voronoi) { regionManager.drawRegions(g); } // If method != 0 (ie no combination) if (method != METHOD_NO_COMBINATION) { applyColourWeighting(gsom, width, height, res, g); } return res; } private void applyColourWeighting(GrowingSOM gsom, int width, int height, BufferedImage res, Graphics2D g) throws SOMToolboxException { // hack epei UMatrix um = new UMatrix(); // we need the grayscale 256 palette for some of the modifcations Palette grayscale256 = Palettes.getPaletteByName("Grayscale256"); if (grayscale256 == null) { throw new SOMToolboxException("Palette 'Grayscale256' is needed for the U-Matrix modifications!"); } um.setPalette(grayscale256); BufferedImage umbi; // = um.createVisualization(0, gsom, width, height); DoubleMatrix2D umm = um.createUMatrix(gsom); Logger.getLogger("at.tuwien.ifs.somtoolbox").finer("MW(UM) = " + umm.zSum() / (umm.rows() * umm.columns())); VectorTools.normalise(umm); double mw = umm.zSum() / (umm.rows() * umm.columns()); Logger.getLogger("at.tuwien.ifs.somtoolbox").finer("norm-MW(UM) = " + mw); double temp_um; for (int r = 0; r < umm.rows(); r++) { for (int c = 0; c < umm.columns(); c++) { temp_um = ((Math.pow(umm.get(r, c) - mw + 0.5, gamma) - 0.5) * contrast + 0.5) * gain; if (temp_um < 0) { temp_um = 0; } if (temp_um > 1) { temp_um = 1; } umm.set(r, c, temp_um); } } Logger.getLogger("at.tuwien.ifs.somtoolbox").finer( "f-norm-MW(UM) = " + umm.zSum() / (umm.rows() * umm.columns())); umbi = um.createImage(gsom, umm, width, height, interpolate); double umatrix[][] = getUmValues(gsom, umm, width, height); double rangedistance = maximumMatrixValue - minimumMatrixValue; if (method == METHOD_ALPHA) { g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); g.drawImage(umbi, null, 0, 0); } else { Color c_CC, c_UM = null, c_DEST; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { c_CC = new Color(res.getRGB(x, y)); c_UM = new Color(umbi.getRGB(x, y)); if (method == METHOD_WHITE) { // mit weiss mischen c_DEST = new Color(c_CC.getRed() * (255 - c_UM.getRed()) / 255 + c_UM.getRed(), c_CC.getGreen() * (255 - c_UM.getGreen()) / 255 + c_UM.getGreen(), c_CC.getBlue() * (255 - c_UM.getBlue()) / 255 + c_UM.getBlue()); } else if (method == METHOD_BLACK) { // mit schwarz mischen c_DEST = new Color(c_CC.getRed() * (255 - c_UM.getRed()) / 255, c_CC.getGreen() * (255 - c_UM.getGreen()) / 255, c_CC.getBlue() * (255 - c_UM.getBlue()) / 255); } else if (method == METHOD_HSV_MODEL) { float[] HSV = new float[3]; HSV = Color.RGBtoHSB(c_CC.getRed(), c_CC.getGreen(), c_CC.getBlue(), HSV); /** Range mapping * */ double percent4range = 100 * (umatrix[x][y] - minimumMatrixValue) / rangedistance; double Saturation = HSVRANGE * percent4range / 100; /** set saturation value * */ HSV[1] = (float) Saturation / 255; c_DEST = new Color(Color.HSBtoRGB(HSV[0], HSV[1], HSV[2])); } else { // Multiplication c_DEST = new Color(clamp(Math.round(c_CC.getRed() * (1 + (float) c_UM.getRed() / (float) 255) / 2), 0, 255), clamp(Math.round(c_CC.getGreen() * (1 + (float) c_UM.getGreen() / (float) 255) / 2), 0, 255), clamp( Math.round(c_CC.getBlue() * (1 + (float) c_UM.getBlue() / (float) 255) / 2), 0, 255)); } res.setRGB(x, y, c_DEST.getRGB()); } } // regionManager.drawDelaunayTrangulation(g); } } public void update(double val, double gamma, double contrast, double gain, int method, boolean voronoi, boolean chessBoard) { this.min_visible_class = val; this.gain = gain; this.gamma = gamma; this.contrast = contrast; this.method = method; this.voronoi = voronoi; this.chessBoard = chessBoard; if (visualizationUpdateListener != null) { visualizationUpdateListener.updateVisualization(); } } class ClassVisControlPanel extends VisualizationControlPanel implements ChangeListener, ActionListener { private static final String TOOL_TIP = "<html>" // + "<h3>Colour Weighting using the U-Matrix</h3>" // + "<p><b>Computation:</b></p>" // + "<ol>" + "<li>Simple Alpha Blending: draw UM onto TCM using" + "<pre>g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f));" + "g.drawImage(UMatrix_BufferedImage, null, 0, 0);</pre>" + "<li>Alpha Blending to white: DEST = TMC * (255 - UM) / 255 + UM" + "<li>Alpha Blending to black: DEST = TMC * (255 - UM) / 255" + "<li>Multiplication: DEST = TMC * (1 + UM / 255) / 2" + "</ol>" + "<p>Gamma, contrast and gain are used to re-calculate the UMatrix using</p>" + "<pre>UM = ((pow(UM - E(UM) + 0.5, gamma) - 0.5) * contrast + 0.5) * gain</pre>" + "<p><br>Notes: <br>" + "TMC ... Thematic Class Map<br>" + "UM ... UMatrix<br>" + "E(x) ...expectation</p>" + // "</html>"; private static final long serialVersionUID = 1L; private ThematicClassMapVisualizer classvisualizer; private javax.swing.JLabel minVisClassLabel; private javax.swing.JLabel gammaLabel; private javax.swing.JLabel contrastLabel; private javax.swing.JLabel gainLabel; private javax.swing.JCheckBox chessBoardCheckbox; private javax.swing.JCheckBox voronoiCheckbox; private javax.swing.JComboBox combinationUM; private JSpinner minSizeSpinner; private JSpinner gammaSpinner; private JSpinner contrastSpinner; private JSpinner gainSpinner; /** Creates new form ClassVisControlPanel */ public ClassVisControlPanel(at.tuwien.ifs.somtoolbox.visualization.ThematicClassMapVisualizer classvisualizer) { super("Class Map Control"); this.classvisualizer = classvisualizer; initComponents(); } public final String[] uMatrixWeightingOptions = new String[] { "No U-Matrix weighting", "Simple Alpha Blending", "Alpha Blending to white", "Alpha Blending to black", "Multiplication", "HSV Colouring" }; /** * This method is called from within the constructor to initialize the form. */ private void initComponents() { JPanel classVisPanel = new JPanel(); GridBagConstraintsIFS constr = new GridBagConstraintsIFS(GridBagConstraintsIFS.NORTHWEST, GridBagConstraintsIFS.HORIZONTAL); constr.setInsets(2, 1); classVisPanel.setLayout(new GridBagLayout()); voronoiCheckbox = new JCheckBox("Vorono lines"); voronoiCheckbox.setSelected(true); voronoiCheckbox.setMnemonic(KeyEvent.VK_V); voronoiCheckbox.addActionListener(this); classVisPanel.add(voronoiCheckbox, constr); chessBoardCheckbox = new JCheckBox("Chessboard"); chessBoardCheckbox.setMnemonic(KeyEvent.VK_C); chessBoardCheckbox.addActionListener(this); classVisPanel.add(chessBoardCheckbox, constr.nextCol()); minVisClassLabel = new javax.swing.JLabel("Min. visible %"); minVisClassLabel.setToolTipText("Only classes which contribute at least the given percentage to a map node are considered"); classVisPanel.add(minVisClassLabel, constr.nextRow()); minSizeSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 5.0)); minSizeSpinner.addChangeListener(this); classVisPanel.add(minSizeSpinner, constr.nextCol()); TitledCollapsiblePanel colourWeightingPanel = new TitledCollapsiblePanel("Colour Weighting", new GridBagLayout(), true); constr.resetWeights().reset(); combinationUM = new javax.swing.JComboBox(uMatrixWeightingOptions); combinationUM.setFont(smallerFont); combinationUM.addActionListener(this); colourWeightingPanel.add(combinationUM, constr.setGridWidth(2).setWeightX(1.0)); // help icon with tool tip JLabel helpLabel = new JLabel(new ImageIcon(ClassLoader.getSystemResource("rsc/icons/help.png"))); helpLabel.setFont(smallerFont); helpLabel.setToolTipText(TOOL_TIP); // Tooltip should stay visible since it's such a long one... ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE); colourWeightingPanel.add(helpLabel, constr.nextCol().nextCol().setGridWidth(1).resetWeights()); gammaLabel = new javax.swing.JLabel("Gamma: "); colourWeightingPanel.add(gammaLabel, constr.nextRow()); gammaSpinner = new JSpinner(new SpinnerNumberModel(1, 0.1, 9.0, 0.05)); gammaSpinner.setFont(smallerFont); gammaSpinner.addChangeListener(this); colourWeightingPanel.add(gammaSpinner, constr.nextCol().setWeightX(1.0)); contrastLabel = new javax.swing.JLabel("Contrast: "); colourWeightingPanel.add(contrastLabel, constr.resetWeights().nextRow()); contrastSpinner = new JSpinner(new SpinnerNumberModel(1, 0.1, 10, 0.05)); contrastSpinner.setFont(smallerFont); contrastSpinner.addChangeListener(this); colourWeightingPanel.add(contrastSpinner, constr.setWeightX(1.0).nextCol()); gainLabel = new javax.swing.JLabel("Gain: "); colourWeightingPanel.add(gainLabel, constr.resetWeights().nextRow()); gainSpinner = new JSpinner(new SpinnerNumberModel(1, 0.1, 10, 0.05)); gainSpinner.setFont(smallerFont); gainSpinner.addChangeListener(this); colourWeightingPanel.add(gainSpinner, constr.setWeightX(1.0).nextCol()); add(classVisPanel, c); c.gridy += 1; add(colourWeightingPanel, c); } @Override public void stateChanged(ChangeEvent e) { JComponent s = (JComponent) e.getSource(); if (s == voronoiCheckbox || s == chessBoardCheckbox) { JCheckBox checkBox = (JCheckBox) s; ChangeListener changeListener = checkBox.getChangeListeners()[0]; checkBox.removeChangeListener(changeListener); regenerateVisualisation(); checkBox.addChangeListener(changeListener); } else if (s == minSizeSpinner // only if we have umatrix weighting --> need to update || (s == gammaSpinner || s == contrastSpinner || s == gainSpinner) && combinationUM.getSelectedIndex() > 0) { JSpinner spinner = (JSpinner) s; ChangeListener changeListener = spinner.getChangeListeners()[0]; spinner.removeChangeListener(changeListener); regenerateVisualisation(); spinner.addChangeListener(changeListener); } } @Override public void actionPerformed(ActionEvent e) { regenerateVisualisation(); } private void regenerateVisualisation() { double val = ((Double) minSizeSpinner.getValue()).doubleValue() / 100; double gamma = ((Double) gammaSpinner.getValue()).doubleValue(); double gain = ((Double) gainSpinner.getValue()).doubleValue(); double contrast = ((Double) contrastSpinner.getValue()).doubleValue(); int method = combinationUM.getSelectedIndex(); classvisualizer.update(val, gamma, contrast, gain, method, voronoiCheckbox.isSelected(), chessBoardCheckbox.isSelected()); } } public boolean hasClassInfo() { return inputObjects.getClassInfo() != null; } /** * Adapted from {@link AbstractMatrixVisualizer#createImage(GrowingSOM, DoubleMatrix2D, int, int, boolean)}.<br> * FIXME: check if the two methods can be merged. * */ private double[][] getUmValues(GrowingSOM gsom, DoubleMatrix2D matrix, int width, int height) { int unitWidth = width / gsom.getLayer().getXSize(); int unitHeight = height / gsom.getLayer().getYSize(); 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()); // bcs.calcDeriv(); int ci = 0; double umatrix[][] = new double[width][height]; 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) { ci = (int) Math.round(bcs.interpolate(y, x) * (HSVRANGE - 1)); if (ci < 0) { ci = 0; } else if (ci >= HSVRANGE) { ci = (int) HSVRANGE; } } } catch (LayerAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (ci < minimumMatrixValue) { minimumMatrixValue = ci; } if (ci > maximumMatrixValue) { maximumMatrixValue = ci; } umatrix[x][y] = ci; } } /** end bicubic spline stuff * */ return umatrix; } public void setZoom(int zoom) { this.zoom = zoom; } /** Deletes all cached elements from this visualisation. */ public void invalidateCache() { for (String visualizationName : VISUALIZATION_SHORT_NAMES) { invalidateCache(visualizationName); } } @Override public HashMap<String, BufferedImage> getVisualizationFlavours(int variantIndex, GrowingSOM gsom, int width, int height) throws SOMToolboxException { HashMap<String, BufferedImage> res = new HashMap<String, BufferedImage>(); // if (needsAdditionalFiles() != null) return res; boolean v = voronoi; boolean ch = chessBoard; double vc = min_visible_class; for (int i = 5; i <= 100;) { String k = String.format("_class%dp", i); // T T update(i / 100d, 0, 0, 0, METHOD_NO_COMBINATION, true, true); res.put(k + "-voronoi-chessBoard", getVisualization(variantIndex, gsom, width, height)); // T F update(i / 100d, 0, 0, 0, METHOD_NO_COMBINATION, true, false); res.put(k + "-voronoi", getVisualization(variantIndex, gsom, width, height)); // F T update(i / 100d, 0, 0, 0, METHOD_NO_COMBINATION, false, true); res.put(k + "-chessBoard", getVisualization(variantIndex, gsom, width, height)); // F F update(i / 100d, 0, 0, 0, METHOD_NO_COMBINATION, false, false); res.put(k, getVisualization(variantIndex, gsom, width, height)); if (i < 60) { i += 5; } else { i += 10; } } update(vc, 0, 0, 0, METHOD_NO_COMBINATION, v, ch); return res; } @Override public HashMap<String, BufferedImage> getVisualizationFlavours(int variantIndex, GrowingSOM gsom, int width, int height, int maxFlavours) throws SOMToolboxException { Logger.getLogger(this.getClass().getName()).warning("Not implemented, creating all flavours"); return getVisualizationFlavours(variantIndex, gsom, width, height); } @Override public HashMap<String, BufferedImage> getVisualizationFlavours(int variantIndex, GrowingSOM gsom, int width, int height, Map<String, String> flavourParameters) throws SOMToolboxException { Logger.getLogger(this.getClass().getName()).warning("Not implemented, creating all flavours"); return getVisualizationFlavours(variantIndex, gsom, width, height); } }