/*
* Copyright (C) 2010-2014 Andreas Maier, Rotimi X Ojo
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/
package edu.stanford.rsl.conrad.phantom.workers;
import java.util.ArrayList;
import java.util.Iterator;
import edu.stanford.rsl.conrad.data.numeric.Grid2D;
import edu.stanford.rsl.conrad.geometry.Projection;
import edu.stanford.rsl.conrad.geometry.shapes.simple.Edge;
import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND;
import edu.stanford.rsl.conrad.geometry.shapes.simple.StraightLine;
import edu.stanford.rsl.conrad.geometry.trajectories.Trajectory;
import edu.stanford.rsl.conrad.geometry.transforms.Translation;
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.phantom.AnalyticPhantom4D;
import edu.stanford.rsl.conrad.phantom.AsciiSTLMeshPhantom;
import edu.stanford.rsl.conrad.physics.PhysicalObject;
import edu.stanford.rsl.conrad.physics.detector.XRayDetector;
import edu.stanford.rsl.conrad.physics.materials.Material;
import edu.stanford.rsl.conrad.physics.materials.database.MaterialsDB;
import edu.stanford.rsl.conrad.rendering.AbstractRayTracer;
import edu.stanford.rsl.conrad.rendering.PrioritizableScene;
import edu.stanford.rsl.conrad.rendering.PriorityRayTracer;
import edu.stanford.rsl.conrad.rendering.WatertightRayTracer;
import edu.stanford.rsl.conrad.utils.CONRAD;
import edu.stanford.rsl.conrad.utils.Configuration;
import edu.stanford.rsl.conrad.utils.RegKeys;
import edu.stanford.rsl.conrad.utils.UserUtil;
/**
* <p>Projects arbitrarily defined phantoms to a detector using ray casting.<br/>
* The pixel value on the detector is determined by the absorption model.<BR><BR>
* <b>If you change anything in this class, notify the conrad-dev mailing list.</b>
*
* @author Rotimi X Ojo
* @author Andreas Maier
*
*/
public class AnalyticPhantomProjectorWorker extends SliceWorker {
protected AnalyticPhantom phantom;
protected Trajectory trajectory = Configuration.getGlobalConfiguration().getGeometry();
protected XRayDetector detector;
private AbstractRayTracer rayTracer;
private static boolean accurate = false;
// First rule of optimization is: Don't optimize!
//Start for speed up
//private StraightLine castLine = new StraightLine(new PointND(0,0,0),new SimpleVector(0,0,0));
//private SimpleVector pixel = new SimpleVector(0, 0);
//private ArrayList<PhysicalObject> segmentsBuff = new ArrayList<PhysicalObject> (1);
protected PhysicalObject environment = new PhysicalObject();
//private PointND startPoint = new PointND(0,0);
//private PointND endPoint = new PointND(0,0);
//private Edge environmentEdge = new Edge(startPoint, endPoint);
//private PointND raySource = new PointND(0,0,0);
//End for speed up
/**
* Allow deriving classes to change the ray tracer
* @return instance of a ray tracer
*/
protected AbstractRayTracer getRayTracer() {
return rayTracer;
}
/**
* Choose the ray tracer depending on the phantom. In case we're dealing with a triangle mesh, the watertight ray tracer is used by default. Otherwise the priority ray tracer is chosen.
* If the user wants to use the (more established) priority ray tracer for any phantom, he can set a registry key to enforce usage of the priority ray tracer.
*/
protected void initRayTracer() {
boolean enforcePriorityRayTracer = Configuration.getGlobalConfiguration().getRegistryEntry(RegKeys.PHANTOM_PROJECTOR_ENFORCE_PRIORITY_RAYTRACER).equals("true");
if (!enforcePriorityRayTracer && phantom instanceof AsciiSTLMeshPhantom) {
rayTracer = new WatertightRayTracer();
} else {
rayTracer = new PriorityRayTracer();
}
}
@Override
public void workOnSlice(int sliceNumber) {
PrioritizableScene phantomScene = phantom;
if (phantom instanceof AnalyticPhantom4D) {
AnalyticPhantom4D scene4D = (AnalyticPhantom4D) phantom;
phantomScene = scene4D.getScene(((double) sliceNumber) / trajectory.getProjectionStackSize());
String disableAutoCenterBoolean = Configuration.getGlobalConfiguration().getRegistryEntry(RegKeys.DISABLE_CENTERING_4DPHANTOM_PROJECTION_RENDERING);
boolean disableAutoCenter = false;
if (disableAutoCenterBoolean != null){
disableAutoCenter = Boolean.parseBoolean(disableAutoCenterBoolean);
}
Translation centerTranslation = new Translation(new SimpleVector(0,0,0));
if (!disableAutoCenter){
SimpleVector center = SimpleOperators.add(phantom.getMax().getAbstractVector(), phantom.getMin().getAbstractVector()).dividedBy(2);
centerTranslation = new Translation(center.negated());
}
for (PhysicalObject o:phantomScene){
o.getShape().applyTransform(centerTranslation);
//System.out.println(o.getShape().getMax() + " " + o.getShape().getMin());
//Translate a part of XCAT to the center of source & detector for 2D projection (e.g. knee at the center of the 2d projection)
String translationString = Configuration.getGlobalConfiguration().getRegistryEntry(RegKeys.GLOBAL_TRANSLATION_4DPHANTOM_PROJECTION_RENDERING);
if (translationString != null){
// Center b/w RKJC & LKJC: -292.6426 211.7856 440.7783 (subj 5, static60),-401.1700 165.9885 478.5600 (subj 2, static60)
// XCAT Center by min & max: -177.73999504606988, 179.8512744259873, 312.19713254613583
// translationVector = (XCAT Center by min & max) - (Center b/w RKJC & LKJC)=>
// 114.9026, -31.9343, -128.5811 (subj5), 120, 3, -110(subj2) Try 114.0568 2.4778 -106.2550
String [] values = translationString.split(", ");
SimpleVector translationVector = new SimpleVector(Double.parseDouble(values[0]), Double.parseDouble(values[1]), Double.parseDouble(values[2]));
Translation translationToRotationCenter = new Translation(translationVector);
o.getShape().applyTransform(translationToRotationCenter);
}
}
//System.out.println(phantomScene.getMax() + " " + phantomScene.getMin());
}
Grid2D slice = raytraceScene(phantomScene, trajectory.getProjectionMatrix(sliceNumber));
this.imageBuffer.add(slice, sliceNumber);
}
public Grid2D raytraceScene(PrioritizableScene phantomScene, Projection projection){
Trajectory geom = Configuration.getGlobalConfiguration().getGeometry();
//Grid2D slice = new Grid2D(geom.getDetectorWidth(), geom.getDetectorHeight());
Grid2D slice = detector.createDetectorGrid(geom.getDetectorWidth(), geom.getDetectorHeight(), projection);
getRayTracer().setScene(phantomScene);
// Second rule of optimization is: Optimize later.
PointND raySource = new PointND(0,0,0);
raySource.setCoordinates(projection.computeCameraCenter());
StraightLine castLine = new StraightLine(raySource,new SimpleVector(0,0,0));
SimpleVector centerPixDir = null;
if (accurate){
centerPixDir = projection.computePrincipalAxis();
}
//SimpleVector prinpoint = trajectory.getProjectionMatrix(sliceNumber).getPrincipalPoint();
double xcorr = 0;//trajectory.getDetectorWidth()/2 - prinpoint.getElement(0);
double ycorr = 0;//trajectory.getDetectorHeight()/2 - prinpoint.getElement(1);
double length = trajectory.getSourceToDetectorDistance();
Edge environmentEdge = new Edge(new PointND(0), new PointND(length));
ArrayList<PhysicalObject> fallBackBackground = new ArrayList<PhysicalObject> (1);
SimpleVector pixel = new SimpleVector(0, 0);
boolean negate = false;
for(int y = 0; y < trajectory.getDetectorHeight(); y++){
for(int x = 0; x < trajectory.getDetectorWidth();x++){
pixel.setElementValue(0, x-xcorr);
pixel.setElementValue(1, y-ycorr);
SimpleVector dir = projection.computeRayDirection(pixel);
if ((y==0) && (x==0)) {
//Check that ray direction is towards origin.
double max = 0;
int index = 0;
for (int i=0; i < 3; i++){
if (Math.abs(dir.getElement(i)) > max) {
max = Math.abs(dir.getElement(i));
index = i;
}
}
double t = - raySource.get(index) / dir.getElement(index);
if (t < 0) negate = true;
}
if (negate) dir.negate();
castLine.setDirection(dir);
ArrayList<PhysicalObject> segments = getRayTracer().castRay(castLine);
if (accurate){
double dirCosine = SimpleOperators.multiplyInnerProd(centerPixDir,dir);
length = trajectory.getSourceToDetectorDistance()/dirCosine;
}
if(segments == null){
fallBackBackground.clear();
segments = fallBackBackground;
} else {
if (accurate) {
environmentEdge = new Edge(new PointND(0), new PointND(length - getTotalSegmentsLength(segments)));
}
}
environment.setShape(environmentEdge);
segments.add(environment);
// old code:
// double integral = absorptionModel.evaluateLineIntegral(segments);
// slice.putPixelValue(x, y, integral);
detector.writeToDetector(slice, x, y, segments);
}
}
return slice;
}
private double getTotalSegmentsLength(ArrayList<PhysicalObject> segments) {
double sum = 0;
Iterator<PhysicalObject> it = segments.iterator();
while(it.hasNext()){
//sum+=((Edge) it.next().getShape()).getLength();
}
return sum;
}
public SliceWorker clone() {
AnalyticPhantomProjectorWorker newRend = new AnalyticPhantomProjectorWorker();
newRend.phantom = phantom;
newRend.detector = detector;
newRend.environment.setMaterial(phantom.getBackgroundMaterial());
newRend.initRayTracer();
newRend.rayTracer.setScene((PrioritizableScene)phantom);
return newRend;
}
@Override
public void configure() throws Exception {
// We will now read this from the Configuration. The detector needs to be preconfigured!
//detector = (XRayDetector) UserUtil.queryObject("Select Detector:", "Detector Selection", XRayDetector.class);
//detector.configure();
detector = Configuration.getGlobalConfiguration().getDetector();
detector.init();
phantom = UserUtil.queryPhantom("Select Phantom", "Select Phantom");
Material mat = null;
do{
String materialstr = UserUtil.queryString("Enter Background Medium:", "vacuum");
mat = MaterialsDB.getMaterialWithName(materialstr);
} while(mat == null);
phantom.setBackground(mat);
phantom.configure();
// Choose the ray tracer depending on the phantom
initRayTracer();
getRayTracer().setScene((PrioritizableScene)phantom);
environment.setMaterial(phantom.getBackgroundMaterial());
super.configure();
}
public void configure(AnalyticPhantom phan, XRayDetector detector) throws Exception {
this.detector = detector;
this.detector.init();
phantom = phan;
if (phantom.getBackgroundMaterial() == null){
Material mat = null;
String materialstr = "vacuum";
mat = MaterialsDB.getMaterialWithName(materialstr);
phantom.setBackground(mat);
}
initRayTracer();
getRayTracer().setScene((PrioritizableScene)phantom);
environment.setMaterial(phantom.getBackgroundMaterial());
super.configure();
}
@Override
public String getProcessName() {
return "Generic Phantom Projector";
}
@Override
public String getBibtexCitation() {
return CONRAD.CONRADBibtex;
}
@Override
public String getMedlineCitation() {
return CONRAD.CONRADMedline;
}
/**
* @return the absorptionModel
*/
public XRayDetector getDetector() {
return detector;
}
/**
* @param absorptionModel the absorptionModel to set
*/
public void setDetector(XRayDetector detector) {
this.detector = detector;
}
}