content: /*
   * 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.io;
  
  import java.io.BufferedReader;
  import java.io.FileNotFoundException;
  import java.io.FileReader;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.io.UnsupportedEncodingException;
  import java.util.StringTokenizer;
  
  import edu.stanford.rsl.conrad.geometry.shapes.activeshapemodels.PCA;
  import edu.stanford.rsl.conrad.numerics.SimpleMatrix;
  import edu.stanford.rsl.conrad.numerics.SimpleVector;
  
  
  /**
   * Class to read and write Eigenvectors, Eigenvalues and the consensus as produced by a principal component analysis.
   * @author Mathias Unberath
   *
   */
  public class PcaIO {
  
  	/**
  	 * Dimension of vertices in a mesh object. Default is 1;
  	 */
  	private int pointDimension = 1;
  	
  	/**
  	 * The filename for input or output operation.
  	 */
  	String filename;
  	
  	/**
  	 * Array containing the eigenvalues of the covariance matrix after singular value decomposition.
  	 */
  	private double[] eigenValues;
  	
  	/**
  	 * Matrix containing the Eigenvectors of the covariance matrix after singular value decomposition.
  	 */
  	private SimpleMatrix eigenVectors;
  	
  	/**
  	 * Connectivity information when dealing with meshes.
  	 */
  	private SimpleMatrix connectivity;
  	
  	/**
  	 * The mean shape.
  	 */
  	private SimpleVector consensus;
  	
  	//==========================================================================================
  	// METHODS
  	//==========================================================================================
  	/**
  	 * Default constructor.
  	 */
  	public PcaIO(){
  		
  	}
  	
  	/**
  	 * Constructs the object and sets the filename for reading operation.
  	 * @param filename The file to be read.
  	 */
  	public PcaIO(String filename){
  		this.filename = filename;
  	}
  	
  	/**
  	 * Reads the variances from the file in the filename class member and stores the data in the corresponding class members.
  	 * @throws IOException 
  	 */
  	public void readVarianceOnly() throws IOException{
  		assert(filename != null) : new Exception("Filename not set.");
  		
  		FileReader fr = new FileReader(filename);
  		BufferedReader br = new BufferedReader(fr);
  		
  		String line = br.readLine();
  		StringTokenizer tok = new StringTokenizer(line);
  		tok.nextToken(); // skip "DIMENSION"
  		String t = tok.nextToken();	
  		@SuppressWarnings("unused")
  		int rows = Integer.parseInt(t);
  		t = tok.nextToken();
  		int cols = Integer.parseInt(t);
  		// read point dimension
  		line = br.readLine();
  		tok = new StringTokenizer(line);
  		tok.nextToken(); // skip "POINTDIMENSION"
  		this.pointDimension = Integer.parseInt(tok.nextToken());
  		
  		// allocate class members
  		this.eigenValues = new double[cols];
  		
  		br.readLine(); // skip "EIGENVALUES"
  		line = br.readLine(); // read eigenvalues
  		tok = new StringTokenizer(line);
  		for(int i = 0; i < cols; i++){
  			eigenValues[i] = Double.parseDouble(tok.nextToken());
  		}
  		br.close();
  		fr.close();
  	}
  	
  	
  	/**
  	 * Reads the file in the filename class member and stores the data in the corresponding class members.
  	 * @throws IOException 
  	 */
  	public void readFile() throws IOException{
  		assert(filename != null) : new Exception("Filename not set.");
  		
  		FileReader fr = new FileReader(filename);
  		BufferedReader br = new BufferedReader(fr);
  		
  		String line = br.readLine();
  		StringTokenizer tok = new StringTokenizer(line);
  		tok.nextToken(); // skip "DIMENSION"
  		String t = tok.nextToken();	
  		int rows = Integer.parseInt(t);
  		t = tok.nextToken();
  		int cols = Integer.parseInt(t);
  		// read point dimension
  		line = br.readLine();
  		tok = new StringTokenizer(line);
  		tok.nextToken(); // skip "POINTDIMENSION"
  		this.pointDimension = Integer.parseInt(tok.nextToken());
  		
  		// allocate class members
  		this.eigenValues = new double[cols];
  		this.eigenVectors = new SimpleMatrix(rows, cols);
  		this.consensus =  new SimpleVector(rows);
  		
  		br.readLine(); // skip "EIGENVALUES"
  		line = br.readLine(); // read eigenvalues
  		tok = new StringTokenizer(line);
  		for(int i = 0; i < cols; i++){
  			eigenValues[i] = Double.parseDouble(tok.nextToken());
  		}
  		br.readLine(); // skip "EIGENVECTORS | CONSENSUS"
  		for(int i = 0; i < rows; i++){
  			line = br.readLine();
  			tok = new StringTokenizer(line);
  			for(int j = 0; j < cols; j++){
  				eigenVectors.setElementValue(i, j, Double.parseDouble(tok.nextToken()));
  			}
  			consensus.setElementValue(i, Double.parseDouble(tok.nextToken())); // last entry is consensus
  		}
  		// try to read connectivity if exists
  		line = br.readLine();
  		if(line != null){
  			tok = new StringTokenizer(line);
  			tok.nextToken();
  			this.connectivity = new SimpleMatrix(Integer.parseInt(tok.nextToken()),Integer.parseInt(tok.nextToken()));
  			for(int i = 0; i < connectivity.getRows(); i++){
  				line = br.readLine();
  				tok = new StringTokenizer(line);
  				tok.nextToken(); // skip leading number of connections
  				for(int j = 0; j < connectivity.getCols(); j++){
  					connectivity.setElementValue(i, j, Double.parseDouble(tok.nextToken()));
  				}
  			}
  		}
  			
  		br.close();
  		fr.close();
  		
  	}
  	
  	/**
  	 * Writes the data in the class members to the file specified in filename.
  	 */
  	public void writeFile(){
  		assert( filename != null) : new Exception("Filename not set.");
  		assert( consensus != null && eigenVectors != null && eigenValues != null) : new Exception("Data not set.");
  		
  		try {
  			System.out.println("Writing Eigen-Values and Eigen-Vectors to file: " + filename);
  			PrintWriter writer = new PrintWriter(filename,"UTF-8");
  			writer.println("DIMENSION " + this.eigenVectors.getRows() + " " + this.eigenVectors.getCols());
  			writer.println("POINTDIMENSION " + this.pointDimension);
  			writer.println("EIGENVALUES");
  			writer.println(buildStringEigenValues());
  			writer.println("EIGENVECTORS | CONSENSUS");
  			for(int i = 0; i < this.eigenVectors.getRows(); i++){
  				writer.println(buildStringEigenVectorAtIndex(i) + consensus.getElement(i));
  			}
  			//write connectivity if exists
  			if(connectivity != null){
  				writer.println("CONNECTIVITY " + connectivity.getRows() + " " +connectivity.getCols());
  				for(int i = 0; i < connectivity.getRows(); i++){
  					writer.println(buildStringConnectivity(i));
  				}
  			}
  			writer.close();
  			System.out.println("Finished writing.");
  			
  		} catch (FileNotFoundException e) {
  			e.printStackTrace();
  		} catch (UnsupportedEncodingException e) {
  			e.printStackTrace();
  		}
  		
  	}
  	
  	/**
  	 * Constructs the string object of connectivity-entries in the row specified for writing operation.
  	 * @param idx The row index.
  	 * @return The string containing the connectivity row.
  	 */
  	private String buildStringConnectivity(int idx){
  		StringBuilder ev = new StringBuilder();
  		ev.append(connectivity.getCols());
  		for(int i = 0; i < connectivity.getCols(); i++){
  			ev.append(" ");
  			ev.append(connectivity.getElement(idx, i));
  		}
  		return ev.toString();
  	}
  	
  	/**
  	 * Constructs the string object of Eigenvector-entries in the row specified for writing operation.
  	 * @param idx The row index.
  	 * @return The string containing the Eigenvector row.
  	 */
  	private String buildStringEigenVectorAtIndex(int idx){
  		StringBuilder ev = new StringBuilder();
  		for(int i = 0; i < eigenValues.length; i++){
  			ev.append(eigenVectors.getElement(idx, i));
  			ev.append(" ");
  		}
  		return ev.toString();
  	}
  	
  	/**
  	 * Constructs the string object of Eigenvalues for writing operation.
  	 * @return The string containing the Eigenvalues.
  	 */
  	private String buildStringEigenValues(){
  		StringBuilder ev = new StringBuilder();
  		for(int i = 0; i < eigenValues.length; i++){
  			ev.append(eigenValues[i]);
  			ev.append(" ");
  		}
  		return ev.toString();
  	}
  	
  	/**
  	 * Sets the filename for IO operation.
  	 * @param filename The file to be read or written.
  	 */
  	public void setFilename(String filename){
  		this.filename = filename;
  	}
  	
  
  	/**
  	 * Constructs the object and sets Eigenvalues, Eigenvectors and the consensus for writing operation.
  	 * @param eVal
  	 * @param eVec
  	 * @param mean
  	 * @param connectivity
  	 */
  	public PcaIO(int pointDim, double[] eVal, SimpleMatrix eVec, SimpleVector mean, SimpleMatrix connectivity){
  		this.pointDimension = pointDim;
  		this.eigenValues = eVal;
  		this.eigenVectors = eVec;
  		this.consensus = mean;
  		this.connectivity = connectivity;
  	}
  	
  	
  	/**
  	 * Constructs the object and sets Eigenvalues, Eigenvectors and the consensus for writing operation.
  	 * @param eVal
  	 * @param eVec
  	 * @param mean
  	 */
  	public PcaIO(int pointDim, double[] eVal, SimpleMatrix eVec, SimpleVector mean){
  		this.pointDimension = pointDim;
  		this.eigenValues = eVal;
  		this.eigenVectors = eVec;
  		this.consensus = mean;
  	}
  	
  	
  	/**
  	 * Constructs the object and sets the filename, point dimension, eigenvalues, eigenvectors, mean shape and connectivity for writing.
  	 * @param filename
  	 * @param pca
  	 */
  	public PcaIO(String filename, PCA pca){
  		this.filename = filename;
  		this.pointDimension = pca.dimension;
  		this.eigenValues = pca.eigenValues;
  		this.eigenVectors = pca.eigenVectors;
  		this.consensus = pca.getConsensus();
  		this.connectivity = pca.connectivity;
  	}
  	
  	/**
  	 * Getter for the dimension of points. Needed if a point-cloud is analyzed using PCA.
  	 * @return The point dimension.
  	 */
  	public int getPointDimension(){
  		return this.pointDimension;
  	}
  	
  	/**
  	 * Getter for the Eigenvalues after reading operation.
  	 * @return The Eigenvalues.
  	 */
  	public double[] getEigenValues(){
  		return this.eigenValues;
  	}
  	
  	/**
  	 * Getter for the Eigenvectors after reading operation.
  	 * @return The Eigenvectors.
  	 */
  	public SimpleMatrix getEigenVectors(){
  		return this.eigenVectors;
  	}
  	
  	/**
  	 * Getter for the connecivity after reading operation.
  	 * @return The connectivity.
  	 */
  	public SimpleMatrix getConnectivity(){
  		return this.connectivity;
  	}
  	
  	/**
  	 * Getter for the consensus object after reading operation.
  	 * @return The consensus object.
  	 */
  	public SimpleVector getConsensus(){
  		return this.consensus;
  	}
  }
  