content: /*
   * Copyright (C) 2010-2014 Peter Fischer, Andreas Maier
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
   */
  package edu.stanford.rsl.conrad.opencl;
  
  import java.nio.FloatBuffer;
  import java.nio.IntBuffer;
  import java.util.ArrayList;
  
  import com.jogamp.opencl.CLBuffer;
  import com.jogamp.opencl.CLCommandQueue;
  import com.jogamp.opencl.CLContext;
  import com.jogamp.opencl.CLDevice;
  import com.jogamp.opencl.CLMemory.Mem;
  
  import edu.stanford.rsl.conrad.data.numeric.Grid2D;
  import edu.stanford.rsl.conrad.geometry.AbstractShape;
  import edu.stanford.rsl.conrad.geometry.splines.TimeVariantSurfaceBSpline;
  import edu.stanford.rsl.conrad.geometry.trajectories.Trajectory;
  import edu.stanford.rsl.conrad.geometry.transforms.Translation;
  import edu.stanford.rsl.conrad.numerics.SimpleMatrix;
  import edu.stanford.rsl.conrad.numerics.SimpleOperators;
  import edu.stanford.rsl.conrad.numerics.SimpleVector;
  import edu.stanford.rsl.conrad.opencl.shapes.OpenCLCompoundShape;
  import edu.stanford.rsl.conrad.opencl.shapes.OpenCLTimeVariantSurfaceBSpline;
  import edu.stanford.rsl.conrad.phantom.AnalyticPhantom;
  import edu.stanford.rsl.conrad.phantom.AnalyticPhantom4D;
  import edu.stanford.rsl.conrad.phantom.MovingBallPhantom;
  import edu.stanford.rsl.conrad.phantom.asmheart.CONRADCardiacModel3D;
  import edu.stanford.rsl.conrad.phantom.renderer.StreamingPhantomRenderer;
  import edu.stanford.rsl.conrad.phantom.xcat.CombinedBreathingHeartScene;
  import edu.stanford.rsl.conrad.physics.PhysicalObject;
  import edu.stanford.rsl.conrad.rendering.PrioritizableScene;
  import edu.stanford.rsl.conrad.utils.Configuration;
  import edu.stanford.rsl.conrad.utils.ImageGridBuffer;
  import edu.stanford.rsl.conrad.utils.RegKeys;
  
  /**
   * Class to enable creation of projections using OpenCL.
   * Interface for Conrad to OpenCLAppendBufferRenderer
   * 
   * @author peterf
   *
   */
  public class OpenCLProjectionPhantomRenderer extends StreamingPhantomRenderer {
  
  	protected AnalyticPhantom phantom;
  	protected Trajectory trajectory = Configuration.getGlobalConfiguration().getGeometry();
  
  	private CLContext clContext;
  	
  	protected OpenCLAppendBufferRenderer renderer; // better use parent class OpenCLRenderer?
  	protected CLBuffer<FloatBuffer> screenBuffer;
  	protected CLBuffer<FloatBuffer> mu;
  	protected CLBuffer<IntBuffer> priorities;
  	protected ArrayList<OpenCLEvaluatable> clEvaluatables = new ArrayList<OpenCLEvaluatable>();
  	protected CLBuffer<FloatBuffer> outputBuffer;
  	private int elementCountU = 100;
  	private int elementCountV = 100;
  	private int elementCountT = 1;
  
  	@Override
  	public void createPhantom() {
  		// TODO: generalize AbstractShape to allow tessellation of all kinds of objects
  		// TODO: tessellate all shapes on GPU
  		System.out.println(phantom.getName());
  		//PrioritizableScene phantomScene = phantom;
  
  		// Generate sampling points for static splines
  		CLBuffer<FloatBuffer> samplingPoints = OpenCLUtil.generateSamplingPoints(elementCountU, elementCountV, renderer.context, renderer.device);
  		CLBuffer<FloatBuffer> samplingPointsVariants; // can only be initialized in the loop
  		CLBuffer<FloatBuffer> samplingPointsVariantsHeart = null;
  		SimpleVector diaphragmMotion = null;
  		for (int k =0; k  < dimz; k++) {
  			SimpleMatrix matrix = Configuration.getGlobalConfiguration().getGeometry().getProjectionMatrices()[k].computePMetric();
  			renderer.setProjectionMatrix(matrix);
  			boolean negativeDepth = Configuration.getGlobalConfiguration().getGeometry().getProjectionMatrices()[k].negativeDepth();
  			double sampleTime = (float)((1.0/ (dimz-1))*k);
  			if (phantom instanceof AnalyticPhantom4D) {
  				sampleTime = ((AnalyticPhantom4D) phantom).getTimeWarper().warpTime(sampleTime);
  			}
  			if (phantom instanceof MovingBallPhantom){
  				clEvaluatables.clear();
  				PrioritizableScene scene = ((MovingBallPhantom) phantom).getScene(sampleTime);
  				Translation centerTranslation = phantom.computeCenterTranslation();
  				// apply heart translation to heart parts.
  				for (PhysicalObject o:scene) {
  					applyCenterTranslation(centerTranslation, o);
  					OpenCLEvaluatable os = OpenCLUtil.getOpenCLEvaluatableSubclass(o.getShape(), renderer.device);		
  					clEvaluatables.add(os);
  				}
  			}
  			if (phantom instanceof CombinedBreathingHeartScene){
  				double heartTime = (float) ((CombinedBreathingHeartScene) phantom).getHeart().getTimeWarper().warpTime(sampleTime);
  				samplingPointsVariantsHeart = generateTimeSamplingPoints((float) heartTime);
  				sampleTime = ((CombinedBreathingHeartScene) phantom).getBreathing().getTimeWarper().warpTime(sampleTime);
  				diaphragmMotion =((CombinedBreathingHeartScene) phantom).getBreathing().getDiaphragmMotionVector(0, sampleTime);
  			}
  			samplingPointsVariants = generateTimeSamplingPoints((float)sampleTime);
  
  			long time = System.nanoTime();
  			for (int ID = 0; ID < clEvaluatables.size(); ID++){
  			//for (int ID = 4; ID < 5; ID++){
  					
  			OpenCLEvaluatable os = clEvaluatables.get(ID);
  				// create spline points
  
  
  				// project points
  				if (phantom instanceof CombinedBreathingHeartScene){
  					CombinedBreathingHeartScene combi = (CombinedBreathingHeartScene) phantom;
  					boolean heartSpline = false;
  					if (os instanceof OpenCLTimeVariantSurfaceBSpline){
  						String title = ((OpenCLTimeVariantSurfaceBSpline)os).getTitle();
  						for (TimeVariantSurfaceBSpline tvs:combi.getHeart().getVariants()){
  							if (tvs.getTitle().equals(title)){
  								heartSpline = true;
  								break;
  							}
  						}
  					}
  					if (heartSpline){
  						os.evaluate(samplingPointsVariantsHeart, outputBuffer);
  						renderer.project(outputBuffer, diaphragmMotion);
  					} else {
  						if (os.isTimeVariant()){
  							os.evaluate(samplingPointsVariants, outputBuffer);
  						} else {
  							os.evaluate(samplingPoints, outputBuffer, elementCountU, elementCountV);
  						}
  						renderer.project(outputBuffer);
  					}
  				} else if(phantom instanceof CONRADCardiacModel3D){
  					if (outputBuffer != null)
  						outputBuffer.release();
  					outputBuffer = null;
  					OpenCLCompoundShape s = (OpenCLCompoundShape) os;
  					outputBuffer = clContext.createFloatBuffer(s.size()*3*3, Mem.READ_WRITE);
  					s.evaluate(samplingPoints, outputBuffer);
  					//renderer.debugOut(outputBuffer);
  					renderer.project(outputBuffer);
  				
  				}else {
  					if (os.isTimeVariant()){
  						os.evaluate(samplingPointsVariants, outputBuffer);
  					} else {
  						os.evaluate(samplingPoints, outputBuffer, elementCountU, elementCountV);
  					}
  					renderer.project(outputBuffer);	
  				}
  				//renderer.debugOut(outputBuffer);
  				// draw on the screen
  				//System.out.println("clockwise: " + os.isClockwise() + " " + negativeDepth);
  				boolean clockwise = os.isClockwise();
  				if (!negativeDepth) clockwise = !clockwise;
  				if(phantom instanceof CONRADCardiacModel3D){
  					renderer.drawTrianglesGlobal(outputBuffer, screenBuffer, ID+1, 1, outputBuffer.getBuffer().capacity()/3, -1);
  				}else if (clockwise){
  					renderer.drawTrianglesGlobal(outputBuffer, screenBuffer, ID+1, elementCountU, elementCountV, -1);
  				} else {
  					renderer.drawTrianglesGlobal(outputBuffer, screenBuffer, ID+1, elementCountU, elementCountV, 1);
  				}
  			}
  			time = System.nanoTime() - time;
  			System.out.println("Open CL computation for projection "+ k +" global took: "+ (time/1000000) +"ms ");
  
  			evaluateAbsorptionModel(k);
  
  			renderer.resetBuffers();			// its not the internal buffer, also used by TestOpenCL
  			samplingPointsVariants.release();
  			if (phantom instanceof CombinedBreathingHeartScene){
  				samplingPointsVariantsHeart.release();
  			}
  		}
  		samplingPoints.release();
  		release();
  	}
  
  	protected void evaluateAbsorptionModel(int k) {
  		// evaluate absorption model
  		long time = System.nanoTime();
  		renderer.drawScreenMonochromatic(screenBuffer, mu, priorities);
  		//render.drawScreen(screenBuffer);
  		time = System.nanoTime() - time;
  		System.out.println("monochromatic screen buffer drawing took: "+(time/1000000)+"ms ");
  
  
  		CLCommandQueue clc = renderer.device.createCommandQueue();
  		clc.putReadBuffer(screenBuffer, true).finish();
  		clc.release();
  
  		float[] array = new float [dimx*dimy]; 
  		for (int j = 0; j < dimy; j++){
  			for (int i = 0; i < dimx; i++){
  				array[(j*dimx)+i] = screenBuffer.getBuffer().get();
  			}
  		}
  		screenBuffer.getBuffer().rewind();
  
  		// Save data to buffer
  		Grid2D image = new Grid2D(array, dimx, dimy);
  		buffer.add(image, k); // its not the buffer, deleted it
  
  		screenBuffer.getBuffer().clear(); 	// its not the screenbuffer, copied TestOpenCL code
  	}
  
  	@Override
  	public String toString() {
  		return "OpenCL Projection Phantom";
  	}
  
  	public void release(){
  		if (screenBuffer != null) screenBuffer.release();
  		screenBuffer = null;
  		if (outputBuffer != null) outputBuffer.release();
  		outputBuffer = null;
  		if (priorities != null) priorities.release();
  		priorities = null;
  		if (mu != null) mu.release();
  		mu = null;
  		if (renderer != null) renderer.release();
  		renderer = null;
  	}
  
  	private void applyCenterTranslation(Translation centerTranslation, PhysicalObject o){
  		o.applyTransform(centerTranslation);
  		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);
  		}		
  	}
  	
  
  
  	public void configure(AnalyticPhantom phantom, CLContext context, CLDevice device, boolean createMus){
  		this.phantom = phantom;
  		this.clContext = context;
  		
  		buffer = new ImageGridBuffer();
  		projectionNumber = -1;
  		dimx = Configuration.getGlobalConfiguration().getGeometry().getDetectorWidth();
  		dimy = Configuration.getGlobalConfiguration().getGeometry().getDetectorHeight();
  		dimz = Configuration.getGlobalConfiguration().getGeometry().getProjectionStackSize() * Configuration.getGlobalConfiguration().getNumSweeps();
  		//originIndexX = (int) Configuration.getGlobalConfiguration().getGeometry().getOriginInPixelsX();
  		//originIndexY = (int) Configuration.getGlobalConfiguration().getGeometry().getOriginInPixelsY();
  		//originIndexZ = (int) Configuration.getGlobalConfiguration().getGeometry().getOriginInPixelsZ();
  
  		// OpenCL specific configurations
  		// TODO: include anti-aliasing
  
  		renderer = new OpenCLAppendBufferRenderer(device);
  		renderer.init(dimx, dimy);
  		screenBuffer = renderer.generateFloatBuffer(dimx, dimy, Mem.READ_WRITE);
  		outputBuffer = context.createFloatBuffer(elementCountU*elementCountV*elementCountT*3, Mem.READ_WRITE);
  
  		// priorities
  		priorities = context.createIntBuffer(phantom.size() +1, Mem.READ_ONLY);
  		priorities.getBuffer().put(0);
  		for (PhysicalObject o: phantom){
  			if(phantom instanceof CONRADCardiacModel3D){
  				int prio = priorities.getBuffer().position();
  				priorities.getBuffer().put(prio);
  				//System.out.println("Priority is: " + prio);
  			}else{
  				priorities.getBuffer().put(phantom.getPriority(o));
  			}
  		}
  		priorities.getBuffer().rewind();
  		device.createCommandQueue().putWriteBuffer(priorities, false).finish().release();
  
  		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: phantom) {
  			applyCenterTranslation(centerTranslation, o);
  			AbstractShape s = o.getShape();
  			if (phantom instanceof CombinedBreathingHeartScene) {
  				CombinedBreathingHeartScene combi = (CombinedBreathingHeartScene) phantom;
  				if (combi.getHeart().getVariants().contains(s)) {
  					// apply heart translation to heart parts.
  					s.applyTransform(combi.getHeartTranslation());
  					OpenCLEvaluatable os = OpenCLUtil.getOpenCLEvaluatableSubclass(s, renderer.device);		
  					clEvaluatables.add(os);
  				} else {
  					OpenCLEvaluatable os = OpenCLUtil.getOpenCLEvaluatableSubclass(s, renderer.device);		
  					clEvaluatables.add(os);
  				}
  			} else {
  				OpenCLEvaluatable os = OpenCLUtil.getOpenCLEvaluatableSubclass(s, renderer.device);
  				clEvaluatables.add(os);				
  			}
  		}
  
  		if (createMus) generateMuMap(context, device);
  
  		configured = true;
  
  
  	}
  
  	protected void generateMuMap(CLContext context, CLDevice device) {
  		// absorption model
  		mu = context.createFloatBuffer(phantom.size() +1, Mem.READ_ONLY); 
  		mu.getBuffer().put((float) phantom.getBackgroundMaterial().getDensity());
  		for (PhysicalObject o: phantom){
  			float density = (float) o.getMaterial().getDensity();
  			mu.getBuffer().put(density);
  			//System.out.println(o.getMaterial().getName() + " density " + o.getMaterial().getDensity());
  		}
  		mu.getBuffer().rewind();
  		device.createCommandQueue().putWriteBuffer(mu, false).finish();
  	}
  
  	@Override
  	public void configure() throws Exception{
  		AnalyticPhantom phantom;
  		if(this.phantom != null){
  			phantom = this.phantom;
  		}else{
  			phantom = AnalyticPhantom.getCurrentPhantom();
  		}
  
  		CLContext context = OpenCLUtil.createContext();
  		CLDevice device = context.getMaxFlopsDevice();
  
  		configure(phantom, context, device, true);
  
  	}
  
  	public CLBuffer<FloatBuffer> generateTimeSamplingPoints(float tIndex){
  		// prepare sampling points
  		CLBuffer<FloatBuffer> samplingPoints = renderer.context.createFloatBuffer(elementCountU * elementCountV*3, Mem.READ_ONLY);
  		for (int j = 0; j < elementCountV; j++){
  			for (int i = 0; i < elementCountU; i++){
  				samplingPoints.getBuffer().put(i*(1.0f / elementCountU));
  				samplingPoints.getBuffer().put(j*(1.0f / elementCountV));
  				samplingPoints.getBuffer().put(tIndex);
  				//System.out.println(i*(1.0f / elementCountU) + " " +j*(1.0f / elementCountV)+ " "+t*(1.0f / elementCountT) );
  			}
  		}
  		samplingPoints.getBuffer().rewind();
  		CLCommandQueue clc = renderer.device.createCommandQueue();
  		clc.putWriteBuffer(samplingPoints, true).finish();
  		clc.release();
  		return samplingPoints;
  	}
  
  	@Override
  	public String getBibtexCitation() {
  		String bibtex = "@Article{Maier12-FSO,\n" +
  				"  author = {Maier, A. and Hofmann, H.G. and Schwemmer, C. and Hornegger, J. and Keil, A. and Fahrig, R.},\n" +
  				"  title = {Fast simulation of x-ray projections of spline-based surfaces using an append buffer},\n" +
  				"  journal = {Physics in Medicine and Biology},\n" +
  				"  volume = {57},\n" +
  				"  number = {19},\n" +
  				"  pages = {6193-6210},\n" +
  				"  year = {2012}\n" +
  				"}";
  		return bibtex;
  	}
  
  	@Override
  	public String getMedlineCitation() {
  		String medline = "Maier A, Hofmann HG, Schwemmer C, Hornegger J, Keil A, Fahrig R. Fast simulation of x-ray projections of spline-based surfaces using an append buffer. Phys Med Biol. 57(19):6193-210. 2012";
  		return medline;
  	}
  
  	/**
  	 * @return the phantom
  	 */
  	public AnalyticPhantom getPhantom() {
  		return phantom;
  	}
  
  	/**
  	 * @param phantom the phantom to set
  	 */
  	public void setPhantom(AnalyticPhantom phantom) {
  		this.phantom = phantom;
  	}
  }