content: package edu.stanford.rsl.conrad.cuda;
  
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.GridLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.text.NumberFormat;
  import java.util.Timer;
  import java.util.TimerTask;
  
  import javax.media.opengl.GL;
  import javax.media.opengl.GLAutoDrawable;
  import javax.swing.JButton;
  import javax.swing.JLabel;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JSlider;
  import javax.swing.SwingUtilities;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  
  import com.jogamp.opengl.util.awt.TextRenderer;
  
  import jcuda.Pointer;
  import jcuda.Sizeof;
  import jcuda.driver.CUDA_ARRAY_DESCRIPTOR;
  import jcuda.driver.CUDA_MEMCPY2D;
  import jcuda.driver.CUaddress_mode;
  import jcuda.driver.CUarray;
  import jcuda.driver.CUarray_format;
  import jcuda.driver.CUcontext;
  import jcuda.driver.CUdevice;
  import jcuda.driver.CUdeviceptr;
  import jcuda.driver.CUfilter_mode;
  import jcuda.driver.CUfunction;
  import jcuda.driver.CUmemorytype;
  import jcuda.driver.CUtexref;
  import jcuda.driver.JCudaDriver;
  
  import edu.stanford.rsl.conrad.utils.ImageUtil;
  import ij.ImagePlus;
  import ij.process.ImageProcessor;
  
  public class ImagePlusVolumeRenderer extends JCudaDriverTextureSample {
  
  	/**
  	 * 
  	 */
  	private static final long serialVersionUID = -2120647421143268776L;
  	private boolean multiFrameMode = false;
  	private CUtexref [] textures;
  	private byte [] [] volumes;
  	private int nFrames=1;
  	private int time = 0;
  
  	public ImagePlusVolumeRenderer(byte[] volumeData, int sizeX, int sizeY,
  			int sizeZ, boolean stereoMode) {
  		super(volumeData, sizeX, sizeY, sizeZ, stereoMode);
  		transferFunc = new float[]
  		                         { 
  				0.0f, 0.0f, 0.0f, 0.0f, 
  				1.0f, 1.0f, 1.0f, 1.0f, 
  				0.0f, 0.0f, 0.0f, 0.0f,
  		                         };
  		start();
  
  	}
  
  	private double [] minmax = null;
  
  	public ImagePlusVolumeRenderer(ImagePlus image){
  		super (new byte[image.getWidth() * image.getHeight() * image.getStackSize() / image.getNFrames()], image.getWidth(), image.getHeight(), image.getStackSize() / image.getNFrames(), false);
  
  		transferFunc = new float[]
  		                         { 
  				0.0f, 0.0f, 0.0f, 0.0f, 
  				1.0f, 1.0f, 1.0f, 1.0f, 
  				0.0f, 0.0f, 0.0f, 0.0f,
  		                         };
  		minmax = ImageUtil.minAndMaxOfImageProcessor(image);
  		minmax[1] *= 1.05;
  		if (image.getNFrames() == 1){
  			for (int k = 0; k < image.getStackSize(); k++) {
  				ImageProcessor img = image.getStack().getProcessor(k + 1);
  				for (int i = 0; i < image.getWidth(); i++){
  					for (int j = 0; j < image.getHeight(); j++){
  						setVoxel(h_volume, i,j,k, img.getPixelValue(i, j));
  					}	
  				}
  			}
  		} else {
  			multiFrameMode = true;
  			nFrames = image.getNFrames();
  			// Allocate the texture references
  			textures = new CUtexref[image.getNFrames()];
  			for (int i = 0; i < image.getNFrames(); i++){
  				textures[i] = new CUtexref();
  			}
  			// rewrite imagePlus container into nFrames single byte volumes
  			volumes = new byte [image.getNFrames()][volumeSize.z*volumeSize.x*volumeSize.y];
  			for (int n = 0; n < image.getNFrames(); n++) {
  				for (int k = 0; k < volumeSize.z; k++) {
  					ImageProcessor img = image.getStack().getProcessor((n*volumeSize.z)+k + 1);
  					for (int i = 0; i < image.getWidth(); i++){
  						for (int j = 0; j < image.getHeight(); j++){
  							setVoxel(volumes[n], i,j,k, img.getPixelValue(i, j));
  						}	
  					}
  				}
  			}
  		}
  		initialized = false;
  		//System.out.println("init");
  		frame.setTitle("Volume: " + image.getTitle());	
  	}
  
  	public void setVoxel(byte [] h_volume, int i, int j, int k, double value){
  		h_volume[(((volumeSize.y * k) + j) * volumeSize.x) + i] = (byte) (((value - minmax[0]) / (minmax[1] - minmax[0])) * 256);
  	}
  
  	/**
  	 * Entry point for this sample.
  	 * 
  	 * @param args not used
  	 */
  	public static void main(String args[])
  	{
  		startSample("Bucky.raw", 32, 32, 32, false);
  
  		// Other input files may be obtained from http://www.volvis.org
  		//startSample("mri_ventricles.raw", 256, 256, 124, false);
  		//startSample("backpack8.raw", 512, 512, 373, false);
  		//startSample("foot.raw", 256, 256, 256, false);
  	}
  
  	/**
  	 * Starts this sample with the data that is read from the file
  	 * with the given name. The data is assumed to have the 
  	 * specified dimensions.
  	 * 
  	 * @param fileName The name of the volume data file to load
  	 * @param sizeX The size of the data set in X direction
  	 * @param sizeY The size of the data set in Y direction
  	 * @param sizeZ The size of the data set in Z direction
  	 * @param stereoMode Whether stereo mode should be used
  	 */
  	private static void startSample(
  			String fileName, final int sizeX, final int sizeY, 
  			final int sizeZ, final boolean stereoMode)
  	{
  		// Try to read the specified file
  		byte data[] = null;
  		try
  		{
  			int size = sizeX * sizeY * sizeZ; 
  			FileInputStream fis = new FileInputStream(fileName);
  			data = new byte[size];
  			fis.read(data);
  		}
  		catch (IOException e)
  		{
  			System.err.println("Could not load input file");
  			e.printStackTrace();
  			return;
  		}
  
  		// Start the sample with the data that was read from the file
  		final byte volumeData[] = data;
  		SwingUtilities.invokeLater(new Runnable()
  		{
  			public void run()
  			{
  				new ImagePlusVolumeRenderer(
  						volumeData, sizeX, sizeY, sizeZ, stereoMode);
  			}
  		});
  	}
  
  	/**
  	 * Stops the animator and calls System.exit() in a new Thread.
  	 * (System.exit() may not be called synchronously inside one 
  	 * of the JOGL callbacks)
  	 */
  	protected void runExit()
  	{
  		if (pbo != 0)
  		{
  			//JCudaDriver.cuGLUnregisterBufferObject(pbo);
  			JCudaDriver.cuCtxDestroy(glCtx);
  			//gl.glDeleteBuffers(1, new int[]{ pbo }, 0);
  			pbo = 0;
  		}
  		new Thread(new Runnable()
  		{
  			public void run()
  			{
  				animatorL.stop();
  				if (animatorR != null)
  				{
  					animatorR.stop();
  				}
  
  				//System.exit(0);
  			}
  		}).start();
  	}
  
  	private Timer playTimer = null;
  	private long frameDuration = 37;
  	
  	@Override
  	protected JPanel createControlPanel(){
  		JPanel controlPanel = super.createControlPanel();
  		if (multiFrameMode) {
  			JPanel panel = null;
  			JSlider slider = null;
  			// Time
  			panel = new JPanel(new GridLayout(1, 2));
  			panel.add(new JLabel("Time:"));
  			slider = new JSlider(0, nFrames-1, 0);
  			slider.addChangeListener(new ChangeListener()
  			{
  				public void stateChanged(ChangeEvent e)
  				{
  					JSlider source = (JSlider) e.getSource();
  					time = source.getValue();
  				}
  			});
  			slider.setPreferredSize(new Dimension(0, 0));
  			panel.add(slider);
  			
  			controlPanel.add(panel);
  			JPanel buttons = new JPanel(new GridLayout(1,3));
  			
  					
  			JButton animateButton = new JButton("play");
  			animateButton.addActionListener(new ActionListener(){
  
  				public void actionPerformed(ActionEvent arg0) {
  					if (playTimer == null){
  						playTimer = new Timer();
  						playTimer.scheduleAtFixedRate(new TimerTask(){
  							@Override
  							public void run() {
  								time = (time +1) % nFrames;
  							}}, 0, frameDuration);
  					} else {
  						playTimer.cancel();
  						playTimer = null;
  					}
  					
  				}
  			}
  			);
  			buttons.add(animateButton);
  			JButton button = new JButton("-");
  			button.addActionListener(new ActionListener(){
  				public void actionPerformed(ActionEvent arg0) {
  					frameDuration +=10;
  					if (frameDuration > 1000) frameDuration = 1000;
  					resetTimer();
  				}
  			}
  			);
  			buttons.add(button);
  			button = new JButton("+");
  			button.addActionListener(new ActionListener(){
  				public void actionPerformed(ActionEvent arg0) {
  					frameDuration -=10;
  					if (frameDuration < 10) frameDuration = 10;
  					resetTimer();
  				}
  			}
  			);
  			buttons.add(button);
  			controlPanel.add(buttons);
  		}
  		return controlPanel;
  	}
  	
  	public void resetTimer(){
  		if (playTimer!=null){
  			playTimer.cancel();
  			playTimer = new Timer();
  			playTimer.scheduleAtFixedRate(new TimerTask(){
  				@Override
  				public void run() {
  					time = (time +1) % nFrames;
  				}}, 0, frameDuration);
  		}
  	}
  
  	@Override
  	public void init(GLAutoDrawable drawable)
  	{
  		if (multiFrameMode) {
  			// Perform the default GL initialization
  			GL gl = drawable.getGL();
  			gl.setSwapInterval(0);
  			gl.glEnable(GL.GL_DEPTH_TEST);
  			gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  			setupView(drawable);
  
  			// Initialize the GL_ARB_pixel_buffer_object extension
  			if (!gl.isExtensionAvailable("GL_ARB_pixel_buffer_object"))
  			{
  				new Thread(new Runnable()
  				{
  					public void run()
  					{
  						JOptionPane.showMessageDialog(null,
  								"GL_ARB_pixel_buffer_object extension not available",
  								"Unavailable extension", JOptionPane.ERROR_MESSAGE);
  						runExit();
  					}
  				}).start();
  			}
  
  			// Create a TextRenderer for the status messages
  			renderer = new TextRenderer(new Font("SansSerif", Font.PLAIN, 12));
  
  			if (initialized)
  			{
  				return;
  			}
  
  			// Initialize the JCudaDriver. Note that this has to be done from 
  			// the same thread that will later use the JCudaDriver API.
  			JCudaDriver.setExceptionsEnabled(true);
  			JCudaDriver.cuInit(0);
  			CUdevice dev = new CUdevice();
  			JCudaDriver.cuDeviceGet(dev, 0);
  			glCtx = new CUcontext();
  			JCudaDriver.cuGLCtxCreate(glCtx, 0, dev);
  
  			// Load the CUBIN file containing the kernel
  			JCudaDriver.cuModuleLoad(module, "volumeRender_kernel.sm_10.cubin");
  
  			// Obtain the global pointer to the inverted view matrix from 
  			// the module
  			JCudaDriver.cuModuleGetGlobal(c_invViewMatrix, new int[1], module,
  			"c_invViewMatrix");
  
  			// Obtain a function pointer to the kernel function. This function
  			// will later be called in the display method of this 
  			// GLEventListener.
  			function = new CUfunction();
  			JCudaDriver.cuModuleGetFunction(function, module,
  			"_Z16d_render_texturePjjjffffi");
  
  			// Initialize CUDA with the current volume data
  			initCuda();
  
  			// Initialize the OpenGL pixel buffer object
  			initPBO(gl);
  
  			initialized = true;
  		} else {
  			super.init(drawable);
  		}
  	}
  
  	@Override
  	void initCuda()
  	{
  		if (multiFrameMode) {
  			for (int n = 0; n < nFrames; n++){
  				NumberFormat nf = NumberFormat.getInstance();
  				nf.setMaximumIntegerDigits(2);
  				nf.setMinimumIntegerDigits(2);
  				nf.setMinimumFractionDigits(0);
  				nf.setMaximumFractionDigits(0);
  				//System.out.println("Filling texture: tex" + nf.format(n));
  				JCudaDriver.cuModuleGetTexRef(textures[n], module, "tex" + nf.format(n));
  				fillTextureReference(textures[n], volumes[n]);
  			}
  
  			CUarray d_transferFuncArray = new CUarray();
  			// The RGBA components of the transfer function texture 
  
  
  			// Create the 2D (float4) array that will contain the
  			// transfer function data. 
  			CUDA_ARRAY_DESCRIPTOR ad = new CUDA_ARRAY_DESCRIPTOR();
  			ad.Format = CUarray_format.CU_AD_FORMAT_FLOAT;
  			ad.Width = transferFunc.length / 4;
  			ad.Height = 1;
  			ad.NumChannels = 4;
  			JCudaDriver.cuArrayCreate(d_transferFuncArray, ad);
  
  			// Copy the transfer function data to the array  
  			CUDA_MEMCPY2D copy2 = new CUDA_MEMCPY2D();
  			copy2.srcMemoryType = CUmemorytype.CU_MEMORYTYPE_HOST;
  			copy2.srcHost = Pointer.to(transferFunc);
  			copy2.srcPitch = transferFunc.length * Sizeof.FLOAT;
  			copy2.dstMemoryType = CUmemorytype.CU_MEMORYTYPE_ARRAY;
  			copy2.dstArray = d_transferFuncArray;
  			copy2.WidthInBytes = transferFunc.length * Sizeof.FLOAT;
  			copy2.Height = 1;
  			JCudaDriver.cuMemcpy2D(copy2);
  
  			// Obtain the transfer texture reference from the module, 
  			// set its parameters and assign the transfer function  
  			// array as its reference.
  			JCudaDriver.cuModuleGetTexRef(transferTex, module, "transferTex");
  			JCudaDriver.cuTexRefSetFilterMode(transferTex,
  					CUfilter_mode.CU_TR_FILTER_MODE_LINEAR);
  			JCudaDriver.cuTexRefSetAddressMode(transferTex, 0,
  					CUaddress_mode.CU_TR_ADDRESS_MODE_CLAMP);
  			JCudaDriver.cuTexRefSetFlags(transferTex,
  					JCudaDriver.CU_TRSF_NORMALIZED_COORDINATES);
  			JCudaDriver.cuTexRefSetFormat(transferTex,
  					CUarray_format.CU_AD_FORMAT_FLOAT, 4);
  			JCudaDriver.cuTexRefSetArray(transferTex, d_transferFuncArray,
  					JCudaDriver.CU_TRSA_OVERRIDE_FORMAT);
  
  			JCudaDriver.cuParamSetTexRef(function, JCudaDriver.CU_PARAM_TR_DEFAULT,
  					transferTex);
  		} else {
  			super.initCuda();
  		}
  	}
  
  	@SuppressWarnings("deprecation")
  	@Override
  	protected void render()
  	{
  		if (multiFrameMode) {
  			// Map the PBO to get a CUDA device pointer
  			CUdeviceptr d_output = new CUdeviceptr();
  			JCudaDriver.cuGLMapBufferObject(d_output, new int[1], pbo);
  			JCudaDriver.cuMemsetD32(d_output, 0, width * height);
  
  			// Set up the execution parameters for the kernel:
  			// - One pointer for the output that is mapped to the PBO
  			// - Two ints for the width and height of the image to render
  			// - Four floats for the visualization parameters of the renderer
  			Pointer dOut = Pointer.to(d_output);
  			Pointer pWidth = Pointer.to(new int[]{width});
  			Pointer pHeight = Pointer.to(new int[]{height});
  			Pointer pDensity = Pointer.to(new float[]{density});
  			Pointer pBrightness = Pointer.to(new float[]{brightness});
  			Pointer pTransferOffset = Pointer.to(new float[]{transferOffset});
  			Pointer pTransferScale = Pointer.to(new float[]{transferScale});
  			Pointer pTime = Pointer.to(new int[]{time});
  
  			int offset = 0;
  
  			offset = align(offset, Sizeof.POINTER);
  			JCudaDriver.cuParamSetv(function, offset, dOut, Sizeof.POINTER);
  			offset += Sizeof.POINTER;
  
  			offset = align(offset, Sizeof.INT);
  			JCudaDriver.cuParamSetv(function, offset, pWidth, Sizeof.INT);
  			offset += Sizeof.INT;
  
  			offset = align(offset, Sizeof.INT);
  			JCudaDriver.cuParamSetv(function, offset, pHeight, Sizeof.INT);
  			offset += Sizeof.INT;
  
  			offset = align(offset, Sizeof.FLOAT);
  			JCudaDriver.cuParamSetv(function, offset, pDensity, Sizeof.FLOAT);
  			offset += Sizeof.FLOAT;
  
  			offset = align(offset, Sizeof.FLOAT);
  			JCudaDriver.cuParamSetv(function, offset, pBrightness, Sizeof.FLOAT);
  			offset += Sizeof.FLOAT;
  
  			offset = align(offset, Sizeof.FLOAT);
  			JCudaDriver.cuParamSetv(function, offset, pTransferOffset, Sizeof.FLOAT);
  			offset += Sizeof.FLOAT;
  
  			offset = align(offset, Sizeof.FLOAT);
  			JCudaDriver.cuParamSetv(function, offset, pTransferScale, Sizeof.FLOAT);
  			offset += Sizeof.FLOAT;
  
  			offset = align(offset, Sizeof.INT);
  			JCudaDriver.cuParamSetv(function, offset, pTime, Sizeof.INT);
  			offset += Sizeof.INT;
  
  			JCudaDriver.cuParamSetSize(function, offset);
  
  			// Call the CUDA kernel, writing the results into the PBO
  			JCudaDriver.cuFuncSetBlockShape(function, blockSize.x, blockSize.y, 1);
  			JCudaDriver.cuLaunchGrid(function, gridSize.x, gridSize.y);
  			JCudaDriver.cuCtxSynchronize();
  			JCudaDriver.cuGLUnmapBufferObject(pbo);
  		} else {
  			super.render();
  		}
  	}
  
  }
  
  /*
   * Copyright (C) 2010-2014 - Andreas Maier 
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
  */
  