/*
* Copyright (C) 2014 Andreas Maier, Maximilian Dankbar
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/
package edu.stanford.rsl.conrad.physics.detector;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import edu.stanford.rsl.apps.gui.GUIConfigurable;
import edu.stanford.rsl.conrad.data.numeric.Grid2D;
import edu.stanford.rsl.conrad.data.numeric.InterpolationOperators;
import edu.stanford.rsl.conrad.geometry.Projection;
import edu.stanford.rsl.conrad.geometry.shapes.simple.Box;
import edu.stanford.rsl.conrad.geometry.shapes.simple.Edge;
import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND;
import edu.stanford.rsl.conrad.geometry.trajectories.Trajectory;
import edu.stanford.rsl.conrad.geometry.transforms.ScaleRotate;
import edu.stanford.rsl.conrad.geometry.transforms.Translation;
import edu.stanford.rsl.conrad.numerics.SimpleMatrix;
import edu.stanford.rsl.conrad.numerics.SimpleVector;
import edu.stanford.rsl.conrad.physics.PhysicalObject;
import edu.stanford.rsl.conrad.physics.absorption.AbsorptionModel;
import edu.stanford.rsl.conrad.physics.materials.Material;
import edu.stanford.rsl.conrad.utils.Configuration;
import edu.stanford.rsl.conrad.utils.UserUtil;
public class XRayDetector extends PhysicalObject implements GUIConfigurable, Serializable{
boolean configured = false;
/**
*
*/
private static final long serialVersionUID = -4281231525147924304L;
protected AbsorptionModel model;
protected Projection projection;
/**
* Method to initialize or to reset parameters of a detector model. This method is called by the rendering classes before rendering starts.
* By default nothing is done here, but the method can be overridden in the corresponding child classes.
*/
public void init(){
}
/**
* Method that is notified once the rendering process using this AbsorptionModel is finished.
* This method is intended to be used with AbsorptionModels that collect information about the
* rendering process. For example a histogram of path lengths could be stored in an AbsortionModel
* and in this call the result could be stored in the registry or a certain file.
* <br>
* The default implementation just does nothing.
*/
public void notifyEndOfRendering(){
model.notifyEndOfRendering();
}
/**
* This method creates the data container for the detector. By default a Grid2D is generated.
* More sophisticated absorption models can also use different grid containers, i.e., if we want
* to generate a material image for each material.
* @param width
* @param height
* @param the projection for the detector.
* @return the grid to store the values in.
*/
public Grid2D createDetectorGrid(int width, int height, Projection proj){
return createDetectorGrid(width, height);
}
/**
* Generates and transforms the box shape of the detector
* @param proj The projection for the detector-source setup
* @param thickness The thickness of the detector box
*/
public void generateDetectorShape(Projection proj, double thickness){
projection = proj;
Trajectory traj = Configuration.getGlobalConfiguration().getGeometry();
// Create Box in Detector Size
Box detectorShape = new Box(traj.getDetectorWidth()*traj.getPixelDimensionX(), traj.getDetectorHeight()* traj.getPixelDimensionY(), thickness);
// shift to origin.
// and include correct detector offset.
SimpleVector principalPoint = proj.computeOffset(new SimpleVector(traj.getDetectorWidth(), traj.getDetectorHeight()));
detectorShape.applyTransform(new Translation((-traj.getDetectorWidth()*traj.getPixelDimensionX()/2)-(principalPoint.getElement(0)*traj.getPixelDimensionX()), (-traj.getDetectorHeight()* traj.getPixelDimensionY()/2)-(principalPoint.getElement(1)*traj.getPixelDimensionY()), -thickness/2));
// default detector orientation (i.e. Box orientation)
// principal ray direction to which we want to align the detector.
SimpleVector targetOrientation = proj.computePrincipalAxis();
targetOrientation.normalizeL2();
// compute rotation matrix
//SimpleMatrix rot = Rotations.getRotationMatrixFromAtoB(currentOrientation, targetOrientation);
SimpleVector principalPointPixels = proj.getPrincipalPoint();
SimpleVector principalPointWorld = proj.computeRayDirection(principalPointPixels);
//determine x axis of the detector in world coordinates
SimpleVector right = principalPointPixels.clone();
right.add(new SimpleVector(1,0));
SimpleVector axisRight = proj.computeRayDirection(right);
axisRight.subtract(principalPointWorld);
axisRight.normalizeL2();
//determine y axis of the detector in world coordinates
SimpleVector up = principalPointPixels.clone();
up.add(new SimpleVector(0,1));
SimpleVector axisUp = proj.computeRayDirection(up);
axisUp.subtract(principalPointWorld);
axisUp.normalizeL2();
//build rotations matrix
SimpleMatrix rot = new SimpleMatrix(3,3);
rot.setColValue(0, axisRight);
rot.setColValue(1, axisUp);
rot.setColValue(2, targetOrientation);
detectorShape.applyTransform(new ScaleRotate(rot));
//move detector so its front plane has the correct distance to the source
detectorShape.applyTransform(new Translation(targetOrientation.multipliedBy(thickness/2)));
//move detector along the target direction to the correct distance
SimpleVector source = proj.computeCameraCenter();
source.add(targetOrientation.multipliedBy(traj.getSourceToDetectorDistance()));
detectorShape.applyTransform(new Translation(source));
this.setShape(detectorShape);
}
/**
* Absorb a single photon.
* @param grid The resulting image of the detector front plane.
* @param point The points of absorbtion in world coordinates.
* @param energy The energy of the photon
*/
public void absorbPhoton(Grid2D grid, PointND point, double energy){
Trajectory traj = Configuration.getGlobalConfiguration().getGeometry();
SimpleVector pixel = new SimpleVector(0,0);
projection.project(point.getAbstractVector(), pixel);
if (pixel.getElement(0) > traj.getDetectorWidth()|| pixel.getElement(1) > traj.getDetectorHeight() || pixel.getElement(0) < 0 || pixel.getElement(1) < 0){
//pixel outside of the grid
//System.err.println("XRayDetector: pixel outside: " + pixel.getElement(0) + ", " + pixel.getElement(1));
} else {
InterpolationOperators.addInterpolateLinear(grid, (int)pixel.getElement(0), (int)pixel.getElement(1), (float)energy);
}
}
public Grid2D createDetectorGrid(int width, int height){
Grid2D grid = new Grid2D(width, height);
return grid;
}
/**
* Method to put the absorption information into the detector. This method will be called by the ray tracer
* to store the right information in the right detector pixel. By default, this method stores the
* line integral computed by evaluateLineIntegral(segments) in each detector pixel.
*
* @param grid the image grid created by createDetectorGrid
* @param x the pixel x coordinate
* @param y the pixel y coordinate
* @param segments the segments that were passed by the ray.
*/
public void writeToDetector(Grid2D grid, int x, int y, ArrayList<PhysicalObject> segments){
grid.putPixelValue(x, y, model.evaluateLineIntegral(segments));
}
@Override
public void configure() throws Exception {
model = (AbsorptionModel) UserUtil.queryObject("Select absorption model:", "Absorption Model Selection", AbsorptionModel.class);
model.configure();
configured = true;
}
public void configure(AbsorptionModel absorpMod) throws Exception {
model = absorpMod;
if (!model.isConfigured())
model.configure();
configured = true;
}
@Override
public boolean isConfigured() {
return configured;
}
/**
* Method that parses the segments and accumulates the path length for each material.
* @param segments the ray tracing segments
* @return a HashMap that can be queried for materials and returns the respective path length.
*/
public static HashMap<Material, Double> accumulatePathLenghtForEachMaterial(ArrayList<PhysicalObject> segments){
HashMap<Material, Double> localMap = new HashMap<Material, Double>();
for (PhysicalObject segment: segments){
Material mat = segment.getMaterial();
Double value = localMap.get(mat);
if (value == null){
value = new Double(0);
}
Edge edge = (Edge) segment.getShape();
value += edge.getLength();
localMap.put(mat, value);
}
return localMap;
}
public String toString(){
String name = "Generic X-Ray Detector";
if (model!= null) name+=" " + model.toString();
return name;
}
/**
* @return the model
*/
public AbsorptionModel getModel() {
return model;
}
/**
* @param model the model to set
*/
public void setModel(AbsorptionModel model) {
this.model = model;
}
/**
* @param configured the configured to set
*/
public void setConfigured(boolean configured) {
this.configured = configured;
}
}