package edu.stanford.rsl.tutorial.RotationalAngiography.ResidualMotionCompensation.reconWithStreakReduction; import ij.IJ; import ij.ImagePlus; import ij.process.FloatProcessor; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Arrays; import com.jogamp.opencl.CLBuffer; import com.jogamp.opencl.CLCommandQueue; import com.jogamp.opencl.CLContext; import com.jogamp.opencl.CLDevice; import com.jogamp.opencl.CLImage2d; import com.jogamp.opencl.CLImageFormat; import com.jogamp.opencl.CLKernel; import com.jogamp.opencl.CLProgram; import com.jogamp.opencl.CLImageFormat.ChannelOrder; import com.jogamp.opencl.CLImageFormat.ChannelType; import com.jogamp.opencl.CLMemory.Mem; import edu.stanford.rsl.apps.gui.Citeable; import edu.stanford.rsl.conrad.data.numeric.Grid2D; import edu.stanford.rsl.conrad.data.numeric.NumericPointwiseOperators; import edu.stanford.rsl.conrad.io.ImagePlusDataSink; import edu.stanford.rsl.conrad.numerics.SimpleMatrix; import edu.stanford.rsl.conrad.opencl.OpenCLUtil; import edu.stanford.rsl.conrad.opencl.TestOpenCL; import edu.stanford.rsl.conrad.reconstruction.VOIBasedReconstructionFilter; import edu.stanford.rsl.conrad.utils.CONRAD; import edu.stanford.rsl.conrad.utils.Configuration; import edu.stanford.rsl.conrad.utils.ImageGridBuffer; import edu.stanford.rsl.conrad.utils.ImageUtil; import edu.stanford.rsl.conrad.utils.UserUtil; public class OpenCLBackProjectorStreakReduction extends VOIBasedReconstructionFilter implements Runnable, Citeable{ /** * */ private static final long serialVersionUID = -8615490043940236889L; private boolean forceSmallVolume = false; static int bpBlockSize[] = {32, 16}; private static boolean debug = true; /** * The OpenCL context */ protected CLContext context; /** * The OpenCL program */ private CLProgram program; /** * The OpenCL device */ private CLDevice device; /** * The OpenCL kernel function binding */ private CLKernel kernelFunction; /** * The OpenCL command queue */ protected CLCommandQueue commandQueue; /** * The 2D projection texture reference */ private CLImage2d<FloatBuffer> projectionTex = null; /** * The volume data that is to be reconstructed */ protected float h_volume[]; private int gat_ign = 3; /** * The global variable of the module which stores the * view matrix. */ protected CLBuffer<FloatBuffer> projectionMatrix = null; private CLBuffer<IntBuffer> volStride = null; private CLBuffer<FloatBuffer> volumePointer = null; private CLBuffer<FloatBuffer> projectionArray = null; private CLBuffer<FloatBuffer> destBuffer = null; protected ImageGridBuffer projections; protected ArrayList<Integer> projectionsAvailable; protected ArrayList<Integer> projectionsDone; private boolean largeVolumeMode = false; private int nSteps = 1; private int subVolumeZ = 0; private boolean initialized = false; public OpenCLBackProjectorStreakReduction () { super(); } @Override public void prepareForSerialization(){ super.prepareForSerialization(); projectionMatrix = null; volStride = null; volumePointer = null; projectionArray = null; projections = null; projectionsAvailable =null; projectionsDone = null; h_volume = null; initialized = false; // JOCL members program = null; device = null; kernelFunction = null; commandQueue = null; projectionTex = null; context = null; } @Override public void configure() throws Exception{ forceSmallVolume = UserUtil.queryBoolean("Force Small Volume Mode?"); configured = true; } public void configure(boolean forceSmallVolumeMode) throws Exception{ this.forceSmallVolume = forceSmallVolumeMode; configured = true; } protected void init(){ if (!initialized) { largeVolumeMode = false; int reconDimensionX = getGeometry().getReconDimensionX(); int reconDimensionY = getGeometry().getReconDimensionY(); int reconDimensionZ = getGeometry().getReconDimensionZ(); projections = new ImageGridBuffer(); projectionsAvailable = new ArrayList<Integer>(); projectionsDone = new ArrayList<Integer>(); // Initialize JOCL. context = OpenCLUtil.createContext(); try { // get the fastest device device = context.getMaxFlopsDevice(); // create the command queue commandQueue = device.createCommandQueue(); // initialize the program if (program==null || !program.getContext().equals(this.context)){ program = context.createProgram(this.getClass().getResourceAsStream("backprojectCLStreakReduction.cl")).build(); //program = context.createProgram(TestOpenCL.class.getResourceAsStream("C:\\LME\\Desktop\\Bachelorarbeit Katrin Mentl\\KONRAD\\CONRAD\\src\\edu\\stanford\\rsl\\science\\mentl\\cardiacVasculatureRecon\\reconWithScatterCorrection\\backprojectCLScatterCorr.cl")).build(); } } catch (Exception e) { if (commandQueue != null) commandQueue.release(); if (kernelFunction != null) kernelFunction.release(); if (program != null) program.release(); // destory context if (context != null) context.release(); // TODO: handle exception e.printStackTrace(); } // check space on device: long memory = device.getMaxMemAllocSize(); long availableMemory = (memory); long requiredMemory = (long)((( ((double) reconDimensionX) * reconDimensionY * ((double) reconDimensionZ) * 4) + (((double)Configuration.getGlobalConfiguration().getGeometry().getDetectorHeight()) * Configuration.getGlobalConfiguration().getGeometry().getDetectorWidth() * 4))); if (debug) { CONRAD.log("Total available Memory on OpenCL card:" + availableMemory); CONRAD.log("Required Memory on OpenCL card:" + requiredMemory); } if (!forceSmallVolume) { if (requiredMemory > availableMemory){ nSteps = (int)OpenCLUtil.iDivUp (requiredMemory, availableMemory); if (debug) CONRAD.log("Switching to large volume mode with nSteps = " + nSteps); largeVolumeMode = true; } } if (debug) { //TODO replace /* CUdevprop prop = new CUdevprop(); JCudaDriver.cuDeviceGetProperties(prop, dev); System.out.println(prop.toFormattedString()); */ } // create the computing kernel kernelFunction = program.createCLKernel("backprojectKernel"); // create the reconstruction volume; int memorysize = reconDimensionX * reconDimensionY * reconDimensionZ * 4; //h_volume = new float[reconDimensionX * reconDimensionY * reconDimensionZ]; //int gat_ign = 4; h_volume = new float[(2*gat_ign +1) *reconDimensionX*reconDimensionY*reconDimensionZ]; for(int i = 0; i < h_volume.length; i = i + 2*gat_ign+1){ h_volume[i] = 0.0f; for(int j = 0; j < gat_ign; j++){ h_volume[i + j] = -100001.0f; } for(int j = gat_ign; j < 2*gat_ign; j++){ h_volume[i + j] = 100001.0f; } } destBuffer = context.createFloatBuffer(reconDimensionX*reconDimensionY*reconDimensionZ, Mem.WRITE_ONLY); // compute adapted volume size // volume size in x = multiple of bpBlockSize[0] // volume size in y = multiple of bpBlockSize[1] System.out.println("RecondimX: " + reconDimensionX); int adaptedVolSize[] = new int[3]; if ((reconDimensionX % bpBlockSize[0] ) == 0){ adaptedVolSize[0] = reconDimensionX; } else { adaptedVolSize[0] = ((reconDimensionX / bpBlockSize[0]) + 1) * bpBlockSize[0]; } System.out.println("AdaptedVolSize0: " + adaptedVolSize[0]); System.out.println("RecondimX: " + reconDimensionX); if ((reconDimensionY % bpBlockSize[1] ) == 0){ adaptedVolSize[1] = reconDimensionY; } else { adaptedVolSize[1] = ((reconDimensionY / bpBlockSize[1]) + 1) * bpBlockSize[1]; } System.out.println("AdaptedVolSize1: " + adaptedVolSize[0]); adaptedVolSize[2] = reconDimensionZ; int volStrideHost [] = new int[2]; // compute volstride and copy it to constant memory volStrideHost[0] = adaptedVolSize[0]; volStrideHost[1] = adaptedVolSize[0] * adaptedVolSize[1]; // copy volume to device volumePointer = context.createFloatBuffer(h_volume.length, Mem.WRITE_ONLY); volumePointer.getBuffer().put(h_volume); volumePointer.getBuffer().rewind(); // copy volume stride to device volStride = context.createIntBuffer(volStrideHost.length, Mem.READ_ONLY); volStride.getBuffer().put(volStrideHost); volStride.getBuffer().rewind(); commandQueue. putWriteBuffer(volumePointer, true). putWriteBuffer(volStride, true). finish(); initialized = true; } } private synchronized void unload(){ if (initialized) { if ((projectionVolume != null) && (!largeVolumeMode)) { int reconDimensionX = getGeometry().getReconDimensionX(); int reconDimensionY = getGeometry().getReconDimensionY(); int reconDimensionZ = getGeometry().getReconDimensionZ(); commandQueue.putReadBuffer(volumePointer, true).finish(); volumePointer.getBuffer().rewind(); volumePointer.getBuffer().get(h_volume); volumePointer.getBuffer().rewind(); destBuffer.getBuffer().rewind(); int length = h_volume.length; //int gat_ign = 4; float[] values = new float[reconDimensionX*reconDimensionY*reconDimensionZ]; //h_volume = new float[length/7]; h_volume = new float[reconDimensionX*reconDimensionY*reconDimensionZ]; System.out.println("Length/(2*gat_ign+1): " + length/(2*gat_ign+1)); System.out.println("recDimensions: " + reconDimensionX*reconDimensionY*reconDimensionZ); //subtract the volumina of the 6 ignore volumina //create the correct grid afterwards /*int v = 0; for(int i = 0; i < length; i = i + 2*gat_ign+1){ float reconValue = volumePointer.getBuffer().get(); for(int ign = 0; ign < 2*gat_ign; ign++){ float val = volumePointer.getBuffer().get(); if(val < 100000.0f && val > -100000.0f){ //subtract the values that shall be ignored reconValue = reconValue - val; } } if(v < values.length){ h_volume[v] = reconValue; v++; } }*/ /*v = 0; for (int x=0;x < projectionVolume.getSize()[0];++x) { for (int y=0;y < projectionVolume.getSize()[1];++y) { //TODO MOEGLICHE FEHLERQUELLE for(int z = 0; z< projectionVolume.getSize()[2]; z++){ projectionVolume.setAtIndex(x,y,z,values[v]); v ++; } } }*/ /*int width = projectionVolume.getSize()[0]; int height = projectionVolume.getSize()[1]; if (this.useVOImap) { for (int k = 0; k < projectionVolume.getSize()[2]; k++){ for (int j = 0; j < height; j++){ for (int i = 0; i < width; i++){ float value = h_volume[(((height * k) + j) * width) + i]; if (voiMap[i][j][k]) { projectionVolume.setAtIndex(i, j, k, value); } else { projectionVolume.setAtIndex(i, j, k, 0); } } } } } else { for (int k = 0; k < projectionVolume.getSize()[2]; k++){ for (int j = 0; j < height; j++){ for (int i = 0; i < width; i++){ float value = h_volume[(((height * k) + j) * width) + i]; projectionVolume.setAtIndex(i, j, k, value); } } } }*/ destBuffer.getBuffer().rewind(); for (int x=0; x < reconDimensionX ;++x) { for (int y=0; y < reconDimensionY;++y) { for(int z = 0; z< reconDimensionZ; z++){ //grid.setAtIndex(x, y, z, imgBuffer.getBuffer().get()); projectionVolume.setAtIndex(x, y, z, destBuffer.getBuffer().get()); } } } } else { CONRAD.log("Check ProjectionVolume. It seems null."); } h_volume = null; // free memory on device commandQueue.release(); if (projectionTex != null) projectionTex.release(); if (projectionMatrix != null) projectionMatrix.release(); if (volStride != null) volStride.release(); if (projectionArray != null) projectionArray.release(); if (volumePointer != null) volumePointer.release(); kernelFunction.release(); program.release(); // destory context context.release(); commandQueue = null; projectionArray = null; projectionMatrix = null; projectionTex = null; volStride = null; volumePointer = null; kernelFunction = null; program = null; context = null; initialized = false; } } protected synchronized void initProjectionMatrix(int projectionNumber){ // load projection Matrix for current Projection. if (getGeometry().getProjectionMatrix(projectionNumber)== null) { CONRAD.log("No geometry found for projection " +projectionNumber + ". Skipping."); return; } SimpleMatrix pMat = getGeometry().getProjectionMatrix(projectionNumber).computeP(); float [] pMatFloat = new float[pMat.getCols() * pMat.getRows()]; for (int j = 0; j< pMat.getRows(); j++) { for (int i = 0; i< pMat.getCols(); i++) { pMatFloat[(j * pMat.getCols()) + i] = (float) pMat.getElement(j, i); } } // Obtain the global pointer to the view matrix from // the module if (projectionMatrix == null) projectionMatrix = context.createFloatBuffer(pMatFloat.length, Mem.READ_ONLY); projectionMatrix.getBuffer().put(pMatFloat); projectionMatrix.getBuffer().rewind(); commandQueue.putWriteBuffer(projectionMatrix, true).finish(); //System.out.println("Uploading matrix " + projectionNumber); } private synchronized void initProjectionData(Grid2D projection){ initialize(projection); if (projection != null){ float [] proj= new float[projection.getWidth() * projection.getHeight()]; for(int i = 0; i< projection.getWidth(); i++){ for (int j =0; j < projection.getHeight(); j++){ proj[(j*projection.getWidth()) + i] = projection.getPixelValue(i, j); } } if (projectionArray == null) { // Create the array that will contain the // projection data. projectionArray = context.createFloatBuffer(projection.getWidth()*projection.getHeight(), Mem.READ_ONLY); } // Copy the projection data to the array projectionArray.getBuffer().put(proj); projectionArray.getBuffer().rewind(); // set the texture CLImageFormat format = new CLImageFormat(ChannelOrder.INTENSITY, ChannelType.FLOAT); projectionTex = context.createImage2d(projectionArray.getBuffer(), projection.getWidth(), projection.getHeight(), format, Mem.READ_ONLY); //projectionArray.release(); } else { CONRAD.log("Projection was null!!"); } } @Override public String getName() { return "OpenCL Backprojector"; } @Override public String getBibtexCitation() { String bibtex = "@inproceedings{Rohkohl08-CCR,\n" + " author = {{Scherl}, H. and {Keck}, B. and {Kowarschik}, M. and {Hornegger}, J.},\n" + " title = {{Fast GPU-Based CT Reconstruction using the Common Unified Device Architecture (CUDA)}},\n" + " booktitle = {{Nuclear Science Symposium, Medical Imaging Conference 2007}},\n" + " publisher = {IEEE},\n" + " volume={6},\n" + " address = {Honolulu, HI, United States},\n" + " year = {2007}\n" + " pages= {4464--4466},\n" + "}"; return bibtex; } @Override public String getMedlineCitation() { return "Scherl H, Keck B, Kowarschik M, Hornegger J. Fast GPU-Based CT Reconstruction using the Common Unified Device Architecture (CUDA). In Nuclear Science Symposium, Medical Imaging Conference Record, IEEE, Honolulu, HI, United States, 2008 6:4464-6."; } public void waitForResult() { OpenCLRun(); } @Override public void backproject(Grid2D projection, int projectionNumber) { appendProjection(projection, projectionNumber); } private void appendProjection(Grid2D projection, int projectionNumber){ projections.add(projection, projectionNumber); projectionsAvailable.add(new Integer(projectionNumber)); } private synchronized void projectSingleProjection(int projectionNumber, int dimz){ // load projection matrix initProjectionMatrix(projectionNumber); // load projection Grid2D projection = (Grid2D)projections.get(projectionNumber).clone(); // Correct for constant part of distance weighting + For angular sampling double D = getGeometry().getSourceToDetectorDistance(); NumericPointwiseOperators.multiplyBy(projection, (float)(10 * D*D * 2* Math.PI * getGeometry().getPixelDimensionX() / getGeometry().getNumProjectionMatrices())); initProjectionData(projection); //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(); // write kernel parameters kernelFunction.rewind(); kernelFunction .putArg(volumePointer) .putArg(destBuffer) .putArg((int) lineOffset) .putArg(reconDimensionZ) .putArg((float) voxelSpacingX) .putArg((float) voxelSpacingY) .putArg((float) voxelSpacingZ) .putArg((float) offsetX) .putArg((float) offsetY) .putArg((float) offsetZ) .putArg(projectionTex) .putArg(volStride) .putArg(projectionMatrix); int[] realLocalSize = {Math.min(device.getMaxWorkGroupSize(),bpBlockSize[0]), Math.min(device.getMaxWorkGroupSize(),bpBlockSize[1])}; // rounded up to the nearest multiple of localWorkSize int[] globalWorkSize = {getGeometry().getReconDimensionX(), getGeometry().getReconDimensionY()}; if ((globalWorkSize[0] % bpBlockSize[0] ) != 0){ globalWorkSize[0] = ((globalWorkSize[0] / bpBlockSize[0]) + 1) * bpBlockSize[0]; } if ((globalWorkSize[1] % bpBlockSize[1] ) != 0){ globalWorkSize[1] = ((globalWorkSize[1] / bpBlockSize[1]) + 1) * bpBlockSize[1]; } // Call the OpenCL kernel, writing the results into the volume which is pointed at commandQueue .putWriteImage(projectionTex, true) .finish() .put2DRangeKernel(kernelFunction, 0, 0, globalWorkSize[0], globalWorkSize[1], realLocalSize[0], realLocalSize[1]) //.finish() //.putReadBuffer(dOut, true) .finish(); projectionTex.release(); projectionTex = null; } public void OpenCLRun() { try { while (projectionsAvailable.size() > 0) { Thread.sleep(CONRAD.INVERSE_SPEEDUP); if (showStatus) { float status = (float) (1.0 / projections.size()); if (largeVolumeMode) { IJ.showStatus("Streaming Projections to OpenCL Buffer"); } else { IJ.showStatus("Backprojecting with OpenCL"); } IJ.showProgress(status); } if (!largeVolumeMode) { //process all projections workOnProjectionData(); } else { checkProjectionData(); } } CONRAD.log("large Volume " + largeVolumeMode); //if (largeVolumeMode){ } catch (InterruptedException e) { e.printStackTrace(); } if (showStatus) IJ.showProgress(1.0); unload(); if (debug) CONRAD.log("Unloaded"); } private synchronized void workOnProjectionData(){ if (projectionsAvailable.size() > 0){ Integer current = projectionsAvailable.get(0); projectionsAvailable.remove(0); projectSingleProjection(current.intValue(), getGeometry().getReconDimensionZ()); projectionsDone.add(current); } } private synchronized void checkProjectionData(){ if (projectionsAvailable.size() > 0){ Integer current = projectionsAvailable.get(0); projectionsAvailable.remove(current); projectionsDone.add(current); } } public void reconstructOffline(ImagePlus imagePlus) throws Exception { ImagePlusDataSink sink = new ImagePlusDataSink(); configure(); init(); for (int i = 0; i < imagePlus.getStackSize(); i++){ backproject(ImageUtil.wrapImageProcessor(imagePlus.getStack().getProcessor(i+1)), i); } waitForResult(); if (Configuration.getGlobalConfiguration().getUseHounsfieldScaling()) applyHounsfieldScaling(); int [] size = projectionVolume.getSize(); //System.out.println(size [0] + " " + size [1] + " " + size[2]); for (int k = 0; k < projectionVolume.getSize()[2]; k++){ FloatProcessor fl = new FloatProcessor(projectionVolume.getSize()[0], projectionVolume.getSize()[1]); for (int j = 0; j< projectionVolume.getSize()[1]; j++){ for (int i = 0; i< projectionVolume.getSize()[0]; i++){ fl.putPixelValue(i, j, projectionVolume.getAtIndex(i, j, k)); } } sink.process(projectionVolume.getSubGrid(k), k); } sink.close(); ImagePlus revan = ImageUtil.wrapGrid3D(sink.getResult(), "Reconstruction of " + imagePlus.getTitle()); revan.setTitle(imagePlus.getTitle() + " reconstructed"); revan.show(); } @Override protected void reconstruct() throws Exception { init(); for (int i = 0; i < nImages; i++){ backproject(inputQueue.get(i), i); } waitForResult(); if (Configuration.getGlobalConfiguration().getUseHounsfieldScaling()) applyHounsfieldScaling(); int [] size = projectionVolume.getSize(); for (int k = 0; k < size[2]; k++){ sink.process(projectionVolume.getSubGrid(k), k); } sink.close(); } @Override public String getToolName(){ return "OpenCL Backprojector with Streak Reduction"; } /** * @return the forceSmallVolume */ public boolean isForceSmallVolume() { return forceSmallVolume; } /** * @param forceSmallVolume the forceSmallVolume to set */ public void setForceSmallVolume(boolean forceSmallVolume) { this.forceSmallVolume = forceSmallVolume; } } /* * Copyright (C) 2010-2014 Martin Berger * CONRAD is developed as an Open Source project under the GNU General Public License (GPL). */