/* * 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; } }