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