package de.lmu.ifi.dbs.elki.visualization.projections; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import java.util.Arrays; import java.util.BitSet; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.math.DoubleMinMax; import de.lmu.ifi.dbs.elki.math.linearalgebra.AffineTransformation; import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; import de.lmu.ifi.dbs.elki.math.scales.LinearScale; /** * Affine projections are the most general class. They are initialized by an * arbitrary affine transformation matrix, and can thus represent any rotation * and scaling, even simple perspective projections. * * However, this comes at the cost of a matrix multiplication. * * @author Erich Schubert */ public class AffineProjection extends AbstractFullProjection implements Projection2D { /** * Affine transformation used in projection */ private AffineTransformation proj; /** * Viewport (cache) */ private CanvasSize viewport = null; /** * Constructor with a given database and axes. * * @param scales Scales to use * @param proj Projection to use */ public AffineProjection(LinearScale[] scales, AffineTransformation proj) { super(scales); this.proj = proj; } /** * Project a vector from scaled space to rendering space. * * @param v vector in scaled space * @return vector in rendering space */ @Override public Vector projectScaledToRender(Vector v) { return proj.apply(v); } /** * Project a vector from rendering space to scaled space. * * @param v vector in rendering space * @return vector in scaled space */ @Override public Vector projectRenderToScaled(Vector v) { return proj.applyInverse(v); } /** * Project a relative vector from scaled space to rendering space. * * @param v relative vector in scaled space * @return relative vector in rendering space */ @Override public Vector projectRelativeScaledToRender(Vector v) { return proj.applyRelative(v); } /** * Project a relative vector from rendering space to scaled space. * * @param v relative vector in rendering space * @return relative vector in scaled space */ @Override public Vector projectRelativeRenderToScaled(Vector v) { return proj.applyRelativeInverse(v); } @Override public CanvasSize estimateViewport() { if(viewport == null) { final int dim = proj.getDimensionality(); DoubleMinMax minmaxx = new DoubleMinMax(); DoubleMinMax minmaxy = new DoubleMinMax(); // Origin Vector orig = new Vector(dim); orig = projectScaledToRender(orig); minmaxx.put(orig.get(0)); minmaxy.put(orig.get(1)); // Diagonal point Vector diag = new Vector(dim); for(int d2 = 0; d2 < dim; d2++) { diag.set(d2, 1); } diag = projectScaledToRender(diag); minmaxx.put(diag.get(0)); minmaxy.put(diag.get(1)); // Axis end points for(int d = 0; d < dim; d++) { Vector v = new Vector(dim); v.set(d, 1); Vector ax = projectScaledToRender(v); minmaxx.put(ax.get(0)); minmaxy.put(ax.get(1)); } viewport = new CanvasSize(minmaxx.getMin(), minmaxx.getMax(), minmaxy.getMin(), minmaxy.getMax()); } return viewport; } /** * Compute an transformation matrix to show only axis ax1 and ax2. * * @param dim Dimensionality * @param ax1 First axis * @param ax2 Second axis * @return transformation matrix */ public static AffineTransformation axisProjection(int dim, int ax1, int ax2) { // setup a projection to get the data into the interval -1:+1 in each // dimension with the intended-to-see dimensions first. AffineTransformation proj = AffineTransformation.reorderAxesTransformation(dim, new int[] { ax1, ax2 }); // Assuming that the data was normalized on [0:1], center it: double[] trans = new double[dim]; for(int i = 0; i < dim; i++) { trans[i] = -.5; } proj.addTranslation(new Vector(trans)); // mirror on the y axis, since the SVG coordinate system is screen // coordinates (y = down) and not mathematical coordinates (y = up) proj.addAxisReflection(2); // scale it up proj.addScaling(SCALE); return proj; } @Override public double[] fastProjectDataToRenderSpace(double[] data) { return fastProjectScaledToRenderSpace(fastProjectDataToScaledSpace(data)); } @Override public double[] fastProjectDataToRenderSpace(NumberVector data) { return fastProjectScaledToRenderSpace(fastProjectDataToScaledSpace(data)); } @Override public double[] fastProjectDataToScaledSpace(double[] data) { // FIXME: implement with less objects? return projectDataToScaledSpace(new Vector(data)).getArrayRef(); } @Override public double[] fastProjectDataToScaledSpace(NumberVector data) { // FIXME: implement with less objects? return projectDataToScaledSpace(data).getArrayRef(); } @Override public double[] fastProjectScaledToRenderSpace(double[] vr) { double x = 0.0; double y = 0.0; double s = 0.0; final double[][] matrix = proj.getTransformation().getArrayRef(); final double[] colx = matrix[0]; final double[] coly = matrix[1]; final double[] cols = matrix[vr.length]; assert (colx.length == coly.length && colx.length == cols.length && cols.length == vr.length + 1); for(int k = 0; k < vr.length; k++) { x += colx[k] * vr[k]; y += coly[k] * vr[k]; s += cols[k] * vr[k]; } // add homogene component: x += colx[vr.length]; y += coly[vr.length]; s += cols[vr.length]; assert (s != 0.0); return new double[] { x / s, y / s }; } @Override public double[] fastProjectRelativeDataToRenderSpace(double[] data) { // FIXME: implement with less objects? return fastProjectRelativeScaledToRenderSpace(projectRelativeDataToScaledSpace(new Vector(data)).getArrayRef()); } @Override public double[] fastProjectRelativeDataToRenderSpace(NumberVector data) { // FIXME: implement with less objects? return fastProjectRelativeScaledToRenderSpace(projectRelativeDataToScaledSpace(data).getArrayRef()); } @Override public double[] fastProjectRelativeScaledToRenderSpace(double[] vr) { double x = 0.0; double y = 0.0; final double[][] matrix = proj.getTransformation().getArrayRef(); final double[] colx = matrix[0]; final double[] coly = matrix[1]; assert (colx.length == coly.length); for(int k = 0; k < vr.length; k++) { x += colx[k] * vr[k]; y += coly[k] * vr[k]; } return new double[] { x, y }; } @Override public double[] fastProjectRenderToDataSpace(double[] data) { double[] ret = fastProjectRenderToScaledSpace(data); for(int d = 0; d < scales.length; d++) { ret[d] = scales[d].getUnscaled(ret[d]); } return ret; } @Override public double[] fastProjectRenderToScaledSpace(double[] v) { if(v.length == scales.length) { return projectRenderToScaled(new Vector(v)).getArrayRef(); } double[] c = Arrays.copyOf(v, scales.length); for(int d = v.length; d < scales.length; d++) { c[d] = 0.5; } return projectRenderToScaled(new Vector(c)).getArrayRef(); } @Override public BitSet getVisibleDimensions2D() { final int dim = proj.getDimensionality(); BitSet actDim = new BitSet(dim); Vector vScale = new Vector(dim); for(int d = 0; d < dim; d++) { vScale.setZero(); vScale.set(d, 1); double[] vRender = fastProjectScaledToRenderSpace(vScale.getArrayRef()); // TODO: Can't we do this by inspecting the projection matrix directly? if(vRender[0] != 0.0 || vRender[1] != 0) { actDim.set(d); } } return actDim; } }