├── .gitignore ├── LICENSE ├── README.md ├── pydbt ├── demos │ ├── __init__.py │ └── shepp_logan_phantom.py ├── example.py ├── functions │ ├── FBP.py │ ├── SART.py │ ├── SIRT.py │ ├── __init__.py │ ├── dataPreProcess.py │ ├── initialConfig.py │ ├── manageDicom.py │ ├── phantoms.py │ ├── projection_operators.py │ ├── utilities.py │ └── utilities2setuptools.py ├── parameters │ ├── __init__.py │ └── parameterSettings.py └── sources │ ├── backprojectionDD │ ├── backprojectionDD.cpp │ └── backprojectionDD.h │ ├── backprojectionDDb │ ├── backprojectionDDb.cpp │ └── backprojectionDDb.hpp │ ├── backprojectionDDb_cuda │ ├── backprojectionDDb_cuda.cpp │ ├── backprojectionDDb_cuda.hpp │ └── kernel.cu │ ├── projectionDD │ ├── projectionDD.cpp │ └── projectionDD.h │ ├── projectionDDb │ ├── projectionDDb.cpp │ └── projectionDDb.hpp │ └── projectionDDb_cuda │ ├── kernel.cu │ ├── projectionDDb_cuda.cpp │ └── projectionDDb_cuda.hpp ├── setup.py └── setup_docker.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | cuda-samples/* 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 113 | .pdm.toml 114 | .pdm-python 115 | .pdm-build/ 116 | 117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 118 | __pypackages__/ 119 | 120 | # Celery stuff 121 | celerybeat-schedule 122 | celerybeat.pid 123 | 124 | # SageMath parsed files 125 | *.sage.py 126 | 127 | # Environments 128 | .env 129 | .venv 130 | env/ 131 | venv/ 132 | ENV/ 133 | env.bak/ 134 | venv.bak/ 135 | 136 | # Spyder project settings 137 | .spyderproject 138 | .spyproject 139 | 140 | # Rope project settings 141 | .ropeproject 142 | 143 | # mkdocs documentation 144 | /site 145 | 146 | # mypy 147 | .mypy_cache/ 148 | .dmypy.json 149 | dmypy.json 150 | 151 | # Pyre type checker 152 | .pyre/ 153 | 154 | # pytype static type analyzer 155 | .pytype/ 156 | 157 | # Cython debug symbols 158 | cython_debug/ 159 | 160 | # PyCharm 161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 163 | # and can be added to the global gitignore or merged into this file. For a more nuclear 164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 165 | #.idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pyDBT 2 | ====== 3 | 4 | This repository is a python extension of the [DBT toolbox](https://github.com/LAVI-USP/DBT-Reconstruction) from LAVI-USP. 5 | 6 | 7 | 8 | ## How to install? 9 | 10 | 1. Download the toolbox or clone the directory: 11 | 12 | * ```git clone https://github.com/LAVI-USP/pyDBT.git``` 13 | 14 | 2. Go to parent directory: 15 | 16 | * ```cd pyDBT``` 17 | 18 | 3. Clone NVIDIA cuda-samples directory inside pyDBT: 19 | 20 | * ```git clone https://github.com/NVIDIA/cuda-samples``` 21 | 22 | 3. Install the package: 23 | 24 | * ```python3 setup.py install``` 25 | 26 | 3. If you have problems with `arch=sm_XX`, modify it in the `setup.py`code according to your NVIDIA-GPU architecture. This [link](https://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/) has some references. 27 | 28 | 4. run the example: 29 | 30 | * ```cd pydbt && python3 example.py``` 31 | 32 | 5. The toolbox was tested on **Linux** (Ubuntu 20) x64, and **macOS** BigSur (Intel) machines, with python **3.7.5**. 33 | * You will need to install gcc for the compilation. You can install gcc 11 on macOS through [homebrew](https://formulae.brew.sh/formula/gcc). 34 | * Fell free to reach me if you want binaries for either Ubuntu or BigSur 35 | 36 | 6. You can also run the [MATLAB version](https://github.com/LAVI-USP/DBT-Reconstruction) of the toolbox. 37 | 38 | ** Please report issues [here](https://github.com/LAVI-USP/pyDBT/issues). ** 39 | 40 | ## Contribute? 41 | 42 | We are pleased with any contributions. Feel free to make any [pull requests](https://github.com/LAVI-USP/pyDBT/pulls) or send us an e-mail. 43 | 44 | 45 | ## Toolbox manual: 46 | 47 | You can find the [manual](https://github.com/LAVI-USP/DBT-Reconstruction/wiki/Toolbox-Manual) from the MATLAB version, which is pretty much the same. I will create a specific one for the python version in the future. 48 | 49 | ## Contact: 50 | 51 | If you have any questions or suggestion, please send us an e-mail: 52 | 53 | - Rodrigo - rodrigo dot vimieiro at gmail dot com 54 | - Marcelo - mvieira at sc dot usp dot br 55 | 56 | ## License: 57 | 58 | The toolbox is licensed under the **GNU General Public License v3.0**. Please check the [licence file](https://github.com/LAVI-USP/pyDBT/blob/master/LICENSE). 59 | 60 | ## Reference: 61 | 62 | If you use the toolbox, we will be very grateful if you refer to this [paper](https://doi.org/10.1007/978-981-13-2517-5_53): 63 | 64 | > Vimieiro R.B., Borges L.R., Vieira M.A.C. (2019) Open-Source Reconstruction Toolbox for Digital Breast Tomosynthesis. In: Costa-Felix R., Machado J., Alvarenga A. (eds) XXVI Brazilian Congress on Biomedical Engineering. IFMBE Proceedings, vol 70/2. Springer, Singapore. 65 | 66 | ## Citations: 67 | 68 | You can find [here](https://scholar.google.com.br/scholar?oi=bibs&hl=pt-BR&cites=3156269064066227282) the papers that have used the toolbox. 69 | 70 | ## Acknowledgments: 71 | 72 | This work was supported by the São Paulo Research Foundation ([FAPESP](http://www.fapesp.br/) grant 2016/25750-0) and by the National Council for Scientific and Technological Development ([CNPq](http://www.cnpq.br/)). Nobody does anything alone, so we would like to thank the contribution of our lab members and the [Barretos Love Hospital](https://www.hcancerbarretos.com.br) for providing the images of DBT. 73 | 74 | --- 75 | 76 | Laboratory of Computer Vision ([Lavi](http://iris.sel.eesc.usp.br/lavi/)) 77 | Department of Electrical and Computer Engineering 78 | São Carlos School of Engineering, University of São Paulo 79 | São Carlos - Brazil 80 | -------------------------------------------------------------------------------- /pydbt/demos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAVI-USP/pyDBT/47801b5f655c382814af70a75737e4ef5e6d44ba/pydbt/demos/__init__.py -------------------------------------------------------------------------------- /pydbt/demos/shepp_logan_phantom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Wed Dec 30 13:49:27 2020 5 | 6 | @author: Rodrigo 7 | """ 8 | 9 | #%% 10 | import matplotlib.pyplot as plt 11 | import numpy as np 12 | import sys 13 | 14 | sys.path.insert(1,'../') 15 | 16 | from parameters.parameterSettings import geometry_settings 17 | 18 | from functions.initialConfig import initialConfig 19 | from functions.phantoms import phantom3d 20 | 21 | from functions.projection_operators import projectionDD 22 | 23 | from functions.FBP import FDK as FBP 24 | from functions.SART import SART 25 | 26 | 27 | #%% Call function for initial configurations 28 | 29 | libFiles = initialConfig() 30 | 31 | #%% Create a DBT geometry 32 | 33 | geo = geometry_settings() 34 | geo.SheppLogan() 35 | 36 | #%% Gen Phantom 37 | 38 | sheppLogan = phantom3d(n=geo.nx) 39 | sheppLogan = sheppLogan[:,:,np.round(np.linspace(0,geo.nx-1,geo.nz)).astype(np.int32)] 40 | 41 | #%% Project Phantom 42 | 43 | print("Starting projection...") 44 | 45 | proj = projectionDD(sheppLogan, geo, -1, libFiles) 46 | 47 | plt.figure() 48 | plt.title('Projected volume') 49 | plt.imshow(proj[:,:,4] , cmap=plt.get_cmap('gist_gray')) 50 | 51 | #%% Set specific recon parameters 52 | 53 | nIter = [2]; # Number of iterations (SIRT) 54 | filterType = 'FBP'; # Filter type: 'BP', 'FBP' 55 | cutoff = 0.75; # Percentage until cut off frequency (FBP) 56 | 57 | #%% 58 | 59 | print("Starting reconstruction...") 60 | 61 | ### Uncomment to use ## 62 | # vol = FBP(proj, geo, filterType, cutoff, libFiles) 63 | 64 | vol = SART(proj, geo, nIter, libFiles) 65 | 66 | plt.figure() 67 | plt.title('Reconstructed slice') 68 | plt.imshow(vol[:,:,64] , cmap=plt.get_cmap('gist_gray')) 69 | -------------------------------------------------------------------------------- /pydbt/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Tue Jan 14 08:37:11 2020 5 | 6 | @author: Rodrigo 7 | """ 8 | 9 | #%% 10 | import matplotlib.pyplot as plt 11 | 12 | from parameters.parameterSettings import geometry_settings 13 | 14 | from functions.manageDicom import readDicom 15 | from functions.dataPreProcess import dataPreProcess 16 | from functions.initialConfig import initialConfig 17 | 18 | from functions.FBP import FDK as FBP 19 | from functions.SIRT import SIRT 20 | from functions.SART import SART 21 | 22 | 23 | #%% Call function for initial configurations 24 | 25 | libFiles = initialConfig() 26 | 27 | #%% Create a DBT geometry 28 | 29 | geo = geometry_settings() 30 | geo.GE() 31 | 32 | #%% Get DICOM data 33 | 34 | dcmPath = "/home/rodrigo/Downloads/imgs/" 35 | proj,_ = readDicom(dcmPath,geo) 36 | 37 | proj = dataPreProcess(proj, geo) 38 | 39 | #%% Set specific recon parameters 40 | 41 | nIter = [2]; # Number of iterations (SIRT) 42 | filterType = 'FBP'; # Filter type: 'BP', 'FBP' 43 | cutoff = 0.75; # Percentage until cut off frequency (FBP) 44 | 45 | #%% 46 | 47 | print("Starting reconstruction...") 48 | 49 | # ## Uncomment to use ## 50 | # vol = FBP(proj, geo, filterType, cutoff, libFiles) 51 | 52 | vol = SART(proj, geo, nIter, libFiles) 53 | 54 | plt.figure() 55 | plt.title('Reconstructed slice') 56 | plt.imshow(vol[:,:,50] , cmap=plt.get_cmap('gist_gray')) 57 | plt.imsave('output/Reconstructed_slice.tiff',vol[:,:,50] , cmap=plt.get_cmap('gist_gray')) 58 | 59 | # print("Starting projection...") 60 | # proj = projectionDDb(vol, geo, libFiles) 61 | # plt.figure() 62 | # plt.title('Projected volume') 63 | # plt.imshow(proj[:,:,4] , cmap=plt.get_cmap('gist_gray')) 64 | 65 | 66 | -------------------------------------------------------------------------------- /pydbt/functions/FBP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Feb 3 07:43:53 2020 5 | 6 | @author: Rodrigo 7 | """ 8 | 9 | import numpy as np 10 | from .projection_operators import backprojectionDDb_cuda 11 | 12 | def FDK(proj, geo, filterType, cutoff, libFiles): 13 | 14 | if filterType == 'FBP': 15 | print("----------------\nStarting FBP... \n\n") 16 | proj = filterProj(proj, geo, cutoff) 17 | elif filterType == 'BP': 18 | print("----------------\nStarting BP... \n\n") 19 | else: 20 | raise ValueError('Unknown filter type.') 21 | 22 | vol = backprojectionDDb_cuda(proj, geo, -1, libFiles) 23 | 24 | return vol 25 | 26 | 27 | 28 | def filterProj(proj, geo, cutoff): 29 | 30 | 31 | filteredProj = np.empty(proj.shape) 32 | 33 | us = np.linspace(geo.nu-1, 0, geo.nu) * geo.du 34 | vs = np.linspace(-(geo.nv-1)/2, (geo.nv-1)/2, geo.nv) * geo.dv 35 | 36 | # Detector Coordinate sytem in (mm) 37 | uCoord, vCoord = np.meshgrid(us, vs) 38 | 39 | # Compute weighted projections (Fessler Book Eq. (3.10.6)) 40 | weightFunction = geo.DSO / np.sqrt((geo.DSD**2)+(vCoord**2)+(uCoord**2)) 41 | 42 | # Apply weighting function on each proj 43 | for i in range(geo.nProj): 44 | filteredProj[:,:,i] = proj[:,:,i] * weightFunction 45 | 46 | 47 | # Increase filter length to two times nv to decrease freq step 48 | h_Length = int(2**np.ceil(np.log2(np.abs(2*geo.nv)))) 49 | 50 | # Builds ramp filter in space domain 51 | ramp_kernel = ramp_builder(h_Length) 52 | 53 | # Window filter in freq domain 54 | H_filter = filter_window(ramp_kernel, h_Length, cutoff) 55 | 56 | # Replicate to all colluns to build a 2D filter kernel 57 | H_filter = np.transpose(np.tile(H_filter, (geo.nu,1))) 58 | 59 | # Proj in freq domain 60 | H_proj = np.zeros([h_Length,geo.nu]) 61 | 62 | # Filter each projection 63 | for i in range(geo.nProj): 64 | 65 | H_proj[0:geo.nv,:] = filteredProj[:,:,i] 66 | 67 | # Fourier transfor in projections 68 | H_proj = np.fft.fftshift(np.fft.fft(H_proj, axis=0)) 69 | 70 | # Multiplication in frequency = convolution in space 71 | H_proj = H_proj * H_filter 72 | 73 | # Inverse Fourier transfor 74 | H_proj = np.real(np.fft.ifft(np.fft.ifftshift(H_proj), axis=0)) 75 | 76 | filteredProj[:,:,i] = H_proj[0:geo.nv,:] 77 | 78 | return filteredProj 79 | 80 | 81 | 82 | 83 | ## Function Ramp Filter 84 | """ 85 | The function builds Ramp Filter in space domain 86 | Reference: Jiang Hsieh's book (second edition,page 73) Eq. 3.29 87 | Reference: Fessler Book Eq.(3.4.14) 88 | """ 89 | def ramp_builder(h_Length): 90 | 91 | n = np.linspace(-h_Length/2, (h_Length/2)-1, h_Length) 92 | h = np.zeros(n.shape) 93 | h[int(h_Length/2)] = 1/4 # Eq. 3.29 94 | odd = np.mod(n,2) == 1 # Eq. 3.29 95 | h[odd] = -1 / (np.pi * n[odd])**2 # Eq. 3.29 96 | 97 | return h 98 | 99 | 100 | ## Function Window Ramp Filter 101 | """ 102 | The function builds Ramp Filter apodizided in frequency domain 103 | Reference: Fessler Book and MIRT 104 | """ 105 | def filter_window(ramp_kernel, h_Length, cutoff): 106 | 107 | # Bring filter to freq domain 108 | H_ramp = np.abs(np.fft.fftshift(np.fft.fft(ramp_kernel))) 109 | 110 | w = np.round(h_Length * cutoff) # Cut off freq 111 | n = np.linspace(-h_Length/2, (h_Length/2)-1, h_Length) 112 | H_window = 0.5 * (1 + np.cos(2 * np.pi * n /w)) # Hanning filter 113 | H_window = H_window * (np.abs(n) < w / 2) # Apply cut off freq 114 | 115 | H_filter = H_ramp * H_window # Apply window filter 116 | 117 | return H_filter 118 | -------------------------------------------------------------------------------- /pydbt/functions/SART.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Wed Feb 16 11:29:43 2022 5 | 6 | @author: rodrigo 7 | """ 8 | 9 | import numpy as np 10 | import time 11 | 12 | from .projection_operators import backprojectionDDb_cuda, projectionDDb_cuda 13 | 14 | def SART(proj, geo, nIter, libFiles): 15 | 16 | highestValue = (2**16) - 1 17 | 18 | # Initial estimated data 19 | reconData3d = np.zeros([geo.ny, geo.nx, geo.nz]) 20 | 21 | # Pre calculation of Projection normalization 22 | proj_norm = projectionDDb_cuda(np.ones([geo.ny, geo.nx, geo.nz]), geo, -1, libFiles) 23 | proj_norm[proj_norm == 0] = 1 24 | 25 | # Pre calculation of Backprojection normalization 26 | vol_norm = backprojectionDDb_cuda(np.ones([geo.nv, geo.nu, geo.nProj]), geo, -1, libFiles) 27 | vol_norm[vol_norm == 0] = 1 28 | 29 | print('----------------\nStarting SART Iterations... \n\n') 30 | 31 | # Start Iterations 32 | for n_iter in range(nIter[-1]): 33 | 34 | startTime = time.time() 35 | 36 | for proj_num in range(geo.nProj): 37 | 38 | # Error between raw data and projection of estimated data 39 | proj_diff = proj - projectionDDb_cuda(reconData3d, geo, proj_num, libFiles) 40 | 41 | # Projection normalization 42 | proj_diff = proj_diff / proj_norm 43 | proj_diff[np.isnan(proj_diff)] = 0 44 | proj_diff[np.isinf(proj_diff)] = 0 45 | 46 | upt_term = backprojectionDDb_cuda(proj_diff, geo, proj_num, libFiles) 47 | upt_term = upt_term / vol_norm # Volume normalization 48 | upt_term[np.isnan(upt_term)] = 0 49 | upt_term[np.isinf(upt_term)] = 0 50 | 51 | # Updates the previous estimation 52 | reconData3d = reconData3d + upt_term 53 | 54 | endTime = time.time() 55 | 56 | # Truncate to highest and minimum value 57 | reconData3d[reconData3d > highestValue] = 0 58 | reconData3d[reconData3d < 0] = 0 59 | 60 | print('Itaration %d Time: %.2fs\n\n' % (n_iter , endTime-startTime)) 61 | 62 | return reconData3d -------------------------------------------------------------------------------- /pydbt/functions/SIRT.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Tue Feb 4 07:50:36 2020 5 | 6 | @author: Rodrigo 7 | """ 8 | 9 | import numpy as np 10 | import time 11 | 12 | from .projection_operators import backprojectionDDb_cuda, projectionDDb_cuda 13 | 14 | def SIRT(proj, geo, nIter, libFiles): 15 | 16 | highestValue = (2**16) - 1 17 | 18 | # Initial estimated data 19 | reconData3d = np.zeros([geo.ny, geo.nx, geo.nz]) 20 | 21 | # Pre calculation of Projection normalization 22 | proj_norm = projectionDDb_cuda(np.ones([geo.ny, geo.nx, geo.nz]), geo, -1, libFiles) 23 | proj_norm[proj_norm == 0] = 1 24 | 25 | # Pre calculation of Backprojection normalization 26 | vol_norm = backprojectionDDb_cuda(np.ones([geo.nv, geo.nu, geo.nProj]), geo, -1, libFiles) 27 | vol_norm[vol_norm == 0] = 1 28 | 29 | print('----------------\nStarting SIRT Iterations... \n\n') 30 | 31 | # Start Iterations 32 | for iter in range(nIter[-1]): 33 | 34 | startTime = time.time() 35 | 36 | # Error between raw data and projection of estimated data 37 | proj_diff = proj - projectionDDb_cuda(reconData3d, geo, -1, libFiles) 38 | 39 | # Projection normalization 40 | proj_diff = proj_diff / proj_norm 41 | proj_diff[np.isnan(proj_diff)] = 0 42 | proj_diff[np.isinf(proj_diff)] = 0 43 | 44 | upt_term = backprojectionDDb_cuda(proj_diff, geo, -1, libFiles) 45 | upt_term = upt_term / vol_norm # Volume normalization 46 | upt_term[np.isnan(upt_term)] = 0 47 | upt_term[np.isinf(upt_term)] = 0 48 | 49 | # Updates the previous estimation 50 | reconData3d = reconData3d + upt_term 51 | 52 | endTime = time.time() 53 | 54 | # Truncate to highest and minimum value 55 | reconData3d[reconData3d > highestValue] = 0 56 | reconData3d[reconData3d < 0] = 0 57 | 58 | print('Itaration %d Time: %.2fs\n\n' % (iter , endTime-startTime)) 59 | 60 | return reconData3d -------------------------------------------------------------------------------- /pydbt/functions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAVI-USP/pyDBT/47801b5f655c382814af70a75737e4ef5e6d44ba/pydbt/functions/__init__.py -------------------------------------------------------------------------------- /pydbt/functions/dataPreProcess.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Tue Oct 22 10:34:24 2019 5 | 6 | @author: rodrigo 7 | """ 8 | 9 | import numpy as np 10 | from scipy.ndimage.filters import uniform_filter1d 11 | from skimage.filters import threshold_otsu 12 | 13 | def dataPreProcess(proj, geo, flagtransfIntensity=True, flagCropProj=True): 14 | 15 | indX = 0 16 | 17 | if flagCropProj: 18 | proj, indX = cropProj(proj) 19 | 20 | if flagtransfIntensity: 21 | proj = transfIntensity(proj) 22 | 23 | # Modifies parameters based on segmentation 24 | geo.nu = proj.shape[1] # Number of pixels (columns) 25 | 26 | return proj, indX 27 | 28 | 29 | 30 | def cropProj(proj): 31 | 32 | Gap = 20; 33 | 34 | # Horizontal Profile 35 | vertProj = np.sum(proj[:,:,proj.shape[2]//2 - 1], axis=0) 36 | 37 | # Smooth the signal and take the first derivative 38 | firstDv = np.gradient(uniform_filter1d(vertProj, size=100)) 39 | 40 | # Smooth the signal and take the second derivative 41 | secondDv = np.gradient(uniform_filter1d(firstDv, size=100)) 42 | 43 | # Takes its min second derivative 44 | indX = np.argmin(secondDv) 45 | 46 | # Alternative method to compare 47 | # Otsu 48 | thresh = threshold_otsu(proj[:,:,proj.shape[2]//2 - 1]) 49 | 50 | # Get mask 51 | mask = proj[:,:,proj.shape[2]//2 - 1] < thresh 52 | 53 | # Weight bounds 54 | mask_w = np.sum(mask, 0) > 0 55 | res = np.where(mask_w == True) 56 | w_min, w_max = res[0][0], res[0][-1] 57 | 58 | # If the difference is big, take the min (most left) 59 | if np.abs(indX - w_min) > 200: 60 | indX = np.min([indX, w_min]) 61 | 62 | indX -= Gap 63 | 64 | proj_crop = proj[:,indX::,:] 65 | 66 | return proj_crop, indX 67 | 68 | 69 | def transfIntensity(proj): 70 | 71 | # Transform intensity image in attenuation coefficients 72 | proj = -np.log(proj/np.max(proj)); 73 | 74 | return proj 75 | 76 | -------------------------------------------------------------------------------- /pydbt/functions/initialConfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Wed Jan 22 13:35:00 2020 5 | 6 | @author: rodrigo 7 | """ 8 | 9 | import pathlib 10 | 11 | def initialConfig(buildDir=None, createOutFolder=True): 12 | 13 | workDir = pathlib.Path().absolute() 14 | 15 | # Build folder was not provided, try find it on up folders 16 | if buildDir == None: 17 | buildDir = findBuildDir(workDir) 18 | else: 19 | buildDir = pathlib.Path(buildDir) 20 | 21 | # Should I create output folder? 22 | if createOutFolder: 23 | outDir = workDir / 'output' 24 | 25 | if not(outDir.exists() and outDir.is_dir()): 26 | pathlib.Path(outDir).mkdir() 27 | 28 | # Find the compiled lib files 29 | libFiles = findLibraries(buildDir) 30 | 31 | return libFiles 32 | 33 | 34 | def findBuildDir(workDir): 35 | 36 | buildDir = workDir / '../build' 37 | 38 | if not(buildDir.exists() and buildDir.is_dir()): 39 | 40 | buildDir = workDir / '../../build' 41 | 42 | if not(buildDir.exists() and buildDir.is_dir()): 43 | 44 | raise ValueError('Cannot find the build folder. Make sure to run the setup.py or follow the instructions on the package Github.') 45 | 46 | return buildDir 47 | 48 | 49 | def findLibraries(buildDir): 50 | 51 | libDir = [] 52 | 53 | # Find libray directory 54 | for f in buildDir.iterdir(): 55 | if f.is_dir(): 56 | if 'lib.' in str(f): 57 | libDir = f 58 | 59 | # Test if libDir is empty 60 | if not libDir: 61 | raise ValueError('Cannot find the lib folder. Make sure to run the setup.py or follow the instructions on the package Github.') 62 | 63 | libFiles = [] 64 | 65 | # Find all .so files 66 | for libFile in pathlib.Path(libDir).glob('*.so'): 67 | libFiles.append((str(libDir),str(libFile).split('/')[-1])) 68 | 69 | 70 | return libFiles 71 | 72 | -------------------------------------------------------------------------------- /pydbt/functions/manageDicom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Tue Oct 8 08:39:59 2019 5 | 6 | @author: rodrigo 7 | """ 8 | 9 | import pydicom 10 | import numpy as np 11 | from pathlib import Path 12 | 13 | def readDicom(path, geo): 14 | 15 | dcmFiles = [str(item) for item in Path(path).glob("*.dcm")] 16 | 17 | # Test if list is empty 18 | if not dcmFiles: 19 | raise ValueError('No DICOM files found in the specified path.') 20 | 21 | proj = [None] * geo.nProj 22 | proj_header = [None] * geo.nProj 23 | 24 | for f in dcmFiles: 25 | nProj = int(f.split('/')[-1].split('.')[0]) 26 | proj_header[nProj] = pydicom.dcmread(f) 27 | proj[nProj] = proj_header[nProj].pixel_array 28 | 29 | proj = np.stack(proj, axis=-1).astype(np.uint16) 30 | 31 | return proj, proj_header -------------------------------------------------------------------------------- /pydbt/functions/phantoms.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on Wed Dec 30 10:57:39 2020 3 | 4 | @author: Blink1073 (Modified by Rodrigo Vimieiro) 5 | 6 | Source: https://gist.github.com/blink1073/6e417c726b0dd03d5ea0 7 | 8 | """ 9 | 10 | import numpy as np 11 | 12 | def phantom3d(phantom='modified-shepp-logan', phantom_matrix=None, n=64): 13 | """ 14 | Three-dimensional Shepp-Logan phantom 15 | Can be used to test 3-D reconstruction algorithms. 16 | Parameters 17 | ========== 18 | phantom: str 19 | One of {'modified-shepp-logan', 'shepp_logan', 'yu_ye_wang'}, 20 | The type of phantom to draw. 21 | n : int, optional 22 | The grid size of the phantom 23 | Notes 24 | ===== 25 | For any given voxel in the output image, the voxel's value is equal to the 26 | sum of the additive intensity values of all ellipsoids that the voxel is a 27 | part of. If a voxel is not part of any ellipsoid, its value is 0. 28 | The additive intensity value A for an ellipsoid can be positive or 29 | negative; if it is negative, the ellipsoid will be darker than the 30 | surrounding pixels. 31 | Note that, depending on the values of A, some voxels may have values 32 | outside the range [0,1]. 33 | Copyright 34 | ========= 35 | BSD License 36 | Copyright 2006 Matthias Christian Schabel (matthias @ stanfordalumni . org) 37 | University of Utah Department of Radiology 38 | Utah Center for Advanced Imaging Research 39 | 729 Arapeen Drive 40 | 41 | """ 42 | if phantom == 'modified-shepp-logan': 43 | ellipse = modified_shepp_logan() 44 | elif phantom == 'shepp_logan': 45 | ellipse = shepp_logan() 46 | elif phantom == 'yu_ye_wang': 47 | ellipse = yu_ye_wang() 48 | else: 49 | ellipse = phantom_matrix 50 | 51 | p = np.zeros(n**3) 52 | rng = np.linspace(-1,1,n) 53 | x, y, z = np.meshgrid(rng, rng, rng) 54 | coord = np.vstack((x.flatten(), y.flatten(), z.flatten())) 55 | pi = np.pi 56 | 57 | for k in np.arange(ellipse.shape[0]): 58 | A = ellipse[k, 0] # Amplitude change for this ellipsoid 59 | asq = ellipse[k, 1] ** 2 # a^2 60 | bsq = ellipse[k, 2] ** 2 # b^2 61 | csq = ellipse[k, 3] ** 2 # c^2 62 | x0 = ellipse[k, 4] # x offset 63 | y0 = ellipse[k, 5] # y offset 64 | z0 = ellipse[k, 6] # z offset 65 | phi = ellipse[k, 7] * pi / 180 # first Euler angle in radians 66 | theta = ellipse[k, 8] * pi / 180 # second Euler angle in radians 67 | psi = ellipse[k, 9] * pi / 180 # third Euler angle in radians 68 | 69 | cphi = np.cos(phi) 70 | sphi = np.sin(phi) 71 | ctheta = np.cos(theta) 72 | stheta = np.sin(theta) 73 | cpsi = np.cos(psi) 74 | spsi = np.sin(psi) 75 | 76 | # Euler rotation matrix 77 | alpha = np.array([[cpsi * cphi - ctheta * sphi * spsi, 78 | cpsi * sphi + ctheta * cphi * spsi, 79 | spsi * stheta], 80 | [-spsi * cphi - ctheta * sphi * cpsi, 81 | -spsi * sphi + ctheta * cphi * cpsi, 82 | cpsi * stheta], 83 | [stheta * sphi, -stheta * cphi, ctheta]]) 84 | 85 | # rotated ellipsoid coordinates 86 | coordp = np.dot(alpha, coord) 87 | idx = np.nonzero((coordp[0, :] - x0) ** 2.0 / asq + 88 | (coordp[1, :] - y0) ** 2.0 / bsq + 89 | (coordp[2, :] - z0) ** 2.0 / csq <= 1)[0] 90 | p[idx] = p[idx] + A 91 | 92 | return p.reshape((n, n, n)) 93 | 94 | 95 | def shepp_logan(): 96 | arr = modified_shepp_logan() 97 | arr[:, 0] = np.array([1, -0.98, -0.02, -0.02, 0.01, 0.01, 0.01, 98 | 0.01, 0.01, 0.01]) 99 | 100 | return arr 101 | 102 | 103 | def modified_shepp_logan(): 104 | ''' 105 | This head phantom is the same as the Shepp-Logan except 106 | the intensities are changed to yield higher contrast in 107 | the image. Taken from Toft, 199-200. 108 | A a b c x0 y0 z0 phi theta psi 109 | ----------------------------------------------------------------- 110 | 1 .6900 .920 .810 0 0 0 0 0 0 111 | -.8 .6624 .874 .780 0 -.0184 0 0 0 0 112 | -.2 .1100 .310 .220 .22 0 0 -18 0 10 113 | -.2 .1600 .410 .280 -.22 0 0 18 0 10 114 | .1 .2100 .250 .410 0 .35 -.15 0 0 0 115 | .1 .0460 .046 .050 0 .1 .25 0 0 0 116 | .1 .0460 .046 .050 0 -.1 .25 0 0 0 117 | .1 .0460 .023 .050 -.08 -.605 0 0 0 0 118 | .1 .0230 .023 .020 0 -.606 0 0 0 0 119 | .1 .0230 .046 .020 .06 -.605 0 0 0 0 120 | ''' 121 | mat = np.array([ 122 | [1.0, 0.69, 0.92, 0.81, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 123 | [-0.8, 0.6624, 0.874, 0.78, 0.0, -0.0184, 0.0, 0.0, 0.0, 0.0], 124 | [-0.2, 0.11, 0.31, 0.22, 0.22, 0.0, 0.0, -18.0, 0.0, 10.0], 125 | [-0.2, 0.16, 0.41, 0.28, -0.22, 0.0, 0.0, 18.0, 0.0, 10.0], 126 | [0.1, 0.21, 0.25, 0.41, 0.0, 0.35, -0.15, 0.0, 0.0, 0.0], 127 | [0.1, 0.046, 0.046, 0.05, 0.0, 0.1, 0.25, 0.0, 0.0, 0.0], 128 | [0.1, 0.046, 0.046, 0.05, 0.0, -0.1, 0.25, 0.0, 0.0, 0.0], 129 | [0.1, 0.046, 0.023, 0.05, -0.08, -0.605, 0.0, 0.0, 0.0, 0.0], 130 | [0.1, 0.023, 0.023, 0.02, 0.0, -0.606, 0.0, 0.0, 0.0, 0.0], 131 | [0.1, 0.023, 0.046, 0.02, 0.06, -0.605, 0.0, 0.0, 0.0, 0.0]]) 132 | 133 | return mat 134 | 135 | 136 | def yu_ye_wang(): 137 | ''' 138 | Yu H, Ye Y, Wang G, Katsevich-Type Algorithms for 139 | Variable Radius Spiral Cone-Beam CT 140 | A a b c x0 y0 z0 phi theta psi 141 | ----------------------------------------------------------------- 142 | 1 .6900 .920 .900 0 0 0 0 0 0 143 | -.8 .6624 .874 .880 0 0 0 0 0 0 144 | -.2 .4100 .160 .210 -.22 0 -.25 108 0 0 145 | -.2 .3100 .110 .220 .22 0 -.25 72 0 0 146 | .2 .2100 .250 .500 0 .35 -.25 0 0 0 147 | .2 .0460 .046 .046 0 .1 -.25 0 0 0 148 | .1 .0460 .023 .020 -.08 -.65 -.25 0 0 0 149 | .1 .0460 .023 .020 .06 -.65 -.25 90 0 0 150 | .2 .0560 .040 .100 .06 -.105 .625 90 0 0 151 | -.2 .0560 .056 .100 0 .100 .625 0 0 0 152 | ''' 153 | 154 | mat = np.array([ 155 | [1.0, 0.69, 0.92, 0.9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 156 | [-0.8, 0.6624, 0.874, 0.88, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 157 | [-0.2, 0.41, 0.16, 0.21, -0.22, 0.0, -0.25, 108.0, 0.0, 0.0], 158 | [-0.2, 0.31, 0.11, 0.22, 0.22, 0.0, -0.25, 72.0, 0.0, 0.0], 159 | [0.2, 0.21, 0.25, 0.5, 0.0, 0.35, -0.25, 0.0, 0.0, 0.0], 160 | [0.2, 0.046, 0.046, 0.046, 0.0, 0.1, -0.25, 0.0, 0.0, 0.0], 161 | [0.1, 0.046, 0.023, 0.02, -0.08, -0.65, -0.25, 0.0, 0.0, 0.0], 162 | [0.1, 0.046, 0.023, 0.02, 0.06, -0.65, -0.25, 90.0, 0.0, 0.0], 163 | [0.2, 0.056, 0.04, 0.1, 0.06, -0.105, 0.625, 90.0, 0.0, 0.0], 164 | [-0.2, 0.056, 0.056, 0.1, 0.0, 0.1, 0.625, 0.0, 0.0, 0.0]]) 165 | 166 | return mat 167 | 168 | -------------------------------------------------------------------------------- /pydbt/functions/projection_operators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Wed Feb 16 10:29:14 2022 5 | 6 | @author: rodrigo 7 | """ 8 | 9 | import numpy as np 10 | import numpy.ctypeslib as ctl 11 | import ctypes 12 | import warnings 13 | 14 | from .utilities import findAndLoadLibray 15 | from .utilities import geoAsNp 16 | 17 | ############################################################################## 18 | # BackProjection # 19 | ############################################################################## 20 | 21 | def backprojectionDD(proj, geo, proj_num, libFiles): 22 | 23 | operator_type = 'backprojectionDD' 24 | 25 | vol = _backprojection(proj, geo, proj_num, libFiles, operator_type) 26 | 27 | return vol 28 | 29 | def backprojectionDDb(proj, geo, proj_num, libFiles): 30 | 31 | operator_type = 'backprojectionDDb' 32 | 33 | vol = _backprojection(proj, geo, proj_num, libFiles, operator_type) 34 | 35 | return vol 36 | 37 | def backprojectionDDb_cuda(proj, geo, proj_num, libFiles): 38 | 39 | operator_type = 'backprojectionDDb_cuda' 40 | 41 | vol = _backprojection(proj, geo, proj_num, libFiles, operator_type) 42 | 43 | return vol 44 | 45 | def _backprojection(proj, geo, proj_num, libFiles, operator_type): 46 | 47 | # Check if the input is in proper size 48 | if not (proj.shape[0] == geo.nv and proj.shape[1] == geo.nu and proj.shape[2] == geo.nProj): 49 | raise ValueError('First argument needs to have the same number of rows, cols and slices as in the configuration file.') 50 | 51 | check_parameters(operator_type, proj_num, geo) 52 | 53 | # Find and library 54 | lib = findAndLoadLibray(libFiles, operator_type) 55 | 56 | 57 | backprojection = getattr(lib, operator_type + '_lib') 58 | 59 | backprojection.argtypes = [ctl.ndpointer(np.float64, flags='aligned, c_contiguous'), 60 | ctl.ndpointer(np.float64, flags='aligned, c_contiguous'), 61 | ctl.ndpointer(np.float32, flags='aligned, c_contiguous'), 62 | ctypes.c_long] 63 | 64 | 65 | # Transform geo class in numpy array 66 | geoNp = geoAsNp(geo) 67 | 68 | 69 | vol_transp = np.empty([geo.nz, geo.nx, geo.ny], dtype=np.float64) 70 | 71 | proj_transp = np.transpose(proj, (2, 1, 0)).copy() 72 | 73 | backprojection(proj_transp, vol_transp, geoNp, proj_num) 74 | 75 | vol = np.transpose(vol_transp, (2, 1, 0)).copy() 76 | 77 | 78 | return vol 79 | 80 | 81 | ############################################################################## 82 | # Projection # 83 | ############################################################################## 84 | 85 | def projectionDD(vol, geo, proj_num, libFiles): 86 | 87 | operator_type = 'projectionDD' 88 | 89 | proj = _projection(vol, geo, proj_num, libFiles, operator_type) 90 | 91 | return proj 92 | 93 | def projectionDDb(vol, geo, proj_num, libFiles): 94 | 95 | operator_type = 'projectionDDb' 96 | 97 | proj = _projection(vol, geo, proj_num, libFiles, operator_type) 98 | 99 | return proj 100 | 101 | def projectionDDb_cuda(vol, geo, proj_num, libFiles): 102 | 103 | operator_type = 'projectionDDb_cuda' 104 | 105 | proj = _projection(vol, geo, proj_num, libFiles, operator_type) 106 | 107 | return proj 108 | 109 | def _projection(vol, geo, proj_num, libFiles, operator_type): 110 | 111 | # Check if the input is in proper size 112 | if not (vol.shape[0] == geo.ny and vol.shape[1] == geo.nx and vol.shape[2] == geo.nz): 113 | raise ValueError('First argument needs to have the same number of rows, cols and slices as in the configuration file.') 114 | 115 | check_parameters(operator_type, proj_num, geo) 116 | 117 | # Find and library 118 | lib = findAndLoadLibray(libFiles, operator_type) 119 | 120 | projection = getattr(lib, operator_type + '_lib') 121 | 122 | projection.argtypes = [ctl.ndpointer(np.float64, flags='aligned, c_contiguous'), 123 | ctl.ndpointer(np.float64, flags='aligned, c_contiguous'), 124 | ctl.ndpointer(np.float32, flags='aligned, c_contiguous'), 125 | ctypes.c_long] 126 | 127 | 128 | # Transform geo class in numpy array 129 | geoNp = geoAsNp(geo) 130 | 131 | 132 | proj_transp = np.empty([geo.nProj, geo.nu, geo.nv], dtype=np.float64) 133 | 134 | vol_transp = np.transpose(vol, (2, 1, 0)).copy() 135 | 136 | projection(vol_transp, proj_transp, geoNp, proj_num) 137 | 138 | proj = np.transpose(proj_transp, (2, 1, 0)).copy() 139 | 140 | return proj 141 | 142 | ############################################################################## 143 | # Auxiliar functions # 144 | ############################################################################## 145 | 146 | 147 | def check_parameters(operator_type, proj_num, geo): 148 | 149 | if proj_num < -1 and proj_num >= geo.nProj: 150 | raise ValueError('Projection number needs to be between 0-(geo.nProj-1). If you want to operate on all projections, set it to -1') 151 | 152 | if geo.detAngle != 0 and 'DDb' in operator_type: 153 | warnings.warn('Your detector rotates, its not recomended to used branchless projectors. Use DD only') 154 | 155 | if (geo.x_offset != 0 or geo.y_offset != 0) and ('DDb' in operator_type): 156 | raise ValueError('Branchless operators dont work with volume offsets') 157 | 158 | return 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /pydbt/functions/utilities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Wed Jan 22 15:45:08 2020 5 | 6 | @author: rodrigo 7 | """ 8 | 9 | import numpy.ctypeslib as ctl 10 | import numpy as np 11 | import os 12 | 13 | 14 | def findAndLoadLibray(libFiles, libname): 15 | 16 | for libFile in libFiles: 17 | if libname == libFile[1].split('.')[0]: 18 | return ctl.load_library(libFile[1], libFile[0]) 19 | else: 20 | ValueError('Cannot find ' + libname + ' libray. Make sure it is compiled.' ) 21 | 22 | def geoAsNp(geo): 23 | 24 | geoList = [] 25 | 26 | geoDict = geo.__dict__ 27 | 28 | for key, value in geoDict.items(): 29 | geoList.append(value) 30 | 31 | geoNp = np.asarray(geoList, dtype=np.float32) 32 | 33 | return geoNp 34 | 35 | def makedir(path2create): 36 | """Create directory if it does not exists.""" 37 | 38 | error = 1 39 | 40 | if not os.path.exists(path2create): 41 | os.makedirs(path2create) 42 | error = 0 43 | 44 | return error 45 | -------------------------------------------------------------------------------- /pydbt/functions/utilities2setuptools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Tue Feb 15 16:41:20 2022 5 | 6 | @author: Rodrigo 7 | 8 | Reference: https://stackoverflow.com/a/13300714/8682939 9 | 10 | """ 11 | import os 12 | 13 | from os.path import join as pjoin 14 | from distutils.command.build_ext import build_ext 15 | 16 | 17 | def find_in_path(name, path): 18 | "Find a file in a search path" 19 | #adapted fom http://code.activestate.com/recipes/52224-find-a-file-given-a-search-path/ 20 | for dir in path.split(os.pathsep): 21 | binpath = pjoin(dir, name) 22 | if os.path.exists(binpath): 23 | return os.path.abspath(binpath) 24 | return None 25 | 26 | def locate_cuda(): 27 | """Locate the CUDA environment on the system 28 | 29 | Returns a dict with keys 'home', 'nvcc', 'include', and 'lib64' 30 | and values giving the absolute path to each directory. 31 | 32 | Starts by looking for the CUDAHOME env variable. If not found, everything 33 | is based on finding 'nvcc' in the PATH. 34 | """ 35 | 36 | # first check if the CUDAHOME env variable is in use 37 | if 'CUDAHOME' in os.environ: 38 | home = os.environ['CUDAHOME'] 39 | nvcc = pjoin(home, 'bin', 'nvcc') 40 | else: 41 | # otherwise, search the PATH for NVCC 42 | nvcc = find_in_path('nvcc', os.environ['PATH']) 43 | if nvcc is None: 44 | raise EnvironmentError('The nvcc binary could not be ' 45 | 'located in your $PATH. Either add it to your path, or set $CUDAHOME') 46 | home = os.path.dirname(os.path.dirname(nvcc)) 47 | 48 | cudaconfig = {'home':home, 'nvcc':nvcc, 49 | 'include': pjoin(home, 'include'), 50 | 'lib64': pjoin(home, 'lib64')} 51 | 52 | for k, v in cudaconfig.items(): 53 | if not os.path.exists(v): 54 | raise EnvironmentError('The CUDA %s path could not be located in %s' % (k, v)) 55 | 56 | return cudaconfig 57 | 58 | def get_custom_build_ext(cuda_env): 59 | 60 | def customize_compiler_for_nvcc(self): 61 | """inject deep into distutils to customize how the dispatch 62 | to gcc/nvcc works. 63 | 64 | If you subclass UnixCCompiler, it's not trivial to get your subclass 65 | injected in, and still have the right customizations (i.e. 66 | distutils.sysconfig.customize_compiler) run on it. So instead of going 67 | the OO route, I have this. Note, it's kindof like a wierd functional 68 | subclassing going on.""" 69 | 70 | # tell the compiler it can processes .cu 71 | self.src_extensions.append('.cu') 72 | 73 | # save references to the default compiler_so and _comple methods 74 | default_compiler_so = self.compiler_so 75 | super = self._compile 76 | 77 | # now redefine the _compile method. This gets executed for each 78 | # object but distutils doesn't have the ability to change compilers 79 | # based on source extension: we add it. 80 | def _compile(obj, src, ext, cc_args, extra_postargs, pp_opts): 81 | if os.path.splitext(src)[1] == '.cu': 82 | # use the cuda for .cu files 83 | self.set_executable('compiler_so', cuda_env['nvcc']) 84 | # use only a subset of the extra_postargs, which are 1-1 translated 85 | # from the extra_compile_args in the Extension class 86 | postargs = extra_postargs['nvcc'] 87 | else: 88 | postargs = extra_postargs['gcc'] 89 | 90 | super(obj, src, ext, cc_args, postargs, pp_opts) 91 | # reset the default compiler_so, which we might have changed for cuda 92 | self.compiler_so = default_compiler_so 93 | 94 | # inject our redefined _compile method into the class 95 | self._compile = _compile 96 | 97 | # run the customize_compiler 98 | class custom_build_ext(build_ext): 99 | def build_extensions(self): 100 | customize_compiler_for_nvcc(self.compiler) 101 | build_ext.build_extensions(self) 102 | 103 | return custom_build_ext -------------------------------------------------------------------------------- /pydbt/parameters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAVI-USP/pyDBT/47801b5f655c382814af70a75737e4ef5e6d44ba/pydbt/parameters/__init__.py -------------------------------------------------------------------------------- /pydbt/parameters/parameterSettings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Jan 13 15:19:22 2020 5 | 6 | @author: Rodrigo 7 | """ 8 | 9 | class geometry_settings(): 10 | def __init__(self, 11 | voxels=None, 12 | detector_el=None, 13 | voxels_size=None, 14 | detector_size=None, 15 | source_dist=None, 16 | gap = None, 17 | n_proj = None, 18 | tube_angle = None, 19 | detector_angle = None, 20 | offset = None 21 | ): 22 | 23 | if voxels is not None: 24 | self.nx = voxels[0] 25 | self.ny = voxels[1] 26 | self.nz = voxels[2] 27 | 28 | if detector_el is not None: 29 | self.nu = detector_el[0] 30 | self.nv = detector_el[1] 31 | 32 | if voxels_size is not None: 33 | self.dx = round(voxels_size[0], 3) 34 | self.dy = round(voxels_size[1], 3) 35 | self.dz = round(voxels_size[2], 3) 36 | 37 | if detector_size is not None: 38 | self.du = detector_size[0] 39 | self.dv = detector_size[1] 40 | 41 | if source_dist is not None: 42 | self.DSD = source_dist 43 | self.DSO = source_dist - (voxels[2]*voxels_size[2]) 44 | self.DDR = 0 45 | self.DSR = self.DSD - self.DDR 46 | 47 | if gap is not None: 48 | self.DAG = abs(gap) 49 | 50 | if n_proj is not None: 51 | self.nProj = n_proj 52 | 53 | if tube_angle is not None: 54 | self.tubeAngle = tube_angle 55 | 56 | if detector_angle is not None: 57 | self.detAngle = detector_angle 58 | 59 | if offset is not None: 60 | self.x_offset = offset[0] 61 | self.y_offset = offset[1] 62 | 63 | def Hologic(self): 64 | 65 | # Breast voxels density 66 | self.nx = 1996 # number of voxels (columns) 67 | self.ny = 2457 # number of voxels (rows) 68 | self.nz = 78 # number of voxels (slices) 69 | 70 | # Detector panel pixel density 71 | self.nu = 1664 # number of pixels (columns) 72 | self.nv = 2048 # number of pixels (rows) 73 | 74 | # Single voxel real size (mm) 75 | self.dx = 0.112 76 | self.dy = 0.112 77 | self.dz = 1 78 | 79 | # Single detector real size (mm) 80 | self.du = 0.14 81 | self.dv = 0.14 82 | 83 | # X-ray source and detector distances 84 | self.DSD = 700 # Distance from source to detector (mm) 85 | self.DSO = 597 # Distance from source to the top of object (mm) 86 | self.DDR = 0 # Distance from detector to pivot (mm) 87 | self.DSR = self.DSD - self.DDR # Distance from source to pivot (mm) 88 | self.DAG = 25 # Distance of Air Gap (mm) 89 | 90 | 91 | # Number of Projections 92 | self.nProj = 15 93 | 94 | # Angle settings (Degrees) 95 | self.tubeAngle = 15 # Tube Angle 96 | 97 | self.detAngle = 4.2 # Detector Angle 98 | 99 | self.x_offset = 0 # Volume X offset (mm) 100 | self.y_offset = 0 # Volume Y offset (mm) 101 | 102 | def GE(self): 103 | 104 | # Breast voxels density 105 | self.nx = 1058 # number of voxels (columns) 106 | self.ny = 1978 # number of voxels (rows) 107 | self.nz = 107 # number of voxels (slices) 108 | 109 | # Detector panel pixel density 110 | self.nu = 2394 # number of pixels (columns) 111 | self.nv = 3062 # number of pixels (rows) 112 | 113 | # Single voxel real size (mm) 114 | self.dx = 0.1 115 | self.dy = 0.1 116 | self.dz = 0.5 117 | 118 | # Single detector real size (mm) 119 | self.du = 0.1 120 | self.dv = 0.1 121 | 122 | # X-ray source and detector distances 123 | self.DSD = 660 # Distance from source to detector (mm) 124 | self.DSO = 580.5 # Distance from source to the top of object (mm) 125 | self.DDR = 40 # Distance from detector to pivot (mm) 126 | self.DSR = self.DSD - self.DDR # Distance from source to pivot (mm) 127 | self.DAG = 22 # Distance of Air Gap (mm) 128 | 129 | 130 | # Number of Projections 131 | self.nProj = 9 132 | 133 | # Angle settings (Degrees) 134 | self.tubeAngle = 25 # Tube Angle 135 | 136 | self.detAngle = 0 # Detector Angle 137 | 138 | self.x_offset = 0 # Volume X offset (mm) 139 | self.y_offset = 0 # Volume Y offset (mm) 140 | 141 | def Siemens(self): 142 | 143 | # Breast voxels density 144 | self.nx = 1372 # number of voxels (columns) 145 | self.ny = 3264 # number of voxels (rows) 146 | self.nz = 49 # number of voxels (slices) 147 | 148 | # Detector panel pixel density 149 | self.nu = 1376 # number of pixels (columns) 150 | self.nv = 3264 # number of pixels (rows) 151 | 152 | # Single voxel real size (mm) 153 | self.dx = 0.085 154 | self.dy = 0.085 155 | self.dz = 1 156 | 157 | # Single detector real size (mm) 158 | self.du = 0.085 159 | self.dv = 0.085 160 | 161 | # X-ray source and detector distances 162 | self.DSD = 650 # Distance from source to detector (mm) 163 | self.DSO = 633 # Distance from source to the top of object (mm) 164 | self.DDR = 47 # Distance from detector to pivot (mm) 165 | self.DSR = self.DSD - self.DDR # Distance from source to pivot (mm) 166 | self.DAG = 17 # Distance of Air Gap (mm) 167 | 168 | # Number of Projections 169 | self.nProj = 25 170 | 171 | # Angle settings (Degrees) 172 | self.tubeAngle = 50 # Tube Angle 173 | 174 | self.detAngle = 0 # Detector Angle 175 | 176 | self.x_offset = 0 # Volume X offset (mm) 177 | self.y_offset = 0 # Volume Y offset (mm) 178 | 179 | def SheppLogan(self): 180 | 181 | # Breast voxels density 182 | self.nx = 150 # number of voxels (columns) 183 | self.ny = 150 # number of voxels (rows) 184 | self.nz = 128 # number of voxels (slices) 185 | 186 | # Detector panel pixel density 187 | self.nu = 256 # number of pixels (columns) 188 | self.nv = 448 # number of pixels (rows) 189 | 190 | # Single voxel real size (mm) 191 | self.dx = 0.112 192 | self.dy = 0.112 193 | self.dz = 1 194 | 195 | # Single detector real size (mm) 196 | self.du = 0.14 197 | self.dv = 0.14 198 | 199 | # X-ray source and detector distances 200 | self.DSD = 700 # Distance from source to detector (mm) 201 | self.DSO = 597 # Distance from source to the top of object (mm) 202 | self.DDR = 0 # Distance from detector to pivot (mm) 203 | self.DSR = self.DSD - self.DDR # Distance from source to pivot (mm) 204 | self.DAG = 25 # Distance of Air Gap (mm) 205 | 206 | 207 | # Number of Projections 208 | self.nProj = 15 209 | 210 | # Angle settings (Degrees) 211 | self.tubeAngle = 15 # Tube Angle 212 | 213 | self.detAngle = 4.2 # Detector Angle 214 | 215 | self.x_offset = 0 # Volume X offset (mm) 216 | self.y_offset = 0 # Volume Y offset (mm) 217 | 218 | -------------------------------------------------------------------------------- /pydbt/sources/backprojectionDD/backprojectionDD.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: January, 2021 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % backprojectionDD_lib(pProj, pVolume, pGeo, idXProj) 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This function reconstruct the 3D volume from projections, based on 12 | % the Distance-Driven principle. It works by calculating the overlap 13 | % in X and Y axis of the volume and the detector boundaries. 14 | % The geometry is for DBT with half cone-beam. All parameters are set 15 | % in "ParameterSettings" code. 16 | % 17 | % INPUT: 18 | % 19 | % - pProj = Pointer to 2D projections for each angle 20 | % - pGeo = Pointer to parameters of all pGeometry 21 | % - idXProj = projection number to be projected 22 | % 23 | % OUTPUT: 24 | % 25 | % - pVolume = Pointer to reconstructed volume. 26 | % 27 | % Reference: Three-Dimensional Digital Tomosynthesis - Yulia 28 | % Levakhina (2014), Cap 3.6 and 3.7. 29 | % 30 | % Original Paper: De Man, Bruno, and Samit Basu. "Distance-driven 31 | % projection and backprojection in three dimensions." Physics in 32 | % Medicine & Biology (2004). 33 | % 34 | % --------------------------------------------------------------------- 35 | % Copyright (C) <2018> 36 | % 37 | % This program is free software: you can redistribute it and/or modify 38 | % it under the terms of the GNU General Public License as published by 39 | % the Free Software Foundation, either version 3 of the License, or 40 | % (at your option) any later version. 41 | % 42 | % This program is distributed in the hope that it will be useful, 43 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 44 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 45 | % GNU General Public License for more details. 46 | % 47 | % You should have received a copy of the GNU General Public License 48 | % along with this program. If not, see . 49 | %} 50 | % ========================================================================= 51 | %% 3-D Distance Driven Back-projection Code 52 | */ 53 | 54 | #include "backprojectionDD.h" 55 | 56 | extern "C" void backprojectionDD_lib(double* const pProj, 57 | double* const pVolume, 58 | float* const pGeo, 59 | const signed int idXProj){ 60 | 61 | 62 | const int unsigned nPixX = (const int)pGeo[0]; 63 | const int unsigned nPixY = (const int)pGeo[1]; 64 | const int unsigned nSlices = (const int)pGeo[2]; 65 | const int unsigned nDetX = (const int)pGeo[3]; 66 | const int unsigned nDetY = (const int)pGeo[4]; 67 | 68 | const double dx = (const double)pGeo[5]; 69 | const double dy = (const double)pGeo[6]; 70 | const double dz = (const double)pGeo[7]; 71 | const double du = (const double)pGeo[8]; 72 | const double dv = (const double)pGeo[9]; 73 | 74 | const double DSD = (const double)pGeo[10]; 75 | const double DDR = (const double)pGeo[12]; 76 | const double DAG = (const double)pGeo[14]; 77 | 78 | const int unsigned nProj = (const int)pGeo[15]; 79 | 80 | const double tubeAngle = (const double)pGeo[16]; 81 | const double detAngle = (const double)pGeo[17]; 82 | 83 | double* const pTubeAngle = (double*)malloc(nProj * sizeof(double)); 84 | double* const pDetAngle = (double*)malloc(nProj * sizeof(double)); 85 | 86 | const double x_offset = (const double)pGeo[18]; 87 | const double y_offset = (const double)pGeo[19]; 88 | 89 | linspace(-tubeAngle/2, tubeAngle/2, nProj, pTubeAngle); 90 | linspace(-detAngle/2, detAngle/2, nProj, pDetAngle); 91 | 92 | //printf("Nx:%d Ny:%d Nz:%d \nNu:%d Nv:%d \nDx:%.2f Dy:%.2f Dz:%.2f \nDu:%.2f Dv:%.2f \nDSD:%.2f DDR:%.2f \n", nPixX, nPixY, nSlices, nDetX, nDetY, dx, dy, dz, du, dv, DSD, DDR); 93 | 94 | const int nDetXMap = nDetX + 1; 95 | const int nDetYMap = nDetY + 1; 96 | const int nPixXMap = nPixX + 1; 97 | const int nPixYMap = nPixY + 1; 98 | 99 | // Memory allocation for projections coordinates 100 | double* const pDetX = (double*)malloc(nDetXMap * sizeof(double)); 101 | double* const pDetY = (double*)malloc(nDetYMap * sizeof(double)); 102 | double* const pDetZ = (double*)malloc(nDetYMap * sizeof(double)); 103 | double* const pObjX = (double*)malloc(nPixXMap * sizeof(double)); 104 | double* const pObjY = (double*)malloc(nPixYMap * sizeof(double)); 105 | double* const pObjZ = (double*)malloc(nSlices * sizeof(double)); 106 | 107 | // Memory allocation for mapped coordinates 108 | double* const pDetmY = (double*)malloc(nDetYMap * sizeof(double)); 109 | double* const pDetmX = (double*)malloc(nDetYMap * nDetXMap * sizeof(double)); 110 | double* const pPixmX = (double*)malloc(nPixXMap * sizeof(double)); 111 | double* const pPixmY = (double*)malloc(nPixYMap * sizeof(double)); 112 | 113 | 114 | // Memory allocation for rotated detecto coords 115 | double* const pRdetY = (double*)malloc(nDetYMap * sizeof(double)); 116 | double* const pRdetZ = (double*)malloc(nDetYMap * sizeof(double)); 117 | 118 | // Map detector and object boudaries 119 | mapBoudaries(pDetX, nDetXMap, (double)nDetX, -du, 0.0); 120 | 121 | mapBoudaries(pDetY, nDetYMap, nDetY / 2.0, dv, 0.0); 122 | 123 | mapBoudaries(pDetZ, nDetYMap, 0.0, 0.0, 0.0); 124 | 125 | mapBoudaries(pObjX, nPixXMap, (double)nPixX, -dx, x_offset); 126 | 127 | mapBoudaries(pObjY, nPixYMap, nPixY / 2.0, dy, y_offset); 128 | 129 | mapBoudaries(pObjZ, nSlices, 0.0, dz, DAG + (dz / 2.0)); 130 | 131 | 132 | // X - ray tube initial position 133 | double tubeX = 0; 134 | double tubeY = 0; 135 | double tubeZ = DSD; 136 | 137 | // Iso - center position 138 | double isoY = 0; 139 | double isoZ = DDR; 140 | 141 | 142 | // Allocate memory for temp projection variable 143 | double* const pVolumet = (double*)malloc(nPixY *nPixX * nSlices * sizeof(double)); 144 | 145 | 146 | // Initiate temp volume with zeros 147 | for (int z = 0; z < nSlices; z++) 148 | for (int x = 0; x < nPixX; x++) 149 | for (int y = 0; y < nPixY; y++) 150 | pVolumet[(z*nPixX*nPixY) + (x*nPixY) + y] = 0; 151 | 152 | 153 | // Allocate memory for flipped projection 154 | double* const pProjf = (double*)malloc(nDetY *nDetX * nProj * sizeof(double)); 155 | 156 | // Flip projection X (Img coord is reverse to Global) 157 | for (int p = 0; p < nProj; p++) 158 | for (int x = 0, x_inv = nDetX - 1; x < nDetX; x++, x_inv--) 159 | for (int y = 0; y < nDetY; y++) 160 | pProjf[(p*nDetX*nDetY) + (x*nDetY) + y] = pProj[(p*nDetX*nDetY) + (x_inv*nDetY) + y]; 161 | 162 | 163 | 164 | // Test if we will loop over all projs or not 165 | unsigned int projIni, projEnd, nProj2Run; 166 | if(idXProj == -1){ 167 | projIni = 0; 168 | projEnd = nProj; 169 | nProj2Run = nProj; 170 | } 171 | else{ 172 | nProj2Run = 1; 173 | projIni = (unsigned int) idXProj; 174 | projEnd = (unsigned int) idXProj + 1; 175 | } 176 | 177 | 178 | // For each projection 179 | for (unsigned int p = projIni; p < projEnd; p++) { 180 | 181 | // Get specif tube angle for the projection 182 | double theta = pTubeAngle[p] * M_PI / 180.0; 183 | 184 | // Get specif detector angle for the projection 185 | double phi = pDetAngle[p] * M_PI / 180.0; 186 | 187 | 188 | // Tubre rotation 189 | double rtubeY = ((tubeY - isoY)*(double)cos(theta) - (tubeZ - isoZ)*(double)sin(theta)) + isoY; 190 | double rtubeZ = ((tubeY - isoY)*(double)sin(theta) + (tubeZ - isoZ)*(double)cos(theta)) + isoZ; 191 | 192 | // Detector rotation 193 | for (int v = 0; v < nDetYMap; v++) { 194 | pRdetY[v] = ((pDetY[v] - isoY)*(double)cos(phi) - (pDetZ[v] - isoZ)*(double)sin(phi)) + isoY; 195 | pRdetZ[v] = ((pDetY[v] - isoY)*(double)sin(phi) + (pDetZ[v] - isoZ)*(double)cos(phi)) + isoZ; 196 | } 197 | 198 | 199 | // Map detector onto XY plane(Inside proj loop in case detector rotates) 200 | mapp2xy(pDetmX, pDetmY, tubeX, rtubeY, rtubeZ, pDetX, pRdetY, pRdetZ, nDetXMap, nDetYMap); 201 | 202 | 203 | // Pixel start index and increment 204 | int detIstart = 0; 205 | int detIinc = 1; 206 | 207 | // Mapped detector length 208 | double deltaDetmY = pDetmY[detIstart + detIinc] - pDetmY[detIstart]; 209 | 210 | 211 | // For each slice 212 | for (int z = 0; z < nSlices; z++) { 213 | 214 | // Tmp Z coords value for Y direction 215 | double* const pObjZt = (double*)malloc(nPixYMap * sizeof(double)); 216 | 217 | // Tmp Pixel X mapped coords 218 | double* const pPixmXt = (double*)malloc(nPixYMap * nPixXMap * sizeof(double)); 219 | 220 | // Get specif Z coord value for each slice 221 | for (int k = 0; k < nPixYMap; k++) pObjZt[k] = pObjZ[z]; 222 | 223 | // Map slice onto XY plane 224 | mapp2xy(pPixmXt, pPixmY, tubeX, rtubeY, rtubeZ, pObjX, pObjY, pObjZt, nPixXMap, nPixYMap); 225 | 226 | // Flip X(Img coord is reverse to Global) 227 | for (int x = 0, x_inv = nPixXMap - 1; x < nPixXMap; x++, x_inv--) 228 | pPixmX[x] = pPixmXt[x_inv*nPixYMap]; 229 | 230 | // Free temp variables 231 | free(pObjZt); 232 | free(pPixmXt); 233 | 234 | // Pixel start index and increment 235 | int pixIstart = 0; 236 | int pixIinc = 1; 237 | 238 | // Mapped pixel length 239 | double deltaPixmX = pPixmX[pixIstart + pixIinc] - pPixmX[pixIstart]; 240 | double deltaPixmY = pPixmY[pixIstart + pixIinc] - pPixmY[pixIstart]; 241 | 242 | // Start pixel and detector indices 243 | int detIndY = detIstart; 244 | int pixIndY = pixIstart; 245 | 246 | // Case 1 247 | // Find first detector overlap maped with pixel maped on Y 248 | if (pDetmY[detIndY] - pPixmY[pixIstart] < -deltaDetmY) 249 | while (pDetmY[detIndY] - pPixmY[pixIstart] < -deltaDetmY) 250 | detIndY = detIndY + detIinc; 251 | 252 | else 253 | // Case 2 254 | // Find first pixel overlap maped with detector maped on Y 255 | if (pDetmY[detIstart] - pPixmY[pixIndY] > deltaPixmY) 256 | while (pDetmY[detIstart] - pPixmY[pixIndY] > deltaPixmY) 257 | pixIndY = pixIndY + pixIinc; 258 | 259 | double moving_left_boundaryY; 260 | 261 | // Get the left coordinate of the first overlap on Y axis 262 | if (pDetmY[detIndY] < pPixmY[pixIndY]) 263 | moving_left_boundaryY = pPixmY[pixIndY]; 264 | else 265 | moving_left_boundaryY = pDetmY[detIndY]; 266 | 267 | 268 | // Allocate memory for specif row of X map detector coords 269 | double* const pDetmXrow = (double*)malloc(nDetXMap * sizeof(double)); 270 | 271 | double overLapY; 272 | 273 | // Loop over Y intersections 274 | while ((detIndY < nDetY) && (pixIndY < nPixY)) { 275 | 276 | double alpha = (double)atan((pDetmY[detIndY] + (deltaDetmY / 2) - rtubeY) / rtubeZ); 277 | 278 | // Case A, when you jump to the next detector boundarie but stay 279 | // in the same pixel 280 | if (pDetmY[detIndY + 1] <= pPixmY[pixIndY + 1]) 281 | overLapY = (pDetmY[detIndY + 1] - moving_left_boundaryY) / deltaDetmY; // Normalized overlap Calculation 282 | 283 | else 284 | // Case B, when you jump to the next pixel boundarie but stay 285 | // in the same detector 286 | overLapY = (pPixmY[pixIndY + 1] - moving_left_boundaryY) / deltaDetmY; // Normalized overlap Calculation 287 | 288 | // ***** X overlap ***** 289 | int detIndX = detIstart; 290 | int pixIndX = pixIstart; 291 | 292 | 293 | // Get row / coll of X flipped, which correspond to that Y overlap det 294 | for (int x = 0, x_inv = nDetXMap - 1; x < nDetXMap; x++, x_inv--) 295 | pDetmXrow[x] = pDetmX[(x_inv*nDetYMap) + detIndY]; 296 | 297 | // Mapped detecor length on X 298 | double deltaDetmX = pDetmXrow[detIstart + detIinc] - pDetmXrow[detIstart]; 299 | 300 | // Case 1 301 | // Find first detector overlap maped with pixel maped on X 302 | if (pDetmXrow[detIndX] - pPixmX[pixIstart] < -deltaDetmX) 303 | while (pDetmXrow[detIndX] - pPixmX[pixIstart] < -deltaDetmX) 304 | detIndX = detIndX + detIinc; 305 | 306 | else 307 | // Case 2 308 | // Find first pixel overlap maped with detector maped on X 309 | if (pDetmXrow[detIstart] - pPixmX[pixIndX] > deltaPixmX) 310 | while (pDetmXrow[detIstart] - pPixmX[pixIndY] > deltaPixmX) 311 | pixIndX = pixIndX + pixIinc; 312 | 313 | double moving_left_boundaryX; 314 | 315 | // Get the left coordinate of the first overlap on X axis 316 | if (pDetmXrow[detIndX] < pPixmX[pixIndX]) 317 | moving_left_boundaryX = pPixmX[pixIndX]; 318 | else 319 | moving_left_boundaryX = pDetmXrow[detIndX]; 320 | 321 | 322 | // Loop over X intersections 323 | while ((detIndX < nDetX) && (pixIndX < nPixX)) { 324 | 325 | double gamma = (double)atan((pDetmXrow[detIndX] + (deltaDetmX / 2) - tubeX) / rtubeZ); 326 | 327 | // Case A, when you jump to the next detector boundarie but stay 328 | // in the same pixel 329 | if (pDetmXrow[detIndX + 1] <= pPixmX[pixIndX + 1]) { 330 | 331 | double overLapX = (pDetmXrow[detIndX + 1] - moving_left_boundaryX) / deltaDetmX; // Normalized overlap Calculation 332 | 333 | pVolumet[(z*nPixX*nPixY) + (pixIndX*nPixY) + pixIndY] += overLapX * overLapY * pProjf[(p*nDetX*nDetY) + (detIndX *nDetY) + detIndY] * dz / ((double)cos(alpha)*(double)cos(gamma)); 334 | 335 | detIndX = detIndX + detIinc; 336 | moving_left_boundaryX = pDetmXrow[detIndX]; 337 | } 338 | else { 339 | // Case B, when you jump to the next pixel boundarie but stay 340 | // in the same detector 341 | 342 | double overLapX = (pPixmX[pixIndX + 1] - moving_left_boundaryX) / deltaDetmX; // Normalized overlap Calculation 343 | 344 | pVolumet[(z*nPixX*nPixY) + (pixIndX*nPixY) + pixIndY] += overLapX * overLapY * pProjf[(p*nDetX*nDetY) + (detIndX *nDetY) + detIndY] * dz / ((double)cos(alpha)*(double)cos(gamma)); 345 | 346 | pixIndX = pixIndX + pixIinc; 347 | moving_left_boundaryX = pPixmX[pixIndX]; 348 | 349 | } 350 | 351 | } 352 | // ***** Back to Y overlap ***** 353 | 354 | // Case A, when you jump to the next detector boundarie but stay 355 | // in the same pixel 356 | if (pDetmY[detIndY + 1] <= pPixmY[pixIndY + 1]) { 357 | detIndY = detIndY + detIinc; 358 | moving_left_boundaryY = pDetmY[detIndY]; 359 | } 360 | else { 361 | // Case B, when you jump to the next pixel boundarie but stay 362 | // in the same detector 363 | pixIndY = pixIndY + pixIinc; 364 | moving_left_boundaryY = pPixmY[pixIndY]; 365 | } 366 | 367 | } // Y Overlap loop 368 | 369 | // Free memory 370 | free(pDetmXrow); 371 | 372 | } // Loop end slices 373 | 374 | } // Loop end Projections 375 | 376 | 377 | // Free memory 378 | free(pProjf); 379 | free(pDetX); 380 | free(pDetY); 381 | free(pDetZ); 382 | free(pObjX); 383 | free(pObjY); 384 | free(pObjZ); 385 | free(pDetmY); 386 | free(pDetmX); 387 | free(pPixmX); 388 | free(pPixmY); 389 | free(pRdetY); 390 | free(pRdetZ); 391 | free(pTubeAngle); 392 | free(pDetAngle); 393 | 394 | // Flip volume back X (Img coord is reverse to Global) 395 | for (int z = 0; z < nSlices; z++) 396 | for (int x = 0, x_inv = nPixX - 1; x < nPixX; x++, x_inv--) 397 | for (int y = 0; y < nPixY; y++) 398 | pVolume[(z*nPixX*nPixY) + (x*nPixY) + y] = pVolumet[(z*nPixX*nPixY) + (x_inv*nPixY) + y] / (double) nProj2Run; 399 | 400 | free(pVolumet); 401 | 402 | return; 403 | } 404 | 405 | 406 | // Make boundaries of detector and slices 407 | void mapBoudaries(double* pBound, 408 | const int nElem, 409 | const double valueLeftBound, 410 | const double sizeElem, 411 | const double offset){ 412 | 413 | for (int k = 0; k < nElem; k++) 414 | pBound[k] = (k - valueLeftBound) * sizeElem + offset; 415 | 416 | return; 417 | } 418 | 419 | // Map on XY plane 420 | void mapp2xy(double* const pXmapp, 421 | double* const pYmapp, 422 | double tubeX, 423 | double tubeY, 424 | double tubeZ, 425 | double * const pXcoord, 426 | double * const pYcoord, 427 | double * const pZcoord, 428 | const int nXelem, 429 | const int nYelem){ 430 | 431 | 432 | for (int x = 0; x < nXelem; x++) 433 | for (int y = 0; y < nYelem; y++) { 434 | 435 | int ind = (x*nYelem) + y; 436 | pXmapp[ind] = pXcoord[x] + pZcoord[y] * (pXcoord[x] - tubeX) / (tubeZ - pZcoord[y]); 437 | 438 | if (x == 0) 439 | pYmapp[y] = pYcoord[y] + pZcoord[y] * (pYcoord[y] - tubeY) / (tubeZ - pZcoord[y]); 440 | } 441 | 442 | 443 | return; 444 | } 445 | 446 | // Linear spaced vector 447 | // Ref: https://stackoverflow.com/a/27030598/8682939 448 | void linspace(double start, 449 | double end, 450 | int num, 451 | double* pLinspaced){ 452 | 453 | double delta; 454 | 455 | if(num == 0) 456 | delta = 0; 457 | else{ 458 | if(num == 1) 459 | delta = 1; 460 | else 461 | delta = (end - start) / (num - 1); 462 | } 463 | 464 | if((abs(start) < 0.00001) && (abs(end) < 0.00001)) 465 | delta = 0; 466 | 467 | 468 | for (int k = 0; k < num; k++){ 469 | pLinspaced[k] = start + k * delta; 470 | } 471 | 472 | return; 473 | } -------------------------------------------------------------------------------- /pydbt/sources/backprojectionDD/backprojectionDD.h: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: January, 2021 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This is the header function 12 | % --------------------------------------------------------------------- 13 | % Copyright (C) <2018> 14 | % 15 | % This program is free software: you can redistribute it and/or modify 16 | % it under the terms of the GNU General Public License as published by 17 | % the Free Software Foundation, either version 3 of the License, or 18 | % (at your option) any later version. 19 | % 20 | % This program is distributed in the hope that it will be useful, 21 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | % GNU General Public License for more details. 24 | % 25 | % You should have received a copy of the GNU General Public License 26 | % along with this program. If not, see . 27 | %} 28 | % ========================================================================= 29 | %% 3-D Distance Driven Back-projection Code 30 | */ 31 | 32 | //typedef double user_dataType; 33 | //typedef float user_dataType; 34 | //#define user_mexdataType mxDOUBLE_CLASS 35 | //#define user_mexdataType mxSINGLE_CLASS 36 | 37 | #include 38 | #define _USE_MATH_DEFINES 39 | #include 40 | #include 41 | 42 | void mapBoudaries(double* pBound, 43 | const int nElem, 44 | const double valueLeftBound, 45 | const double sizeElem, 46 | const double offset); 47 | 48 | void mapp2xy(double* const pXmapp, 49 | double* const pYmapp, 50 | double tubeX, 51 | double tubeY, 52 | double tubeZ, 53 | double * const pXcoord, 54 | double * const pYcoord, 55 | double * const pZcoord, 56 | const int nXelem, 57 | const int nYelem); 58 | 59 | void linspace(double start, 60 | double end, 61 | int num, 62 | double* pLinspaced); -------------------------------------------------------------------------------- /pydbt/sources/backprojectionDDb/backprojectionDDb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: January, 2020 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % backprojectionDDb_lib(pProj, pVolume, pGeo, idXProj) 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This function reconstruct the 3D volume from projections, based on 12 | % the Distance-Driven principle. It works by calculating the overlap 13 | % in X and Y axis of the volume and the detector boundaries. 14 | % The pGeometry is for DBT with half cone-beam. All parameters are set 15 | % in "ParameterSettings" code. 16 | % 17 | % INPUT: 18 | % 19 | % - pProj = Pointer to 2D projections for each angle 20 | % - pGeo = Pointer to parameters of all pGeometry 21 | % - idXProj = projection number to be projected 22 | % 23 | % OUTPUT: 24 | % 25 | % - pVolume = Pointer to reconstructed volume. 26 | % 27 | % Reference: 28 | % - Branchless Distance Driven Projection and Backprojection, 29 | % Samit Basu and Bruno De Man (2006) 30 | % - GPU Acceleration of Branchless Distance Driven Projection and 31 | % Backprojection, Liu et al (2016) 32 | % - GPU-Based Branchless Distance-Driven Projection and Backprojection, 33 | % Liu et al (2017) 34 | % - A GPU Implementation of Distance-Driven Computed Tomography, 35 | % Ryan D. Wagner (2017) 36 | % --------------------------------------------------------------------- 37 | % Copyright (C) <2020> 38 | % 39 | % This program is free software: you can redistribute it and/or modify 40 | % it under the terms of the GNU General Public License as published by 41 | % the Free Software Foundation, either version 3 of the License, or 42 | % (at your option) any later version. 43 | % 44 | % This program is distributed in the hope that it will be useful, 45 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 46 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 47 | % GNU General Public License for more details. 48 | % 49 | % You should have received a copy of the GNU General Public License 50 | % along with this program. If not, see . 51 | %} 52 | % ========================================================================= 53 | %% 3-D Back-projection Branchless Distance Driven Code (CPU-Multithread) 54 | */ 55 | 56 | #include "backprojectionDDb.hpp" 57 | 58 | extern "C" void backprojectionDDb_lib(double* const pProj, 59 | double* const pVolume, 60 | float* const pGeo, 61 | const signed int idXProj){ 62 | 63 | 64 | const int unsigned nPixX = (const int)pGeo[0]; 65 | const int unsigned nPixY = (const int)pGeo[1]; 66 | const int unsigned nSlices = (const int)pGeo[2]; 67 | const int unsigned nDetX = (const int)pGeo[3]; 68 | const int unsigned nDetY = (const int)pGeo[4]; 69 | 70 | const double dx = (const double)pGeo[5]; 71 | const double dy = (const double)pGeo[6]; 72 | const double dz = (const double)pGeo[7]; 73 | const double du = (const double)pGeo[8]; 74 | const double dv = (const double)pGeo[9]; 75 | 76 | const double DSD = (const double)pGeo[10]; 77 | const double DDR = (const double)pGeo[12]; 78 | const double DAG = (const double)pGeo[14]; 79 | 80 | const int unsigned nProj = (const int)pGeo[15]; 81 | 82 | const double tubeAngle = (const double)pGeo[16]; 83 | const double detAngle = (const double)pGeo[17]; 84 | 85 | double* const pTubeAngle = (double*)malloc(nProj * sizeof(double)); 86 | double* const pDetAngle = (double*)malloc(nProj * sizeof(double)); 87 | 88 | const double x_offset = (const double)pGeo[18]; 89 | const double y_offset = (const double)pGeo[19]; 90 | 91 | linspace(-tubeAngle/2, tubeAngle/2, nProj, pTubeAngle); 92 | linspace(-detAngle/2, detAngle/2, nProj, pDetAngle); 93 | 94 | // printf("Nx:%d Ny:%d Nz:%d \nNu:%d Nv:%d \nDx:%.2f Dy:%.2f Dz:%.2f \nDu:%.2f Dv:%.2f \nDSD:%.2f DDR:%.2f \nTube angle:%.2f \nDet angle:%.2f", nPixX, nPixY, nSlices, nDetX, nDetY, dx, dy, dz, du, dv, DSD, DDR, tubeAngle, detAngle); 95 | 96 | backprojectionDDb(pVolume, pProj, pTubeAngle, pDetAngle, nProj, nPixX, nPixY, nSlices, nDetX, nDetY, idXProj, x_offset, y_offset, dx, dy, dz, du, dv, DSD, DDR, DAG); 97 | 98 | free(pTubeAngle); 99 | free(pDetAngle); 100 | 101 | return; 102 | 103 | } 104 | 105 | 106 | // CPU Branchless Distance-driven back-projection 107 | void backprojectionDDb(double* const pVolume, 108 | double* const pProj, 109 | double* const pTubeAngle, 110 | double* const pDetAngle, 111 | const unsigned int nProj, 112 | const unsigned int nPixX, 113 | const unsigned int nPixY, 114 | const unsigned int nSlices, 115 | const unsigned int nDetX, 116 | const unsigned int nDetY, 117 | const signed int idXProj, 118 | const double x_offset, 119 | const double y_offset, 120 | const double dx, 121 | const double dy, 122 | const double dz, 123 | const double du, 124 | const double dv, 125 | const double DSD, 126 | const double DDR, 127 | const double DAG) 128 | { 129 | 130 | nThreads = omp_get_max_threads(); 131 | 132 | printf("CPU running with maximum number of threads: %d \n", nThreads); 133 | 134 | clock_t time; 135 | 136 | time = clock(); 137 | 138 | // Number of mapped detector and pixels 139 | const int nDetXMap = nDetX + 1; 140 | const int nDetYMap = nDetY + 1; 141 | const int nPixXMap = nPixX + 1; 142 | const int nPixYMap = nPixY + 1; 143 | 144 | 145 | // Allocate memory 146 | double* const pSliceI = (double*)malloc(nPixXMap*nPixYMap * sizeof(double)); 147 | 148 | 149 | // Pointer for projections coordinates 150 | // Allocate memory for projections coordinates 151 | double* const pDetX = (double*)malloc(nDetXMap * sizeof(double)); 152 | double* const pDetY = (double*)malloc(nDetYMap * sizeof(double)); 153 | double* const pDetZ = (double*)malloc(nDetYMap * sizeof(double)); 154 | double* const pObjX = (double*)malloc(nPixXMap * sizeof(double)); 155 | double* const pObjY = (double*)malloc(nPixYMap * sizeof(double)); 156 | double* const pObjZ = (double*)malloc(nSlices * sizeof(double)); 157 | 158 | 159 | // Pointer for mapped coordinates 160 | // Allocate memory for mapped coordinates 161 | double* const pDetmY = (double*)malloc(nDetYMap * sizeof(double)); 162 | double* const pDetmX = (double*)malloc(nDetYMap * nDetXMap * sizeof(double)); 163 | 164 | 165 | 166 | // Pointer for rotated detector coords 167 | // Allocate memory for rotated detector coords 168 | double* const pRdetY = (double*)malloc(nDetYMap * sizeof(double)); 169 | double* const pRdetZ = (double*)malloc(nDetYMap * sizeof(double)); 170 | 171 | 172 | // Map detector and object boudaries 173 | mapBoudaries(pDetX, nDetXMap, (double)nDetX, -du, 0.0); 174 | 175 | mapBoudaries(pDetY, nDetYMap, nDetY / 2.0, dv, 0.0); 176 | 177 | mapBoudaries(pDetZ, nDetYMap, 0.0, 0.0, 0.0); 178 | 179 | mapBoudaries(pObjX, nPixXMap, (double)nPixX, -dx, x_offset); 180 | 181 | mapBoudaries(pObjY, nPixYMap, nPixY / 2.0, dy, y_offset); 182 | 183 | mapBoudaries(pObjZ, nSlices, 0.0, dz, DAG + (dz / 2.0)); 184 | 185 | 186 | // X - ray tube initial position 187 | double tubeX = 0; 188 | double tubeY = 0; 189 | double tubeZ = DSD; 190 | 191 | // Iso - center position 192 | double isoY = 0; 193 | double isoZ = DDR; 194 | 195 | 196 | // Allocate memory for temp projection variable 197 | double* const pProjt = (double*)malloc(nDetYMap *nDetXMap * nProj * sizeof(double)); 198 | double* const pVolumet = (double*)malloc(nPixY *nPixX * nSlices * sizeof(double)); 199 | 200 | 201 | // Initiate variables value with 0 202 | for (int nz = 0; nz < nSlices; nz++) 203 | for (int x = 0; x < nPixX; x++) 204 | for (int y = 0; y < nPixY; y++) 205 | pVolumet[(nz*nPixY *nPixX) + (x*nPixY) + y] = 0; 206 | 207 | 208 | // Integration of 2D slices over the whole volume 209 | // (S.1.Integration. - Liu et al(2017)) 210 | /* 211 | 212 | 2 - D image 213 | 214 | -->J 215 | | ------------- 216 | v | | 217 | I | | 218 | | | 219 | | | 220 | ------------- 221 | */ 222 | 223 | 224 | // Initialize first column and row with zeros 225 | for (int p = 0; p < nProj; p++) { 226 | 227 | for (int y = 0; y < nDetYMap; y++) 228 | pProjt[(p*nDetYMap *nDetXMap) + y] = 0; 229 | 230 | for (int x = 1; x < nDetXMap; x++) 231 | pProjt[(p*nDetYMap *nDetXMap) + (x*nDetYMap)] = 0; 232 | } 233 | 234 | 235 | // Integrate on I direction 236 | for (int p = 0; p < nProj; p++) 237 | #pragma omp parallel for num_threads(nThreads) schedule(static) 238 | for (int x = 0; x < nDetX; x++){ 239 | double sum = 0; 240 | for (int y = 0; y < nDetY; y++){ 241 | sum += pProj[(p*nDetY *nDetX) + (x*nDetY) + y]; 242 | pProjt[(p*nDetYMap *nDetXMap) + ((x+1)*nDetYMap) + y + 1] = sum; 243 | } 244 | } 245 | 246 | // Integrate on J direction 247 | for (int p = 0; p < nProj; p++) 248 | #pragma omp parallel for num_threads(nThreads) schedule(static) 249 | for (int y = 1; y < nDetYMap; y++) 250 | for (int x = 2; x < nDetXMap; x++) 251 | pProjt[(p*nDetYMap *nDetXMap) + (x*nDetYMap) + y] += pProjt[(p*nDetYMap *nDetXMap) + ((x - 1)*nDetYMap) + y]; 252 | 253 | 254 | double* pDetmX_tmp = pDetmX + (nDetYMap * (nDetXMap - 2)); 255 | 256 | // Test if we will loop over all projs or not 257 | unsigned int projIni, projEnd, nProj2Run; 258 | if(idXProj == -1){ 259 | projIni = 0; 260 | projEnd = nProj; 261 | nProj2Run = nProj; 262 | } 263 | else{ 264 | nProj2Run = 1; 265 | projIni = (unsigned int) idXProj; 266 | projEnd = (unsigned int) idXProj + 1; 267 | } 268 | 269 | 270 | // For each projection 271 | for (unsigned int p = projIni; p < projEnd; p++) { 272 | 273 | // Get specif tube angle for the projection 274 | double theta = pTubeAngle[p] * M_PI / 180.0; 275 | 276 | // Get specif detector angle for the projection 277 | double phi = pDetAngle[p] * M_PI / 180.0; 278 | 279 | //printf("Tube angle:%f Det angle:%f\n", theta, phi); 280 | 281 | // Tube rotation 282 | double rtubeY = ((tubeY - isoY)*cos(theta) - (tubeZ - isoZ)*sin(theta)) + isoY; 283 | double rtubeZ = ((tubeY - isoY)*sin(theta) + (tubeZ - isoZ)*cos(theta)) + isoZ; 284 | 285 | //printf("R tube Y:%f R tube Z:%f\n", rtubeY, rtubeZ); 286 | 287 | // Detector rotation 288 | for (int y = 0; y < nDetYMap; y++) { 289 | pRdetY[y] = ((pDetY[y] - isoY)*cos(phi) - (pDetZ[y] - isoZ)*sin(phi)) + isoY; 290 | pRdetZ[y] = ((pDetY[y] - isoY)*sin(phi) + (pDetZ[y] - isoZ)*cos(phi)) + isoZ; 291 | } 292 | 293 | 294 | // For each slice 295 | for (int nz = 0; nz < nSlices; nz++) { 296 | 297 | /* 298 | 299 | Map detector onto XY plane(Inside proj loop in case detector rotates) 300 | 301 | *** Note: Matlab has linear indexing as a column of elements, i.e, the elements are actually stored in memory as queued columns. 302 | 303 | */ 304 | 305 | // Map slice onto XY plane 306 | mapDet2Slice(pDetmX, pDetmY, tubeX, rtubeY, rtubeZ, pDetX, pRdetY, pRdetZ, pObjZ[nz], nDetXMap, nDetYMap); 307 | 308 | /* 309 | S.2. Interpolation - Liu et al (2017) 310 | */ 311 | 312 | bilinear_interpolation(pSliceI, pProjt, pObjX, pObjY, pDetmX_tmp, pDetmY, nPixXMap, nPixYMap, nDetXMap, nDetYMap, nDetX, nDetY, p); 313 | 314 | /* 315 | S.3. Differentiation - Eq. 24 - Liu et al (2017) 316 | */ 317 | 318 | differentiation(pVolumet, pSliceI, tubeX, rtubeY, rtubeZ, pObjX, pObjY, pObjZ, nPixX, nPixY, nPixXMap, nPixYMap, du, dv, dx, dy, dz, nz); 319 | 320 | } // Loop end slices 321 | 322 | } // Loop end Projections 323 | 324 | 325 | // Copy pVolumet to pVolume 326 | for (int nz = 0; nz < nSlices; nz++) 327 | for (int x = 0; x < nPixX; x++) 328 | for (int y = 0; y < nPixY; y++) 329 | pVolume[(nz*nPixX*nPixY) + (x*nPixY) + y] = pVolumet[(nz*nPixX*nPixY) + (x*nPixY) + y] / (double) nProj2Run; 330 | 331 | 332 | free(pProjt); 333 | free(pSliceI); 334 | free(pVolumet); 335 | free(pDetX); 336 | free(pDetY); 337 | free(pDetZ); 338 | free(pObjX); 339 | free(pObjY); 340 | free(pObjZ); 341 | free(pDetmY); 342 | free(pDetmX); 343 | free(pRdetY); 344 | free(pRdetZ); 345 | 346 | time = clock() - time; 347 | printf("Processing time: %.2f seconds.\n", ((double)time) / CLOCKS_PER_SEC); 348 | 349 | return; 350 | } 351 | 352 | // Linear spaced vector 353 | // Ref: https://stackoverflow.com/a/27030598/8682939 354 | void linspace(double start, 355 | double end, 356 | int num, 357 | double* pLinspaced){ 358 | 359 | double delta; 360 | 361 | if(num == 0) 362 | delta = 0; 363 | else{ 364 | if(num == 1) 365 | delta = 1; 366 | else 367 | delta = (end - start) / (num - 1); 368 | } 369 | 370 | if((abs(start) < 0.00001) && (abs(end) < 0.00001)) 371 | delta = 0; 372 | 373 | 374 | for (int k = 0; k < num; k++){ 375 | pLinspaced[k] = start + k * delta; 376 | } 377 | 378 | return; 379 | } 380 | 381 | // Make boundaries of detector and slices 382 | void mapBoudaries(double* pBound, 383 | const int nElem, 384 | const double valueLeftBound, 385 | const double sizeElem, 386 | const double offset){ 387 | 388 | for (int k = 0; k < nElem; k++) 389 | pBound[k] = (k - valueLeftBound) * sizeElem + offset; 390 | 391 | return; 392 | } 393 | 394 | // Map on XY plane 395 | void mapDet2Slice(double* const pXmapp, 396 | double* const pYmapp, 397 | double tubeX, 398 | double tubeY, 399 | double tubeZ, 400 | double * const pXcoord, 401 | double * const pYcoord, 402 | double * const pZcoord, 403 | double ZSlicecoord, 404 | const int nXelem, 405 | const int nYelem){ 406 | 407 | #pragma omp parallel num_threads(nThreads) 408 | { 409 | int ind; 410 | #pragma omp for schedule(static) 411 | for (int x = 0; x < nXelem; x++) 412 | for (int y = 0; y < nYelem; y++) { 413 | 414 | ind = (x*nYelem) + y; 415 | pXmapp[ind] = ((pXcoord[x] - tubeX)*(ZSlicecoord - pZcoord[y]) - (pXcoord[x] * tubeZ) + (pXcoord[x] * pZcoord[y])) / (-tubeZ + pZcoord[y]); 416 | 417 | if (x == 0) 418 | pYmapp[y] = ((pYcoord[y] - tubeY)*(ZSlicecoord - pZcoord[y]) - (pYcoord[y] * tubeZ) + (pYcoord[y] * pZcoord[y])) / (-tubeZ + pZcoord[y]); 419 | } 420 | } 421 | 422 | return; 423 | } 424 | 425 | // Bilinear interpolation 426 | void bilinear_interpolation(double* pSliceI, 427 | double* pProj, 428 | double* pObjX, 429 | double* pObjY, 430 | double* pDetmX, 431 | double* pDetmY, 432 | const int nPixXMap, 433 | const int nPixYMap, 434 | const int nDetXMap, 435 | const int nDetYMap, 436 | const int nDetX, 437 | const int nDetY, 438 | const unsigned int np){ 439 | 440 | /* 441 | 442 | S.2. Interpolation - Liu et al (2017) 443 | 444 | Reference: 445 | - https://en.wikipedia.org/wiki/Bilinear_interpolation 446 | - https://stackoverflow.com/questions/21128731/bilinear-interpolation-in-c-c-and-cuda 447 | 448 | Note: *** We are using the Unit Square equation *** 449 | 450 | alpha = X - X1 451 | 1-alpha = X2 - X 452 | 453 | beta = Y - Y1 454 | 1-beta = Y2 - Y 455 | 456 | ----> Xcoord 457 | | 0___________ 458 | v |_d00_|_d10_| 459 | Ycoord |_d01_|_d11_| 460 | 461 | Matlab code -- 462 | objX = nDetX - (objX ./ detmX(1, end - 1)); 463 | objY = (objY ./ detmX(1, end - 1)) - (detmY(1) / detmX(1, end - 1)); 464 | 465 | */ 466 | 467 | #pragma omp parallel for num_threads(nThreads) schedule(static) 468 | for (int x = 0; x < nPixXMap; x++) 469 | for (int y = 0; y < nPixYMap; y++) { 470 | 471 | // Adjust the mapped coordinates to cross the range of (0-nDetX).*duMap 472 | // Divide by pixelSize to get a unitary pixel size 473 | const double xNormData = nDetX - pObjX[x] / pDetmX[0]; 474 | const signed int xData = (signed int) floor(xNormData); 475 | const double alpha = xNormData - xData; 476 | 477 | // Adjust the mapped coordinates to cross the range of (0-nDetY).*dyMap 478 | // Divide by pixelSize to get a unitary pixel size 479 | const double yNormData = (pObjY[y] / pDetmX[0]) - (pDetmY[0] / pDetmX[0]); 480 | const signed int yData = (signed int) floor(yNormData); 481 | const double beta = yNormData - yData; 482 | 483 | double d00, d01, d10, d11; 484 | if (((xNormData) >= 0) && ((xNormData) <= nDetX) && ((yNormData) >= 0) && ((yNormData) <= nDetY)) d00 = pProj[(np*nDetYMap*nDetXMap) + (xData*nDetYMap + yData)]; else d00 = 0.0; 485 | if (((xData + 1) > 0) && ((xData + 1) <= nDetX) && ((yNormData) >= 0) && ((yNormData) <= nDetY)) d10 = pProj[(np*nDetYMap*nDetXMap) + ((xData + 1)*nDetYMap + yData)]; else d10 = 0.0; 486 | if (((xNormData) >= 0) && ((xNormData) <= nDetX) && ((yData + 1) > 0) && ((yData + 1) <= nDetY)) d01 = pProj[(np*nDetYMap*nDetXMap) + (xData*nDetYMap + yData + 1)]; else d01 = 0.0; 487 | if (((xData + 1) > 0) && ((xData + 1) <= nDetX) && ((yData + 1) > 0) && ((yData + 1) <= nDetY)) d11 = pProj[(np*nDetYMap*nDetXMap) + ((xData + 1)*nDetYMap + yData + 1)]; else d11 = 0.0; 488 | 489 | double result_temp1 = alpha * d10 + (-d00 * alpha + d00); 490 | double result_temp2 = alpha * d11 + (-d01 * alpha + d01); 491 | 492 | pSliceI[x * nPixYMap + y] = beta * result_temp2 + (-result_temp1 * beta + result_temp1); 493 | } 494 | 495 | return; 496 | } 497 | 498 | // Differentiation 499 | void differentiation(double* pVolume, 500 | double* pSliceI, 501 | double tubeX, 502 | double rtubeY, 503 | double rtubeZ, 504 | double* const pObjX, 505 | double* const pObjY, 506 | double* const pObjZ, 507 | const int nPixX, 508 | const int nPixY, 509 | const int nPixXMap, 510 | const int nPixYMap, 511 | const double du, 512 | const double dv, 513 | const double dx, 514 | const double dy, 515 | const double dz, 516 | const unsigned int nz){ 517 | 518 | /* 519 | 520 | S.3. Differentiation - Eq. 24 - Liu et al (2017) 521 | 522 | Slice integral projection 523 | ___________ 524 | |_A_|_B_|___| 525 | |_C_|_D_|___| 526 | |___|___|___| 527 | 528 | 529 | (y,x) 530 | ________________ 531 | |_A_|__B__|_____| 532 | |_C_|(0,0)|(0,1)| 533 | |___|(1,0)|(1,1)| 534 | 535 | 536 | Coordinates on intergal slice: 537 | 538 | A = x * nPixYMap + y 539 | B = ((x+1) * nPixYMap) + y 540 | C = x * nPixYMap + y + 1 541 | D = ((x+1) * nPixYMap) + y + 1 542 | 543 | */ 544 | 545 | #pragma omp parallel num_threads(nThreads) 546 | { 547 | unsigned int coordA; 548 | unsigned int coordB; 549 | unsigned int coordC; 550 | unsigned int coordD; 551 | 552 | double gamma; 553 | double alpha; 554 | 555 | double dA, dB, dC, dD; 556 | 557 | #pragma omp for schedule(static) 558 | for (int x = 0; x < nPixX; x++) 559 | for (int y = 0; y < nPixY; y++) { 560 | 561 | coordA = x * nPixYMap + y; 562 | coordB = ((x + 1) * nPixYMap) + y; 563 | coordC = coordA + 1; 564 | coordD = coordB + 1; 565 | 566 | // x - ray angle in X coord 567 | gamma = atan((pObjX[x] + (dx / 2) - tubeX) / (rtubeZ - pObjZ[nz])); 568 | 569 | // x - ray angle in Y coord 570 | alpha = atan((pObjY[y] + (dy / 2) - rtubeY) / (rtubeZ - pObjZ[nz])); 571 | 572 | 573 | dA = pSliceI[coordA]; 574 | dB = pSliceI[coordB]; 575 | dC = pSliceI[coordC]; 576 | dD = pSliceI[coordD]; 577 | 578 | // Treat border of interpolated integral detector 579 | if (dC == 0 && dD == 0) { 580 | dC = dA; 581 | dD = dB; 582 | } 583 | 584 | 585 | // S.3.Differentiation - Eq. 24 - Liu et al(2017) 586 | pVolume[(nPixX*nPixY*nz) + (x * nPixY) + y] += ((dD - dC - dB + dA)*(du*dv*dz / (cos(alpha)*cos(gamma)*dx*dy))); 587 | } 588 | } 589 | return; 590 | } -------------------------------------------------------------------------------- /pydbt/sources/backprojectionDDb/backprojectionDDb.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: March, 2019 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This is the header function 12 | % --------------------------------------------------------------------- 13 | % Copyright (C) <2019> 14 | % 15 | % This program is free software: you can redistribute it and/or modify 16 | % it under the terms of the GNU General Public License as published by 17 | % the Free Software Foundation, either version 3 of the License, or 18 | % (at your option) any later version. 19 | % 20 | % This program is distributed in the hope that it will be useful, 21 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | % GNU General Public License for more details. 24 | % 25 | % You should have received a copy of the GNU General Public License 26 | % along with this program. If not, see . 27 | %} 28 | % ========================================================================= 29 | %% 3-D Back-projection Branchless Distance Driven Code (CPU-Multithread) 30 | */ 31 | 32 | //typedef double user_dataType; 33 | //typedef float user_dataType; 34 | //#define user_mexdataType mxDOUBLE_CLASS 35 | //#define user_mexdataType mxSINGLE_CLASS 36 | 37 | #include 38 | #include 39 | #define _USE_MATH_DEFINES 40 | #include 41 | #include 42 | 43 | // Global variable 44 | int nThreads; 45 | 46 | void backprojectionDDb(double* const pVolume, 47 | double* const pProj, 48 | double* const pTubeAngle, 49 | double* const pDetAngle, 50 | const unsigned int nProj, 51 | const unsigned int nPixX, 52 | const unsigned int nPixY, 53 | const unsigned int nSlices, 54 | const unsigned int nDetX, 55 | const unsigned int nDetY, 56 | const signed int idXProj, 57 | const double x_offset, 58 | const double y_offset, 59 | const double dx, 60 | const double dy, 61 | const double dz, 62 | const double du, 63 | const double dv, 64 | const double DSD, 65 | const double DDR, 66 | const double DAG); 67 | 68 | void linspace(double start, 69 | double end, 70 | int num, 71 | double* pLinspaced); 72 | 73 | void mapBoudaries(double* pBound, 74 | const int nElem, 75 | const double valueLeftBound, 76 | const double sizeElem, 77 | const double offset); 78 | 79 | void mapDet2Slice(double* const pXmapp, 80 | double* const pYmapp, 81 | double tubeX, 82 | double tubeY, 83 | double tubeZ, 84 | double * const pXcoord, 85 | double * const pYcoord, 86 | double * const pZcoord, 87 | double ZSlicecoord, 88 | const int nXelem, 89 | const int nYelem); 90 | 91 | void bilinear_interpolation(double* pSliceI, 92 | double* pProj, 93 | double* pObjX, 94 | double* pObjY, 95 | double* pDetmX, 96 | double* pDetmY, 97 | const int nPixXMap, 98 | const int nPixYMap, 99 | const int nDetXMap, 100 | const int nDetYMap, 101 | const int nDetX, 102 | const int nDetY, 103 | const unsigned int np); 104 | 105 | void differentiation(double* pVolume, 106 | double* pSliceI, 107 | double tubeX, 108 | double rtubeY, 109 | double rtubeZ, 110 | double* const pObjX, 111 | double* const pObjY, 112 | double* const pObjZ, 113 | const int nPixX, 114 | const int nPixY, 115 | const int nPixXMap, 116 | const int nPixYMap, 117 | const double du, 118 | const double dv, 119 | const double dx, 120 | const double dy, 121 | const double dz, 122 | const unsigned int nz); 123 | -------------------------------------------------------------------------------- /pydbt/sources/backprojectionDDb_cuda/backprojectionDDb_cuda.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: Feb, 2022 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % backprojectionDDb_cuda_lib(pProj, pVolume, pGeo, idXProj) 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This function reconstruct the 3D volume from projections, based on 12 | % the Distance-Driven principle. It works by calculating the overlap 13 | % in X and Y axis of the volume and the detector boundaries. 14 | % The geometry is for DBT with half cone-beam. All parameters are set 15 | % in "ParameterSettings" code. 16 | % 17 | % INPUT: 18 | % 19 | % - pProj = Pointer to 2D projections for each angle 20 | % - pGeo = Pointer to parameters of all pGeometry 21 | % - idXProj = projection number to be projected 22 | % 23 | % OUTPUT: 24 | % 25 | % - pVolume = Pointer to reconstructed volume. 26 | % 27 | % Reference: 28 | % - Branchless Distance Driven Projection and Backprojection, 29 | % Samit Basu and Bruno De Man (2006) 30 | % - GPU Acceleration of Branchless Distance Driven Projection and 31 | % Backprojection, Liu et al (2016) 32 | % - GPU-Based Branchless Distance-Driven Projection and Backprojection, 33 | % Liu et al (2017) 34 | % - A GPU Implementation of Distance-Driven Computed Tomography, 35 | % Ryan D. Wagner (2017) 36 | % --------------------------------------------------------------------- 37 | % Copyright (C) <2022> 38 | % 39 | % This program is free software: you can redistribute it and/or modify 40 | % it under the terms of the GNU General Public License as published by 41 | % the Free Software Foundation, either version 3 of the License, or 42 | % (at your option) any later version. 43 | % 44 | % This program is distributed in the hope that it will be useful, 45 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 46 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 47 | % GNU General Public License for more details. 48 | % 49 | % You should have received a copy of the GNU General Public License 50 | % along with this program. If not, see . 51 | %} 52 | % ========================================================================= 53 | %% 3-D Distance Driven Back-projection Code 54 | */ 55 | 56 | #include "backprojectionDDb_cuda.hpp" 57 | 58 | extern "C" void backprojectionDDb_cuda_lib(double* const pProj, 59 | double* const pVolume, 60 | float* const pGeo, 61 | const signed int idXProj){ 62 | 63 | 64 | const int unsigned nPixX = (const int)pGeo[0]; 65 | const int unsigned nPixY = (const int)pGeo[1]; 66 | const int unsigned nSlices = (const int)pGeo[2]; 67 | const int unsigned nDetX = (const int)pGeo[3]; 68 | const int unsigned nDetY = (const int)pGeo[4]; 69 | 70 | const double dx = (const double)pGeo[5]; 71 | const double dy = (const double)pGeo[6]; 72 | const double dz = (const double)pGeo[7]; 73 | const double du = (const double)pGeo[8]; 74 | const double dv = (const double)pGeo[9]; 75 | 76 | const double DSD = (const double)pGeo[10]; 77 | const double DDR = (const double)pGeo[12]; 78 | const double DAG = (const double)pGeo[14]; 79 | 80 | const int unsigned nProj = (const int)pGeo[15]; 81 | 82 | const double tubeAngle = (const double)pGeo[16]; 83 | const double detAngle = (const double)pGeo[17]; 84 | 85 | double* const pTubeAngle = (double*)malloc(nProj * sizeof(double)); 86 | double* const pDetAngle = (double*)malloc(nProj * sizeof(double)); 87 | 88 | const double x_offset = (const double)pGeo[18]; 89 | const double y_offset = (const double)pGeo[19]; 90 | 91 | linspace(-tubeAngle/2, tubeAngle/2, nProj, pTubeAngle); 92 | linspace(-detAngle/2, detAngle/2, nProj, pDetAngle); 93 | 94 | // printf("Nx:%d Ny:%d Nz:%d \nNu:%d Nv:%d \nDx:%.2f Dy:%.2f Dz:%.2f \nDu:%.2f Dv:%.2f \nDSD:%.2f DDR:%.2f \nTube angle:%.2f \nDet angle:%.2f", nPixX, nPixY, nSlices, nDetX, nDetY, dx, dy, dz, du, dv, DSD, DDR, tubeAngle, detAngle); 95 | 96 | backprojectionDDb(pVolume, pProj, pTubeAngle, pDetAngle, nProj, nPixX, nPixY, nSlices, nDetX, nDetY, idXProj, x_offset, y_offset, dx, dy, dz, du, dv, DSD, DDR, DAG); 97 | 98 | free(pTubeAngle); 99 | free(pDetAngle); 100 | 101 | return; 102 | 103 | } 104 | 105 | // Linear spaced vector 106 | // Ref: https://stackoverflow.com/a/27030598/8682939 107 | void linspace(double start, 108 | double end, 109 | int num, 110 | double* pLinspaced){ 111 | 112 | double delta; 113 | 114 | if(num == 0) 115 | delta = 0; 116 | else{ 117 | if(num == 1) 118 | delta = 1; 119 | else 120 | delta = (end - start) / (num - 1); 121 | } 122 | 123 | if((abs(start) < 0.00001) && (abs(end) < 0.00001)) 124 | delta = 0; 125 | 126 | 127 | for (int k = 0; k < num; k++){ 128 | pLinspaced[k] = start + k * delta; 129 | } 130 | 131 | return; 132 | } -------------------------------------------------------------------------------- /pydbt/sources/backprojectionDDb_cuda/backprojectionDDb_cuda.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: Feb, 2022 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This is the header function 12 | % --------------------------------------------------------------------- 13 | % Copyright (C) <2022> 14 | % 15 | % This program is free software: you can redistribute it and/or modify 16 | % it under the terms of the GNU General Public License as published by 17 | % the Free Software Foundation, either version 3 of the License, or 18 | % (at your option) any later version. 19 | % 20 | % This program is distributed in the hope that it will be useful, 21 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | % GNU General Public License for more details. 24 | % 25 | % You should have received a copy of the GNU General Public License 26 | % along with this program. If not, see . 27 | %} 28 | % ========================================================================= 29 | %% 3-D Distance Driven Back-projection header 30 | */ 31 | 32 | #include 33 | #define _USE_MATH_DEFINES 34 | #include 35 | 36 | // Includes CUDA 37 | #include 38 | 39 | // Utilities and timing functions 40 | #include // includes cuda.h and cuda_runtime_api.h 41 | 42 | // CUDA helper functions 43 | #include // helper functions for CUDA error check 44 | 45 | #define integrateXcoord 1 46 | #define integrateYcoord 0 47 | 48 | 49 | /* 50 | Reference: TIGRE - https://github.com/CERN/TIGRE 51 | */ 52 | #define cudaCheckErrors(msg) \ 53 | do { \ 54 | cudaError_t __err = cudaGetLastError(); \ 55 | if (__err != cudaSuccess) { \ 56 | printf("%s \n",msg);\ 57 | } \ 58 | } while (0) 59 | 60 | void linspace(double start, 61 | double end, 62 | int num, 63 | double* pLinspaced); 64 | 65 | void backprojectionDDb(double* const h_pVolume, 66 | double* const h_pProj, 67 | double* const h_pTubeAngle, 68 | double* const h_pDetAngle, 69 | const unsigned int nProj, 70 | const unsigned int nPixX, 71 | const unsigned int nPixY, 72 | const unsigned int nSlices, 73 | const unsigned int nDetX, 74 | const unsigned int nDetY, 75 | const signed int idXProj, 76 | const double x_offset, 77 | const double y_offset, 78 | const double dx, 79 | const double dy, 80 | const double dz, 81 | const double du, 82 | const double dv, 83 | const double DSD, 84 | const double DDR, 85 | const double DAG); 86 | -------------------------------------------------------------------------------- /pydbt/sources/backprojectionDDb_cuda/kernel.cu: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: Feb, 2022 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % backprojectionDDb_cuda_lib(pProj, pVolume, pGeo, idXProj) 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This function reconstruct the 3D volume from projections, based on 12 | % the Distance-Driven principle. It works by calculating the overlap 13 | % in X and Y axis of the volume and the detector boundaries. 14 | % The geometry is for DBT with half cone-beam. All parameters are set 15 | % in "ParameterSettings" code. 16 | % 17 | % INPUT: 18 | % 19 | % - pProj = Pointer to 2D projections for each angle 20 | % - pGeo = Pointer to parameters of all pGeometry 21 | % - idXProj = projection number to be projected 22 | % 23 | % OUTPUT: 24 | % 25 | % - pVolume = Pointer to reconstructed volume. 26 | % 27 | % Reference: 28 | % - Branchless Distance Driven Projection and Backprojection, 29 | % Samit Basu and Bruno De Man (2006) 30 | % - GPU Acceleration of Branchless Distance Driven Projection and 31 | % Backprojection, Liu et al (2016) 32 | % - GPU-Based Branchless Distance-Driven Projection and Backprojection, 33 | % Liu et al (2017) 34 | % - A GPU Implementation of Distance-Driven Computed Tomography, 35 | % Ryan D. Wagner (2017) 36 | % --------------------------------------------------------------------- 37 | % Copyright (C) <2022> 38 | % 39 | % This program is free software: you can redistribute it and/or modify 40 | % it under the terms of the GNU General Public License as published by 41 | % the Free Software Foundation, either version 3 of the License, or 42 | % (at your option) any later version. 43 | % 44 | % This program is distributed in the hope that it will be useful, 45 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 46 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 47 | % GNU General Public License for more details. 48 | % 49 | % You should have received a copy of the GNU General Public License 50 | % along with this program. If not, see . 51 | %} 52 | % ========================================================================= 53 | %% 3-D Distance Driven Back-projection Code 54 | */ 55 | 56 | #include "backprojectionDDb_cuda.hpp" 57 | 58 | 59 | /**************************************************************************** 60 | * CUDA Kernels * 61 | ****************************************************************************/ 62 | __global__ void pad_projections_kernel(double* d_img, const int nDetXMap, const int nDetYMap, const int nElem, unsigned int np) { 63 | 64 | const int threadGId = blockIdx.x * blockDim.x + threadIdx.x; 65 | 66 | // Make sure we don't try and access memory outside 67 | // by having any threads mapped there return early 68 | if (threadGId >= nElem) 69 | return; 70 | 71 | d_img[(np*nDetYMap *nDetXMap) + (threadGId*nDetYMap)] = 0; 72 | 73 | return; 74 | } 75 | 76 | __global__ void map_boudaries_kernel(double* d_pBound, const int nElem, const double valueLeftBound, const double sizeElem, const double offset) { 77 | 78 | const int threadGId = blockIdx.x * blockDim.x + threadIdx.x; 79 | 80 | // Make sure we don't try and access memory outside 81 | // by having any threads mapped there return early 82 | if (threadGId >= nElem) 83 | return; 84 | 85 | d_pBound[threadGId] = (threadGId - valueLeftBound) * sizeElem + offset; 86 | 87 | return; 88 | } 89 | 90 | __global__ void rot_detector_kernel(double* d_pRdetY, double* d_pRdetZ, double* d_pYcoord, double* d_pZcoord, const double yOffset, const double zOffset, 91 | const double phi, const int nElem) { 92 | 93 | const int threadGId = blockIdx.x * blockDim.x + threadIdx.x; 94 | 95 | // Make sure we don't try and access memory outside 96 | // by having any threads mapped there return early 97 | if (threadGId >= nElem) 98 | return; 99 | 100 | // cos and sin are in measured in radians. 101 | 102 | d_pRdetY[threadGId] = ((d_pYcoord[threadGId] - yOffset)* cos(phi) - (d_pZcoord[threadGId] - zOffset)* sin(phi)) + yOffset; 103 | d_pRdetZ[threadGId] = ((d_pYcoord[threadGId] - yOffset)* sin(phi) + (d_pZcoord[threadGId] - zOffset)* cos(phi)) + zOffset; 104 | 105 | return; 106 | 107 | } 108 | 109 | __global__ void mapDet2Slice_kernel(double* const pXmapp, double* const pYmapp, double tubeX, double tubeY, double tubeZ, double* const pXcoord, 110 | double* const pYcoord, double* const pZcoord, double* const pZSlicecoord, const int nDetXMap, const int nDetYMap, const unsigned int nz) { 111 | 112 | /* 113 | 114 | Note: Matlab has linear indexing as a column of elements, i.e, the elements are actually stored in memory as queued columns. 115 | So threads in X are launched in the column direction (DBT Y coord), and threads in Y are launched in the row direction (DBT X coord). 116 | 117 | */ 118 | 119 | const int2 thread_2D_pos = make_int2(blockIdx.x * blockDim.x + threadIdx.x, 120 | blockIdx.y * blockDim.y + threadIdx.y); 121 | 122 | const int thread_1D_pos = thread_2D_pos.y * nDetYMap + thread_2D_pos.x; 123 | 124 | // Make sure we don't try and access memory outside the detector 125 | // by having any threads mapped there return early 126 | if (thread_2D_pos.x >= nDetYMap || thread_2D_pos.y >= nDetXMap) 127 | return; 128 | 129 | pXmapp[thread_1D_pos] = ((pXcoord[thread_2D_pos.y] - tubeX)*(pZSlicecoord[nz] - pZcoord[thread_2D_pos.x]) - (pXcoord[thread_2D_pos.y] * tubeZ) + (pXcoord[thread_2D_pos.y] * pZcoord[thread_2D_pos.x])) / (-tubeZ + pZcoord[thread_2D_pos.x]); 130 | 131 | if (thread_2D_pos.y == 0) 132 | pYmapp[thread_2D_pos.x] = ((pYcoord[thread_2D_pos.x] - tubeY)*(pZSlicecoord[nz] - pZcoord[thread_2D_pos.x]) - (pYcoord[thread_2D_pos.x] * tubeZ) + (pYcoord[thread_2D_pos.x] * pZcoord[thread_2D_pos.x])) / (-tubeZ + pZcoord[thread_2D_pos.x]); 133 | 134 | return; 135 | } 136 | 137 | __global__ void img_integration_kernel(double* d_img, const int nPixX, const int nPixY, bool direction, unsigned int offsetX, unsigned int offsetY, unsigned int nSlices) { 138 | 139 | /* 140 | 141 | Integration of 2D slices over the whole volume 142 | 143 | (S.1.Integration. - Liu et al(2017)) 144 | 145 | ** Perfom an inclusive scan ** 146 | 147 | */ 148 | 149 | const int3 memory_2D_pos = make_int3(blockIdx.x * blockDim.x + threadIdx.x + offsetX, 150 | blockIdx.y * blockDim.y + threadIdx.y + offsetY, 151 | blockIdx.z * blockDim.z + threadIdx.z); 152 | 153 | const int2 thread_2D_pos = make_int2(blockIdx.x * blockDim.x + threadIdx.x, 154 | blockIdx.y * blockDim.y + threadIdx.y); 155 | 156 | 157 | // Make sure we don't try and access memory outside the detector 158 | // by having any threads mapped there return early 159 | if (memory_2D_pos.x >= nPixY || memory_2D_pos.y >= nPixX || memory_2D_pos.z >= nSlices) 160 | return; 161 | 162 | 163 | if (direction == integrateXcoord) { 164 | 165 | for (int s = 1; s <= blockDim.y; s *= 2) { 166 | 167 | int spot = thread_2D_pos.y - s; 168 | 169 | double val = 0; 170 | 171 | if (spot >= 0) { 172 | val = d_img[(memory_2D_pos.z*nPixY*nPixX) + (offsetY + spot) * nPixY + memory_2D_pos.x]; 173 | } 174 | __syncthreads(); 175 | 176 | if (spot >= 0) { 177 | d_img[(memory_2D_pos.z*nPixY*nPixX) + (memory_2D_pos.y * nPixY) + memory_2D_pos.x] += val; 178 | } 179 | __syncthreads(); 180 | } 181 | } 182 | else 183 | { 184 | 185 | for (int s = 1; s <= blockDim.x; s *= 2) { 186 | 187 | int spot = thread_2D_pos.x - s; 188 | 189 | double val = 0; 190 | 191 | if (spot >= 0) { 192 | val = d_img[(memory_2D_pos.z*nPixY*nPixX) + memory_2D_pos.y * nPixY + spot + offsetX]; 193 | } 194 | __syncthreads(); 195 | 196 | if (spot >= 0) { 197 | d_img[(memory_2D_pos.z*nPixY*nPixX) + (memory_2D_pos.y * nPixY) + memory_2D_pos.x] += val; 198 | } 199 | __syncthreads(); 200 | } 201 | 202 | } 203 | return; 204 | } 205 | 206 | __global__ void bilinear_interpolation_kernel_GPU(double* d_sliceI, double* d_pProj, double* d_pObjX, double* d_pObjY, double* d_pDetmX, double* d_pDetmY, const int nPixXMap, const int nPixYMap, 207 | const int nDetXMap, const int nDetYMap, const int nDetX, const int nDetY, const unsigned int np) { 208 | 209 | const int2 thread_2D_pos = make_int2(blockIdx.x * blockDim.x + threadIdx.x, 210 | blockIdx.y * blockDim.y + threadIdx.y); 211 | 212 | // Make sure we don't try and access memory outside the detector 213 | // by having any threads mapped there return early 214 | if (thread_2D_pos.x >= nPixYMap || thread_2D_pos.y >= nPixXMap) 215 | return; 216 | 217 | /* 218 | 219 | S.2. Interpolation - Liu et al (2017) 220 | 221 | Reference: 222 | - https://en.wikipedia.org/wiki/Bilinear_interpolation 223 | - https://stackoverflow.com/questions/21128731/bilinear-interpolation-in-c-c-and-cuda 224 | 225 | Note: *** We are using the Unit Square equation *** 226 | 227 | alpha = X - X1 228 | 1-alpha = X2 - X 229 | 230 | beta = Y - Y1 231 | 1-beta = Y2 - Y 232 | 233 | ----> Xcoord (thread_2D_pos.y) 234 | | 0___________ 235 | v |_d00_|_d10_| 236 | Ycoord (thread_2D_pos.x) |_d01_|_d11_| 237 | 238 | Matlab code -- 239 | objX = nDetX - (objX ./ detmX(1, end - 1)); 240 | objY = (objY ./ detmX(1, end - 1)) - (detmY(1) / detmX(1, end - 1)); 241 | 242 | */ 243 | 244 | // Adjust the mapped coordinates to cross the range of (0-nDetX).*duMap 245 | // Divide by pixelSize to get a unitary pixel size 246 | const double xNormData = nDetX - d_pObjX[thread_2D_pos.y] / d_pDetmX[0]; 247 | const signed int xData = floor(xNormData); 248 | const double alpha = xNormData - xData; 249 | 250 | // Adjust the mapped coordinates to cross the range of (0-nDetY).*dyMap 251 | // Divide by pixelSize to get a unitary pixel size 252 | const double yNormData = (d_pObjY[thread_2D_pos.x] / d_pDetmX[0]) - (d_pDetmY[0] / d_pDetmX[0]); 253 | const signed int yData = floor(yNormData); 254 | const double beta = yNormData - yData; 255 | 256 | double d00, d01, d10, d11; 257 | if (((xNormData) >= 0) && ((xNormData) <= nDetX) && ((yNormData) >= 0) && ((yNormData) <= nDetY)) d00 = d_pProj[(np*nDetYMap*nDetXMap) + (xData*nDetYMap + yData)]; else d00 = 0.0; 258 | if (((xData + 1) > 0) && ((xData + 1) <= nDetX) && ((yNormData) >= 0) && ((yNormData) <= nDetY)) d10 = d_pProj[(np*nDetYMap*nDetXMap) + ((xData + 1)*nDetYMap + yData)]; else d10 = 0.0; 259 | if (((xNormData) >= 0) && ((xNormData) <= nDetX) && ((yData + 1) > 0) && ((yData + 1) <= nDetY)) d01 = d_pProj[(np*nDetYMap*nDetXMap) + (xData*nDetYMap + yData + 1)]; else d01 = 0.0; 260 | if (((xData + 1) > 0) && ((xData + 1) <= nDetX) && ((yData + 1) > 0) && ((yData + 1) <= nDetY)) d11 = d_pProj[(np*nDetYMap*nDetXMap) + ((xData + 1)*nDetYMap + yData + 1)]; else d11 = 0.0; 261 | 262 | double result_temp1 = alpha * d10 + (-d00 * alpha + d00); 263 | double result_temp2 = alpha * d11 + (-d01 * alpha + d01); 264 | 265 | d_sliceI[thread_2D_pos.y * nPixYMap + thread_2D_pos.x] = beta * result_temp2 + (-result_temp1 * beta + result_temp1); 266 | 267 | } 268 | 269 | __global__ void differentiation_kernel(double* d_pVolume, double* d_sliceI, double tubeX, double rtubeY, double rtubeZ, 270 | double* const d_pObjX, double* const d_pObjY, double* const d_pObjZ, const int nPixX, const int nPixY, const int nPixXMap, 271 | const int nPixYMap, const double du, const double dv, const double dx, const double dy, const double dz, const unsigned int nz) { 272 | 273 | const int2 thread_2D_pos = make_int2(blockIdx.x * blockDim.x + threadIdx.x, 274 | blockIdx.y * blockDim.y + threadIdx.y); 275 | 276 | const int thread_1D_pos = (nPixX*nPixY*nz) + (thread_2D_pos.y * nPixY) + thread_2D_pos.x; 277 | 278 | // Make sure we don't try and access memory outside the detector 279 | // by having any threads mapped there return early 280 | if (thread_2D_pos.x >= nPixY || thread_2D_pos.y >= nPixX) 281 | return; 282 | 283 | /* 284 | 285 | S.3. Differentiation - Eq. 24 - Liu et al (2017) 286 | 287 | Detector integral projection 288 | ___________ 289 | |_A_|_B_|___| 290 | |_C_|_D_|___| 291 | |___|___|___| 292 | 293 | 294 | (thread_2D_pos.x,thread_2D_pos.y) 295 | ________________ 296 | |_A_|__B__|_____| 297 | |_C_|(0,0)|(0,1)| 298 | |___|(1,0)|(1,1)| 299 | 300 | Threads are lauched from D up to nPixX (thread_2D_pos.y) and nPixY (thread_2D_pos.x) 301 | i.e., they are running on the detector image. Thread (0,0) is on D. 302 | 303 | Coordinates on intergal projection: 304 | 305 | A = thread_2D_pos.y * nPixYMap + thread_2D_pos.x 306 | B = ((thread_2D_pos.y+1) * nPixYMap) + thread_2D_pos.x 307 | C = thread_2D_pos.y * nPixYMap + thread_2D_pos.x + 1 308 | D = ((thread_2D_pos.y+1) * nPixYMap) + thread_2D_pos.x + 1 309 | 310 | */ 311 | 312 | unsigned int coordA = thread_2D_pos.y * nPixYMap + thread_2D_pos.x; 313 | unsigned int coordB = ((thread_2D_pos.y + 1) * nPixYMap) + thread_2D_pos.x; 314 | unsigned int coordC = coordA + 1; 315 | unsigned int coordD = coordB + 1; 316 | 317 | // x - ray angle in X coord 318 | double gamma = atan((d_pObjX[thread_2D_pos.y] + (dx / 2.0) - tubeX) / (rtubeZ - d_pObjZ[nz])); 319 | 320 | // x - ray angle in Y coord 321 | double alpha = atan((d_pObjY[thread_2D_pos.x] + (dy / 2.0) - rtubeY) / (rtubeZ - d_pObjZ[nz])); 322 | 323 | 324 | double dA, dB, dC, dD; 325 | 326 | dA = d_sliceI[coordA]; 327 | dB = d_sliceI[coordB]; 328 | dC = d_sliceI[coordC]; 329 | dD = d_sliceI[coordD]; 330 | 331 | // Treat border of interpolated integral detector 332 | if (dC == 0 && dD == 0) { 333 | dC = dA; 334 | dD = dB; 335 | } 336 | 337 | 338 | // S.3.Differentiation - Eq. 24 - Liu et al(2017) 339 | d_pVolume[thread_1D_pos] += ((dD - dC - dB + dA)*(du*dv*dz / (cos(alpha)*cos(gamma)*dx*dy))); 340 | 341 | return; 342 | } 343 | 344 | __global__ void division_kernel(double* d_img, const int nPixX, const int nPixY, unsigned int nSlices, unsigned int nProj){ 345 | 346 | const int3 thread_2D_pos = make_int3(blockIdx.x * blockDim.x + threadIdx.x, 347 | blockIdx.y * blockDim.y + threadIdx.y, 348 | blockIdx.z * blockDim.z + threadIdx.z); 349 | 350 | const int thread_1D_pos = (nPixX*nPixY*thread_2D_pos.z) + (thread_2D_pos.y * nPixY) + thread_2D_pos.x; 351 | 352 | // Make sure we don't try and access memory outside the detector 353 | // by having any threads mapped there return early 354 | if (thread_2D_pos.x >= nPixY || thread_2D_pos.y >= nPixX || thread_2D_pos.z >= nSlices) 355 | return; 356 | 357 | d_img[thread_1D_pos] /= (double) nProj; 358 | 359 | } 360 | 361 | 362 | /**************************************************************************** 363 | * function: backprojectionDDb() - CUDA backprojection Branchless Distance Driven. * 364 | ****************************************************************************/ 365 | 366 | void backprojectionDDb(double* const h_pVolume, 367 | double* const h_pProj, 368 | double* const h_pTubeAngle, 369 | double* const h_pDetAngle, 370 | const unsigned int nProj, 371 | const unsigned int nPixX, 372 | const unsigned int nPixY, 373 | const unsigned int nSlices, 374 | const unsigned int nDetX, 375 | const unsigned int nDetY, 376 | const signed int idXProj, 377 | const double x_offset, 378 | const double y_offset, 379 | const double dx, 380 | const double dy, 381 | const double dz, 382 | const double du, 383 | const double dv, 384 | const double DSD, 385 | const double DDR, 386 | const double DAG) 387 | { 388 | 389 | // Unique GPU 390 | int devID = 0; 391 | 392 | cudaDeviceProp deviceProp; 393 | cudaGetDeviceProperties(&deviceProp, devID); 394 | cudaCheckErrors("cudaGetDeviceProperties"); 395 | 396 | const unsigned int maxThreadsPerBlock = deviceProp.maxThreadsPerBlock; 397 | 398 | 399 | printf("GPU Device %d: \"%s\" with compute capability %d.%d has %d Multi-Processors and %zu bytes of global memory\n\n", devID, 400 | deviceProp.name, deviceProp.major, deviceProp.minor, deviceProp.multiProcessorCount, deviceProp.totalGlobalMem); 401 | 402 | 403 | // Create timmer variables and start it 404 | StopWatchInterface *timer = NULL; 405 | sdkCreateTimer(&timer); 406 | sdkStartTimer(&timer); 407 | 408 | //cudaStream_t stream1; 409 | //cudaStreamCreate(&stream1); 410 | 411 | dim3 threadsPerBlock(1, 1, 1); 412 | dim3 blockSize(1, 1, 1); 413 | 414 | 415 | // Number of mapped detector and pixels 416 | const int nDetXMap = nDetX + 1; 417 | const int nDetYMap = nDetY + 1; 418 | const int nPixXMap = nPixX + 1; 419 | const int nPixYMap = nPixY + 1; 420 | 421 | 422 | // Convention: h_ variables live on host 423 | // Convention: d_ variables live on device (GPU global mem) 424 | double* d_pProj; 425 | double* d_sliceI; 426 | double* d_pVolume; 427 | double* d_pTubeAngle; 428 | double* d_pDetAngle; 429 | 430 | 431 | // Allocate global memory on the device, place result in "d_----" 432 | cudaMalloc((void **)&d_pProj, nDetXMap*nDetYMap*nProj * sizeof(double)); 433 | cudaMalloc((void **)&d_sliceI, nPixXMap*nPixYMap * sizeof(double)); 434 | cudaMalloc((void **)&d_pVolume, nPixX*nPixY*nSlices * sizeof(double)); 435 | cudaMalloc((void **)&d_pTubeAngle, nProj * sizeof(double)); 436 | cudaMalloc((void **)&d_pDetAngle, nProj * sizeof(double)); 437 | 438 | cudaCheckErrors("cudaMalloc Initial"); 439 | 440 | // Copy data from host memory "h_----" to device memory "d_----" 441 | cudaMemcpy((void *)d_pTubeAngle, (void *)h_pTubeAngle, nProj * sizeof(double), cudaMemcpyHostToDevice); 442 | cudaMemcpy((void *)d_pDetAngle, (void *)h_pDetAngle, nProj * sizeof(double), cudaMemcpyHostToDevice); 443 | 444 | cudaCheckErrors("cudaMemcpy Angles"); 445 | 446 | 447 | /* 448 | Copy projection data from host memory "h_----" to device memory "d_----" padding with zeros for image integation 449 | */ 450 | 451 | 452 | // Initialize first column and row with zeros 453 | double* h_pProj_tmp; 454 | double* d_pProj_tmp; 455 | 456 | threadsPerBlock.x = maxThreadsPerBlock; 457 | blockSize.x = (nDetXMap / maxThreadsPerBlock) + 1; 458 | 459 | for (unsigned int np = 0; np < nProj; np++) { 460 | 461 | // Pad on X coord direction 462 | pad_projections_kernel << > > (d_pProj, nDetXMap, nDetYMap, nDetXMap, np); 463 | 464 | // Pad on Y coord direction 465 | d_pProj_tmp = d_pProj + (nDetXMap*nDetYMap*np) + 1; 466 | cudaMemset(d_pProj_tmp, 0, nPixY * sizeof(double)); 467 | cudaCheckErrors("cudaMemset Padding Projections"); 468 | } 469 | 470 | 471 | // Copy projections data from host memory 472 | for (unsigned int np = 0; np < nProj; np++) 473 | for (unsigned int c = 0; c < nDetX; c++) { 474 | 475 | h_pProj_tmp = h_pProj + (c *nDetY) + (nDetX*nDetY*np); 476 | d_pProj_tmp = d_pProj + (((c + 1) *nDetYMap) + 1) + (nDetXMap*nDetYMap*np); 477 | 478 | cudaMemcpy((void *)d_pProj_tmp, (void *)h_pProj_tmp, nDetY * sizeof(double), cudaMemcpyHostToDevice); 479 | cudaCheckErrors("cudaMemcpy Projections"); 480 | } 481 | 482 | 483 | 484 | // Pointer for projections coordinates 485 | double* d_pDetX; 486 | double* d_pDetY; 487 | double* d_pDetZ; 488 | double* d_pObjX; 489 | double* d_pObjY; 490 | double* d_pObjZ; 491 | 492 | // Allocate global memory on the device for projections coordinates 493 | cudaMalloc((void **)&d_pDetX, nDetXMap * sizeof(double)); 494 | cudaMalloc((void **)&d_pDetY, nDetYMap * sizeof(double)); 495 | cudaMalloc((void **)&d_pDetZ, nDetYMap * sizeof(double)); 496 | cudaMalloc((void **)&d_pObjX, nPixXMap * sizeof(double)); 497 | cudaMalloc((void **)&d_pObjY, nPixYMap * sizeof(double)); 498 | cudaMalloc((void **)&d_pObjZ, nSlices * sizeof(double)); 499 | 500 | cudaCheckErrors("cudaMalloc Coordinates"); 501 | 502 | 503 | 504 | // Pointer for mapped coordinates 505 | double* d_pDetmY; 506 | double* d_pDetmX; 507 | 508 | 509 | // Allocate global memory on the device for mapped coordinates 510 | cudaMalloc((void **)&d_pDetmY, nDetYMap * sizeof(double)); 511 | cudaMalloc((void **)&d_pDetmX, nDetYMap * nDetXMap * sizeof(double)); 512 | 513 | cudaCheckErrors("cudaMalloc Map-Coordinates"); 514 | 515 | 516 | // Pointer for rotated detector coords 517 | double* d_pRdetY; 518 | double* d_pRdetZ; 519 | 520 | // Allocate global memory on the device for for rotated detector coords 521 | cudaMalloc((void **)&d_pRdetY, nDetYMap * sizeof(double)); 522 | cudaMalloc((void **)&d_pRdetZ, nDetYMap * sizeof(double)); 523 | 524 | cudaCheckErrors("cudaMalloc Rot-Coordinates"); 525 | 526 | // Generate detector and object boudaries 527 | 528 | threadsPerBlock.x = maxThreadsPerBlock; 529 | 530 | blockSize.x = (nDetX / maxThreadsPerBlock) + 1; 531 | 532 | map_boudaries_kernel << > > (d_pDetX, nDetXMap, (double)nDetX, -du, 0.0); 533 | 534 | blockSize.x = (nDetY / maxThreadsPerBlock) + 1; 535 | 536 | map_boudaries_kernel << > > (d_pDetY, nDetYMap, nDetY / 2.0, dv, 0.0); 537 | 538 | blockSize.x = (nPixX / maxThreadsPerBlock) + 1; 539 | 540 | map_boudaries_kernel << > > (d_pObjX, nPixXMap, (double)nPixX, -dx, 0.0); 541 | 542 | blockSize.x = (nPixY / maxThreadsPerBlock) + 1; 543 | 544 | map_boudaries_kernel << > > (d_pObjY, nPixYMap, nPixY / 2.0, dy, 0.0); 545 | 546 | blockSize.x = (nSlices / maxThreadsPerBlock) + 1; 547 | 548 | map_boudaries_kernel << > > (d_pObjZ, nSlices, 0.0, dz, DAG + (dz / 2.0)); 549 | 550 | 551 | //printf("Map Boundaries -- Threads:%d Blocks:%d \n", threads, blocks); 552 | 553 | 554 | // Initiate variables value with 0 555 | cudaMemset(d_pDetZ, 0, nDetYMap * sizeof(double)); 556 | cudaMemset(d_pVolume, 0, nPixX * nPixY * nSlices * sizeof(double)); 557 | 558 | cudaCheckErrors("cudaMemset Zeros"); 559 | 560 | //cudaDeviceSynchronize(); 561 | 562 | // X - ray tube initial position 563 | double tubeX = 0; 564 | double tubeY = 0; 565 | double tubeZ = DSD; 566 | 567 | // Iso - center position 568 | double isoY = 0; 569 | double isoZ = DDR; 570 | 571 | 572 | // Integration of 2D projection over the whole projections 573 | // (S.1.Integration. - Liu et al(2017)) 574 | 575 | // Naive integration o the X coord 576 | threadsPerBlock.x = 10; 577 | threadsPerBlock.y = 10; 578 | threadsPerBlock.z = 10; 579 | 580 | blockSize.x = (unsigned int)ceil(((double)nDetYMap / (threadsPerBlock.x - 1))); 581 | blockSize.y = 1; 582 | blockSize.z = (unsigned int)ceil((double)nProj / threadsPerBlock.z); 583 | 584 | for (int k = 0; k < ceil((double)nDetXMap / (threadsPerBlock.x - 1)); k++) { 585 | 586 | img_integration_kernel << > > (d_pProj, nDetXMap, nDetYMap, integrateXcoord, 0, k * 9, nProj); 587 | 588 | //printf("integration kernel -- threadsX:%d blocksX:%d threadsY:%d blocksY:%d \n", threadsPerBlock.x, blockSize.x, threadsPerBlock.y, blockSize.y); 589 | 590 | } 591 | 592 | // Naive integration o the Y coord 593 | threadsPerBlock.x = 10; 594 | threadsPerBlock.y = 10; 595 | threadsPerBlock.z = 10; 596 | 597 | blockSize.x = 1; 598 | blockSize.y = (unsigned int)ceil((double)nDetXMap / (threadsPerBlock.y - 1)); 599 | blockSize.z = (unsigned int)ceil((double)nProj / threadsPerBlock.z); 600 | 601 | 602 | for (int k = 0; k < ceil((double)nDetYMap / (threadsPerBlock.y - 1)); k++) { 603 | 604 | img_integration_kernel << > > (d_pProj, nDetXMap, nDetYMap, integrateYcoord, k * 9, 0, nProj); 605 | 606 | //printf("integration kernel -- threadsX:%d blocksX:%d threadsY:%d blocksY:%d \n", threadsPerBlock.x, blockSize.x, threadsPerBlock.y, blockSize.y); 607 | 608 | } 609 | 610 | double* d_pDetmX_tmp = d_pDetmX + (nDetYMap * (nDetXMap-2)); 611 | 612 | // Test if we will loop over all projs or not 613 | unsigned int projIni, projEnd, nProj2Run; 614 | if(idXProj == -1){ 615 | projIni = 0; 616 | projEnd = nProj; 617 | nProj2Run = nProj; 618 | } 619 | else{ 620 | nProj2Run = 1; 621 | projIni = (unsigned int) idXProj; 622 | projEnd = (unsigned int) idXProj + 1; 623 | } 624 | 625 | 626 | // For each projection 627 | for (unsigned int p = projIni; p < projEnd; p++) { 628 | 629 | // Get specif tube angle for the projection 630 | double theta = h_pTubeAngle[p] * M_PI / 180.0; 631 | 632 | // Get specif detector angle for the projection 633 | double phi = h_pDetAngle[p] * M_PI / 180.0; 634 | 635 | //printf("Tube angle:%f Det angle:%f\n", theta, phi); 636 | 637 | // Tube rotation 638 | double rtubeY = ((tubeY - isoY)*cos(theta) - (tubeZ - isoZ)*sin(theta)) + isoY; 639 | double rtubeZ = ((tubeY - isoY)*sin(theta) + (tubeZ - isoZ)*cos(theta)) + isoZ; 640 | 641 | //printf("R tube Y:%f R tube Z:%f\n", rtubeY, rtubeZ); 642 | 643 | // Detector rotation 644 | threadsPerBlock.x = maxThreadsPerBlock; 645 | threadsPerBlock.y = 1; 646 | threadsPerBlock.z = 1; 647 | 648 | blockSize.x = (nDetYMap / maxThreadsPerBlock) + 1; 649 | blockSize.y = 1; 650 | blockSize.z = 1; 651 | 652 | rot_detector_kernel << > > (d_pRdetY, d_pRdetZ, d_pDetY, d_pDetZ, isoY, isoZ, phi, nDetYMap); 653 | 654 | //cudaDeviceSynchronize(); 655 | 656 | //printf("Detector rotation -- Threads:%d Blocks:%d \n", threads, blocks); 657 | 658 | 659 | // For each slice 660 | for (unsigned int nz = 0; nz < nSlices; nz++) { 661 | 662 | /* 663 | 664 | Map detector onto XY plane(Inside proj loop in case detector rotates) 665 | 666 | *** Note: Matlab has linear indexing as a column of elements, i.e, the elements are actually stored in memory as queued columns. 667 | So threads in X are launched in the column direction (DBT Y coord), and threads in Y are launched in the row direction (DBT X coord). 668 | 669 | */ 670 | 671 | threadsPerBlock.x = 32; 672 | threadsPerBlock.y = 32; 673 | threadsPerBlock.z = 1; 674 | 675 | blockSize.x = (nDetYMap / threadsPerBlock.x) + 1; 676 | blockSize.y = (nDetXMap / threadsPerBlock.y) + 1; 677 | blockSize.z = 1; 678 | 679 | 680 | mapDet2Slice_kernel << > > (d_pDetmX, d_pDetmY, tubeX, rtubeY, rtubeZ, d_pDetX, d_pRdetY, d_pRdetZ, d_pObjZ, nDetXMap, nDetYMap, nz); 681 | //cudaDeviceSynchronize(); 682 | 683 | //printf("Map detector onto XY plane -- ThreadsX:%d ThreadsY:%d BlocksX:%d BlocksY:%d\n", threadsPerBlock.x, threadsPerBlock.y, blockSizeX, blockSizeY); 684 | 685 | /* 686 | S.2. Interpolation - Liu et al (2017) 687 | */ 688 | 689 | blockSize.x = (nPixYMap / threadsPerBlock.x) + 1; 690 | blockSize.y = (nPixXMap / threadsPerBlock.y) + 1; 691 | 692 | bilinear_interpolation_kernel_GPU << > > (d_sliceI, d_pProj, d_pObjX, d_pObjY, d_pDetmX_tmp, d_pDetmY, nPixXMap, nPixYMap, nDetXMap, nDetYMap, nDetX, nDetY, p); 693 | 694 | 695 | /* 696 | S.3. Differentiation - Eq. 24 - Liu et al (2017) 697 | */ 698 | 699 | blockSize.x = (nPixY / threadsPerBlock.x) + 1; 700 | blockSize.y = (nPixX / threadsPerBlock.y) + 1; 701 | 702 | differentiation_kernel << > > (d_pVolume, d_sliceI, tubeX, rtubeY, rtubeZ, d_pObjX, d_pObjY, d_pObjZ, nPixX, nPixY, nPixXMap, nPixYMap, du, dv, dx, dy, dz, nz); 703 | 704 | } // Loop end slices 705 | 706 | } // Loop end Projections 707 | 708 | 709 | // Normalize volume dividing by the number of projs 710 | threadsPerBlock.x = 10; 711 | threadsPerBlock.y = 10; 712 | threadsPerBlock.z = 10; 713 | 714 | blockSize.x = (nPixY / threadsPerBlock.x) + 1; 715 | blockSize.y = (nPixX / threadsPerBlock.y) + 1; 716 | blockSize.z = (nSlices / threadsPerBlock.z) + 1; 717 | 718 | division_kernel << > > (d_pVolume, nPixX, nPixY, nSlices, nProj2Run); 719 | 720 | // d_pVolume 721 | cudaMemcpy((void *)h_pVolume, (void *)d_pVolume, nSlices* nPixX * nPixY * sizeof(double), cudaMemcpyDeviceToHost); 722 | cudaCheckErrors("cudaMemcpy Final"); 723 | 724 | 725 | cudaFree(d_pProj); 726 | cudaFree(d_sliceI); 727 | cudaFree(d_pVolume); 728 | cudaFree(d_pTubeAngle); 729 | cudaFree(d_pDetAngle); 730 | cudaFree(d_pDetX); 731 | cudaFree(d_pDetY); 732 | cudaFree(d_pDetZ); 733 | cudaFree(d_pObjX); 734 | cudaFree(d_pObjY); 735 | cudaFree(d_pObjZ); 736 | cudaFree(d_pDetmY); 737 | cudaFree(d_pDetmX); 738 | cudaFree(d_pRdetY); 739 | cudaFree(d_pRdetZ); 740 | 741 | cudaCheckErrors("cudaFree Final"); 742 | 743 | sdkStopTimer(&timer); 744 | printf("Processing time: %f (ms)\n", sdkGetTimerValue(&timer)); 745 | sdkDeleteTimer(&timer); 746 | 747 | cudaDeviceReset(); 748 | 749 | return; 750 | 751 | } -------------------------------------------------------------------------------- /pydbt/sources/projectionDD/projectionDD.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: January, 2021 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % projectionDD_lib(pVolume, pProj, pGeo, idXProj) 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This function calculates the volume projection based on the 12 | % Distance-Driven principle. It works by calculating the overlap in X 13 | % and Y axis of the volume and the detector boundaries. 14 | % The geometry is for DBT with half cone-beam. All parameters are set 15 | % in "ParameterSettings" code. 16 | % 17 | % INPUT: 18 | % 19 | % - pVolume = 3D volume for projection 20 | % - pGeo = Parameter of all geometry 21 | % - idXProj = projection number to be projected 22 | % 23 | % OUTPUT: 24 | % 25 | % - pProj = pointer to projections for each angle. 26 | % 27 | % Reference: Three-Dimensional Digital Tomosynthesis - Yulia 28 | % Levakhina (2014), Cap 3.6 and 3.7. 29 | % 30 | % Original Paper: De Man, Bruno, and Samit Basu. "Distance-driven 31 | % projection and backprojection in three dimensions." Physics in 32 | % Medicine & Biology (2004). 33 | % 34 | % --------------------------------------------------------------------- 35 | % Copyright (C) <2018> 36 | % 37 | % This program is free software: you can redistribute it and/or modify 38 | % it under the terms of the GNU General Public License as published by 39 | % the Free Software Foundation, either version 3 of the License, or 40 | % (at your option) any later version. 41 | % 42 | % This program is distributed in the hope that it will be useful, 43 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 44 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 45 | % GNU General Public License for more details. 46 | % 47 | % You should have received a copy of the GNU General Public License 48 | % along with this program. If not, see . 49 | %} 50 | % ========================================================================= 51 | %% 3-D Distance Driven Projection Code 52 | */ 53 | 54 | #include "projectionDD.h" 55 | 56 | extern "C" void projectionDD_lib(double* const pVolume, 57 | double* const pProj, 58 | float* const pGeo, 59 | const signed int idXProj){ 60 | 61 | 62 | const int unsigned nPixX = (const int)pGeo[0]; 63 | const int unsigned nPixY = (const int)pGeo[1]; 64 | const int unsigned nSlices = (const int)pGeo[2]; 65 | const int unsigned nDetX = (const int)pGeo[3]; 66 | const int unsigned nDetY = (const int)pGeo[4]; 67 | 68 | const double dx = (const double)pGeo[5]; 69 | const double dy = (const double)pGeo[6]; 70 | const double dz = (const double)pGeo[7]; 71 | const double du = (const double)pGeo[8]; 72 | const double dv = (const double)pGeo[9]; 73 | 74 | const double DSD = (const double)pGeo[10]; 75 | const double DDR = (const double)pGeo[12]; 76 | const double DAG = (const double)pGeo[14]; 77 | 78 | const int unsigned nProj = (const int)pGeo[15]; 79 | 80 | const double tubeAngle = (const double)pGeo[16]; 81 | const double detAngle = (const double)pGeo[17]; 82 | 83 | const double x_offset = (const double)pGeo[18]; 84 | const double y_offset = (const double)pGeo[19]; 85 | 86 | double* const pTubeAngle = (double*)malloc(nProj * sizeof(double)); 87 | double* const pDetAngle = (double*)malloc(nProj * sizeof(double)); 88 | 89 | linspace(-tubeAngle/2, tubeAngle/2, nProj, pTubeAngle); 90 | linspace(-detAngle/2, detAngle/2, nProj, pDetAngle); 91 | 92 | 93 | //printf("Nx:%d Ny:%d Nz:%d \nNu:%d Nv:%d \nDx:%.2f Dy:%.2f Dz:%.2f \nDu:%.2f Dv:%.2f \nDSD:%.2f DDR:%.2f \n", nPixX, nPixY, nSlices, nDetX, nDetY, dx, dy, dz, du, dv, DSD, DDR); 94 | 95 | 96 | const int nDetXMap = nDetX + 1; 97 | const int nDetYMap = nDetY + 1; 98 | const int nPixXMap = nPixX + 1; 99 | const int nPixYMap = nPixY + 1; 100 | 101 | // Memory allocation for projections coordinates 102 | double* const pDetX = (double*)malloc(nDetXMap * sizeof(double)); 103 | double* const pDetY = (double*)malloc(nDetYMap * sizeof(double)); 104 | double* const pDetZ = (double*)malloc(nDetYMap * sizeof(double)); 105 | double* const pObjX = (double*)malloc(nPixXMap * sizeof(double)); 106 | double* const pObjY = (double*)malloc(nPixYMap * sizeof(double)); 107 | double* const pObjZ = (double*)malloc(nSlices * sizeof(double)); 108 | 109 | // Memory allocation for mapped coordinates 110 | double* const pDetmY = (double*)malloc(nDetYMap * sizeof(double)); 111 | double* const pDetmX = (double*)malloc(nDetYMap * nDetXMap * sizeof(double)); 112 | double* const pPixmX = (double*)malloc(nPixXMap * sizeof(double)); 113 | double* const pPixmY = (double*)malloc(nPixYMap * sizeof(double)); 114 | 115 | 116 | // Allocate memory for rotated detector coords 117 | double* const pRdetY = (double*)malloc(nDetYMap * sizeof(double)); 118 | double* const pRdetZ = (double*)malloc(nDetYMap * sizeof(double)); 119 | 120 | // Memory allocation for slice 121 | double* const pSlice = (double*)malloc(nPixX * nPixY * sizeof(double)); 122 | 123 | 124 | // Map detector and object boudaries 125 | mapBoudaries(pDetX, nDetXMap, (double)nDetX, -du, 0.0); 126 | 127 | mapBoudaries(pDetY, nDetYMap, nDetY / 2.0, dv, 0.0); 128 | 129 | mapBoudaries(pDetZ, nDetYMap, 0.0, 0.0, 0.0); 130 | 131 | mapBoudaries(pObjX, nPixXMap, (double)nPixX, -dx, x_offset); 132 | 133 | mapBoudaries(pObjY, nPixYMap, nPixY / 2.0, dy, y_offset); 134 | 135 | mapBoudaries(pObjZ, nSlices, 0.0, dz, DAG + (dz / 2.0)); 136 | 137 | 138 | // X - ray tube initial position 139 | double tubeX = 0; 140 | double tubeY = 0; 141 | double tubeZ = DSD; 142 | 143 | // Iso - center position 144 | double isoY = 0; 145 | double isoZ = DDR; 146 | 147 | // Allocate memory for temp projection variable 148 | double* const pProjt = (double*)malloc(nDetY * nDetX * nProj * sizeof(double)); 149 | 150 | // Initiate temp proj values with zeros 151 | for (int p = 0; p < nProj; p++) 152 | for (int u = 0; u < nDetX; u++) 153 | for (int v = 0; v < nDetY; v++) 154 | pProjt[(p * nDetY * nDetX) + (u * nDetY) + v] = 0.0; 155 | 156 | // Test if we will loop over all projs or not 157 | unsigned int projIni, projEnd; 158 | if(idXProj == -1){ 159 | projIni = 0; 160 | projEnd = nProj; 161 | } 162 | else{ 163 | projIni = (unsigned int) idXProj; 164 | projEnd = (unsigned int) idXProj + 1; 165 | } 166 | 167 | 168 | // For each projection 169 | for (unsigned int p = projIni; p < projEnd; p++) { 170 | 171 | // Get specif tube angle for the projection 172 | double theta = pTubeAngle[p] * M_PI / 180.0; 173 | 174 | // Get specif detector angle for the projection 175 | double phi = pDetAngle[p] * M_PI / 180.0; 176 | 177 | 178 | // Tube rotation 179 | double rtubeY = ((tubeY - isoY)*cos(theta) - (tubeZ - isoZ)*sin(theta)) + isoY; 180 | double rtubeZ = ((tubeY - isoY)*sin(theta) + (tubeZ - isoZ)*cos(theta)) + isoZ; 181 | 182 | // printf("R tube Y:%f R tube Z:%f\n", rtubeY, rtubeZ); 183 | 184 | // Detector rotation 185 | for (int y = 0; y < nDetYMap; y++) { 186 | pRdetY[y] = ((pDetY[y] - isoY)*cos(phi) - (pDetZ[y] - isoZ)*sin(phi)) + isoY; 187 | pRdetZ[y] = ((pDetY[y] - isoY)*sin(phi) + (pDetZ[y] - isoZ)*cos(phi)) + isoZ; 188 | } 189 | 190 | 191 | // Map detector onto XY plane(Inside proj loop in case detector rotates) 192 | mapp2xy(pDetmX, pDetmY, tubeX, rtubeY, rtubeZ, pDetX, pRdetY, pRdetZ, nDetXMap, nDetYMap); 193 | 194 | 195 | // Pixel start index and increment 196 | int detIstart = 0; 197 | int detIinc = 1; 198 | 199 | // Mapped detector length 200 | double deltaDetmY = pDetmY[detIstart + detIinc] - pDetmY[detIstart]; 201 | 202 | 203 | // For each slice 204 | for (int z = 0; z < nSlices; z++) { 205 | 206 | // Flip X(Img coord is reverse to Global) 207 | for (int x = 0, x_inv = nPixX - 1; x < nPixX; x++, x_inv--) 208 | for (int y = 0; y < nPixY; y++) 209 | pSlice[(x * nPixY) + y] = pVolume[(z * nPixX * nPixY) + (x_inv * nPixY) + y]; 210 | 211 | // Tmp Z coords value for Y direction 212 | double* const pObjZt = (double*)malloc(nPixYMap * sizeof(double)); 213 | 214 | // Tmp Pixel X mapped coords 215 | double* const pPixmXt = (double*)malloc(nPixYMap * nPixXMap * sizeof(double)); 216 | 217 | // Get specif Z coord value for each slice 218 | for (int k = 0; k < nPixYMap; k++) pObjZt[k] = pObjZ[z]; 219 | 220 | // Map slice onto XY plane 221 | mapp2xy(pPixmXt, pPixmY, tubeX, rtubeY, rtubeZ, pObjX, pObjY, pObjZt, nPixXMap, nPixYMap); 222 | 223 | // Flip X(Img coord is reverse to Global) 224 | for (int x = 0, x_inv = nPixXMap - 1; x < nPixXMap; x++, x_inv--) 225 | pPixmX[x] = pPixmXt[x_inv * nPixYMap]; 226 | 227 | // Free temp variables 228 | free(pObjZt); 229 | free(pPixmXt); 230 | 231 | // Pixel start index and increment 232 | int pixIstart = 0; 233 | int pixIinc = 1; 234 | 235 | // Mapped pixel length 236 | double deltaPixmX = pPixmX[pixIstart + pixIinc] - pPixmX[pixIstart]; 237 | double deltaPixmY = pPixmY[pixIstart + pixIinc] - pPixmY[pixIstart]; 238 | 239 | // Start pixel and detector indices 240 | int detIndY = detIstart; 241 | int pixIndY = pixIstart; 242 | 243 | // Case 1 244 | // Find first detector overlap maped with pixel maped on Y 245 | if (pDetmY[detIndY] - pPixmY[pixIstart] < -deltaDetmY) 246 | while (pDetmY[detIndY] - pPixmY[pixIstart] < -deltaDetmY) 247 | detIndY = detIndY + detIinc; 248 | 249 | else 250 | // Case 2 251 | // Find first pixel overlap maped with detector maped on Y 252 | if (pDetmY[detIstart] - pPixmY[pixIndY] > deltaPixmY) 253 | while (pDetmY[detIstart] - pPixmY[pixIndY] > deltaPixmY) 254 | pixIndY = pixIndY + pixIinc; 255 | 256 | double moving_left_boundaryY; 257 | 258 | // Get the left coordinate of the first overlap on Y axis 259 | if (pDetmY[detIndY] < pPixmY[pixIndY]) 260 | moving_left_boundaryY = pPixmY[pixIndY]; 261 | else 262 | moving_left_boundaryY = pDetmY[detIndY]; 263 | 264 | 265 | // Allocate memory for specif row of X map detector coords 266 | double* const pDetmXrow = (double*)malloc(nDetXMap * sizeof(double)); 267 | 268 | double overLapY; 269 | 270 | // Loop over Y intersections 271 | while ((detIndY < nDetY) && (pixIndY < nPixY)) { 272 | 273 | double alpha = (double)atan((pDetmY[detIndY] + (deltaDetmY / 2) - rtubeY) / rtubeZ); 274 | 275 | // Case A, when you jump to the next detector boundarie but stay 276 | // in the same pixel 277 | if (pDetmY[detIndY + 1] <= pPixmY[pixIndY + 1]) 278 | overLapY = (pDetmY[detIndY + 1] - moving_left_boundaryY) / deltaDetmY; // Normalized overlap Calculation 279 | 280 | else 281 | // Case B, when you jump to the next pixel boundarie but stay 282 | // in the same detector 283 | overLapY = (pPixmY[pixIndY + 1] - moving_left_boundaryY) / deltaDetmY; // Normalized overlap Calculation 284 | 285 | // ***** X overlap ***** 286 | int detIndX = detIstart; 287 | int pixIndX = pixIstart; 288 | 289 | 290 | // Get row / coll of X flipped, which correspond to that Y overlap det 291 | for (int x = 0, x_inv = nDetXMap - 1; x < nDetXMap; x++, x_inv--) 292 | pDetmXrow[x] = pDetmX[(x_inv * nDetYMap) + detIndY]; 293 | 294 | // Mapped detecor length on X 295 | double deltaDetmX = pDetmXrow[detIstart + detIinc] - pDetmXrow[detIstart]; 296 | 297 | // Case 1 298 | // Find first detector overlap maped with pixel maped on X 299 | if (pDetmXrow[detIndX] - pPixmX[pixIstart] < -deltaDetmX) 300 | while (pDetmXrow[detIndX] - pPixmX[pixIstart] < -deltaDetmX) 301 | detIndX = detIndX + detIinc; 302 | 303 | else 304 | // Case 2 305 | // Find first pixel overlap maped with detector maped on X 306 | if (pDetmXrow[detIstart] - pPixmX[pixIndX] > deltaPixmX) 307 | while (pDetmXrow[detIstart] - pPixmX[pixIndY] > deltaPixmX) 308 | pixIndX = pixIndX + pixIinc; 309 | 310 | double moving_left_boundaryX; 311 | 312 | // Get the left coordinate of the first overlap on X axis 313 | if (pDetmXrow[detIndX] < pPixmX[pixIndX]) 314 | moving_left_boundaryX = pPixmX[pixIndX]; 315 | else 316 | moving_left_boundaryX = pDetmXrow[detIndX]; 317 | 318 | 319 | // Loop over X intersections 320 | while ((detIndX < nDetX) && (pixIndX < nPixX)) { 321 | 322 | double gamma = (double)atan((pDetmXrow[detIndX] + (deltaDetmX / 2) - tubeX) / rtubeZ); 323 | 324 | // Case A, when you jump to the next detector boundarie but stay 325 | // in the same pixel 326 | if (pDetmXrow[detIndX + 1] <= pPixmX[pixIndX + 1]) { 327 | 328 | double overLapX = (pDetmXrow[detIndX + 1] - moving_left_boundaryX) / deltaDetmX; // Normalized overlap Calculation 329 | 330 | pProjt[(p * nDetY * nDetX) + (detIndX * nDetY) + detIndY] += overLapX * overLapY * pSlice[pixIndX * nPixY + pixIndY] * dz / ((double)cos(alpha) * (double)cos(gamma)); 331 | 332 | detIndX = detIndX + detIinc; 333 | moving_left_boundaryX = pDetmXrow[detIndX]; 334 | } 335 | else { 336 | // Case B, when you jump to the next pixel boundarie but stay 337 | // in the same detector 338 | 339 | double overLapX = (pPixmX[pixIndX + 1] - moving_left_boundaryX) / deltaDetmX; // Normalized overlap Calculation 340 | 341 | pProjt[(p * nDetY * nDetX) + (detIndX * nDetY) + detIndY] += overLapX * overLapY * pSlice[pixIndX * nPixY + pixIndY] * dz / ((double)cos(alpha) * (double)cos(gamma)); 342 | 343 | pixIndX = pixIndX + pixIinc; 344 | moving_left_boundaryX = pPixmX[pixIndX]; 345 | 346 | } 347 | 348 | } 349 | // ***** Back to Y overlap ***** 350 | 351 | // Case A, when you jump to the next detector boundarie but stay 352 | // in the same pixel 353 | if (pDetmY[detIndY + 1] <= pPixmY[pixIndY + 1]) { 354 | detIndY = detIndY + detIinc; 355 | moving_left_boundaryY = pDetmY[detIndY]; 356 | } 357 | else { 358 | // Case B, when you jump to the next pixel boundarie but stay 359 | // in the same detector 360 | pixIndY = pixIndY + pixIinc; 361 | moving_left_boundaryY = pPixmY[pixIndY]; 362 | } 363 | 364 | } // Y Overlap loop 365 | 366 | // Free memory 367 | free(pDetmXrow); 368 | 369 | } // Loop end slices 370 | 371 | } // Loop end Projections 372 | 373 | // Free memory 374 | free(pSlice); 375 | free(pDetX); 376 | free(pDetY); 377 | free(pDetZ); 378 | free(pObjX); 379 | free(pObjY); 380 | free(pObjZ); 381 | free(pDetmY); 382 | free(pDetmX); 383 | free(pPixmX); 384 | free(pPixmY); 385 | free(pRdetY); 386 | free(pRdetZ); 387 | free(pTubeAngle); 388 | free(pDetAngle); 389 | 390 | // Flip projection back X(Img coord is reverse to Global) 391 | for (int p = 0; p < nProj; p++) 392 | for (int x = 0, x_inv = nDetX - 1; x < nDetX; x++, x_inv--) 393 | for (int y = 0; y < nDetY; y++) 394 | pProj[(p * nDetX * nDetY) + (x * nDetY) + y] = pProjt[(p * nDetX * nDetY) + (x_inv * nDetY) + y]; 395 | 396 | // Free memory 397 | free(pProjt); 398 | 399 | return; 400 | } 401 | 402 | // Make boundaries of detector and slices 403 | void mapBoudaries(double* pBound, 404 | const int nElem, 405 | const double valueLeftBound, 406 | const double sizeElem, 407 | const double offset){ 408 | 409 | for (int k = 0; k < nElem; k++) 410 | pBound[k] = (k - valueLeftBound) * sizeElem + offset; 411 | 412 | return; 413 | } 414 | 415 | // Map on XY plane 416 | void mapp2xy(double* const pXmapp, 417 | double* const pYmapp, 418 | double tubeX, 419 | double tubeY, 420 | double tubeZ, 421 | double* const pXcoord, 422 | double* const pYcoord, 423 | double* const pZcoord, 424 | const int nXelem, 425 | const int nYelem) { 426 | 427 | 428 | for (int x = 0; x < nXelem; x++) 429 | for (int y = 0; y < nYelem; y++) { 430 | 431 | int ind = (x * nYelem) + y; 432 | pXmapp[ind] = pXcoord[x] + pZcoord[y] * (pXcoord[x] - tubeX) / (tubeZ - pZcoord[y]); 433 | 434 | if (x == 0) 435 | pYmapp[y] = pYcoord[y] + pZcoord[y] * (pYcoord[y] - tubeY) / (tubeZ - pZcoord[y]); 436 | } 437 | 438 | 439 | return; 440 | } 441 | 442 | // Linear spaced vector 443 | // Ref: https://stackoverflow.com/a/27030598/8682939 444 | void linspace(double start, 445 | double end, 446 | int num, 447 | double* pLinspaced){ 448 | 449 | double delta; 450 | 451 | if(num == 0) 452 | delta = 0; 453 | else{ 454 | if(num == 1) 455 | delta = 1; 456 | else 457 | delta = (end - start) / (num - 1); 458 | } 459 | 460 | if((abs(start) < 0.00001) && (abs(end) < 0.00001)) 461 | delta = 0; 462 | 463 | for (int k = 0; k < num; k++){ 464 | pLinspaced[k] = start + k * delta; 465 | } 466 | 467 | return; 468 | } -------------------------------------------------------------------------------- /pydbt/sources/projectionDD/projectionDD.h: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: January, 2021 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This is the header function 12 | % --------------------------------------------------------------------- 13 | % Copyright (C) <2018> 14 | % 15 | % This program is free software: you can redistribute it and/or modify 16 | % it under the terms of the GNU General Public License as published by 17 | % the Free Software Foundation, either version 3 of the License, or 18 | % (at your option) any later version. 19 | % 20 | % This program is distributed in the hope that it will be useful, 21 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | % GNU General Public License for more details. 24 | % 25 | % You should have received a copy of the GNU General Public License 26 | % along with this program. If not, see . 27 | %} 28 | % ========================================================================= 29 | %% 3-D Distance Driven Projection Code 30 | */ 31 | 32 | #include 33 | #define _USE_MATH_DEFINES 34 | #include 35 | 36 | void mapBoudaries(double* pBound, 37 | const int nElem, 38 | const double valueLeftBound, 39 | const double sizeElem, 40 | const double offset); 41 | 42 | void mapp2xy(double* const pXmapp, 43 | double* const pYmapp, 44 | double tubeX, 45 | double tubeY, 46 | double tubeZ, 47 | double* const pXcoord, 48 | double* const pYcoord, 49 | double* const pZcoord, 50 | const int nXelem, 51 | const int nYelem); 52 | 53 | void linspace(double start, 54 | double end, 55 | int num, 56 | double* pLinspaced); -------------------------------------------------------------------------------- /pydbt/sources/projectionDDb/projectionDDb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: January, 2020 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % projectionDDb_lib(pVolume, pProj, pGeo, idXProj) 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This function calculates the volume projection based on the 12 | % Branchless Distance-Driven principle. 13 | % The pGeometry is for DBT with half cone-beam. All parameters are set 14 | % in "ParameterSettings" code. 15 | % 16 | % INPUT: 17 | % 18 | % - pVolume = 3D volume for projection 19 | % - pGeo = Parameter of all geometry 20 | % - idXProj = projection number to be projected 21 | % 22 | % OUTPUT: 23 | % 24 | % - pProj = pointer to projections for each angle. 25 | % 26 | % Reference: 27 | % - Branchless Distance Driven Projection and Backprojection, 28 | % Samit Basu and Bruno De Man (2006) 29 | % - GPU Acceleration of Branchless Distance Driven Projection and 30 | % Backprojection, Liu et al (2016) 31 | % - GPU-Based Branchless Distance-Driven Projection and Backprojection, 32 | % Liu et al (2017) 33 | % - A GPU Implementation of Distance-Driven Computed Tomography, 34 | % Ryan D. Wagner (2017) 35 | % --------------------------------------------------------------------- 36 | % Copyright (C) <2020> 37 | % 38 | % This program is free software: you can redistribute it and/or modify 39 | % it under the terms of the GNU General Public License as published by 40 | % the Free Software Foundation, either version 3 of the License, or 41 | % (at your option) any later version. 42 | % 43 | % This program is distributed in the hope that it will be useful, 44 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 45 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 46 | % GNU General Public License for more details. 47 | % 48 | % You should have received a copy of the GNU General Public License 49 | % along with this program. If not, see . 50 | %} 51 | % ========================================================================= 52 | %% 3-D Projection Branchless Distance Driven Code (CPU-Multithread) 53 | */ 54 | 55 | #include "projectionDDb.hpp" 56 | 57 | extern "C" void projectionDDb_lib(double* const pVolume, 58 | double* const pProj, 59 | float* const pGeo, 60 | const signed int idXProj){ 61 | 62 | 63 | const int unsigned nPixX = (const int)pGeo[0]; 64 | const int unsigned nPixY = (const int)pGeo[1]; 65 | const int unsigned nSlices = (const int)pGeo[2]; 66 | const int unsigned nDetX = (const int)pGeo[3]; 67 | const int unsigned nDetY = (const int)pGeo[4]; 68 | 69 | const double dx = (const double)pGeo[5]; 70 | const double dy = (const double)pGeo[6]; 71 | const double dz = (const double)pGeo[7]; 72 | const double du = (const double)pGeo[8]; 73 | const double dv = (const double)pGeo[9]; 74 | 75 | const double DSD = (const double)pGeo[10]; 76 | const double DDR = (const double)pGeo[12]; 77 | const double DAG = (const double)pGeo[14]; 78 | 79 | const int unsigned nProj = (const int)pGeo[15]; 80 | 81 | const double tubeAngle = (const double)pGeo[16]; 82 | const double detAngle = (const double)pGeo[17]; 83 | 84 | double* const pTubeAngle = (double*)malloc(nProj * sizeof(double)); 85 | double* const pDetAngle = (double*)malloc(nProj * sizeof(double)); 86 | 87 | const double x_offset = (const double)pGeo[18]; 88 | const double y_offset = (const double)pGeo[19]; 89 | 90 | linspace(-tubeAngle/2, tubeAngle/2, nProj, pTubeAngle); 91 | linspace(-detAngle/2, detAngle/2, nProj, pDetAngle); 92 | 93 | // printf("Nx:%d Ny:%d Nz:%d \nNu:%d Nv:%d \nDx:%.2f Dy:%.2f Dz:%.2f \nDu:%.2f Dv:%.2f \nDSD:%.2f DDR:%.2f \nTube angle:%.2f \nDet angle:%.2f", nPixX, nPixY, nSlices, nDetX, nDetY, dx, dy, dz, du, dv, DSD, DDR, tubeAngle, detAngle); 94 | 95 | projectionDDb(pProj, pVolume, pTubeAngle, pDetAngle, nProj, nPixX, nPixY, nSlices, nDetX, nDetY, idXProj, x_offset, y_offset, dx, dy, dz, du, dv, DSD, DDR, DAG); 96 | 97 | free(pTubeAngle); 98 | free(pDetAngle); 99 | 100 | return; 101 | 102 | } 103 | 104 | 105 | //CPU Branchless Distance-driven projection 106 | void projectionDDb(double* const pProj, 107 | double* const pVolume, 108 | double* const pTubeAngle, 109 | double* const pDetAngle, 110 | const unsigned int nProj, 111 | const unsigned int nPixX, 112 | const unsigned int nPixY, 113 | const unsigned int nSlices, 114 | const unsigned int nDetX, 115 | const unsigned int nDetY, 116 | const signed int idXProj, 117 | const double x_offset, 118 | const double y_offset, 119 | const double dx, 120 | const double dy, 121 | const double dz, 122 | const double du, 123 | const double dv, 124 | const double DSD, 125 | const double DDR, 126 | const double DAG) 127 | { 128 | 129 | nThreads = omp_get_max_threads(); 130 | 131 | printf("CPU running with maximum number of threads: %d \n", nThreads); 132 | 133 | clock_t time; 134 | 135 | time = clock(); 136 | 137 | // Number of mapped detector and pixels 138 | const int nDetXMap = nDetX + 1; 139 | const int nDetYMap = nDetY + 1; 140 | const int nPixXMap = nPixX + 1; 141 | const int nPixYMap = nPixY + 1; 142 | 143 | 144 | // Allocate memory 145 | double* const projI = (double*)malloc(nDetXMap*nDetYMap * sizeof(double)); 146 | 147 | 148 | // Pointer for projections coordinates 149 | // Allocate memory for projections coordinates 150 | double* const pDetX = (double*)malloc(nDetXMap * sizeof(double)); 151 | double* const pDetY = (double*)malloc(nDetYMap * sizeof(double)); 152 | double* const pDetZ = (double*)malloc(nDetYMap * sizeof(double)); 153 | double* const pObjX = (double*)malloc(nPixXMap * sizeof(double)); 154 | double* const pObjY = (double*)malloc(nPixYMap * sizeof(double)); 155 | double* const pObjZ = (double*)malloc(nSlices * sizeof(double)); 156 | 157 | 158 | // Pointer for mapped coordinates 159 | // Allocate memory for mapped coordinates 160 | double* const pDetmY = (double*)malloc(nDetYMap * sizeof(double)); 161 | double* const pDetmX = (double*)malloc(nDetYMap * nDetXMap * sizeof(double)); 162 | 163 | 164 | 165 | // Pointer for rotated detector coords 166 | // Allocate memory for rotated detector coords 167 | double* const pRdetY = (double*)malloc(nDetYMap * sizeof(double)); 168 | double* const pRdetZ = (double*)malloc(nDetYMap * sizeof(double)); 169 | 170 | 171 | // Map detector and object boudaries 172 | mapBoudaries(pDetX, nDetXMap, (double)nDetX, -du, 0.0); 173 | 174 | mapBoudaries(pDetY, nDetYMap, nDetY / 2.0, dv, 0.0); 175 | 176 | mapBoudaries(pDetZ, nDetYMap, 0.0, 0.0, 0.0); 177 | 178 | mapBoudaries(pObjX, nPixXMap, (double)nPixX, -dx, x_offset); 179 | 180 | mapBoudaries(pObjY, nPixYMap, nPixY / 2.0, dy, y_offset); 181 | 182 | mapBoudaries(pObjZ, nSlices, 0.0, dz, DAG + (dz / 2.0)); 183 | 184 | 185 | // X - ray tube initial position 186 | double tubeX = 0; 187 | double tubeY = 0; 188 | double tubeZ = DSD; 189 | 190 | // Iso - center position 191 | double isoY = 0; 192 | double isoZ = DDR; 193 | 194 | 195 | // Allocate memory for temp projection variable 196 | double* const pProjt = (double*)malloc(nDetY *nDetX * nProj * sizeof(double)); 197 | double* const pVolumet = (double*)malloc(nPixYMap *nPixXMap * nSlices * sizeof(double)); 198 | 199 | 200 | // Initiate variables value with 0 201 | for (int p = 0; p < nProj; p++) 202 | for (int x = 0; x < nDetX; x++) 203 | for (int y = 0; y < nDetY; y++) 204 | pProjt[(p*nDetY *nDetX) + (x*nDetY) + y] = 0; 205 | 206 | 207 | // Integration of 2D slices over the whole volume 208 | // (S.1.Integration. - Liu et al(2017)) 209 | /* 210 | 211 | 2 - D image 212 | 213 | -->J 214 | | ------------- 215 | v | | 216 | I | | 217 | | | 218 | | | 219 | ------------- 220 | */ 221 | 222 | 223 | // Initialize first column and row with zeros 224 | for (int nz = 0; nz < nSlices; nz++) { 225 | 226 | for (int y = 0; y < nPixYMap; y++) 227 | pVolumet[(nz*nPixYMap *nPixXMap) + y] = 0; 228 | 229 | for (int x = 1; x < nPixXMap; x++) 230 | pVolumet[(nz*nPixYMap *nPixXMap) + (x*nPixYMap)] = 0; 231 | } 232 | 233 | 234 | // Integrate on I direction 235 | for (int nz = 0; nz < nSlices; nz++) 236 | #pragma omp parallel for num_threads(nThreads) schedule(static) 237 | for (int x = 0; x < nPixX; x++){ 238 | double sum = 0; 239 | for (int y = 0; y < nPixY; y++){ 240 | sum += pVolume[(nz*nPixY *nPixX) + (x*nPixY) + y]; 241 | pVolumet[(nz*nPixYMap *nPixXMap) + ((x+1)*nPixYMap) + y + 1] = sum; 242 | } 243 | } 244 | 245 | // Integrate on J direction 246 | for (int nz = 0; nz < nSlices; nz++) 247 | #pragma omp parallel for num_threads(nThreads) schedule(static) 248 | for (int y = 1; y < nPixYMap; y++) 249 | for (int x = 2; x < nPixXMap; x++) 250 | pVolumet[(nz*nPixYMap *nPixXMap) + (x*nPixYMap) + y] += pVolumet[(nz*nPixYMap *nPixXMap) + ((x - 1)*nPixYMap) + y]; 251 | 252 | 253 | // Test if we will loop over all projs or not 254 | unsigned int projIni, projEnd; 255 | if(idXProj == -1){ 256 | projIni = 0; 257 | projEnd = nProj; 258 | } 259 | else{ 260 | projIni = (unsigned int) idXProj; 261 | projEnd = (unsigned int) idXProj + 1; 262 | } 263 | 264 | 265 | // For each projection 266 | for (unsigned int p = projIni; p < projEnd; p++) { 267 | 268 | // Get specif tube angle for the projection 269 | double theta = pTubeAngle[p] * M_PI / 180.0; 270 | 271 | // Get specif detector angle for the projection 272 | double phi = pDetAngle[p] * M_PI / 180.0; 273 | 274 | // printf("Tube angle:%f Det angle:%f\n", theta, phi); 275 | 276 | // Tube rotation 277 | double rtubeY = ((tubeY - isoY)*cos(theta) - (tubeZ - isoZ)*sin(theta)) + isoY; 278 | double rtubeZ = ((tubeY - isoY)*sin(theta) + (tubeZ - isoZ)*cos(theta)) + isoZ; 279 | 280 | // printf("R tube Y:%f R tube Z:%f\n", rtubeY, rtubeZ); 281 | 282 | // Detector rotation 283 | for (int y = 0; y < nDetYMap; y++) { 284 | pRdetY[y] = ((pDetY[y] - isoY)*cos(phi) - (pDetZ[y] - isoZ)*sin(phi)) + isoY; 285 | pRdetZ[y] = ((pDetY[y] - isoY)*sin(phi) + (pDetZ[y] - isoZ)*cos(phi)) + isoZ; 286 | } 287 | 288 | 289 | // For each slice 290 | for (int nz = 0; nz < nSlices; nz++) { 291 | 292 | /* 293 | 294 | Map detector onto XY plane(Inside proj loop in case detector rotates) 295 | 296 | *** Note: Matlab has linear indexing as a column of elements, i.e, the elements are actually stored in memory as queued columns. 297 | 298 | */ 299 | 300 | // Map slice onto XY plane 301 | mapDet2Slice(pDetmX, pDetmY, tubeX, rtubeY, rtubeZ, pDetX, pRdetY, pRdetZ, pObjZ[nz], nDetXMap, nDetYMap); 302 | 303 | /* 304 | S.2. Interpolation - Liu et al (2017) 305 | */ 306 | 307 | bilinear_interpolation(projI, pVolumet, pDetmX, pDetmY, nDetXMap, nDetYMap, nPixXMap, nPixYMap, dx, nz); 308 | 309 | /* 310 | S.3. Differentiation - Eq. 24 - Liu et al (2017) 311 | */ 312 | 313 | differentiation(pProjt, projI, pDetmX, pDetmY, tubeX, rtubeY, rtubeZ, pDetX, pRdetY, pRdetZ, nDetX, nDetY, nDetXMap, nDetYMap, du, dv, dx, dy, dz, p); 314 | 315 | } // Loop end slices 316 | 317 | } // Loop end Projections 318 | 319 | 320 | // Copy pProjt to pProj 321 | for (int p = 0; p < nProj; p++) 322 | for (int x = 0; x < nDetX; x++) 323 | for (int y = 0; y < nDetY; y++) 324 | pProj[(p*nDetX*nDetY) + (x*nDetY) + y] = pProjt[(p*nDetX*nDetY) + (x*nDetY) + y]; 325 | 326 | 327 | free(pProjt); 328 | free(projI); 329 | free(pVolumet); 330 | free(pDetX); 331 | free(pDetY); 332 | free(pDetZ); 333 | free(pObjX); 334 | free(pObjY); 335 | free(pObjZ); 336 | free(pDetmY); 337 | free(pDetmX); 338 | free(pRdetY); 339 | free(pRdetZ); 340 | 341 | time = clock() - time; 342 | printf("Processing time: %.2f seconds.\n", ((double)time) / CLOCKS_PER_SEC); 343 | 344 | return; 345 | } 346 | 347 | // Linear spaced vector 348 | // Ref: https://stackoverflow.com/a/27030598/8682939 349 | void linspace(double start, 350 | double end, 351 | int num, 352 | double* pLinspaced){ 353 | 354 | double delta; 355 | 356 | if(num == 0) 357 | delta = 0; 358 | else{ 359 | if(num == 1) 360 | delta = 1; 361 | else 362 | delta = (end - start) / (num - 1); 363 | } 364 | 365 | if((abs(start) < 0.00001) && (abs(end) < 0.00001)) 366 | delta = 0; 367 | 368 | for (int k = 0; k < num; k++){ 369 | pLinspaced[k] = start + k * delta; 370 | } 371 | 372 | return; 373 | } 374 | 375 | // Make boundaries of detector and slices 376 | void mapBoudaries(double* pBound, 377 | const int nElem, 378 | const double valueLeftBound, 379 | const double sizeElem, 380 | const double offset){ 381 | 382 | for (int k = 0; k < nElem; k++) 383 | pBound[k] = (k - valueLeftBound) * sizeElem + offset; 384 | 385 | return; 386 | } 387 | 388 | // Map on XY plane 389 | void mapDet2Slice(double* const pXmapp, 390 | double* const pYmapp, 391 | double tubeX, 392 | double tubeY, 393 | double tubeZ, 394 | double * const pXcoord, 395 | double * const pYcoord, 396 | double * const pZcoord, 397 | double ZSlicecoord, 398 | const int nXelem, 399 | const int nYelem){ 400 | 401 | #pragma omp parallel num_threads(nThreads) 402 | { 403 | int ind; 404 | #pragma omp for schedule(static) 405 | for (int x = 0; x < nXelem; x++) 406 | for (int y = 0; y < nYelem; y++) { 407 | 408 | ind = (x*nYelem) + y; 409 | pXmapp[ind] = ((pXcoord[x] - tubeX)*(ZSlicecoord - pZcoord[y]) - (pXcoord[x] * tubeZ) + (pXcoord[x] * pZcoord[y])) / (-tubeZ + pZcoord[y]); 410 | 411 | if (x == 0) 412 | pYmapp[y] = ((pYcoord[y] - tubeY)*(ZSlicecoord - pZcoord[y]) - (pYcoord[y] * tubeZ) + (pYcoord[y] * pZcoord[y])) / (-tubeZ + pZcoord[y]); 413 | } 414 | } 415 | 416 | return; 417 | } 418 | 419 | // Bilinear interpolation 420 | void bilinear_interpolation(double* projI, 421 | double* pVolume, 422 | double* pDetmX, 423 | double* pDetmY, 424 | const int nDetXMap, 425 | const int nDetYMap, 426 | const int nPixXMap, 427 | const int nPixYMap, 428 | const double pixelSize, 429 | const unsigned int nz) { 430 | 431 | 432 | /* 433 | 434 | S.2. Interpolation - Liu et al (2017) 435 | 436 | Reference: 437 | - https://en.wikipedia.org/wiki/Bilinear_interpolation 438 | - https://stackoverflow.com/questions/21128731/bilinear-interpolation-in-c-c-and-cuda 439 | 440 | Note: *** We are using the Unit Square equation *** 441 | 442 | alpha = X - X1 443 | 1-alpha = X2 - X 444 | 445 | beta = Y - Y1 446 | 1-beta = Y2 - Y 447 | 448 | ----> Xcoord 449 | | 0___________ 450 | v |_d00_|_d10_| 451 | Ycoord |_d01_|_d11_| 452 | 453 | */ 454 | 455 | // These are the boundaries of the slices, ranging from (0-nPixX) 456 | const double PixXbound = (double) (nPixXMap - 1); 457 | const double PixYbound = (double) (nPixYMap - 1); 458 | 459 | #pragma omp parallel for num_threads(nThreads) schedule(static) 460 | for (int x = 0; x < nDetXMap; x++) 461 | for (int y = 0; y < nDetYMap; y++) { 462 | 463 | // Adjust the mapped coordinates to cross the range of (0-nPixX).*dx 464 | // Divide by pixelSize to get a unitary pixel size 465 | const double xNormData = PixXbound - pDetmX[x * nDetYMap + y] / pixelSize; 466 | const signed int xData = (signed int)floor(xNormData); 467 | double alpha = xNormData - xData; 468 | 469 | // Adjust the mapped coordinates to cross the range of (0-nPixY).*dy 470 | // Divide by pixelSize to get a unitary pixel size 471 | const double yNormData = (PixYbound / 2.0) + (pDetmY[y] / pixelSize); 472 | const signed int yData = (signed int)floor(yNormData); 473 | double beta = yNormData - yData; 474 | 475 | double d00, d01, d10, d11; 476 | if (((xNormData) >= 0) && ((xNormData) <= PixXbound) && ((yNormData) >= 0) && ((yNormData) <= PixYbound)) d00 = pVolume[(nz*nPixYMap*nPixXMap) + (xData*nPixYMap + yData)]; else d00 = 0.0; 477 | if (((xData + 1) > 0) && ((xData + 1) <= PixXbound) && ((yNormData) >= 0) && ((yNormData) <= PixYbound)) d10 = pVolume[(nz*nPixYMap*nPixXMap) + ((xData + 1)*nPixYMap + yData)]; else d10 = 0.0; 478 | if (((xNormData) >= 0) && ((xNormData) <= PixXbound) && ((yData + 1) > 0) && ((yData + 1) <= PixYbound)) d01 = pVolume[(nz*nPixYMap*nPixXMap) + (xData*nPixYMap + yData + 1)]; else d01 = 0.0; 479 | if (((xData + 1) > 0) && ((xData + 1) <= PixXbound) && ((yData + 1) > 0) && ((yData + 1) <= PixYbound)) d11 = pVolume[(nz*nPixYMap*nPixXMap) + ((xData + 1)*nPixYMap + yData + 1)]; else d11 = 0.0; 480 | 481 | double result_temp1 = alpha * d10 + (-d00 * alpha + d00); 482 | double result_temp2 = alpha * d11 + (-d01 * alpha + d01); 483 | 484 | projI[x * nDetYMap + y] = beta * result_temp2 + (-result_temp1 * beta + result_temp1); 485 | 486 | } 487 | 488 | return; 489 | } 490 | 491 | // Differentiation 492 | void differentiation(double* pProj, 493 | double* projI, 494 | double* const pDetmX, 495 | double* const pDetmY, 496 | double tubeX, 497 | double rtubeY, 498 | double rtubeZ, 499 | double* const pDetX, 500 | double* const pRdetY, 501 | double* const pRdetZ, 502 | const int nDetX, 503 | const int nDetY, 504 | const int nDetXMap, 505 | const int nDetYMap, 506 | const double du, 507 | const double dv, 508 | const double dx, 509 | const double dy, 510 | const double dz, 511 | const unsigned int p) { 512 | 513 | 514 | /* 515 | 516 | S.3. Differentiation - Eq. 24 - Liu et al (2017) 517 | 518 | Detector integral projection 519 | ___________ 520 | |_A_|_B_|___| 521 | |_C_|_D_|___| 522 | |___|___|___| 523 | 524 | 525 | (y,x) 526 | ________________ 527 | |_A_|__B__|_____| 528 | |_C_|(0,0)|(0,1)| 529 | |___|(1,0)|(1,1)| 530 | 531 | 532 | Coordinates on intergal projection: 533 | 534 | A = x * nDetYMap + y 535 | B = ((x+1) * nDetYMap) + y 536 | C = x * nDetYMap + y + 1 537 | D = ((x+1) * nDetYMap) + y + 1 538 | 539 | */ 540 | 541 | #pragma omp parallel num_threads(nThreads) 542 | { 543 | unsigned int coordA; 544 | unsigned int coordB; 545 | unsigned int coordC; 546 | unsigned int coordD; 547 | 548 | double detMapSizeX; 549 | double detMapSizeY; 550 | double gamma; 551 | double alpha; 552 | 553 | double dA, dB, dC, dD; 554 | 555 | #pragma omp for schedule(static) 556 | for (int x = 0; x < nDetX; x++) 557 | for (int y = 0; y < nDetY; y++) { 558 | 559 | coordA = x * nDetYMap + y; 560 | coordB = ((x + 1) * nDetYMap) + y; 561 | coordC = coordA + 1; 562 | coordD = coordB + 1; 563 | 564 | 565 | // Detector X coord overlap calculation 566 | detMapSizeX = pDetmX[coordA] - pDetmX[coordB]; 567 | 568 | // Detector Y coord overlap calculation 569 | detMapSizeY = pDetmY[y + 1] - pDetmY[y]; 570 | 571 | // x - ray angle in X coord 572 | gamma = atan((pDetX[x] + (du / 2) - tubeX) / rtubeZ); 573 | 574 | // x - ray angle in Y coord 575 | alpha = atan((pRdetY[y] + (dv / 2) - rtubeY) / rtubeZ); 576 | 577 | 578 | dA = projI[coordA]; 579 | dB = projI[coordB]; 580 | dC = projI[coordC]; 581 | dD = projI[coordD]; 582 | 583 | // Treat border of interpolated integral detector 584 | if (dC == 0 && dD == 0) { 585 | dC = dA; 586 | dD = dB; 587 | } 588 | 589 | 590 | // S.3.Differentiation - Eq. 24 - Liu et al(2017) 591 | pProj[(nDetX*nDetY*p) + (x * nDetY) + y] += ((dD - dC - dB + dA)*(dx*dy*dz / (cos(alpha)*cos(gamma)*detMapSizeX*detMapSizeY))); 592 | } 593 | } 594 | return; 595 | } -------------------------------------------------------------------------------- /pydbt/sources/projectionDDb/projectionDDb.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: January, 2020 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This is the header function 12 | % --------------------------------------------------------------------- 13 | % Copyright (C) <2020> 14 | % 15 | % This program is free software: you can redistribute it and/or modify 16 | % it under the terms of the GNU General Public License as published by 17 | % the Free Software Foundation, either version 3 of the License, or 18 | % (at your option) any later version. 19 | % 20 | % This program is distributed in the hope that it will be useful, 21 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | % GNU General Public License for more details. 24 | % 25 | % You should have received a copy of the GNU General Public License 26 | % along with this program. If not, see . 27 | %} 28 | % ========================================================================= 29 | %% 3-D Projection Branchless Distance Driven Code (CPU-Multithread) 30 | */ 31 | 32 | #include 33 | #include 34 | #define _USE_MATH_DEFINES 35 | #include 36 | #include 37 | 38 | // Global variable 39 | int nThreads; 40 | 41 | void projectionDDb(double* const pProj, 42 | double* const pVolume, 43 | double* const pTubeAngle, 44 | double* const pDetAngle, 45 | const unsigned int nProj, 46 | const unsigned int nPixX, 47 | const unsigned int nPixY, 48 | const unsigned int nSlices, 49 | const unsigned int nDetX, 50 | const unsigned int nDetY, 51 | const signed int idXProj, 52 | const double x_offset, 53 | const double y_offset, 54 | const double dx, 55 | const double dy, 56 | const double dz, 57 | const double du, 58 | const double dv, 59 | const double DSD, 60 | const double DDR, 61 | const double DAG); 62 | 63 | void linspace(double start, 64 | double end, 65 | int num, 66 | double* pLinspaced); 67 | 68 | void mapBoudaries(double* pBound, 69 | const int nElem, 70 | const double valueLeftBound, 71 | const double sizeElem, 72 | const double offset); 73 | 74 | void mapDet2Slice(double* const pXmapp, 75 | double* const pYmapp, 76 | double tubeX, 77 | double tubeY, 78 | double tubeZ, 79 | double * const pXcoord, 80 | double * const pYcoord, 81 | double * const pZcoord, 82 | double ZSlicecoord, 83 | const int nXelem, 84 | const int nYelem); 85 | 86 | void bilinear_interpolation(double* projI, 87 | double* pVolume, 88 | double* pDetmX, 89 | double* pDetmY, 90 | const int nDetXMap, 91 | const int nDetYMap, 92 | const int nPixXMap, 93 | const int nPixYMap, 94 | const double pixelSize, 95 | const unsigned int nz); 96 | 97 | void differentiation(double* pProj, 98 | double* projI, 99 | double* const pDetmX, 100 | double* const pDetmY, 101 | double tubeX, 102 | double rtubeY, 103 | double rtubeZ, 104 | double* const pDetX, 105 | double* const pRdetY, 106 | double* const pRdetZ, 107 | const int nDetX, 108 | const int nDetY, 109 | const int nDetXMap, 110 | const int nDetYMap, 111 | const double du, 112 | const double dv, 113 | const double dx, 114 | const double dy, 115 | const double dz, 116 | const unsigned int p); 117 | -------------------------------------------------------------------------------- /pydbt/sources/projectionDDb_cuda/kernel.cu: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: Feb, 2022 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % projectionDDb_cuda_lib(pVolume, pProj, ppGeo) 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This function calculates the volume projection based on the 12 | % Branchless Distance-Driven principle. 13 | % The geometry is for DBT with half cone-beam. All parameters are set 14 | % in "ParameterSettings" code. 15 | % 16 | % INPUT: 17 | % 18 | % - data3d = 3D volume for projection 19 | % - param = Parameter of all geometry 20 | % - nProj = projection number to be projected 21 | % 22 | % OUTPUT: 23 | % 24 | % - proj = projections for each angle. 25 | % 26 | % Reference: 27 | % - Branchless Distance Driven Projection and Backprojection, 28 | % Samit Basu and Bruno De Man (2006) 29 | % - GPU Acceleration of Branchless Distance Driven Projection and 30 | % Backprojection, Liu et al (2016) 31 | % - GPU-Based Branchless Distance-Driven Projection and Backprojection, 32 | % Liu et al (2017) 33 | % - A GPU Implementation of Distance-Driven Computed Tomography, 34 | % Ryan D. Wagner (2017) 35 | % --------------------------------------------------------------------- 36 | % Copyright (C) <2019> 37 | % 38 | % This program is free software: you can redistribute it and/or modify 39 | % it under the terms of the GNU General Public License as published by 40 | % the Free Software Foundation, either version 3 of the License, or 41 | % (at your option) any later version. 42 | % 43 | % This program is distributed in the hope that it will be useful, 44 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 45 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 46 | % GNU General Public License for more details. 47 | % 48 | % You should have received a copy of the GNU General Public License 49 | % along with this program. If not, see . 50 | %} 51 | % ========================================================================= 52 | %% 3-D Projection Branchless Distance Driven Code 53 | */ 54 | 55 | #include "projectionDDb_cuda.hpp" 56 | 57 | /**************************************************************************** 58 | * CUDA Kernels * 59 | ****************************************************************************/ 60 | __global__ void pad_volume_kernel(double* d_img, const int nPixXMap, const int nPixYMap, const int nElem, unsigned int nz) { 61 | 62 | const int threadGId = blockIdx.x * blockDim.x + threadIdx.x; 63 | 64 | // Make sure we don't try and access memory outside 65 | // by having any threads mapped there return early 66 | if (threadGId >= nElem) 67 | return; 68 | 69 | d_img[(nz*nPixYMap *nPixXMap) + (threadGId*nPixYMap)] = 0; 70 | 71 | return; 72 | } 73 | 74 | __global__ void map_boudaries_kernel(double* d_pBound, const int nElem, const double valueLeftBound, const double sizeElem, const double offset) { 75 | 76 | const int threadGId = blockIdx.x * blockDim.x + threadIdx.x; 77 | 78 | // Make sure we don't try and access memory outside 79 | // by having any threads mapped there return early 80 | if (threadGId >= nElem) 81 | return; 82 | 83 | d_pBound[threadGId] = (threadGId - valueLeftBound) * sizeElem + offset; 84 | 85 | return; 86 | } 87 | 88 | __global__ void rot_detector_kernel(double* d_pRdetY, double* d_pRdetZ, double* d_pYcoord, double* d_pZcoord, const double yOffset, const double zOffset, 89 | const double phi, const int nElem) { 90 | 91 | const int threadGId = blockIdx.x * blockDim.x + threadIdx.x; 92 | 93 | // Make sure we don't try and access memory outside 94 | // by having any threads mapped there return early 95 | if (threadGId >= nElem) 96 | return; 97 | 98 | // cos and sin are in measured in radians. 99 | 100 | d_pRdetY[threadGId] = ((d_pYcoord[threadGId] - yOffset)* cos(phi) - (d_pZcoord[threadGId] - zOffset)* sin(phi)) + yOffset; 101 | d_pRdetZ[threadGId] = ((d_pYcoord[threadGId] - yOffset)* sin(phi) + (d_pZcoord[threadGId] - zOffset)* cos(phi)) + zOffset; 102 | 103 | return; 104 | 105 | } 106 | 107 | __global__ void mapDet2Slice_kernel(double* const pXmapp, double* const pYmapp, double tubeX, double tubeY, double tubeZ, double* const pXcoord, 108 | double* const pYcoord, double* const pZcoord, double* const pZSlicecoord, const int nDetXMap, const int nDetYMap, const unsigned int nz) { 109 | 110 | /* 111 | 112 | Note: Matlab has linear indexing as a column of elements, i.e, the elements are actually stored in memory as queued columns. 113 | So threads in X are launched in the column direction (DBT Y coord), and threads in Y are launched in the row direction (DBT X coord). 114 | 115 | */ 116 | 117 | const int2 thread_2D_pos = make_int2(blockIdx.x * blockDim.x + threadIdx.x, 118 | blockIdx.y * blockDim.y + threadIdx.y); 119 | 120 | const int thread_1D_pos = thread_2D_pos.y * nDetYMap + thread_2D_pos.x; 121 | 122 | // Make sure we don't try and access memory outside the detector 123 | // by having any threads mapped there return early 124 | if (thread_2D_pos.x >= nDetYMap || thread_2D_pos.y >= nDetXMap) 125 | return; 126 | 127 | pXmapp[thread_1D_pos] = ((pXcoord[thread_2D_pos.y] - tubeX)*(pZSlicecoord[nz] - pZcoord[thread_2D_pos.x]) - (pXcoord[thread_2D_pos.y]*tubeZ) + (pXcoord[thread_2D_pos.y]*pZcoord[thread_2D_pos.x]))/(-tubeZ + pZcoord[thread_2D_pos.x]); 128 | 129 | if (thread_2D_pos.y == 0) 130 | pYmapp[thread_2D_pos.x] = ((pYcoord[thread_2D_pos.x] - tubeY)*(pZSlicecoord[nz] - pZcoord[thread_2D_pos.x]) - (pYcoord[thread_2D_pos.x]*tubeZ) + (pYcoord[thread_2D_pos.x]*pZcoord[thread_2D_pos.x]))/(-tubeZ + pZcoord[thread_2D_pos.x]); 131 | 132 | return; 133 | } 134 | 135 | __global__ void img_integration_kernel(double* d_img, const int nPixX, const int nPixY, bool direction, unsigned int offsetX, unsigned int offsetY, unsigned int nSlices) { 136 | 137 | /* 138 | 139 | Integration of 2D slices over the whole volume 140 | 141 | (S.1.Integration. - Liu et al(2017)) 142 | 143 | ** Perfom an inclusive scan ** 144 | 145 | */ 146 | 147 | const int3 memory_2D_pos = make_int3(blockIdx.x * blockDim.x + threadIdx.x + offsetX, 148 | blockIdx.y * blockDim.y + threadIdx.y + offsetY, 149 | blockIdx.z * blockDim.z + threadIdx.z); 150 | 151 | const int2 thread_2D_pos = make_int2(blockIdx.x * blockDim.x + threadIdx.x, 152 | blockIdx.y * blockDim.y + threadIdx.y); 153 | 154 | 155 | // Make sure we don't try and access memory outside the detector 156 | // by having any threads mapped there return early 157 | if (memory_2D_pos.x >= nPixY || memory_2D_pos.y >= nPixX || memory_2D_pos.z >= nSlices) 158 | return; 159 | 160 | 161 | if (direction == integrateXcoord){ 162 | 163 | for (int s = 1; s <= blockDim.y; s *= 2) { 164 | 165 | int spot = thread_2D_pos.y - s; 166 | 167 | double val = 0; 168 | 169 | if (spot >= 0) { 170 | val = d_img[(memory_2D_pos.z*nPixY*nPixX) + (offsetY + spot) * nPixY + memory_2D_pos.x]; 171 | } 172 | __syncthreads(); 173 | 174 | if (spot >= 0) { 175 | d_img[(memory_2D_pos.z*nPixY*nPixX) + (memory_2D_pos.y * nPixY) + memory_2D_pos.x] += val; 176 | } 177 | __syncthreads(); 178 | } 179 | } 180 | else 181 | { 182 | 183 | for (int s = 1; s <= blockDim.x; s *= 2) { 184 | 185 | int spot = thread_2D_pos.x - s; 186 | 187 | double val = 0; 188 | 189 | if (spot >= 0) { 190 | val = d_img[(memory_2D_pos.z*nPixY*nPixX) + memory_2D_pos.y * nPixY + spot + offsetX]; 191 | } 192 | __syncthreads(); 193 | 194 | if (spot >= 0) { 195 | d_img[(memory_2D_pos.z*nPixY*nPixX) + (memory_2D_pos.y * nPixY) + memory_2D_pos.x] += val; 196 | } 197 | __syncthreads(); 198 | } 199 | 200 | } 201 | return; 202 | } 203 | 204 | 205 | __global__ void bilinear_interpolation_kernel_GPU(double* d_projI, double* d_pVolume, double* d_pDetmX, double* d_pDetmY, const int nDetXMap, const int nDetYMap, 206 | const int nPixXMap, const int nPixYMap, const int nPixX, const int nPixY, const double pixelSize, const unsigned int nz) { 207 | 208 | const int2 thread_2D_pos = make_int2(blockIdx.x * blockDim.x + threadIdx.x, 209 | blockIdx.y * blockDim.y + threadIdx.y); 210 | 211 | // Make sure we don't try and access memory outside the detector 212 | // by having any threads mapped there return early 213 | if (thread_2D_pos.x >= nDetYMap || thread_2D_pos.y >= nDetXMap) 214 | return; 215 | 216 | /* 217 | 218 | S.2. Interpolation - Liu et al (2017) 219 | 220 | Reference: 221 | - https://en.wikipedia.org/wiki/Bilinear_interpolation 222 | - https://stackoverflow.com/questions/21128731/bilinear-interpolation-in-c-c-and-cuda 223 | 224 | Note: *** We are using the Unit Square equation *** 225 | 226 | alpha = X - X1 227 | 1-alpha = X2 - X 228 | 229 | beta = Y - Y1 230 | 1-beta = Y2 - Y 231 | 232 | ----> Xcoord (thread_2D_pos.y) 233 | | 0___________ 234 | v |_d00_|_d10_| 235 | Ycoord (thread_2D_pos.x) |_d01_|_d11_| 236 | 237 | */ 238 | 239 | // Adjust the mapped coordinates to cross the range of (0-nPixX).*dx 240 | // Divide by pixelSize to get a unitary pixel size 241 | const double xNormData = nPixX - d_pDetmX[thread_2D_pos.y * nDetYMap + thread_2D_pos.x] / pixelSize; 242 | const signed int xData = floor(xNormData); 243 | const double alpha = xNormData - xData; 244 | 245 | // Adjust the mapped coordinates to cross the range of (0-nPixY).*dy 246 | // Divide by pixelSize to get a unitary pixel size 247 | const double yNormData = (nPixY / 2.0) + (d_pDetmY[thread_2D_pos.x] / pixelSize); 248 | const signed int yData = floor(yNormData); 249 | const double beta = yNormData - yData; 250 | 251 | double d00, d01, d10, d11; 252 | if (((xNormData) >= 0) && ((xNormData) <= nPixX) && ((yNormData) >= 0) && ((yNormData) <= nPixY)) d00 = d_pVolume[(nz*nPixYMap*nPixXMap) + (xData*nPixYMap + yData)]; else d00 = 0.0; 253 | if (((xData + 1) > 0) && ((xData + 1) <= nPixX) && ((yNormData) >= 0) && ((yNormData) <= nPixY)) d10 = d_pVolume[(nz*nPixYMap*nPixXMap) + ((xData + 1)*nPixYMap + yData)]; else d10 = 0.0; 254 | if (((xNormData) >= 0) && ((xNormData) <= nPixX) && ((yData + 1) > 0) && ((yData + 1) <= nPixY)) d01 = d_pVolume[(nz*nPixYMap*nPixXMap) + (xData*nPixYMap + yData + 1)]; else d01 = 0.0; 255 | if (((xData + 1) > 0) && ((xData + 1) <= nPixX) && ((yData + 1) > 0) && ((yData + 1) <= nPixY)) d11 = d_pVolume[(nz*nPixYMap*nPixXMap) + ((xData + 1)*nPixYMap + yData + 1)]; else d11 = 0.0; 256 | 257 | double result_temp1 = alpha * d10 + (-d00 * alpha + d00); 258 | double result_temp2 = alpha * d11 + (-d01 * alpha + d01); 259 | 260 | d_projI[thread_2D_pos.y * nDetYMap + thread_2D_pos.x] = beta * result_temp2 + (-result_temp1 * beta + result_temp1); 261 | 262 | } 263 | 264 | __global__ void differentiation_kernel(double* d_pProj, double* d_projI, double* const d_pDetmX, double* const d_pDetmY, double tubeX, double rtubeY, double rtubeZ, 265 | double* const d_pDetX, double* const d_pRdetY, double* const d_pRdetZ, const int nDetX, const int nDetY, const int nDetXMap, 266 | const int nDetYMap, const double du, const double dv, const double dx, const double dy, const double dz, const unsigned int p) { 267 | 268 | const int2 thread_2D_pos = make_int2(blockIdx.x * blockDim.x + threadIdx.x, 269 | blockIdx.y * blockDim.y + threadIdx.y); 270 | 271 | const int thread_1D_pos = (nDetX*nDetY*p) + (thread_2D_pos.y * nDetY) + thread_2D_pos.x; 272 | 273 | // Make sure we don't try and access memory outside the detector 274 | // by having any threads mapped there return early 275 | if (thread_2D_pos.x >= nDetY || thread_2D_pos.y >= nDetX) 276 | return; 277 | 278 | /* 279 | 280 | S.3. Differentiation - Eq. 24 - Liu et al (2017) 281 | 282 | Detector integral projection 283 | ___________ 284 | |_A_|_B_|___| 285 | |_C_|_D_|___| 286 | |___|___|___| 287 | 288 | 289 | (thread_2D_pos.x,thread_2D_pos.y) 290 | ________________ 291 | |_A_|__B__|_____| 292 | |_C_|(0,0)|(0,1)| 293 | |___|(1,0)|(1,1)| 294 | 295 | Threads are lauched from D up to nDetX (thread_2D_pos.y) and nDetY (thread_2D_pos.x) 296 | i.e., they are running on the detector image. Thread (0,0) is on D. 297 | 298 | Coordinates on intergal projection: 299 | 300 | A = thread_2D_pos.y * nDetYMap + thread_2D_pos.x 301 | B = ((thread_2D_pos.y+1) * nDetYMap) + thread_2D_pos.x 302 | C = thread_2D_pos.y * nDetYMap + thread_2D_pos.x + 1 303 | D = ((thread_2D_pos.y+1) * nDetYMap) + thread_2D_pos.x + 1 304 | 305 | */ 306 | 307 | unsigned int coordA = thread_2D_pos.y * nDetYMap + thread_2D_pos.x; 308 | unsigned int coordB = ((thread_2D_pos.y + 1) * nDetYMap) + thread_2D_pos.x; 309 | unsigned int coordC = coordA + 1; 310 | unsigned int coordD = coordB + 1; 311 | 312 | 313 | // Detector X coord overlap calculation 314 | double detMapSizeX = d_pDetmX[coordA] - d_pDetmX[coordB]; 315 | 316 | // Detector Y coord overlap calculation 317 | double detMapSizeY = d_pDetmY[thread_2D_pos.x+1] - d_pDetmY[thread_2D_pos.x]; 318 | 319 | // x - ray angle in X coord 320 | double gamma = atan((d_pDetX[thread_2D_pos.y] + (du / 2.0) - tubeX) / rtubeZ); 321 | 322 | // x - ray angle in Y coord 323 | double alpha = atan((d_pRdetY[thread_2D_pos.x] + (dv / 2.0) - rtubeY) / rtubeZ); 324 | 325 | 326 | double dA, dB, dC, dD; 327 | 328 | dA = d_projI[coordA]; 329 | dB = d_projI[coordB]; 330 | dC = d_projI[coordC]; 331 | dD = d_projI[coordD]; 332 | 333 | // Treat border of interpolated integral detector 334 | if (dC == 0 && dD == 0) { 335 | dC = dA; 336 | dD = dB; 337 | } 338 | 339 | 340 | // S.3.Differentiation - Eq. 24 - Liu et al(2017) 341 | d_pProj[thread_1D_pos] += ((dD - dC - dB + dA)*(dx*dy*dz / (cos(alpha)*cos(gamma)*detMapSizeX*detMapSizeY))); 342 | 343 | return; 344 | } 345 | 346 | 347 | 348 | /**************************************************************************** 349 | * function: projectionDDb() - CUDA projection Branchless Distance Driven. * 350 | ****************************************************************************/ 351 | 352 | void projectionDDb(double* const h_pProj, 353 | double* const h_pVolume, 354 | double* const h_pTubeAngle, 355 | double* const h_pDetAngle, 356 | const unsigned int nProj, 357 | const unsigned int nPixX, 358 | const unsigned int nPixY, 359 | const unsigned int nSlices, 360 | const unsigned int nDetX, 361 | const unsigned int nDetY, 362 | const signed int idXProj, 363 | const double x_offset, 364 | const double y_offset, 365 | const double dx, 366 | const double dy, 367 | const double dz, 368 | const double du, 369 | const double dv, 370 | const double DSD, 371 | const double DDR, 372 | const double DAG) 373 | { 374 | 375 | // Unique GPU 376 | int devID = 0; 377 | 378 | cudaDeviceProp deviceProp; 379 | cudaGetDeviceProperties(&deviceProp, devID); 380 | cudaCheckErrors("cudaGetDeviceProperties"); 381 | 382 | const unsigned int maxThreadsPerBlock = deviceProp.maxThreadsPerBlock; 383 | 384 | 385 | printf("GPU Device %d: \"%s\" with compute capability %d.%d has %d Multi-Processors and %zu bytes of global memory\n\n", devID, 386 | deviceProp.name, deviceProp.major, deviceProp.minor, deviceProp.multiProcessorCount, deviceProp.totalGlobalMem); 387 | 388 | 389 | // Create timmer variables and start it 390 | StopWatchInterface *timer = NULL; 391 | sdkCreateTimer(&timer); 392 | sdkStartTimer(&timer); 393 | 394 | //cudaStream_t stream1; 395 | //cudaStreamCreate(&stream1); 396 | 397 | dim3 threadsPerBlock(1, 1, 1); 398 | dim3 blockSize(1, 1, 1); 399 | 400 | 401 | // Number of mapped detector and pixels 402 | const int nDetXMap = nDetX + 1; 403 | const int nDetYMap = nDetY + 1; 404 | const int nPixXMap = nPixX + 1; 405 | const int nPixYMap = nPixY + 1; 406 | 407 | 408 | // Convention: h_ variables live on host 409 | // Convention: d_ variables live on device (GPU global mem) 410 | double* d_pProj; 411 | double* d_projI; 412 | double* d_pVolume; 413 | double* d_pTubeAngle; 414 | double* d_pDetAngle; 415 | 416 | 417 | // Allocate global memory on the device, place result in "d_----" 418 | cudaMalloc((void **)&d_pProj, nDetX*nDetY*nProj * sizeof(double)); 419 | cudaMalloc((void **)&d_projI, nDetXMap*nDetYMap * sizeof(double)); 420 | cudaMalloc((void **)&d_pVolume, nPixXMap*nPixYMap*nSlices * sizeof(double)); 421 | cudaMalloc((void **)&d_pTubeAngle, nProj * sizeof(double)); 422 | cudaMalloc((void **)&d_pDetAngle, nProj * sizeof(double)); 423 | 424 | cudaCheckErrors("cudaMalloc Initial"); 425 | 426 | // Copy data from host memory "h_----" to device memory "d_----" 427 | cudaMemcpy((void *)d_pTubeAngle, (void *)h_pTubeAngle, nProj * sizeof(double), cudaMemcpyHostToDevice); 428 | cudaMemcpy((void *)d_pDetAngle, (void *)h_pDetAngle, nProj * sizeof(double), cudaMemcpyHostToDevice); 429 | 430 | cudaCheckErrors("cudaMemcpy Angles"); 431 | 432 | 433 | /* 434 | Copy volume data from host memory "h_----" to device memory "d_----" padding with zeros for image integation 435 | */ 436 | 437 | 438 | // Initialize first column and row with zeros 439 | double* h_pVolume_tmp; 440 | double* d_pVolume_tmp; 441 | 442 | threadsPerBlock.x = maxThreadsPerBlock; 443 | blockSize.x = (nPixXMap / maxThreadsPerBlock) + 1; 444 | 445 | for (int nz = 0; nz < nSlices; nz++) { 446 | 447 | // Pad on X coord direction 448 | pad_volume_kernel << > > (d_pVolume, nPixXMap, nPixYMap, nPixXMap, nz); 449 | 450 | // Pad on Y coord direction 451 | d_pVolume_tmp = d_pVolume + (nPixXMap*nPixYMap*nz) + 1; 452 | cudaMemset(d_pVolume_tmp, 0, nPixY * sizeof(double)); 453 | cudaCheckErrors("cudaMemset Padding Volume"); 454 | } 455 | 456 | 457 | // Copy volume data from host memory 458 | for (unsigned int nz = 0; nz < nSlices; nz++) 459 | for (unsigned int c = 0; c < nPixX; c++) { 460 | 461 | h_pVolume_tmp = h_pVolume + (c *nPixY) + (nPixX*nPixY*nz); 462 | d_pVolume_tmp = d_pVolume + (((c+1) *nPixYMap) + 1) + (nPixXMap*nPixYMap*nz); 463 | 464 | cudaMemcpy((void *)d_pVolume_tmp, (void *)h_pVolume_tmp, nPixY * sizeof(double), cudaMemcpyHostToDevice); 465 | cudaCheckErrors("cudaMemset Volume"); 466 | } 467 | 468 | 469 | 470 | // Pointer for projections coordinates 471 | double* d_pDetX; 472 | double* d_pDetY; 473 | double* d_pDetZ; 474 | double* d_pObjX; 475 | double* d_pObjY; 476 | double* d_pObjZ; 477 | 478 | // Allocate global memory on the device for projections coordinates 479 | cudaMalloc((void **)&d_pDetX, nDetXMap * sizeof(double)); 480 | cudaMalloc((void **)&d_pDetY, nDetYMap * sizeof(double)); 481 | cudaMalloc((void **)&d_pDetZ, nDetYMap * sizeof(double)); 482 | cudaMalloc((void **)&d_pObjX, nPixXMap * sizeof(double)); 483 | cudaMalloc((void **)&d_pObjY, nPixYMap * sizeof(double)); 484 | cudaMalloc((void **)&d_pObjZ, nSlices * sizeof(double)); 485 | 486 | cudaCheckErrors("cudaMalloc Coordinates"); 487 | 488 | 489 | 490 | // Pointer for mapped coordinates 491 | double* d_pDetmY; 492 | double* d_pDetmX; 493 | 494 | 495 | // Allocate global memory on the device for mapped coordinates 496 | cudaMalloc((void **)&d_pDetmY, nDetYMap * sizeof(double)); 497 | cudaMalloc((void **)&d_pDetmX, nDetYMap * nDetXMap * sizeof(double)); 498 | 499 | cudaCheckErrors("cudaMalloc Map-Coordinates"); 500 | 501 | 502 | // Pointer for rotated detector coords 503 | double* d_pRdetY; 504 | double* d_pRdetZ; 505 | 506 | // Allocate global memory on the device for for rotated detector coords 507 | cudaMalloc((void **)&d_pRdetY, nDetYMap * sizeof(double)); 508 | cudaMalloc((void **)&d_pRdetZ, nDetYMap * sizeof(double)); 509 | 510 | cudaCheckErrors("cudaMalloc Rot-Coordinates"); 511 | 512 | // Generate detector and object boudaries 513 | 514 | threadsPerBlock.x = maxThreadsPerBlock; 515 | 516 | blockSize.x = (nDetX / maxThreadsPerBlock) + 1; 517 | 518 | map_boudaries_kernel << > > (d_pDetX, nDetXMap, (double)nDetX, -du, 0.0); 519 | 520 | blockSize.x = (nDetY / maxThreadsPerBlock) + 1; 521 | 522 | map_boudaries_kernel << > > (d_pDetY, nDetYMap, nDetY / 2.0, dv, 0.0); 523 | 524 | blockSize.x = (nPixX / maxThreadsPerBlock) + 1; 525 | 526 | map_boudaries_kernel << > > (d_pObjX, nPixXMap, (double)nPixX, -dx, x_offset); 527 | 528 | blockSize.x = (nPixY / maxThreadsPerBlock) + 1; 529 | 530 | map_boudaries_kernel << > > (d_pObjY, nPixYMap, nPixY / 2.0, dy, y_offset); 531 | 532 | blockSize.x = (nSlices / maxThreadsPerBlock) + 1; 533 | 534 | map_boudaries_kernel << > > (d_pObjZ, nSlices, 0.0, dz, DAG + (dz / 2.0)); 535 | 536 | 537 | //printf("Map Boundaries -- Threads:%d Blocks:%d \n", threads, blocks); 538 | 539 | 540 | // Initiate variables value with 0 541 | cudaMemset(d_pDetZ, 0, nDetYMap * sizeof(double)); 542 | cudaMemset(d_pProj, 0, nDetX * nDetY * nProj * sizeof(double)); 543 | 544 | cudaCheckErrors("cudaMemset Zeros"); 545 | 546 | //cudaDeviceSynchronize(); 547 | 548 | // X - ray tube initial position 549 | double tubeX = 0; 550 | double tubeY = 0; 551 | double tubeZ = DSD; 552 | 553 | // Iso - center position 554 | double isoY = 0; 555 | double isoZ = DDR; 556 | 557 | 558 | // Integration of 2D slices over the whole volume 559 | // (S.1.Integration. - Liu et al(2017)) 560 | 561 | // Naive integration o the X coord 562 | threadsPerBlock.x = 10; 563 | threadsPerBlock.y = 10; 564 | threadsPerBlock.z = 10; 565 | 566 | blockSize.x = (unsigned int) ceil(((double)nPixYMap / (threadsPerBlock.x-1))); 567 | blockSize.y = 1; 568 | blockSize.z = (unsigned int) ceil((double)nSlices / threadsPerBlock.z); 569 | 570 | //printf("Divisao: %f \n", (double)nPixY / (threadsPerBlock.x - 1)); 571 | //printf("Divisao ceil: %d \n", (unsigned int)ceil((double)nPixY / (threadsPerBlock.x - 1))); 572 | 573 | for (int k = 0; k < ceil((double)nPixXMap / (threadsPerBlock.x-1)); k++) { 574 | 575 | img_integration_kernel << > > (d_pVolume, nPixXMap, nPixYMap, integrateXcoord, 0, k * 9, nSlices); 576 | 577 | //printf("integration kernel -- threadsX:%d blocksX:%d threadsY:%d blocksY:%d \n", threadsPerBlock.x, blockSize.x, threadsPerBlock.y, blockSize.y); 578 | 579 | } 580 | 581 | // Naive integration o the Y coord 582 | threadsPerBlock.x = 10; 583 | threadsPerBlock.y = 10; 584 | threadsPerBlock.z = 10; 585 | 586 | blockSize.x = 1; 587 | blockSize.y = (unsigned int) ceil((double)nPixXMap / (threadsPerBlock.y-1)); 588 | blockSize.z = (unsigned int) ceil((double)nSlices / threadsPerBlock.z); 589 | 590 | 591 | for (int k = 0; k < ceil((double)nPixYMap / (threadsPerBlock.y-1)); k++) { 592 | 593 | img_integration_kernel << > > (d_pVolume, nPixXMap, nPixYMap, integrateYcoord, k * 9, 0, nSlices); 594 | 595 | //printf("integration kernel -- threadsX:%d blocksX:%d threadsY:%d blocksY:%d \n", threadsPerBlock.x, blockSize.x, threadsPerBlock.y, blockSize.y); 596 | 597 | } 598 | 599 | // Test if we will loop over all projs or not 600 | unsigned int projIni, projEnd; 601 | if(idXProj == -1){ 602 | projIni = 0; 603 | projEnd = nProj; 604 | } 605 | else{ 606 | projIni = (unsigned int) idXProj; 607 | projEnd = (unsigned int) idXProj + 1; 608 | } 609 | 610 | // For each projection 611 | for (unsigned int p = projIni; p < projEnd; p++) { 612 | 613 | // Get specif tube angle for the projection 614 | double theta = h_pTubeAngle[p] * M_PI / 180.0; 615 | 616 | // Get specif detector angle for the projection 617 | double phi = h_pDetAngle[p] * M_PI / 180.0; 618 | 619 | //printf("Tube angle:%f Det angle:%f\n", theta, phi); 620 | 621 | // Tube rotation 622 | double rtubeY = ((tubeY - isoY)*cos(theta) - (tubeZ - isoZ)*sin(theta)) + isoY; 623 | double rtubeZ = ((tubeY - isoY)*sin(theta) + (tubeZ - isoZ)*cos(theta)) + isoZ; 624 | 625 | //printf("R tube Y:%f R tube Z:%f\n", rtubeY, rtubeZ); 626 | 627 | // Detector rotation 628 | threadsPerBlock.x = maxThreadsPerBlock; 629 | threadsPerBlock.y = 1; 630 | threadsPerBlock.z = 1; 631 | 632 | blockSize.x = (nDetYMap / maxThreadsPerBlock) + 1; 633 | blockSize.y = 1; 634 | blockSize.z = 1; 635 | 636 | rot_detector_kernel << > > (d_pRdetY, d_pRdetZ, d_pDetY, d_pDetZ, isoY, isoZ, phi, nDetYMap); 637 | 638 | //cudaDeviceSynchronize(); 639 | 640 | //printf("Detector rotation -- Threads:%d Blocks:%d \n", threads, blocks); 641 | 642 | 643 | // For each slice 644 | for (unsigned int nz = 0; nz < nSlices; nz++) { 645 | 646 | /* 647 | 648 | Map detector onto XY plane(Inside proj loop in case detector rotates) 649 | 650 | *** Note: Matlab has linear indexing as a column of elements, i.e, the elements are actually stored in memory as queued columns. 651 | So threads in X are launched in the column direction (DBT Y coord), and threads in Y are launched in the row direction (DBT X coord). 652 | 653 | */ 654 | 655 | threadsPerBlock.x = 32; 656 | threadsPerBlock.y = 32; 657 | threadsPerBlock.z = 1; 658 | 659 | blockSize.x = (nDetYMap / threadsPerBlock.x) + 1; 660 | blockSize.y = (nDetXMap / threadsPerBlock.y) + 1; 661 | blockSize.z = 1; 662 | 663 | 664 | mapDet2Slice_kernel << > > (d_pDetmX, d_pDetmY, tubeX, rtubeY, rtubeZ, d_pDetX, d_pRdetY, d_pRdetZ, d_pObjZ, nDetXMap, nDetYMap, nz); 665 | //cudaDeviceSynchronize(); 666 | 667 | //printf("Map detector onto XY plane -- ThreadsX:%d ThreadsY:%d BlocksX:%d BlocksY:%d\n", threadsPerBlock.x, threadsPerBlock.y, blockSizeX, blockSizeY); 668 | 669 | /* 670 | S.2. Interpolation - Liu et al (2017) 671 | */ 672 | 673 | blockSize.x = (nDetYMap / threadsPerBlock.x) + 1; 674 | blockSize.y = (nDetXMap / threadsPerBlock.y) + 1; 675 | 676 | bilinear_interpolation_kernel_GPU << > > (d_projI, d_pVolume, d_pDetmX, d_pDetmY, nDetXMap, nDetYMap, nPixXMap, nPixYMap, nPixX, nPixY, dx, nz); 677 | 678 | 679 | /* 680 | S.3. Differentiation - Eq. 24 - Liu et al (2017) 681 | */ 682 | 683 | blockSize.x = (nDetY / threadsPerBlock.x) + 1; 684 | blockSize.y = (nDetX / threadsPerBlock.y) + 1; 685 | 686 | differentiation_kernel << > > (d_pProj, d_projI, d_pDetmX, d_pDetmY, tubeX, rtubeY, rtubeZ, d_pDetX, d_pRdetY, d_pRdetZ, nDetX, nDetY, nDetXMap, nDetYMap, du, dv, dx, dy, dz, p); 687 | 688 | } // Loop end slices 689 | 690 | } // Loop end Projections 691 | 692 | // d_pProj 693 | cudaMemcpy((void *)h_pProj, (void *)d_pProj, nProj* nDetX * nDetY * sizeof(double), cudaMemcpyDeviceToHost); 694 | cudaCheckErrors("cudaMemcpy Final"); 695 | 696 | 697 | cudaFree(d_pProj); 698 | cudaFree(d_projI); 699 | cudaFree(d_pVolume); 700 | cudaFree(d_pTubeAngle); 701 | cudaFree(d_pDetAngle); 702 | cudaFree(d_pDetX); 703 | cudaFree(d_pDetY); 704 | cudaFree(d_pDetZ); 705 | cudaFree(d_pObjX); 706 | cudaFree(d_pObjY); 707 | cudaFree(d_pObjZ); 708 | cudaFree(d_pDetmY); 709 | cudaFree(d_pDetmX); 710 | cudaFree(d_pRdetY); 711 | cudaFree(d_pRdetZ); 712 | 713 | cudaCheckErrors("cudaFree Final"); 714 | 715 | sdkStopTimer(&timer); 716 | printf("Processing time: %f (ms)\n", sdkGetTimerValue(&timer)); 717 | sdkDeleteTimer(&timer); 718 | 719 | cudaDeviceReset(); 720 | 721 | return; 722 | 723 | } -------------------------------------------------------------------------------- /pydbt/sources/projectionDDb_cuda/projectionDDb_cuda.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: Feb, 2022 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % projectionDDb_cuda_lib(pVolume, pProj, pGeo, idXProj) 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This function calculates the volume projection based on the 12 | % Branchless Distance-Driven principle. 13 | % The geometry is for DBT with half cone-beam. All parameters are set 14 | % in "ParameterSettings" code. 15 | % 16 | % INPUT: 17 | % 18 | % - pVolume = 3D volume for projection 19 | % - pGeo = Parameter of all geometry 20 | % - idXProj = projection number to be projected 21 | % 22 | % OUTPUT: 23 | % 24 | % - pProj = projections for each angle. 25 | % 26 | % Reference: 27 | % - Branchless Distance Driven Projection and Backprojection, 28 | % Samit Basu and Bruno De Man (2006) 29 | % - GPU Acceleration of Branchless Distance Driven Projection and 30 | % Backprojection, Liu et al (2016) 31 | % - GPU-Based Branchless Distance-Driven Projection and Backprojection, 32 | % Liu et al (2017) 33 | % - A GPU Implementation of Distance-Driven Computed Tomography, 34 | % Ryan D. Wagner (2017) 35 | % --------------------------------------------------------------------- 36 | % Copyright (C) <2022> 37 | % 38 | % This program is free software: you can redistribute it and/or modify 39 | % it under the terms of the GNU General Public License as published by 40 | % the Free Software Foundation, either version 3 of the License, or 41 | % (at your option) any later version. 42 | % 43 | % This program is distributed in the hope that it will be useful, 44 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 45 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 46 | % GNU General Public License for more details. 47 | % 48 | % You should have received a copy of the GNU General Public License 49 | % along with this program. If not, see . 50 | %} 51 | % ========================================================================= 52 | %% 3-D Projection Branchless Distance Driven Code (CUDA) 53 | */ 54 | 55 | #include "projectionDDb_cuda.hpp" 56 | 57 | 58 | extern "C" void projectionDDb_cuda_lib(double* const pVolume, 59 | double* const pProj, 60 | float* const pGeo, 61 | const signed int idXProj){ 62 | 63 | 64 | const int unsigned nPixX = (const int)pGeo[0]; 65 | const int unsigned nPixY = (const int)pGeo[1]; 66 | const int unsigned nSlices = (const int)pGeo[2]; 67 | const int unsigned nDetX = (const int)pGeo[3]; 68 | const int unsigned nDetY = (const int)pGeo[4]; 69 | 70 | const double dx = (const double)pGeo[5]; 71 | const double dy = (const double)pGeo[6]; 72 | const double dz = (const double)pGeo[7]; 73 | const double du = (const double)pGeo[8]; 74 | const double dv = (const double)pGeo[9]; 75 | 76 | const double DSD = (const double)pGeo[10]; 77 | const double DDR = (const double)pGeo[12]; 78 | const double DAG = (const double)pGeo[14]; 79 | 80 | const int unsigned nProj = (const int)pGeo[15]; 81 | 82 | const double tubeAngle = (const double)pGeo[16]; 83 | const double detAngle = (const double)pGeo[17]; 84 | 85 | double* const pTubeAngle = (double*)malloc(nProj * sizeof(double)); 86 | double* const pDetAngle = (double*)malloc(nProj * sizeof(double)); 87 | 88 | const double x_offset = (const double)pGeo[18]; 89 | const double y_offset = (const double)pGeo[19]; 90 | 91 | linspace(-tubeAngle/2, tubeAngle/2, nProj, pTubeAngle); 92 | linspace(-detAngle/2, detAngle/2, nProj, pDetAngle); 93 | 94 | // printf("Nx:%d Ny:%d Nz:%d \nNu:%d Nv:%d \nDx:%.2f Dy:%.2f Dz:%.2f \nDu:%.2f Dv:%.2f \nDSD:%.2f DDR:%.2f \nTube angle:%.2f \nDet angle:%.2f", nPixX, nPixY, nSlices, nDetX, nDetY, dx, dy, dz, du, dv, DSD, DDR, tubeAngle, detAngle); 95 | 96 | projectionDDb(pProj, pVolume, pTubeAngle, pDetAngle, nProj, nPixX, nPixY, nSlices, nDetX, nDetY, idXProj, x_offset, y_offset, dx, dy, dz, du, dv, DSD, DDR, DAG); 97 | 98 | free(pTubeAngle); 99 | free(pDetAngle); 100 | 101 | return; 102 | 103 | } 104 | 105 | // Linear spaced vector 106 | // Ref: https://stackoverflow.com/a/27030598/8682939 107 | void linspace(double start, 108 | double end, 109 | int num, 110 | double* pLinspaced){ 111 | 112 | double delta; 113 | 114 | if(num == 0) 115 | delta = 0; 116 | else{ 117 | if(num == 1) 118 | delta = 1; 119 | else 120 | delta = (end - start) / (num - 1); 121 | } 122 | 123 | if((abs(start) < 0.00001) && (abs(end) < 0.00001)) 124 | delta = 0; 125 | 126 | for (int k = 0; k < num; k++){ 127 | pLinspaced[k] = start + k * delta; 128 | } 129 | 130 | return; 131 | } -------------------------------------------------------------------------------- /pydbt/sources/projectionDDb_cuda/projectionDDb_cuda.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | %% Author: Rodrigo de Barros Vimieiro 3 | % Date: Feb, 2022 4 | % rodrigo.vimieiro@gmail.com 5 | % ========================================================================= 6 | %{ 7 | % ------------------------------------------------------------------------- 8 | % 9 | % ------------------------------------------------------------------------- 10 | % DESCRIPTION: 11 | % This is the header function 12 | % --------------------------------------------------------------------- 13 | % Copyright (C) <2022> 14 | % 15 | % This program is free software: you can redistribute it and/or modify 16 | % it under the terms of the GNU General Public License as published by 17 | % the Free Software Foundation, either version 3 of the License, or 18 | % (at your option) any later version. 19 | % 20 | % This program is distributed in the hope that it will be useful, 21 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | % GNU General Public License for more details. 24 | % 25 | % You should have received a copy of the GNU General Public License 26 | % along with this program. If not, see . 27 | %} 28 | % ========================================================================= 29 | %% 3-D Projection Branchless Distance Driven header 30 | */ 31 | 32 | #include 33 | #define _USE_MATH_DEFINES 34 | #include 35 | 36 | // Includes CUDA 37 | #include 38 | 39 | // Utilities and timing functions 40 | #include // includes cuda.h and cuda_runtime_api.h 41 | 42 | // CUDA helper functions 43 | #include // helper functions for CUDA error check 44 | 45 | #define integrateXcoord 1 46 | #define integrateYcoord 0 47 | 48 | 49 | /* 50 | Reference: TIGRE - https://github.com/CERN/TIGRE 51 | */ 52 | #define cudaCheckErrors(msg) \ 53 | do { \ 54 | cudaError_t __err = cudaGetLastError(); \ 55 | if (__err != cudaSuccess) { \ 56 | printf("%s \n",msg);\ 57 | } \ 58 | } while (0) 59 | 60 | void linspace(double start, 61 | double end, 62 | int num, 63 | double* pLinspaced); 64 | 65 | void projectionDDb(double* const h_pProj, 66 | double* const h_pVolume, 67 | double* const h_pTubeAngle, 68 | double* const h_pDetAngle, 69 | const unsigned int nProj, 70 | const unsigned int nPixX, 71 | const unsigned int nPixY, 72 | const unsigned int nSlices, 73 | const unsigned int nDetX, 74 | const unsigned int nDetY, 75 | const signed int idXProj, 76 | const double x_offset, 77 | const double y_offset, 78 | const double dx, 79 | const double dy, 80 | const double dz, 81 | const double du, 82 | const double dv, 83 | const double DSD, 84 | const double DDR, 85 | const double DAG); 86 | 87 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Tue Jan 21 18:23:29 2020 5 | 6 | @author: rodrigo 7 | """ 8 | 9 | import setuptools 10 | import os 11 | 12 | from sys import platform 13 | from pydbt.functions.utilities2setuptools import locate_cuda, get_custom_build_ext 14 | 15 | # Find CUDA (NVCC) 16 | cuda_env = locate_cuda() 17 | 18 | # Create custom build ext 19 | custom_build_ext = get_custom_build_ext(cuda_env) 20 | 21 | 22 | if platform == "darwin": 23 | os.environ["CC"] = "gcc-11" 24 | os.environ["CXX"] = "g++-11" 25 | 26 | elif platform == "win32": 27 | raise ValueError('Windows is not supported yet.') 28 | 29 | 30 | 31 | projectionDD = setuptools.Extension('projectionDD', 32 | sources = ['pydbt/sources/projectionDD/projectionDD.cpp'], 33 | language='c++', 34 | extra_compile_args = {"gcc" : [], 35 | "nvcc" : []}) 36 | 37 | backprojectionDD = setuptools.Extension('backprojectionDD', 38 | sources = ['pydbt/sources/backprojectionDD/backprojectionDD.cpp'], 39 | language='c++', 40 | extra_compile_args = {"gcc" : [], 41 | "nvcc" : []}) 42 | 43 | 44 | projectionDDb = setuptools.Extension('projectionDDb', 45 | sources = ['pydbt/sources/projectionDDb/projectionDDb.cpp'], 46 | language='c++', 47 | extra_compile_args = {"gcc" : ['-fopenmp'], 48 | "nvcc" : []}, 49 | extra_link_args=['-lgomp']) 50 | 51 | backprojectionDDb = setuptools.Extension('backprojectionDDb', 52 | sources = ['pydbt/sources/backprojectionDDb/backprojectionDDb.cpp'], 53 | language='c++', 54 | extra_compile_args = {"gcc" : ['-fopenmp'], 55 | "nvcc" : []}, 56 | extra_link_args=['-lgomp']) 57 | 58 | projectionDDb_cuda = setuptools.Extension('projectionDDb_cuda', 59 | sources = ['pydbt/sources/projectionDDb_cuda/projectionDDb_cuda.cpp', 'pydbt/sources/projectionDDb_cuda/kernel.cu'], 60 | library_dirs=[cuda_env['lib64']], 61 | libraries=['cudart'], 62 | language='c++', 63 | runtime_library_dirs=[cuda_env['lib64']], 64 | extra_compile_args = {"gcc" : [], 65 | "nvcc" : ['-arch=sm_75', '--ptxas-options=-v', '-c', '--compiler-options', "'-fPIC'"]}, 66 | include_dirs = [cuda_env['include'], 'cuda-samples/Common/']) 67 | 68 | backprojectionDDb_cuda = setuptools.Extension('backprojectionDDb_cuda', 69 | sources = ['pydbt/sources/backprojectionDDb_cuda/backprojectionDDb_cuda.cpp', 'pydbt/sources/backprojectionDDb_cuda/kernel.cu'], 70 | library_dirs=[cuda_env['lib64']], 71 | libraries=['cudart'], 72 | language='c++', 73 | runtime_library_dirs=[cuda_env['lib64']], 74 | extra_compile_args = {"gcc" : [], 75 | "nvcc" : ['-arch=sm_75', '--ptxas-options=-v', '-c', '--compiler-options', "'-fPIC'"]}, 76 | include_dirs = [cuda_env['include'], 'cuda-samples/Common/']) 77 | 78 | 79 | with open("README.md", "r") as fh: 80 | long_description = fh.read() 81 | 82 | setuptools.setup( 83 | name="pyDBT", 84 | version="0.0.3", 85 | author="Rodrigo Vimieiro", 86 | description="This package is a python extension of the DBT toolbox from LAVI-USP", 87 | long_description=long_description, 88 | long_description_content_type="text/markdown", 89 | url="https://github.com/LAVI-USP/pyDBT", 90 | packages=setuptools.find_packages(), 91 | classifiers=[ 92 | "Programming Language :: Python :: 3", 93 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 94 | "Operating System :: OS Independent", 95 | ], 96 | python_requires='>=3.6', 97 | install_requires=["numpy", "matplotlib", "pydicom"], 98 | ext_modules = [projectionDD, backprojectionDD, projectionDDb, backprojectionDDb, projectionDDb_cuda, backprojectionDDb_cuda], 99 | 100 | # Inject our custom trigger 101 | cmdclass={'build_ext': custom_build_ext}, 102 | ) -------------------------------------------------------------------------------- /setup_docker.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | from setuptools.command.build_ext import build_ext 3 | 4 | CUDA_LIB = '/opt/nvidia/hpc_sdk/Linux_x86_64/24.3/cuda/12.3/targets/x86_64-linux/lib/' 5 | CUDA_INC = '/opt/nvidia/hpc_sdk/Linux_x86_64/24.3/cuda/12.3/include/' 6 | ARCH = '-arch=sm_75' 7 | 8 | class CustomBuildExt(build_ext): 9 | def build_extensions(self): 10 | self.compiler.src_extensions.append('.cu') 11 | 12 | for ext in self.extensions: 13 | print(ext) 14 | if any(src.endswith('.cu') for src in ext.sources): 15 | ext.extra_compile_args = [ARCH, '--ptxas-options=-v', '-c', '--compiler-options', '-fPIC'] 16 | ext.extra_link_args = ['-shared', '-lcudart'] 17 | self.compiler.set_executable("compiler_so", "nvcc") 18 | self.compiler.set_executable("linker_so", "nvcc") 19 | 20 | elif 'projectionDDb' in ext.name or 'backprojectionDDb' in ext.name: 21 | ext.extra_compile_args = ['-fopenmp', '-fPIC'] 22 | ext.extra_link_args = ['-shared', '-lgomp', '-fPIC'] 23 | self.compiler.set_executable("compiler_so", "gcc") 24 | self.compiler.set_executable("linker_so", "gcc") 25 | 26 | else: 27 | ext.extra_link_args = ['-shared'] 28 | self.compiler.set_executable("compiler_so", "gcc") 29 | self.compiler.set_executable("linker_so", "gcc") 30 | 31 | super().build_extensions() 32 | 33 | projectionDD = Extension( 34 | 'projectionDD', 35 | sources=['pydbt/sources/projectionDD/projectionDD.cpp'], 36 | extra_link_args=['-shared'], 37 | language='c++' 38 | ) 39 | 40 | backprojectionDD = Extension( 41 | 'backprojectionDD', 42 | sources=['pydbt/sources/backprojectionDD/backprojectionDD.cpp'], 43 | extra_link_args=['-shared'], 44 | language='c++' 45 | ) 46 | 47 | projectionDDb = Extension( 48 | 'projectionDDb', 49 | sources=['pydbt/sources/projectionDDb/projectionDDb.cpp'], 50 | extra_compile_args=['-fopenmp','-fPIC'], 51 | extra_link_args=['-shared', '-lgomp'], 52 | language='c++' 53 | ) 54 | 55 | backprojectionDDb = Extension( 56 | 'backprojectionDDb', 57 | sources=['pydbt/sources/backprojectionDDb/backprojectionDDb.cpp'], 58 | extra_compile_args=['-fopenmp','-fPIC'], 59 | extra_link_args=['-shared', '-lgomp'], 60 | language='c++' 61 | ) 62 | 63 | # CUDA-based extensions 64 | projectionDDb_cuda = Extension( 65 | 'projectionDDb_cuda', 66 | sources=[ 67 | 'pydbt/sources/projectionDDb_cuda/projectionDDb_cuda.cpp', 68 | 'pydbt/sources/projectionDDb_cuda/kernel.cu' 69 | ], 70 | library_dirs=[CUDA_LIB], 71 | libraries=['cudart'], 72 | include_dirs=[CUDA_INC, 'cuda-samples/Common/'], 73 | extra_link_args=['-lcudart'], 74 | language='c++' 75 | ) 76 | 77 | backprojectionDDb_cuda = Extension( 78 | 'backprojectionDDb_cuda', 79 | sources=[ 80 | 'pydbt/sources/backprojectionDDb_cuda/backprojectionDDb_cuda.cpp', 81 | 'pydbt/sources/backprojectionDDb_cuda/kernel.cu' 82 | ], 83 | library_dirs=[CUDA_LIB], 84 | libraries=['cudart'], 85 | include_dirs=[CUDA_INC, 'cuda-samples/Common/'], 86 | extra_link_args=['-lcudart'], 87 | language='c++' 88 | ) 89 | 90 | with open("README.md", "r") as fh: 91 | long_description = fh.read() 92 | 93 | setup( 94 | name="pyDBT", 95 | version="0.0.4", 96 | author="Rodrigo Vimieiro", 97 | description="This package is a python extension of the DBT toolbox from LAVI-USP", 98 | long_description=long_description, 99 | long_description_content_type="text/markdown", 100 | ext_modules=[projectionDD, backprojectionDD, projectionDDb, backprojectionDDb], 101 | cmdclass={'build_ext': CustomBuildExt}, 102 | classifiers=[ 103 | "Programming Language :: Python :: 3", 104 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 105 | "Operating System :: OS Independent", 106 | ], 107 | python_requires='>=3.6', 108 | install_requires=["numpy", "matplotlib", "pydicom"], 109 | ) 110 | 111 | setup( 112 | name="pyDBT", 113 | version="0.0.4", 114 | author="Rodrigo Vimieiro", 115 | description="This package is a python extension of the DBT toolbox from LAVI-USP", 116 | long_description=long_description, 117 | long_description_content_type="text/markdown", 118 | ext_modules=[projectionDDb_cuda, backprojectionDDb_cuda], 119 | cmdclass={'build_ext': CustomBuildExt}, 120 | classifiers=[ 121 | "Programming Language :: Python :: 3", 122 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 123 | "Operating System :: OS Independent", 124 | ], 125 | python_requires='>=3.6', 126 | install_requires=["numpy", "matplotlib", "pydicom"], 127 | ) 128 | --------------------------------------------------------------------------------