/*
* Copyright (C) 2010-2014 Mathias Unberath
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/
package edu.stanford.rsl.conrad.geometry.shapes.mesh;
import java.util.ArrayList;
import edu.stanford.rsl.conrad.geometry.shapes.activeshapemodels.GPA;
import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND;
import edu.stanford.rsl.conrad.numerics.SimpleMatrix;
import edu.stanford.rsl.conrad.numerics.SimpleVector;
/**
* This class stores meshes as column vectors in a SimpleMatrix structures. Centers of mass and scalings can be stored in ArrayLists as well,
* so that original positions and sizes can be restored after e.g. a zero-mean-shift.
* @author Mathias Unberath
*
*/
public class DataMatrix extends SimpleMatrix{
private static final long serialVersionUID = 1L;
/**
* Dimension of the data-set elements, needed due to single column storage.
*/
public int dimension = 0;
/**
* Array to store the scaling factor for the single meshes stored in the SimpleMatrix.
* The factor can be used to scale the meshes to a common size, needed in e.g. Generalized Procrustes Alignment.
*/
public ArrayList<Float> scaling;
/**
* Array to store the centers of mass of the single meshes stored in the SimpleMatrix.
* The centers can be used to restore the meshes' position after shifting the mean value to the origin,
* needed in e.g. Generalized Procrustes Alignment.
*/
public ArrayList<PointND> centers;
/**
* The consensus object of the data-sets after GPA.
*/
public SimpleMatrix consensus;
/**
* Flag to show whether or not connectivity information has been provided.
*/
public boolean HAS_CONNECTIVITY = false;
/**
* List to store the connectivity information. One list is enough as we assume that the meshes have the same
* amount of vertices and point correspondence to be established beforehand.
*/
public SimpleMatrix connectivity;
/**
* Flag to check whether or not a GPA object has been used for construction. Needed for the initialization of
* the ArrayLists.
*/
private boolean USE_GPA = false;
//==========================================================================================
// METHODS
//==========================================================================================
/**
* Convenience constructor. As this class is intended to be used for principal component analysis after a
* generalized procrustes analysis, the object can be constructed directly from a GPA object.
* @param gpa
*/
public DataMatrix(GPA gpa){
USE_GPA = true;
this.connectivity = gpa.connectivity;
if(gpa.connectivity != null){
this.HAS_CONNECTIVITY = true;
}
this.centers = gpa.centers;
this.scaling = gpa.scaling;
this.consensus = gpa.consensus;
this.dimension = gpa.dimension;
init(gpa.numPoints * dimension, gpa.numPc);
for(int i = 0; i < cols; i++){
setSimpleMatrixAtIndex(gpa.pointList.get(i), i);
}
}
public DataMatrix() {
// TODO Auto-generated constructor stub
}
/**
* Copy Constructor.
* @param otherMat The DataMatrix to be copied
*/
public DataMatrix(DataMatrix otherMat) {
this.setDimensions(otherMat.getRows()/otherMat.dimension,otherMat.dimension,otherMat.getCols());
this.buf = new double[otherMat.buf.length][otherMat.buf[0].length];
for( int i = 0; i < otherMat.buf.length; i++ ) {
System.arraycopy(otherMat.buf[i], 0, this.buf[i], 0, otherMat.buf[i].length);
}
this.scaling = otherMat.scaling;
this.consensus = otherMat.consensus;
this.centers = otherMat.centers;
if(otherMat.HAS_CONNECTIVITY) {
this.connectivity = otherMat.connectivity;
this.HAS_CONNECTIVITY = true;
}
}
/**
* Sets the number of rows via the number of mesh vertices and their dimension.
* Therefore the number of rows is #dimension times the number of mesh vertices.
* Sets the number of columns via the number of meshes used for the model. The meshes are stored column-wise
* in the matrix. Therefore the number of meshes is equal to the number of columns.
* @param numPoints The number of mesh vertices.
* @param dim Dimension of vertices.
* @param numMesh The number of meshes used for the analysis.
*/
public void setDimensions(int numPoints, int dim, int numMesh){
this.dimension = dim;
int cols = numMesh;
int rows = dimension * numPoints;
init(rows,cols);
}
/**
* Sets the number of rows via the number of mesh vertices. 3 dimensional points are assumed.
* Therefore the number of rows is 3 times the number of mesh vertices.
* Sets the number of columns via the number of meshes used for the model. The meshes are stored column-wise
* in the matrix. Therefore the number of meshes is equal to the number of columns.
* @param numPoints The number of mesh vertices.
* @param numMesh The number of meshes used for the analysis.
*/
public void setDimensions(int numPoints, int numMesh){
int cols = numMesh;
if(dimension == 0){
System.out.println("Dimension of elements has not been set. Assuming 3D.");
this.dimension = 3;
}
int rows = dimension * numPoints; // assumes 3 dimensional points in mesh!
init(rows,cols);
}
/**
* Adds the center of mass of a mesh object at a certain index position. Needed to restore original mesh positions after
* a shift to zero-mean.
* @param colIdx List-index to be written.
* @param centOfMass Value to be written at list-index.
*/
public void addCenterOfMassAtIndex(int colIdx, PointND centOfMass){
assert(colIdx < cols) : new IllegalArgumentException("Column index for this mesh-matrix is out of bounds.");
this.centers.add(colIdx, centOfMass);
}
/**
* Adds the center of mass of a mesh object at a certain index position. Needed to restore original mesh positions after
* a shift to zero-mean.
* @param colIdx List-index to be written.
* @param centOfMass Value to be written at list-index.
*/
public void addScalingAtIndex(int colIdx, float scaling){
assert(colIdx < this.cols) : new IllegalArgumentException("Column index for this mesh-matrix is out of bounds.");
this.scaling.add(colIdx, scaling);
}
/**
* Sets the center of mass of a mesh object at a certain index position. Needed to restore original mesh positions after
* a shift to zero-mean.
* @param colIdx List-index to be written.
* @param centOfMass Value to be written at list-index.
*/
public void setCenterOfMassAtIndex(int colIdx, PointND centOfMass){
assert(colIdx < this.cols) : new IllegalArgumentException("Column index for this mesh-matrix is out of bounds.");
this.centers.set(colIdx, centOfMass);
}
/**
* Sets the center of mass of a mesh object at a certain index position. Needed to restore original mesh positions after
* a shift to zero-mean.
* @param colIdx List-index to be written.
* @param centOfMass Value to be written at list-index.
*/
public void setScalingAtIndex(int colIdx, float scaling){
assert(colIdx < this.cols) : new IllegalArgumentException("Column index for this mesh-matrix is out of bounds.");
this.scaling.set(colIdx, scaling);
}
/**
* Writes the vertex coordinates of a mesh object into the <colIdx> column of a SimpleMatrix structure.
* The SimpleMatrix has to be initialized beforehand.
* The structure inside the matrix will be as follows: i-th vertex p_i with coordinates (x_ij) will be
* written to M_(i*j+j)_k, where k has to be passed to the method as <colIdx>.
* @param mesh The mesh object containing the point list.
* @param colIdx The column index.
*/
public void setMeshAtIndex(Mesh mesh, int colIdx){
assert(colIdx < this.cols) : new IllegalArgumentException("Column index for this mesh is out of bounds.");
assert((mesh.getPoints().getRows() * dimension) - 1 < this.rows) : new IllegalArgumentException("Row index for this mesh is out of bounds.");
SimpleMatrix points = mesh.getPoints();
for(int i = 0; i < mesh.getPoints().getRows(); i++){
int rowMajor = dimension * i;
writePointNdAtIndex(rowMajor, colIdx, points.getRow(i));
}
}
/**
* Writes the vertex coordinates of a SimpleMatrix into the <colIdx> column of a SimpleMatrix structure.
* The SimpleMatrix has to be initialized beforehand.
* The structure inside the matrix will be as follows: i-th row p_i with elements (x_ij) will be
* written to M_(i*j+j)_k, where k has to be passed to the method as <colIdx>.
* @param mat The SimpleMatrix.
* @param colIdx The column index.
*/
public void setSimpleMatrixAtIndex(SimpleMatrix mat, int colIdx){
assert(colIdx < this.cols) : new IllegalArgumentException("Column index for this mesh is out of bounds.");
assert((mat.getRows() * dimension) - 1 < this.rows) : new IllegalArgumentException("Row index for this mesh is out of bounds.");
for(int i = 0; i < mat.getRows(); i++){
int rowMajor = dimension * i;
for(int j = 0; j < dimension; j++){
this.setElementValue(rowMajor+j, colIdx, mat.getElement(i, j));
}
}
}
/**
* Sets the connectivity information for the meshes stored in the SimpleMatrix structure.
* @param connectivity The indices of the vertices making up the connectivity information.
*/
public void setConnectivity(SimpleMatrix connectivity){
this.connectivity = connectivity;
this.HAS_CONNECTIVITY = true;
}
/**
* Writes a PointND to <dimension> consecutive row-values starting at a certain index in a certain column.
* @param rowMajor The row starting index.
* @param colIdx The column index.
* @param point The PointND object to be written to the SimpleMatrix structure.
*/
private void writePointNdAtIndex(int rowMajor, int colIdx, SimpleVector point){
for(int i = 0; i < dimension; i++){
this.setElementValue(rowMajor+i, colIdx, point.getElement(i));
}
}
/**
* Initialize zero matrix and zero mesh-centers and scaling.
* Mesh centers and scalings are only initialized if the class wasn't constructed using a GPA object.
*
* @param rows number of rows
* @param cols number of columns
*/
@Override public void init(final int rows, final int cols) {
assert (rows >= 0) : new IllegalArgumentException("Number of rows has to be greater than or equal to zero!");
assert (cols >= 0) : new IllegalArgumentException("Number of columns has to be greater than or equal to zero!");
if (this.rows != rows || this.cols != cols) {
this.rows = rows;
this.cols = cols;
this.buf = new double[this.rows][this.cols];
if(!USE_GPA){
this.centers = new ArrayList<PointND>(cols);
this.scaling = new ArrayList<Float>(cols);
}
}
}
/**
* Returns the column colIdx of the {@link DataMatrix} in its original numVertices x dimension {@link SimpleMatrix} form.
* @param colIdx The column index where the linearized matrix is stored.
* @return SimpleMatrix of the form numVertices x dimension.
*/
public SimpleMatrix getSimpleMatrixAtIndex(int colIdx){
assert(colIdx < this.cols) : new IllegalArgumentException("Column index for this mesh is out of bounds.");
SimpleMatrix mat = new SimpleMatrix(this.rows/this.dimension, this.dimension);
for(int i = 0; i < mat.getRows(); i++){
int rowMajor = dimension * i;
for(int j = 0; j < dimension; j++){
mat.setElementValue(i, j, this.getElement(rowMajor + j, colIdx));
}
}
return mat;
}
/**
* Creates a new sub matrix with entries from ordered rows and ordered columns provided
* @param selectRows is ordered array containing rows to be copied
* @param selectCols is ordered array containing columns to be copied
* @return a sub matrix of this matrix
*/
@Override
public DataMatrix getSubMatrix(final int[] selectRows, final int[] selectCols) {
final DataMatrix subMatrix = new DataMatrix();
subMatrix.setDimensions(selectRows.length/this.dimension, this.dimension, selectCols.length);
int subRow = 0;
for (int r : selectRows) {
int subCol = 0;
for (int c : selectCols) {
subMatrix.buf[subRow][subCol] = this.buf[r][c];
++subCol;
}
++subRow;
}
subMatrix.scaling = new ArrayList<Float>();
for(int c : selectCols) {
subMatrix.scaling.add(this.scaling.get(c));
}
// Columns have been omitted. Therefore, the consensus needs to be recalculated.
subMatrix.calculateConsensus();
return subMatrix;
}
/**
* Calculates the mean over all observations stored column-wise and saves the result to consensus.
*/
private void calculateConsensus() {
if(consensus != null) return;
this.consensus = new SimpleMatrix(this.getSimpleMatrixAtIndex(0));
for(int i = 1; i < this.getCols(); i++) {
this.consensus.add(this.getSimpleMatrixAtIndex(i));
}
this.consensus = this.consensus.dividedBy( (double) (this.getCols()) );
}
}