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). */