Skip to content

Commit

Permalink
CUDA Branchless Distance Driven codes
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigovimieiro committed Feb 16, 2022
1 parent 89d8f8b commit 60eadc4
Show file tree
Hide file tree
Showing 18 changed files with 2,357 additions and 73 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ This repository is a python extension of the [DBT toolbox](https://github.com/LA

* ```cd pyDBT```

3. Clone NVIDIA cuda-samples directory inside pyDBT:

* ```git clone https://github.com/NVIDIA/cuda-samples```

3. Install the package:

* ```python3 setup.py install```

3. If you have problems with `arch=sm_XX`, modify it in the `setup.py` accordingly to your NVIDIA-GPU architecture. This [link](https://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/) has some references.

4. run the example:

* ```cd pydbt && python3 example.py```
Expand Down
5 changes: 2 additions & 3 deletions pydbt/functions/FBP.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"""

import numpy as np
from .backprojectionDDb import backprojectionDDb
from .backprojectionDD import backprojectionDD
from .projection_operators import backprojectionDD

def FDK(proj, geo, filterType, cutoff, libFiles):

Expand All @@ -20,7 +19,7 @@ def FDK(proj, geo, filterType, cutoff, libFiles):
else:
raise ValueError('Unknown filter type.')

vol = backprojectionDD(proj, geo, libFiles)
vol = backprojectionDD(proj, geo, -1, libFiles)

return vol

Expand Down
13 changes: 7 additions & 6 deletions pydbt/functions/SIRT.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import numpy as np
import time

from .backprojectionDDb import backprojectionDDb
from .projectionDDb import projectionDDb
from .projection_operators import backprojectionDDb, projectionDDb

def SIRT(proj, geo, nIter, libFiles):

Expand All @@ -20,10 +19,12 @@ def SIRT(proj, geo, nIter, libFiles):
reconData3d = np.zeros([geo.ny, geo.nx, geo.nz])

# Pre calculation of Projection normalization
proj_norm = projectionDDb(np.ones([geo.ny, geo.nx, geo.nz]), geo, libFiles)
proj_norm = projectionDDb(np.ones([geo.ny, geo.nx, geo.nz]), geo, -1, libFiles)
proj_norm[proj_norm == 0] = 1

# Pre calculation of Backprojection normalization
vol_norm = backprojectionDDb(np.ones([geo.nv, geo.nu, geo.nProj]), geo, libFiles)
vol_norm = backprojectionDDb(np.ones([geo.nv, geo.nu, geo.nProj]), geo, -1, libFiles)
vol_norm[vol_norm == 0] = 1

print('----------------\nStarting SIRT Iterations... \n\n')

Expand All @@ -33,14 +34,14 @@ def SIRT(proj, geo, nIter, libFiles):
startTime = time.time()

# Error between raw data and projection of estimated data
proj_diff = proj - projectionDDb(reconData3d, geo, libFiles)
proj_diff = proj - projectionDDb(reconData3d, geo, -1, libFiles)

# Projection normalization
proj_diff = proj_diff / proj_norm
proj_diff[np.isnan(proj_diff)] = 0
proj_diff[np.isinf(proj_diff)] = 0

upt_term = backprojectionDDb(proj_diff, geo, libFiles)
upt_term = backprojectionDDb(proj_diff, geo, -1, libFiles)
upt_term = upt_term / vol_norm # Volume normalization
upt_term[np.isnan(upt_term)] = 0
upt_term[np.isinf(upt_term)] = 0
Expand Down
162 changes: 162 additions & 0 deletions pydbt/functions/projection_operators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Feb 16 10:29:14 2022
@author: rodrigo
"""

import numpy as np
import numpy.ctypeslib as ctl
import ctypes
import warnings

from .utilities import findAndLoadLibray
from .utilities import geoAsNp

##############################################################################
# BackProjection #
##############################################################################

def backprojectionDD(proj, geo, proj_num, libFiles):

operator_type = 'backprojectionDD'

vol = _backprojection(proj, geo, proj_num, libFiles, operator_type)

return vol

def backprojectionDDb(proj, geo, proj_num, libFiles):

operator_type = 'backprojectionDDb'

vol = _backprojection(proj, geo, proj_num, libFiles, operator_type)

return vol

def backprojectionDDb_cuda(proj, geo, proj_num, libFiles):

operator_type = 'backprojectionDDb_cuda'

vol = _backprojection(proj, geo, proj_num, libFiles, operator_type)

return vol

def _backprojection(proj, geo, proj_num, libFiles, operator_type):

# Check if the input is in proper size
if not (proj.shape[0] == geo.nv and proj.shape[1] == geo.nu and proj.shape[2] == geo.nProj):
raise ValueError('First argument needs to have the same number of rows, cols and slices as in the configuration file.')

check_parameters(operator_type, proj_num, geo)

# Find and library
lib = findAndLoadLibray(libFiles, operator_type)


backprojection = getattr(lib, operator_type + '_lib')

backprojection.argtypes = [ctl.ndpointer(np.float64, flags='aligned, c_contiguous'),
ctl.ndpointer(np.float64, flags='aligned, c_contiguous'),
ctl.ndpointer(np.float32, flags='aligned, c_contiguous'),
ctypes.c_long]


# Transform geo class in numpy array
geoNp = geoAsNp(geo)


vol_transp = np.empty([geo.nz, geo.nx, geo.ny], dtype=np.float64)

proj_transp = np.transpose(proj, (2, 1, 0)).copy()

backprojection(proj_transp, vol_transp, geoNp, proj_num)

vol = np.transpose(vol_transp, (2, 1, 0)).copy()


return vol


##############################################################################
# Projection #
##############################################################################

def projectionDD(vol, geo, proj_num, libFiles):

operator_type = 'projectionDD'

proj = _projection(vol, geo, proj_num, libFiles, operator_type)

return proj

def projectionDDb(vol, geo, proj_num, libFiles):

operator_type = 'projectionDDb'

proj = _projection(vol, geo, proj_num, libFiles, operator_type)

return proj

def projectionDDb_cuda(vol, geo, proj_num, libFiles):

operator_type = 'projectionDDb_cuda'

proj = _projection(vol, geo, proj_num, libFiles, operator_type)

return proj

def _projection(vol, geo, proj_num, libFiles, operator_type):

# Check if the input is in proper size
if not (vol.shape[0] == geo.ny and vol.shape[1] == geo.nx and vol.shape[2] == geo.nz):
raise ValueError('First argument needs to have the same number of rows, cols and slices as in the configuration file.')

check_parameters(operator_type, proj_num, geo)

# Find and library
lib = findAndLoadLibray(libFiles, operator_type)

projection = getattr(lib, operator_type + '_lib')

projection.argtypes = [ctl.ndpointer(np.float64, flags='aligned, c_contiguous'),
ctl.ndpointer(np.float64, flags='aligned, c_contiguous'),
ctl.ndpointer(np.float32, flags='aligned, c_contiguous'),
ctypes.c_long]


# Transform geo class in numpy array
geoNp = geoAsNp(geo)


proj_transp = np.empty([geo.nProj, geo.nu, geo.nv], dtype=np.float64)

vol_transp = np.transpose(vol, (2, 1, 0)).copy()

projection(vol_transp, proj_transp, geoNp, proj_num)

proj = np.transpose(proj_transp, (2, 1, 0)).copy()

return proj

##############################################################################
# Auxiliar functions #
##############################################################################


def check_parameters(operator_type, proj_num, geo):

if proj_num < -1 and proj_num >= geo.nProj:
raise ValueError('Projection number needs to be between 0-(geo.nProj-1). If you want to operate on all projections, set it to -1')

if geo.detAngle != 0 and 'DDb' in operator_type:
warnings.warn('Your detector rotates, its not recomended to used branchless projectors. Use DD only')

if (geo.x_offset != 0 or geo.y_offset != 0) and ('DDb' in operator_type):
raise ValueError('Branchless operators dont work with volume offsets')

return




103 changes: 103 additions & 0 deletions pydbt/functions/utilities2setuptools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 15 16:41:20 2022
@author: Rodrigo
Reference: https://stackoverflow.com/a/13300714/8682939
"""
import os

from os.path import join as pjoin
from distutils.command.build_ext import build_ext


def find_in_path(name, path):
"Find a file in a search path"
#adapted fom http://code.activestate.com/recipes/52224-find-a-file-given-a-search-path/
for dir in path.split(os.pathsep):
binpath = pjoin(dir, name)
if os.path.exists(binpath):
return os.path.abspath(binpath)
return None

def locate_cuda():
"""Locate the CUDA environment on the system
Returns a dict with keys 'home', 'nvcc', 'include', and 'lib64'
and values giving the absolute path to each directory.
Starts by looking for the CUDAHOME env variable. If not found, everything
is based on finding 'nvcc' in the PATH.
"""

# first check if the CUDAHOME env variable is in use
if 'CUDAHOME' in os.environ:
home = os.environ['CUDAHOME']
nvcc = pjoin(home, 'bin', 'nvcc')
else:
# otherwise, search the PATH for NVCC
nvcc = find_in_path('nvcc', os.environ['PATH'])
if nvcc is None:
raise EnvironmentError('The nvcc binary could not be '
'located in your $PATH. Either add it to your path, or set $CUDAHOME')
home = os.path.dirname(os.path.dirname(nvcc))

cudaconfig = {'home':home, 'nvcc':nvcc,
'include': pjoin(home, 'include'),
'lib64': pjoin(home, 'lib64')}

for k, v in cudaconfig.items():
if not os.path.exists(v):
raise EnvironmentError('The CUDA %s path could not be located in %s' % (k, v))

return cudaconfig

def get_custom_build_ext(cuda_env):

def customize_compiler_for_nvcc(self):
"""inject deep into distutils to customize how the dispatch
to gcc/nvcc works.
If you subclass UnixCCompiler, it's not trivial to get your subclass
injected in, and still have the right customizations (i.e.
distutils.sysconfig.customize_compiler) run on it. So instead of going
the OO route, I have this. Note, it's kindof like a wierd functional
subclassing going on."""

# tell the compiler it can processes .cu
self.src_extensions.append('.cu')

# save references to the default compiler_so and _comple methods
default_compiler_so = self.compiler_so
super = self._compile

# now redefine the _compile method. This gets executed for each
# object but distutils doesn't have the ability to change compilers
# based on source extension: we add it.
def _compile(obj, src, ext, cc_args, extra_postargs, pp_opts):
if os.path.splitext(src)[1] == '.cu':
# use the cuda for .cu files
self.set_executable('compiler_so', cuda_env['nvcc'])
# use only a subset of the extra_postargs, which are 1-1 translated
# from the extra_compile_args in the Extension class
postargs = extra_postargs['nvcc']
else:
postargs = extra_postargs['gcc']

super(obj, src, ext, cc_args, postargs, pp_opts)
# reset the default compiler_so, which we might have changed for cuda
self.compiler_so = default_compiler_so

# inject our redefined _compile method into the class
self._compile = _compile

# run the customize_compiler
class custom_build_ext(build_ext):
def build_extensions(self):
customize_compiler_for_nvcc(self.compiler)
build_ext.build_extensions(self)

return custom_build_ext
Loading

0 comments on commit 60eadc4

Please sign in to comment.