package edu.stanford.rsl.conrad.geometry.splines;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import ij.IJ;
import ij.gui.GenericDialog;
import ij.process.FloatProcessor;
import edu.stanford.rsl.conrad.data.numeric.Grid2D;
import edu.stanford.rsl.conrad.geometry.AbstractShape;
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.shapes.simple.VectorPoint3D;
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.AnalyticPhantom4D;
import edu.stanford.rsl.conrad.phantom.workers.SliceWorker;
import edu.stanford.rsl.conrad.phantom.xcat.XCatScene;
import edu.stanford.rsl.conrad.physics.PhysicalObject;
import edu.stanford.rsl.conrad.physics.absorption.AbsorptionModel;
import edu.stanford.rsl.conrad.physics.absorption.SelectableEnergyMonochromaticAbsorptionModel;
import edu.stanford.rsl.conrad.physics.detector.XRayDetector;
import edu.stanford.rsl.conrad.physics.materials.Material;
import edu.stanford.rsl.conrad.physics.materials.utils.AttenuationType;
import edu.stanford.rsl.conrad.rendering.AbstractRayTracer;
import edu.stanford.rsl.conrad.rendering.AbstractScene;
import edu.stanford.rsl.conrad.rendering.PrioritizableScene;
import edu.stanford.rsl.conrad.rendering.Priority1DRayTracer;
import edu.stanford.rsl.conrad.utils.Configuration;
import edu.stanford.rsl.conrad.utils.FileUtil;
import edu.stanford.rsl.conrad.utils.RegKeys;
import edu.stanford.rsl.conrad.utils.UserUtil;
public class SurfaceBSplineVolumePhantom extends SliceWorker {
private ArrayList<SurfaceBSpline> list;
private AbstractScene scene;
private PointND [] hullPoints;
private ArrayList<Iterator<VectorPoint3D>> voxelList;
private SimpleVector bounds;
private boolean preserveAspects = true;
private ArrayList<SimpleVector> localBounds;
private boolean showRaster = false;
private boolean showVertices = false;
private boolean renderSolid = true;
protected double xrayEnergy = 80;
protected AttenuationType attType = AttenuationType.TOTAL_WITH_COHERENT_ATTENUATION;
boolean renderAttenuation = false;
@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());
if (renderSolid){
if (renderAttenuation && xrayEnergy > 0)
slice.setValue(scene.getBackgroundMaterial().getAttenuation(xrayEnergy,attType));
else
slice.setValue(scene.getBackgroundMaterial().getDensity());
slice.fill();
}
AbstractRayTracer tracer = new Priority1DRayTracer();
tracer.setScene(scene);
if (showVertices) {
Iterator<VectorPoint3D> iterator = voxelList.get(sliceNumber);
while(iterator.hasNext()){
VectorPoint3D voxel = iterator.next();
slice.putPixelValue((int)voxel.getX(), (int)voxel.getY(), voxel.getVector().getElement(0));
}
}
double z = (sliceNumber - originIndexZ) * voxelSizeZ;
double xFirst = (-originIndexX) * voxelSizeX;
double xLast = (slice.getWidth()-originIndexX) * voxelSizeX;
if (renderSolid) {
//long basetimeSlice = System.currentTimeMillis();
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();
//System.out.println(line.getDirection());
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);
int iy = (int) Math.round((y / voxelSizeY) + originIndexY);
if (renderAttenuation && xrayEnergy > 0)
slice.setValue(o.getMaterial().getAttenuation(xrayEnergy, attType)*o.getMaterial().getDensity());
else
slice.setValue(o.getMaterial().getDensity());
slice.drawLine(ix1, iy, ix2, iy);
}
//long renderTime = System.currentTimeMillis() - basetime;
//if (sliceNumber == 50) System.out.println("Cast time " + castTime + " render time " + renderTime + " " + segments.size());
}
}
//long slicetime = System.currentTimeMillis() - basetimeSlice;
//if (sliceNumber == 123) System.out.println("Slice time: " + slicetime);
}
if (showRaster) {
for (double splineNum = 0; splineNum < list.size(); splineNum++) {
for (PointND p: hullPoints){
if (Math.round(((p.get(2) / voxelSizeZ) + originIndexZ)) == sliceNumber){
slice.putPixelValue((int)((p.get(0) / voxelSizeX) + originIndexX), (int)((p.get(1) / voxelSizeY) + originIndexY), splineNum+1);
}
}
}
}
Grid2D grid = new Grid2D((float[]) slice.getPixels(), slice.getWidth(), slice.getHeight());
imageBuffer.add(grid, sliceNumber);
}
@Override
public String getProcessName() {
return "Surface BSpline Phantom";
}
@Override
public String getBibtexCitation() {
return "see medline";
}
@Override
public String getMedlineCitation() {
return "L. Piegl and W. Tiller. The NURBS Book. Second Edition. Springer, Berlin, Heidelberg, New York. 1997.";
}
private void initBounds(){
localBounds = new ArrayList<SimpleVector>();
for (int i =0 ; i < list.size(); i++){
SurfaceBSpline spline = list.get(i);
SimpleVector bounds = SimpleOperators.concatenateVertically(spline.getMin().getAbstractVector(), spline.getMax().getAbstractVector());
//System.out.println(bounds);
localBounds.add(bounds);
}
SimpleVector max = SimpleOperators.max((SimpleVector []) localBounds.toArray(new SimpleVector [localBounds.size()])).getSubVec(3, 3);
SimpleVector min = SimpleOperators.min((SimpleVector []) localBounds.toArray(new SimpleVector [localBounds.size()])).getSubVec(0, 3);
bounds = SimpleOperators.concatenateVertically(min, max);
}
public void readSplineListFromFile(String filename) throws IOException{
list = SurfaceBSpline.readSplinesFromFile(filename);
initBounds();
}
public void setSplineList(ArrayList<SurfaceBSpline> list){
this.list = list;
initBounds();
}
public void resizeVolumeToMatchBounds(PointND min, PointND max){
bounds = SimpleOperators.concatenateVertically(min.getAbstractVector(), max.getAbstractVector());
double rangeX = (max.getAbstractVector().getElement(0) - min.getAbstractVector().getElement(0))*1.05;
double rangeY = (max.getAbstractVector().getElement(1) - min.getAbstractVector().getElement(1))*1.05;
double rangeZ = (max.getAbstractVector().getElement(2) - min.getAbstractVector().getElement(2))*1.05;
//System.out.println("Range Z: " + rangeZ);
Trajectory traj = Configuration.getGlobalConfiguration().getGeometry();
double voxelSizeX = rangeX / ((double) traj.getReconDimensionX());
double voxelSizeY = rangeY / ((double) traj.getReconDimensionY());
double voxelSizeZ = rangeZ / ((double) traj.getReconDimensionZ());
double shiftX = 0;
double shiftY = 0;
double shiftZ = 0;
if (preserveAspects) {
double maxRatio = voxelSizeX;
if (maxRatio < voxelSizeY) maxRatio = voxelSizeY;
if (maxRatio < voxelSizeZ) maxRatio = voxelSizeZ;
// size of object in voxel space
double sizeX = rangeX / maxRatio;
double sizeY = rangeY / maxRatio;
double sizeZ = rangeZ / maxRatio;
voxelSizeX = maxRatio;
voxelSizeY = maxRatio;
voxelSizeZ = maxRatio;
shiftX = (traj.getReconDimensionX() - sizeX) / 2;
shiftY = (traj.getReconDimensionY() - sizeY) / 2;
shiftZ = (traj.getReconDimensionZ() - sizeZ) / 2;
}
traj.setOriginInPixelsX(shiftX-(min.getAbstractVector().getElement(0) / voxelSizeX));
traj.setOriginInPixelsY(shiftY-(min.getAbstractVector().getElement(1) / voxelSizeY));
traj.setOriginInPixelsZ(shiftZ-(min.getAbstractVector().getElement(2) / voxelSizeZ));
traj.setVoxelSpacingX(voxelSizeZ);
traj.setVoxelSpacingY(voxelSizeY);
traj.setVoxelSpacingZ(voxelSizeX);
}
public void setBoundsFromGeometry(AnalyticPhantom4D phantom4d){
Trajectory traj = Configuration.getGlobalConfiguration().getGeometry();
SimpleVector minVec = new SimpleVector(traj.getOriginX(), traj.getOriginY(), traj.getOriginZ());
String key = Configuration.getGlobalConfiguration().getRegistry().get(RegKeys.RENDER_PHANTOM_VOLUME_AUTO_CENTER);
boolean set= false;
if (key != null){
if (key.equals("true")) {
traj.setOriginToPhantomCenter(phantom4d);
set=true;
}
}
if (!set) {
SimpleVector max = new SimpleVector(traj.getVoxelSpacingX()*traj.getReconDimensionX(),traj.getVoxelSpacingY()*traj.getReconDimensionY(), traj.getVoxelSpacingZ()*traj.getReconDimensionZ());
max.add(minVec);
bounds = SimpleOperators.concatenateVertically(minVec, max);
}
}
public void resizeVolumeToMatchSplineSpace(){
double rangeX = (bounds.getElement(3) - bounds.getElement(0))*1.05;
double rangeY = (bounds.getElement(4) - bounds.getElement(1))*1.05;
double rangeZ = (bounds.getElement(5) - bounds.getElement(2))*1.05;
//System.out.println("Range Z: " + rangeZ);
Trajectory traj = Configuration.getGlobalConfiguration().getGeometry();
double voxelSizeX = rangeX / ((double) traj.getReconDimensionX());
double voxelSizeY = rangeY / ((double) traj.getReconDimensionY());
double voxelSizeZ = rangeZ / ((double) traj.getReconDimensionZ());
double shiftX = 0;
double shiftY = 0;
double shiftZ = 0;
if (preserveAspects) {
double maxRatio = voxelSizeX;
if (maxRatio < voxelSizeY) maxRatio = voxelSizeY;
if (maxRatio < voxelSizeZ) maxRatio = voxelSizeZ;
// size of object in voxel space
double sizeX = rangeX / maxRatio;
double sizeY = rangeY / maxRatio;
double sizeZ = rangeZ / maxRatio;
voxelSizeX = maxRatio;
voxelSizeY = maxRatio;
voxelSizeZ = maxRatio;
shiftX = (traj.getReconDimensionX() - sizeX) / 2;
shiftY = (traj.getReconDimensionY() - sizeY) / 2;
shiftZ = (traj.getReconDimensionZ() - sizeZ) / 2;
}
traj.setOriginInPixelsX(shiftX - (bounds.getElement(0) / voxelSizeX));
traj.setOriginInPixelsY(shiftY - (bounds.getElement(1) / voxelSizeY));
traj.setOriginInPixelsZ(shiftZ - (bounds.getElement(2) / voxelSizeZ));
traj.setVoxelSpacingX(voxelSizeZ);
traj.setVoxelSpacingY(voxelSizeY);
traj.setVoxelSpacingZ(voxelSizeX);
}
public ArrayList<AbstractShape> tesselateSplines(double samplingU, double samplingV){
Trajectory traj = Configuration.getGlobalConfiguration().getGeometry();
ArrayList<AbstractShape> meshList = new ArrayList<AbstractShape>();
int numSlices = traj.getReconDimensionZ();
voxelList = new ArrayList<Iterator<VectorPoint3D>>();
ArrayList<ArrayList<VectorPoint3D>> pointList = new ArrayList<ArrayList<VectorPoint3D>>();
for (int i = 0; i < numSlices; i++){
pointList.add(new ArrayList<VectorPoint3D>());
}
double voxelSizeX = traj.getVoxelSpacingX();
double voxelSizeY = traj.getVoxelSpacingY();
double voxelSizeZ = traj.getVoxelSpacingZ();
double originIndexX = traj.getOriginInPixelsX();
double originIndexY = traj.getOriginInPixelsY();
double originIndexZ = traj.getOriginInPixelsZ();
SimpleVector scaling = new SimpleVector(1.0 / voxelSizeX, 1.0 / voxelSizeY, 1.0 / voxelSizeZ);
SimpleVector shift = new SimpleVector(originIndexX, originIndexY, originIndexZ);
// Voxelize surface
double samplingFactorU = samplingU / traj.getReconDimensionX();
double samplingFactorV = samplingV / traj.getReconDimensionY();
for (double splineNum = 0; splineNum < list.size(); splineNum++) {
SimpleVector bound = localBounds.get((int) splineNum);
double rangeX = (bound.getElement(3) - bound.getElement(0)) / voxelSizeX;
double rangeY = (bound.getElement(4) - bound.getElement(1)) / voxelSizeY;
double rangeZ = (bound.getElement(5) - bound.getElement(2)) / voxelSizeZ;
int maxRange = (int) Math.ceil(Math.max(Math.max(rangeX, rangeY), rangeZ));
samplingU = ((int)(maxRange * samplingFactorU));
samplingV = ((int)(maxRange * samplingFactorV));
// Old rendering (Sampline of the surface at a fixed grid):
if (showStatus) IJ.showStatus("Sampling Shape " + list.get((int) splineNum).getTitle() + " (dim "+ (int)rangeX + "x"+ (int)rangeY + "x" + (int)rangeZ +") with " + samplingU + "x" +samplingV + " grid");
//System.out.println("Sampling Shape " + list.get((int) splineNum).getTitle());
double u = splineNum/list.size();
if (showStatus) IJ.showProgress(u);
if (showVertices) {
for(double i =0 ; i < samplingU; i++){
for (double j = 0; j < samplingV; j++){
PointND p = list.get((int)splineNum).evaluate(i/samplingU, j/ samplingV);
p.getAbstractVector().multiplyElementWiseBy(scaling);
p.getAbstractVector().add(shift);
int zPosition = (int) p.getAbstractVector().getElement(2);
if (zPosition >= 0 && zPosition < numSlices){
ArrayList<VectorPoint3D> list = pointList.get(zPosition);
list.add(new VectorPoint3D(p, splineNum+1));
}
}
}
}
AbstractShape mesh = list.get((int)splineNum).tessellateMesh(samplingU, samplingV);
//System.out.println("Building done");
meshList.add(mesh);
}
if (showStatus) IJ.showProgress(1.0);
// prepare iterators for rendering.
for (int i = 0; i < numSlices; i++){
voxelList.add(Collections.synchronizedList(pointList.get(i)).iterator());
}
return meshList;
}
public void setScene(AbstractScene scene){
this.scene = scene;
}
public void generateDefaultScene(double samplingU, double samplingV) throws IOException{
scene = new PrioritizableScene();
ArrayList<AbstractShape> meshList = tesselateSplines(samplingU, samplingV);
for (int i = 0; i<meshList.size(); i++){
AbstractShape mesh = meshList.get(i);
PhysicalObject obj = new PhysicalObject();
obj.setNameString(list.get((int)i).getTitle());
obj.setMaterial(new Material(i+1));
obj.setShape(mesh);
((PrioritizableScene)scene).add(obj, PrioritizableScene.ADD_LOWEST_PRIORITY);
if (showRaster) {
hullPoints = mesh.getRasterPoints(200000);
}
}
}
public void configure() throws Exception{
Trajectory traj = Configuration.getGlobalConfiguration().getGeometry();
renderAttenuation = UserUtil.queryBoolean("Render energy dependent attenuation?");
if(renderAttenuation)
xrayEnergy = UserUtil.queryDouble("Monochromatic Xray energy [keV]", xrayEnergy);
GenericDialog gd = new GenericDialog("Configure Surface BSpline Volume");
int width = traj.getReconDimensionX();
int height = traj.getReconDimensionY();
gd.addSlider("Sampling in u direction", 10, width, width/2);
gd.addSlider("Sampling in v direction", 10, height, height/2);
gd.showDialog();
if (gd.wasCanceled()){
throw new RuntimeException("User cancelled selection.");
}
double samplingU = gd.getNextNumber();
double samplingV = gd.getNextNumber();
String filename = FileUtil.myFileChoose(".nrb", false);
readSplineListFromFile(filename);
generateDefaultScene(samplingU, samplingV);
super.configure();
}
@Override
public SliceWorker clone() {
SurfaceBSplineVolumePhantom clone = new SurfaceBSplineVolumePhantom();
clone.list = list;
clone.voxelList = voxelList;
clone.scene = scene;
clone.hullPoints = hullPoints;
clone.showStatus = showStatus;
clone.renderAttenuation = renderAttenuation;
clone.xrayEnergy = xrayEnergy;
return clone;
}
/**
* Returns an SimpleVector that specifies the bounding box of the BSpline Phantom with six entries:<BR>
* <li>Minimum X Coordinate</li>
* <li>Minimum Y Coordinate</li>
* <li>Minimum Z Coordinate</li>
* <li>Maximum X Coordinate</li>
* <li>Maximum Y Coordinate</li>
* <li>Maximum Z Coordinate</li>
* @return the bounding box vector
*/
public SimpleVector getBounds(){
return bounds;
}
public void setXrayEnergy(double xrayEnergy) {
this.xrayEnergy = xrayEnergy;
}
public void setRenderAttenuation(boolean renderAttenuation) {
this.renderAttenuation = renderAttenuation;
}
}
/*
* Copyright (C) 2010-2014 Andreas Maier
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/