content: /*
   * Copyright (C) 2014 Andreas Maier
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
   */
  package edu.stanford.rsl.tutorial.motion.compensation;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.nio.FloatBuffer;
  
  import com.jogamp.opencl.CLBuffer;
  
  import edu.stanford.rsl.conrad.data.numeric.Grid2D;
  import edu.stanford.rsl.conrad.data.numeric.NumericPointwiseOperators;
  import edu.stanford.rsl.conrad.geometry.motion.MotionField;
  import edu.stanford.rsl.conrad.geometry.motion.OpenCLParzenWindowMotionField;
  import edu.stanford.rsl.conrad.geometry.motion.ParzenWindowMotionField;
  import edu.stanford.rsl.conrad.opencl.OpenCLBackProjector;
  import edu.stanford.rsl.conrad.opencl.OpenCLUtil;
  import edu.stanford.rsl.conrad.utils.Configuration;
  
  public class OpenCLMotionCompensatedBackProjector extends OpenCLBackProjector {
  
  	/**
  	 * 
  	 */
  	private static final long serialVersionUID = 3263500016065695870L;
  	protected MotionField motion = null;
  	protected double referenceTime = 0;
  	protected CLBuffer<FloatBuffer> motionParameters = null;
  
  	/**
  	 * @return the motion
  	 */
  	public MotionField getMotion() {
  		return motion;
  	}
  
  	/**
  	 * @param motion the motion to set
  	 */
  	public void setMotion(MotionField motion) {
  		this.motion = motion;
  	}
  
  
  
  	protected String readCompleteRessourceAsString(String resource) throws IOException{
  		InputStream inStream = OpenCLMotionCompensatedBackProjector.class.getResourceAsStream(resource);
  		BufferedReader br = new BufferedReader(new InputStreamReader(inStream));
  		String content = "";
  		String line = br.readLine();
  		while (line != null){
  			content += line + "\n";
  			line = br.readLine();
  		};
  		return content;
  	}
  
  	@Override
  	protected void createProgram() throws IOException{
  		// initialize the program
  		if (program==null || !program.getContext().equals(this.context)){
  			String motionProgram = "";
  			if (motion instanceof ParzenWindowMotionField){
  				motionProgram = readCompleteRessourceAsString("applyMotionDenseMotionField.cl");
  			}
  			String backprojectProgram = readCompleteRessourceAsString("generalMotionCompensatedBackprojectCL.cl");
  			program = context.createProgram(motionProgram + backprojectProgram).build();
  		}
  	}
  
  	protected void initMotionParameters(double referenceTime, double currentTime){
  		Configuration config = Configuration.getGlobalConfiguration();
  		if (motion instanceof ParzenWindowMotionField){
  			if (motionParameters != null) {
  				motionParameters.release();
  				motionParameters = null;
  			}
  			ParzenWindowMotionField motion = (ParzenWindowMotionField) this.motion;
  			OpenCLParzenWindowMotionField cl;
  			try {
  				cl = new OpenCLParzenWindowMotionField(motion, context, device);
  				int factor = 16; 
  				motionParameters = cl.getMotionFieldAsArrayReduceZGridXY(referenceTime, currentTime, OpenCLUtil.iDivUp(config.getGeometry().getReconDimensionX(),factor), OpenCLUtil.iDivUp(config.getGeometry().getReconDimensionY(),factor), commandQueue, true);
  			} catch (IOException e) {
  				// TODO Auto-generated catch block
  				e.printStackTrace();
  			}
  		}
  
  
  	}
  
  	@Override
  	protected synchronized void projectSingleProjection(int projectionNumber, int dimz){
  		// load projection matrix
  		initProjectionMatrix(projectionNumber);
  
  		// Correct for constant part of distance weighting + For angular sampling
  		double D =  getGeometry().getSourceToDetectorDistance();
  		float projectionMultiplier = (float)(10 * D*D * 2* Math.PI * getGeometry().getPixelDimensionX() / getGeometry().getNumProjectionMatrices());
  
  		initProjectionData(projections.get(projectionNumber));
  		//System.out.println("Uploading projection " + projectionNumber);
  		if (!largeVolumeMode) {
  			projections.remove(projectionNumber);
  		}
  		// backproject for each slice
  		// OpenCL Grids are only two dimensional!
  		int reconDimensionZ = dimz;
  		double voxelSpacingX = getGeometry().getVoxelSpacingX();
  		double voxelSpacingY = getGeometry().getVoxelSpacingY();
  		double voxelSpacingZ = getGeometry().getVoxelSpacingZ();
  
  
  		initMotionParameters(referenceTime, ((float)projectionNumber)/(projections.size()-1));
  
  		// write kernel parameters
  		kernelFunction.rewind();
  		kernelFunction
  		.putArg(volumePointer)
  		.putArg(getGeometry().getReconDimensionX())
  		.putArg(getGeometry().getReconDimensionY())
  		.putArg(reconDimensionZ)
  		.putArg((int) lineOffset)
  		.putArg((float) voxelSpacingX)
  		.putArg((float) voxelSpacingY)
  		.putArg((float) voxelSpacingZ)
  		.putArg((float) offsetX)
  		.putArg((float) offsetY)
  		.putArg((float) offsetZ)
  		.putArg(projectionTex)
  		.putArg(projectionMatrix)
  		.putArg(projectionMultiplier)
  		.putArg(motionParameters);
  
  		int[] realLocalSize = new int[2];
  		realLocalSize[0] = Math.min(device.getMaxWorkGroupSize(),bpBlockSize[0]);
  		realLocalSize[1] = Math.max(1, Math.min(device.getMaxWorkGroupSize()/realLocalSize[0], bpBlockSize[1]));
  
  		// rounded up to the nearest multiple of localWorkSize
  		int[] globalWorkSize = {getGeometry().getReconDimensionX(), getGeometry().getReconDimensionY()}; 
  		if ((globalWorkSize[0] % realLocalSize[0] ) != 0){
  			globalWorkSize[0] = ((globalWorkSize[0] / realLocalSize[0]) + 1) * realLocalSize[0];
  		}
  		if ((globalWorkSize[1] % realLocalSize[1] ) != 0){
  			globalWorkSize[1] = ((globalWorkSize[1] / realLocalSize[1]) + 1) * realLocalSize[1];
  		}
  
  		// Call the OpenCL kernel, writing the results into the volume which is pointed at
  		commandQueue
  		.putWriteImage(projectionTex, true)
  		.put2DRangeKernel(kernelFunction, 0, 0, globalWorkSize[0], globalWorkSize[1], realLocalSize[0], realLocalSize[1])
  		//.finish()
  		//.putReadBuffer(dOut, true)
  		.finish();
  	}
  
  	/**
  	 * @return the referenceTime
  	 */
  	public double getReferenceTime() {
  		return referenceTime;
  	}
  	
  	@Override
  	public String getToolName(){
  		return "Motion Compensated OpenCL Backprojector";
  	}
  
  	/**
  	 * @param referenceTime the referenceTime to set
  	 */
  	public void setReferenceTime(double referenceTime) {
  		this.referenceTime = referenceTime;
  	}
  	
  	@Override
  	protected synchronized void unload() {
  		if(motionParameters!=null && !motionParameters.isReleased())
  			motionParameters.release();
  		super.unload();
  	}
  
  }
  