package edu.stanford.rsl.conrad.phantom.workers;
import ij.process.FloatProcessor;
import java.util.ArrayList;
import edu.stanford.rsl.conrad.data.numeric.Grid2D;
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.SimpleVector;
import edu.stanford.rsl.conrad.phantom.AnalyticPhantom;
import edu.stanford.rsl.conrad.phantom.AnalyticPhantom4D;
import edu.stanford.rsl.conrad.physics.PhysicalObject;
import edu.stanford.rsl.conrad.physics.materials.utils.AttenuationType;
import edu.stanford.rsl.conrad.rendering.AbstractRayTracer;
import edu.stanford.rsl.conrad.rendering.PrioritizableScene;
import edu.stanford.rsl.conrad.rendering.Priority1DRayTracer;
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;
/**
* Renders arbitrarily defined phantoms
* Works now with the correct origin computations.
*
* @author Rotimi X Ojo
*
*/
public class AnalyticPhantom3DVolumeRenderer extends SliceWorker {
protected boolean renderAttenuation = false;
protected AnalyticPhantom phantom = null;
protected PrioritizableScene currentScene = null;
private static double DEFAULT_ATTENUATION = -1024; // air(HU)
protected double xrayEnergy = 80;
protected AttenuationType attType = AttenuationType.TOTAL_WITH_COHERENT_ATTENUATION;
@Override
public String getProcessName() {
return "Generic Phantom Renderer";
}
@Override
public String getBibtexCitation() {
return CONRAD.CONRADBibtex;
}
@Override
public String getMedlineCitation() {
return CONRAD.CONRADMedline;
}
@Override
public void workOnSlice(int sliceNumber) {
Trajectory geom = Configuration.getGlobalConfiguration().getGeometry();
double originIndexX = geom.getOriginInPixelsX();
double originIndexY = geom.getOriginInPixelsY();
double originIndexZ = geom.getOriginInPixelsZ();
double voxelSizeX = geom.getVoxelSpacingX();
double voxelSizeY = geom.getVoxelSpacingY();
double voxelSizeZ = geom.getVoxelSpacingZ();
FloatProcessor slice = new FloatProcessor(geom.getReconDimensionX(), geom.getReconDimensionY());
PrioritizableScene phantomScene = phantom;
AbstractRayTracer tracer = new Priority1DRayTracer();
if (phantom instanceof AnalyticPhantom4D){
if (currentScene == null) {
AnalyticPhantom4D scene = (AnalyticPhantom4D) phantom;
phantomScene = scene.getScene(0);
String key = Configuration.getGlobalConfiguration().getRegistry().get(RegKeys.RENDER_PHANTOM_VOLUME_AUTO_CENTER);
if (key != null){
if (key.equals("true")) {
Translation translate = phantom.computeCenterTranslation();
for (PhysicalObject o : phantomScene){
o.applyTransform(translate);
}
}
}
currentScene = phantomScene;
}
phantomScene = currentScene;
}
tracer.setScene(phantomScene);
slice.add(DEFAULT_ATTENUATION);
double z = (sliceNumber - originIndexZ) * voxelSizeZ;
double xFirst = (-originIndexX) * voxelSizeX;
double xLast = (slice.getWidth()-originIndexX) * voxelSizeX;
if (renderAttenuation && xrayEnergy > 0)
slice.setValue(phantomScene.getBackgroundMaterial().getAttenuation(xrayEnergy,attType));
else
slice.setValue(phantomScene.getBackgroundMaterial().getDensity());
slice.fill();
for (int i = 0; i < slice.getHeight(); i ++){
double y = (i - originIndexY) * voxelSizeY;
PointND pointLeft = new PointND(xFirst, y, z);
PointND pointRight = new PointND(xLast, y, z);
long basetime = System.currentTimeMillis();
StraightLine line = new StraightLine(pointLeft, pointRight);
line.normalize();
ArrayList<PhysicalObject> segments = tracer.castRay(line);
long castTime = System.currentTimeMillis() - basetime;
basetime = System.currentTimeMillis();
if (segments != null) {
for (PhysicalObject o: segments){
Edge lineSegment = (Edge) o.getShape();
PointND p1 = lineSegment.getPoint();
PointND p2 = lineSegment.getEnd();
int ix1 = (int) Math.round((p1.get(0) / voxelSizeX) + originIndexX);
int ix2 = (int) Math.round((p2.get(0) / voxelSizeX) + originIndexX);
if (ix2 <= ix1){
continue;
}
int iy = (int) Math.round((y / voxelSizeY) + originIndexY);
if (renderAttenuation && xrayEnergy > 0)
slice.setValue(o.getMaterial().getAttenuation(xrayEnergy, attType));
else
slice.setValue(o.getMaterial().getDensity());
slice.drawLine(ix1, iy, ix2-1, iy);
}
long renderTime = System.currentTimeMillis() - basetime;
if (sliceNumber == 123) System.out.println("Cast time " + castTime + " render time " + renderTime + " " + segments.size());
}
}
Grid2D grid = new Grid2D((float[])slice.getPixels(), slice.getWidth(), slice.getHeight());
this.imageBuffer.add(grid, sliceNumber);
}
public SliceWorker clone() {
AnalyticPhantom3DVolumeRenderer newRend = new AnalyticPhantom3DVolumeRenderer();
newRend.phantom = this.phantom;
newRend.attType=attType;
newRend.xrayEnergy = xrayEnergy;
newRend.renderAttenuation = renderAttenuation;
newRend.showStatus = showStatus;
return newRend;
}
@Override
public void configure() throws Exception {
super.configure();
phantom = UserUtil.queryPhantom("Select Phantom", "Select Phantom");
phantom.configure();
renderAttenuation = UserUtil.queryBoolean("Render energy dependent attenuation?");
if(renderAttenuation)
xrayEnergy = UserUtil.queryDouble("Monochromatic Xray energy [keV]", xrayEnergy);
}
}
/*
* Copyright (C) 2010-2014 Andreas Maier, Rotimi X Ojo
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/