/*
* 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.phantom.asmheart;
import java.io.File;
import edu.stanford.rsl.apps.activeshapemodel.BuildCONRADCardiacModel.heartComponents;
import edu.stanford.rsl.conrad.geometry.General;
import edu.stanford.rsl.conrad.geometry.shapes.activeshapemodels.ActiveShapeModel;
import edu.stanford.rsl.conrad.geometry.shapes.compound.CompoundShape;
import edu.stanford.rsl.conrad.geometry.shapes.mesh.Mesh;
import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND;
import edu.stanford.rsl.conrad.geometry.shapes.simple.Triangle;
import edu.stanford.rsl.conrad.io.RotTransIO;
import edu.stanford.rsl.conrad.io.VTKMeshIO;
import edu.stanford.rsl.conrad.numerics.SimpleMatrix;
import edu.stanford.rsl.conrad.numerics.SimpleOperators;
import edu.stanford.rsl.conrad.numerics.SimpleVector;
import edu.stanford.rsl.conrad.phantom.AnalyticPhantom;
import edu.stanford.rsl.conrad.physics.PhysicalObject;
import edu.stanford.rsl.conrad.physics.materials.database.MaterialsDB;
import edu.stanford.rsl.conrad.utils.CONRAD;
import edu.stanford.rsl.conrad.utils.UserUtil;
public class CONRADCardiacModel3D extends AnalyticPhantom{
/**
*
*/
private static final long serialVersionUID = 3491173229903879891L;
boolean DEBUG = false;
/**
* File containing the PcaIO file containing the necessary information for model generation.
*/
private String heartBase;
private SimpleMatrix rot;
private SimpleVector trans;
//==========================================================================================
// METHODS
//==========================================================================================
/**
* Configures the heart simulation.
*/
public void configure() throws Exception{
//heartBase = UserUtil.queryString("Specify path to model directory.", "C:\\Stanford\\CONRAD\\data\\CardiacModel\\");
heartBase = System.getProperty("user.dir") + "\\data\\CardiacModel\\";
// perform sanity check
File scF = new File(heartBase);
File[] listOfFiles = scF.listFiles();
if(listOfFiles.length < 7){
throw new Exception("CONRADCardiacModel files are not found at "+heartBase+".\n Please download them from: https://www5.cs.fau.de/conrad/data/heart-model/");
}
int phase = UserUtil.queryInt("Specify phase to be modeled [0,9].", 0);
boolean rotTrans = UserUtil.queryBoolean("Apply rotatation and translation?");
if(!rotTrans){
new SimpleMatrix();
this.rot = SimpleMatrix.I_3;
this.trans = new SimpleVector(3);
}else{
String rtfile = UserUtil.queryString("Specify file containing rotation and translation:", heartBase + "rotTrans.txt");
RotTransIO rtinput = new RotTransIO(rtfile);
this.rot = rtinput.getRotation();
this.trans = rtinput.getTranslation();
}
// read config file for offsets etc
CONRADCardiacModelConfig info = new CONRADCardiacModelConfig(heartBase);
info.read();
// read the PCs of all phases
ActiveShapeModel parameters = new ActiveShapeModel(heartBase + "\\CCmScores.ccm");
double[] scores;
boolean predefined = UserUtil.queryBoolean("Use predefined model?");
if(predefined){
Object tobj = UserUtil.chooseObject("Choose heart to be simulated:", "Predefined models:", PredefinedModels.getList(), PredefinedModels.getList()[0]);
scores = PredefinedModels.getValue(tobj.toString());
}else{
scores = UserUtil.queryArray("Specify model parameters: ", new double[parameters.numComponents]);
// the scores are defined with respect to variance but we want to have them with respect to standard deviation therefore divide by
// sqrt of variance
for(int i = 0; i < parameters.numComponents; i++){
scores[i] /= Math.sqrt(parameters.getEigenvalues()[i]);
}
}
int start = 0;
for(int i = 0; i < phase; i++){
start += (i==0) ? 0:info.principalComp[i-1];
}
// gets the mode parameters only for the phase specified
double[] param = parameters.getModel(scores).getPoints().getCol(0).getSubVec(start, info.principalComp[phase]).copyAsDoubleArray();
String pcaFile = heartBase + "\\CardiacModel\\phase_" + phase + ".ccm";
createPhantom(pcaFile, param, info);
}
/**
* Create the phantom using the PCA file and parameters specified via user util. The material assignment could be reworked.
* @param pcaFile
* @param parameters
* @param info
*/
private void createPhantom(String pcaFile, double[] parameters, CONRADCardiacModelConfig info){
ActiveShapeModel asm = new ActiveShapeModel(pcaFile);
Mesh allComp = asm.getModel(parameters);
int count = 0;
for(heartComponents hc : heartComponents.values()){
String material;
if(hc.getName().contains("myocardium")){
material = "Heart";
}else if(hc.getName().contains("aorta")){
material = "Aorta";
}else if(hc.getName().contains("leftVentricle")){
material = "CoronaryArtery";
}else{
material = "Blood";
}
Mesh comp = new Mesh();
SimpleMatrix pts = allComp.getPoints().getSubMatrix(info.vertexOffs[count], 0, hc.getNumVertices(), info.vertexDim);
// rotate and translate points
for(int i = 0; i < pts.getRows(); i++){
SimpleVector row = SimpleOperators.multiply(rot, pts.getRow(i));
row.add(trans);
pts.setRowValue(i, row);
}
comp.setPoints(pts);
comp.setConnectivity(allComp.getConnectivity().getSubMatrix(info.triangleOffs[count], 0, hc.getNumTriangles(), 3));
PhysicalObject modelPO = createPhysicalObject(hc.getName(), comp, material);
add(modelPO);
count++;
if(DEBUG){
String inspectionFolder = heartBase + "meshReference/";
File inspect = new File(inspectionFolder);
if(!inspect.exists()) inspect.mkdirs();
String inspectionFile = inspectionFolder + hc.getName() + ".vtk";
VTKMeshIO wr = new VTKMeshIO(inspectionFile);
wr.setMesh(comp);
wr.write();
}
}
}
/**
* Creates a physical object from a mesh for rendering.
* @param m The mesh to be converted.
* @param material The material of the physical object.
* @return The mesh as physical object with specified material.
*/
private PhysicalObject createPhysicalObject(String nameStr, Mesh m, String material){
PhysicalObject po = new PhysicalObject();
po.setMaterial(MaterialsDB.getMaterialWithName(material));
po.setShape(createCompoundShape(m));
po.setNameString(nameStr);
return po;
}
/**
* Creates a CompoundShape consisting of all faces of the input mesh. The mesh has to be a triangular mesh.
* @param m The triangular mesh.
* @return The compound shape consisting of all faces of the mesh represented as triangles.
*/
private CompoundShape createCompoundShape(Mesh m){
CompoundShape cs = new CompoundShape();
SimpleMatrix vertices = m.getPoints();
SimpleMatrix faces = m.getConnectivity();
for(int i = 0; i < m.numConnections; i++){
addTriangleAtIndex(cs, vertices, faces, i);
}
return cs;
}
/**
* Constructs the triangle corresponding to the i-th face in a mesh given the connectivity information fcs and the vertices vtc and adds it to
* the CompoundShape.
* @param vtc The vertices of the mesh.
* @param fcs The faces of the mesh, i.e connectivity information.
* @param i The index of the face to be constructed.
*/
private void addTriangleAtIndex(CompoundShape cs, SimpleMatrix vtc, SimpleMatrix fcs, int i){
SimpleVector face = fcs.getRow(i);
SimpleVector dirU = vtc.getRow((int)face.getElement(1));
dirU.subtract(vtc.getRow((int)face.getElement(0)));
double l2 = dirU.normL2();
SimpleVector dirV = vtc.getRow((int)face.getElement(2));
dirV.subtract(vtc.getRow((int)face.getElement(0)));
if(dirV.normL2() < l2){
l2 = dirV.normL2();
}
double nN = General.crossProduct(dirU.normalizedL2(), dirV.normalizedL2()).normL2();
if(l2 < Math.sqrt(CONRAD.DOUBLE_EPSILON) || nN < Math.sqrt(CONRAD.DOUBLE_EPSILON)){
}else{
Triangle t = new Triangle( new PointND(vtc.getRow((int)face.getElement(0))),
new PointND(vtc.getRow((int)face.getElement(1))),
new PointND(vtc.getRow((int)face.getElement(2))));
cs.add(t);
}
}
@Override
public String getBibtexCitation() {
// TODO Auto-generated method stub
return CONRAD.CONRADBibtex;
}
@Override
public String getMedlineCitation() {
// TODO Auto-generated method stub
return CONRAD.CONRADMedline;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return "ConradCardiacModel";
}
}