/* * 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.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import javax.swing.ButtonGroup; import javax.swing.JPanel; import javax.swing.JRadioButton; import org.apache.commons.lang.ArrayUtils; import flanagan.interpolation.BiCubicSplineFast; import at.tuwien.ifs.somtoolbox.SOMToolboxException; import at.tuwien.ifs.somtoolbox.data.InputData; import at.tuwien.ifs.somtoolbox.data.SOMVisualisationData; import at.tuwien.ifs.somtoolbox.models.GrowingSOM; import at.tuwien.ifs.somtoolbox.util.StdErrProgressWriter; /** * @author Michael Dittenbach * @author Rudolf Mayer * @version $Id: RhythmPatternsAttributeVisualizer.java 3874 2010-11-02 14:14:38Z mayer $ */ public class RhythmPatternsAttributeVisualizer extends AbstractMatrixVisualizer implements BackgroundImageVisualizer, ActionListener { private static final String LOW_FREQ_DOM = "low freq dom"; private static final String NON_AGGRESSIVE = "non-aggressive"; private static final String BASS = "bass"; private static final String MAX_FLUX = "max flux"; private static final String[] attributeTypes = { MAX_FLUX, BASS, LOW_FREQ_DOM, NON_AGGRESSIVE }; private double[][] maxfluxValues = null; private double[][] bassValues = null; private double[][] nonaggValues = null; private double[][] lfdValues = null; private String selectedAttributeType = MAX_FLUX; public RhythmPatternsAttributeVisualizer() { NUM_VISUALIZATIONS = 1; VISUALIZATION_NAMES = new String[] { "Rhythm Patterns Attributes" }; VISUALIZATION_SHORT_NAMES = new String[] { "RPAttributes" }; VISUALIZATION_DESCRIPTIONS = new String[] { "max flux, bass, non-aggressive, low frequencies dominant" }; // input vector file is only needed for determining dimensions of RP matrix, // not for actual vis calcualtion neededInputObjects = new String[] { SOMVisualisationData.INPUT_VECTOR }; reversePalette(); // don't initialise the control panel if we have no graphics environment (e.g. in server applications) if (!GraphicsEnvironment.isHeadless()) { try { controlPanel = new RPAControlPanel(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 index, int width, int height) { return getCacheKey(gsom, index, width, height, selectedAttributeType); } protected String getCacheKey(GrowingSOM gsom, int index, int width, int height, String attributeType) { return super.getCacheKey(gsom, index, width, height) + CACHE_KEY_SECTION_SEPARATOR + "attribute:" + attributeType; } @Override public BufferedImage createVisualization(int index, GrowingSOM gsom, int width, int height) throws SOMToolboxException { // FIXME: this cache of the matrices doesn't take into account any changes in the width & height! if (maxfluxValues == null && bassValues == null && nonaggValues == null && lfdValues == null) { initVisualizationMatrices(gsom, width, height); } if (cache.get(getCacheKey(gsom, index, width, height)) == null) { // create all four images at once cache.put(getCacheKey(gsom, 0, width, height, MAX_FLUX), createImage(maxfluxValues, width, height, interpolate)); cache.put(getCacheKey(gsom, 0, width, height, BASS), createImage(bassValues, width, height, interpolate)); cache.put(getCacheKey(gsom, 0, width, height, NON_AGGRESSIVE), createImage(nonaggValues, width, height, interpolate)); cache.put(getCacheKey(gsom, 0, width, height, LOW_FREQ_DOM), createImage(lfdValues, width, height, interpolate)); } return cache.get(getCacheKey(gsom, index, width, height)); } private void initVisualizationMatrices(GrowingSOM gsom, int width, int height) throws SOMToolboxException { int nBark = -1; int nModFreq = -1; InputData inputVectors = inputObjects.getInputData(); if (inputVectors == null) { Logger.getLogger("at.tuwien.ifs.somtoolbox").warning( "Input vector file not available - Cannot determine dimensions of Rhythm Pattern! - Guessing default values."); } else { // those 2 methods return -1 if $DATA_DIM header line is not present in input vector file nBark = inputVectors.getFeatureMatrixRows(); nModFreq = inputVectors.getFeatureMatrixColumns(); } if (nBark == -1) { Logger.getLogger("at.tuwien.ifs.somtoolbox").warning( "Number of Bark bands for Rhythm Patterns attribute visualization could not be determined. Assuming 24."); nBark = 24; } if (nModFreq == -1) { Logger.getLogger("at.tuwien.ifs.somtoolbox").warning( "Number of Modulation frequencies for Rhythm Patterns attribute visualization could not be determined. Assuming 60."); nModFreq = 60; } if (gsom.getLayer().getDim() != nBark * nModFreq) { // try to determine the number of bark bands by assuming 60 modulation frequencies // rule of thumb: has to be a integer number, and 10 <= bands <=25 if (gsom.getLayer().getDim() % nModFreq == 0 && gsom.getLayer().getDim() / nModFreq <= 25 && gsom.getLayer().getDim() / nModFreq >= 10) { int nBarkOld = nBark; nBark = gsom.getLayer().getDim() / nModFreq; System.out.println("Vector dimension (" + gsom.getLayer().getDim() + ") " + "did not match RP dimension (" + nBarkOld + "x" + nModFreq + "); assuming 60 modulation frequencies, calculated " + nBark + " bark bands, continuing."); } else { throw new SOMToolboxException("Initialization of Rhythm Patterns Attributes Visualization failed, " + "because vector dimension (" + gsom.getLayer().getDim() + ") " + "does not match RP dimension (" + nBark + "x" + nModFreq + ")."); } } int mapXSize = gsom.getLayer().getXSize(); int mapYsize = gsom.getLayer().getYSize(); maxfluxValues = new double[mapXSize][mapYsize]; bassValues = new double[mapXSize][mapYsize]; nonaggValues = new double[mapXSize][mapYsize]; lfdValues = new double[mapXSize][mapYsize]; /* * max flux: highest value in the rhythm pattern */ for (int y = 0; y < mapYsize; y++) { for (int x = 0; x < mapXSize; x++) { double max = 0; double[] vec = null; vec = gsom.getLayer().getUnit(x, y).getWeightVector(); for (int i = 0; i < gsom.getLayer().getDim(); i++) { if (vec[i] > max) { max = vec[i]; } } maxfluxValues[x][y] = max; } } /* * bass: sum of the values in the two lowest frequency bands (Bark 1-2) with a modulation frequency higher than 1Hz. */ for (int y = 0; y < mapYsize; y++) { for (int x = 0; x < mapXSize; x++) { bassValues[x][y] = 0; double[] vec = null; vec = gsom.getLayer().getUnit(x, y).getWeightVector(); for (int bark = 0; bark <= 1; bark++) { for (int i = 6; i <= nModFreq; i++) { bassValues[x][y] += vec[bark * nModFreq + i]; } } } } /* * non_aggressiveness: the ratio of the sum of values modulation frequencies below 0.5Hz and Bark greater or equal 3, compared to the sum of * all. */ /* * // normalize weight vectors by attribute double[][][] normWeights = new double[gsom.xSize()][gsom.ySize()][gsom.dim()]; for (int d=0; * d<gsom.dim(); d++) { // for each dimension // get max and min values of dimension double maxdimval = 0; double mindimval = * Double.MAX_VALUE; Unit[] units = gsom.getAllUnits(); for (int u=0; u<units.length; u++) { if (units[u].weightVector()[d] > maxdimval) { * maxdimval = units[u].weightVector()[d]; } if (units[u].weightVector()[d] < mindimval) { mindimval = units[u].weightVector()[d]; } } for * (int yy=0; yy<gsom.ySize(); yy++) { for (int xx=0; xx<gsom.xSize(); xx++) { normWeights[xx][yy][d] = * (gsom.getUnit(xx,yy).weightVector()[d]-mindimval)/(maxdimval-mindimval); } } } */ // normalize weight vector elements to interval [0-1] double[][][] normWeights = new double[mapXSize][mapYsize][gsom.getLayer().getDim()]; for (int y = 0; y < mapYsize; y++) { for (int x = 0; x < mapXSize; x++) { double maxval = 0; double minval = Double.MAX_VALUE; double[] vec = null; vec = gsom.getLayer().getUnit(x, y).getWeightVector(); for (double element : vec) { if (element > maxval) { maxval = element; } if (element < minval) { minval = element; } } for (int i = 0; i < vec.length; i++) { normWeights[x][y][i] = (vec[i] - minval) / (maxval - minval); } } } for (int y = 0; y < mapYsize; y++) { for (int x = 0; x < mapXSize; x++) { nonaggValues[x][y] = 0; for (int bark = 2; bark < nBark; bark++) { for (int i = 0; i <= 2; i++) { nonaggValues[x][y] += normWeights[x][y][bark * nModFreq + i]; } } } } normWeights = null; /* * low frequencies dominant: ratio between the sum of the values in the highest 5 and lowest 5 frequency bands */ for (int y = 0; y < mapYsize; y++) { for (int x = 0; x < mapXSize; x++) { lfdValues[x][y] = 0; double[] vec = null; vec = gsom.getLayer().getUnit(x, y).getWeightVector(); // low value double lowval = 0; for (int bark = 0; bark <= 4; bark++) { for (int i = 0; i < nModFreq; i++) { lowval = vec[bark * nModFreq + i]; } } // high value double highval = 0; for (int bark = nBark - 5; bark < nBark; bark++) { for (int i = 0; i < nModFreq; i++) { highval += vec[bark * nModFreq + i]; } } lfdValues[x][y] = lowval / highval; } } /* * for (int c=paletteSize-1; c>=0; c--) { int r = 0+(int)(c((double)255/((double)paletteSize))); int g = * 0+(int)(c((double)255/((double)paletteSize))); int b = 0+(int)(c((double)255/((double)paletteSize))); palette[c] = new Color(r,g,b); } */ /* try segmentation begin */ /* * int rgbval[] = { 150,150,150 }; WritableRaster wraster = maxfluxImage.getRaster(); for (int y=0; y < maxfluxImage.getHeight(); y++) { for * (int x=0; x < maxfluxImage.getWidth(); x++) { for(int band = 0; band < 3; band++) { if ((wraster.getSample(x, y, band) > rgbval[band])) * wraster.setSample(x, y, band, 0); else wraster.setSample(x, y, band, 255); } } } */ /* try segmentation end */ } private BufferedImage createImage(double[][] values, int width, int height, boolean interpolate) { // FIXME: this should be unified with createImage in AbstractMatrixVisualizer BufferedImage res = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = (Graphics2D) res.getGraphics(); drawBackground(width, height, g); int xSize = values.length; int ySize = values[0].length; int unitWidth = width / xSize; int unitHeight = height / ySize; // normalization of values double max = 0; double min = Double.MAX_VALUE; for (int j = 0; j < ySize; j++) { for (int i = 0; i < xSize; i++) { if (values[i][j] > max) { max = values[i][j]; } if (values[i][j] < min) { min = values[i][j]; } } } int ci = 0; double[] x1 = new double[xSize + 2]; x1[0] = 0; for (int x = 0; x < xSize; x++) { x1[x + 1] = x * unitWidth + unitWidth / 2; } x1[xSize + 1] = width; double[] x2 = new double[ySize + 2]; x2[0] = 0; for (int y = 0; y < ySize; y++) { x2[y + 1] = y * unitHeight + unitHeight / 2; } x2[ySize + 1] = height; double[][] valuesWithBorders = new double[xSize + 2][ySize + 2]; valuesWithBorders[0][0] = Math.max(values[0][0] - (values[1][1] - values[0][0]) / 2, 0d); // top-left corner valuesWithBorders[xSize + 1][0] = Math.max(values[xSize - 1][0] - (values[xSize - 2][1] - values[xSize - 1][0]) / 2, 0d); // top-right // corner valuesWithBorders[0][ySize + 1] = Math.max(values[0][ySize - 1] - (values[1][ySize - 2] - values[0][ySize - 1]) / 2, 0d); // bottom-left // corner valuesWithBorders[xSize + 1][ySize + 1] = Math.max(values[xSize - 1][ySize - 1] - (values[xSize - 2][ySize - 2] - values[xSize - 1][ySize - 1]) / 2, 0d); // bottom-right corner for (int x = 1; x < xSize + 1; x++) { valuesWithBorders[x][0] = Math.max(values[x - 1][0] - (values[x - 1][1] - values[x - 1][0]) / 2, 0d); // top // row valuesWithBorders[x][ySize + 1] = Math.max(values[x - 1][ySize - 1] - (values[x - 1][ySize - 2] - values[x - 1][ySize - 1]) / 2, 0d); // bottom // row } for (int y = 1; y < ySize + 1; y++) { valuesWithBorders[0][y] = Math.max(values[0][y - 1] - (values[1][y - 1] - values[0][y - 1]) / 2, 0d); // left // column valuesWithBorders[xSize + 1][y] = Math.max(values[xSize - 1][y - 1] - (values[xSize - 2][y - 1] - values[xSize - 1][y - 1]) / 2, 0d); // right // column } for (int y = 0; y < ySize; y++) { for (int x = 0; x < xSize; x++) { valuesWithBorders[x + 1][y + 1] = values[x][y]; } } if (interpolate) { BiCubicSplineFast bcs = new BiCubicSplineFast(x1, x2, valuesWithBorders); // bcs.calcDeriv(); 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++) { ci = (int) Math.round((bcs.interpolate(x + 0.5, y + 0.5) - min) / (max - min) * palette.maxColourIndex()); g.setPaint(palette.getColorConstrained(ci)); g.fill(new Rectangle(x, y, 1, 1)); progress.progress(); } } } else { for (int y = 0; y < ySize; y++) { for (int x = 0; x < xSize; x++) { ci = (int) Math.round((valuesWithBorders[x][y] - min) / (max - min) * palette.maxColourIndex()); g.setPaint(palette.getColorConstrained(ci)); g.fill(new Rectangle(x * unitWidth, y * unitWidth, unitWidth, unitHeight)); } } } return res; } public String getSelectedAttributeType() { return selectedAttributeType; } public void selectNextAttributeType() { int nextIndex = ArrayUtils.indexOf(attributeTypes, selectedAttributeType) + 1; if (nextIndex == getNumberOfAttributeTypes()) { nextIndex = 0; } selectedAttributeType = attributeTypes[nextIndex]; } public int getNumberOfAttributeTypes() { return 4; } @Override public void actionPerformed(ActionEvent e) { selectedAttributeType = ((JRadioButton) e.getSource()).getActionCommand(); if (visualizationUpdateListener != null) { visualizationUpdateListener.updateVisualization(); } } private class RPAControlPanel extends VisualizationControlPanel { private static final long serialVersionUID = 1L; private JRadioButton rbMaxFlux = null; private JRadioButton rbBass = null; private JRadioButton rbNonAggressive = null; private JRadioButton rbLowFreqDom = null; private ButtonGroup bg = null; private RPAControlPanel(RhythmPatternsAttributeVisualizer rpa) { super("Rhythm Patterns Attribute Control"); // FIXME: add tooltips to buttons that explain the attributes visualised rbMaxFlux = new JRadioButton(MAX_FLUX, true); rbMaxFlux.addActionListener(rpa); rbMaxFlux.setActionCommand(MAX_FLUX); rbBass = new JRadioButton(BASS, false); rbBass.addActionListener(rpa); rbBass.setActionCommand(BASS); rbNonAggressive = new JRadioButton(NON_AGGRESSIVE, false); rbNonAggressive.addActionListener(rpa); rbNonAggressive.setActionCommand(NON_AGGRESSIVE); rbLowFreqDom = new JRadioButton(LOW_FREQ_DOM, false); rbLowFreqDom.addActionListener(rpa); rbLowFreqDom.setActionCommand(LOW_FREQ_DOM); bg = new ButtonGroup(); bg.add(rbMaxFlux); bg.add(rbBass); bg.add(rbNonAggressive); bg.add(rbLowFreqDom); // JPanel rpaPanel = new JPanel(new GridLayout(2, 2)); JPanel rpaPanel = new JPanel(new GridBagLayout()); GridBagConstraints c2 = new GridBagConstraints(); c2.gridx = GridBagConstraints.RELATIVE; c2.gridy = 0; c2.fill = GridBagConstraints.HORIZONTAL; rpaPanel.add(rbMaxFlux, c2); rpaPanel.add(rbNonAggressive, c2); c2.gridy = c2.gridy + 1; rpaPanel.add(rbBass, c2); rpaPanel.add(rbLowFreqDom, c2); add(rpaPanel, c); } } /** Saves all flavours of RP attribute types (#attributeTypes). */ @Override public HashMap<String, BufferedImage> getVisualizationFlavours(int variantIndex, GrowingSOM gsom, int width, int height) throws SOMToolboxException { HashMap<String, BufferedImage> hashMap = new HashMap<String, BufferedImage>(attributeTypes.length, 1); // optimal // size for (String attributeType : attributeTypes) { hashMap.put("_" + getSelectedAttributeType(), getVisualization(variantIndex, gsom, width, height)); selectNextAttributeType(); } return hashMap; } @Override public HashMap<String, BufferedImage> getVisualizationFlavours(int variantIndex, GrowingSOM gsom, int width, int height, int maxFlavours) throws SOMToolboxException { // we can't really select any subset of these very distinctive flavours. if (maxFlavours != attributeTypes.length) { Logger.getLogger("at.tuwien.ifs.somtoolbox").info( "RhythmPatternsAttributeVisualizer will always generate the " + attributeTypes.length + " distinctive flavours, ignoring specified max value of " + maxFlavours + "."); } 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 { // FIXME: implement this return super.getVisualizationFlavours(variantIndex, gsom, width, height, flavourParameters); } }