Skip to content

Commit 2583efe

Browse files
authored
Merge branch 'main' into recon_name_change
2 parents ff66e68 + ee37877 commit 2583efe

File tree

4 files changed

+178
-2
lines changed

4 files changed

+178
-2
lines changed

httomolibgpu/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
)
1313

1414
from httomolibgpu.recon.algorithm import (
15+
FBP2d_astra,
1516
FBP3d_tomobar,
1617
LPRec3d_tomobar,
1718
SIRT3d_tomobar,
1819
CGLS3d_tomobar,
1920
)
21+
2022
from httomolibgpu.recon.rotation import find_center_vo, find_center_360, find_center_pc

httomolibgpu/recon/algorithm.py

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
from unittest.mock import Mock
3030

31+
from tomobar.methodsDIR import RecToolsDIR
32+
3133
if cupy_run:
3234
from tomobar.methodsDIR_CuPy import RecToolsDIRCuPy
3335
from tomobar.methodsIR_CuPy import RecToolsIRCuPy
@@ -40,6 +42,7 @@
4042

4143

4244
__all__ = [
45+
"FBP2d_astra",
4346
"FBP3d_tomobar",
4447
"LPRec3d_tomobar",
4548
"SIRT3d_tomobar",
@@ -49,6 +52,83 @@
4952
input_data_axis_labels = ["angles", "detY", "detX"] # set the labels of the input data
5053

5154

55+
## %%%%%%%%%%%%%%%%%%%%%%% FBP2d_astra reconstruction %%%%%%%%%%%%%%%%%%%%%%%%%%%% ##
56+
def FBP2d_astra(
57+
data: np.ndarray,
58+
angles: np.ndarray,
59+
center: Optional[float] = None,
60+
filter_type: str = "ram-lak",
61+
filter_parameter: Optional[float] = None,
62+
filter_d: Optional[float] = None,
63+
recon_size: Optional[int] = None,
64+
recon_mask_radius: float = 0.95,
65+
neglog: bool = False,
66+
gpu_id: int = 0,
67+
) -> np.ndarray:
68+
"""
69+
Perform Filtered Backprojection (FBP) reconstruction slice-by-slice (2d) using ASTRA toolbox :cite:`van2016fast` and
70+
ToMoBAR :cite:`kazantsev2020tomographic` wrappers.
71+
This is a 2D recon using ASTRA's API for the FBP method, see for more parameters ASTRA's documentation here:
72+
https://astra-toolbox.com/docs/algs/FBP_CUDA.html.
73+
74+
Parameters`
75+
----------
76+
data : np.ndarray
77+
Projection data as a 3d numpy array.
78+
angles : np.ndarray
79+
An array of angles given in radians.
80+
center : float, optional
81+
The center of rotation (CoR).
82+
filter_type: str
83+
Type of projection filter, see ASTRA's API for all available options for filters.
84+
filter_parameter: float, optional
85+
Parameter value for the 'tukey', 'gaussian', 'blackman' and 'kaiser' filter types.
86+
filter_d: float, optional
87+
D parameter value for 'shepp-logan', 'cosine', 'hamming' and 'hann' filter types.
88+
recon_size : int, optional
89+
The [recon_size, recon_size] shape of the reconstructed slice in pixels.
90+
By default (None), the reconstructed size will be the dimension of the horizontal detector.
91+
recon_mask_radius: float
92+
The radius of the circular mask that applies to the reconstructed slice in order to crop
93+
out some undesirable artifacts. The values outside the given diameter will be set to zero.
94+
It is recommended to keep the value in the range [0.7-1.0].
95+
neglog: bool
96+
Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
97+
assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
98+
gpu_id : int
99+
A GPU device index to perform operation on.
100+
101+
Returns
102+
-------
103+
np.ndarray
104+
The FBP reconstructed volume as a numpy array.
105+
"""
106+
data_shape = np.shape(data)
107+
if recon_size is None:
108+
recon_size = data_shape[2]
109+
110+
RecTools = _instantiate_direct_recon2d_class(
111+
data, angles, center, recon_size, gpu_id
112+
)
113+
114+
detY_size = data_shape[1]
115+
reconstruction = np.empty(
116+
(recon_size, detY_size, recon_size), dtype=np.float32(), order="C"
117+
)
118+
_take_neg_log_np(data) if neglog else data
119+
120+
# loop over detY slices
121+
for slice_index in range(0, detY_size):
122+
reconstruction[:, slice_index, :] = RecTools.FBP(
123+
data[:, slice_index, :],
124+
filter_type=filter_type,
125+
filter_parameter=filter_parameter,
126+
filter_d=filter_d,
127+
recon_mask_radius=recon_mask_radius,
128+
)
129+
return reconstruction
130+
131+
52132
## %%%%%%%%%%%%%%%%%%%%%%% FBP reconstruction %%%%%%%%%%%%%%%%%%%%%%%%%%%% ##
53133
def FBP3d_tomobar(
54134
data: cp.ndarray,
@@ -68,7 +148,7 @@ def FBP3d_tomobar(
68148
Parameters
69149
----------
70150
data : cp.ndarray
71-
Projection data as a CuPy array.
151+
Projection data as a 3d CuPy array.
72152
angles : np.ndarray
73153
An array of angles given in radians.
74154
center : float, optional
@@ -124,7 +204,7 @@ def LPRec3d_tomobar(
124204
Parameters
125205
----------
126206
data : cp.ndarray
127-
Projection data as a CuPy array.
207+
Projection data as a 3d CuPy array.
128208
angles : np.ndarray
129209
An array of angles given in radians.
130210
center : float, optional
@@ -315,6 +395,43 @@ def _instantiate_direct_recon_class(
315395
return RecToolsCP
316396

317397

398+
## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ##
399+
def _instantiate_direct_recon2d_class(
400+
data: np.ndarray,
401+
angles: np.ndarray,
402+
center: Optional[float] = None,
403+
recon_size: Optional[int] = None,
404+
gpu_id: int = 0,
405+
) -> Type:
406+
"""instantiate ToMoBAR's direct recon class for 2d reconstruction
407+
408+
Args:
409+
data (cp.ndarray): data array
410+
angles (np.ndarray): angles
411+
center (Optional[float], optional): center of recon. Defaults to None.
412+
recon_size (Optional[int], optional): recon_size. Defaults to None.
413+
gpu_id (int, optional): gpu ID. Defaults to 0.
414+
415+
Returns:
416+
Type[RecToolsDIR]: an instance of the direct recon class
417+
"""
418+
if center is None:
419+
center = data.shape[2] // 2 # making a crude guess
420+
if recon_size is None:
421+
recon_size = data.shape[2]
422+
RecTools = RecToolsDIR(
423+
DetectorsDimH=data.shape[2], # Horizontal detector dimension
424+
DetectorsDimV=None, # 2d case
425+
CenterRotOffset=data.shape[2] / 2
426+
- center
427+
- 0.5, # Center of Rotation scalar or a vector
428+
AnglesVec=-angles, # A vector of projection angles in radians
429+
ObjSize=recon_size, # Reconstructed object dimensions (scalar)
430+
device_projector=gpu_id,
431+
)
432+
return RecTools
433+
434+
318435
def _instantiate_iterative_recon_class(
319436
data: cp.ndarray,
320437
angles: np.ndarray,
@@ -361,3 +478,12 @@ def _take_neg_log(data: cp.ndarray) -> cp.ndarray:
361478
data[cp.isnan(data)] = 6.0
362479
data[cp.isinf(data)] = 0
363480
return data
481+
482+
483+
def _take_neg_log_np(data: np.ndarray) -> np.ndarray:
484+
"""Taking negative log"""
485+
data[data <= 0] = 1
486+
data = -np.log(data)
487+
data[np.isnan(data)] = 6.0
488+
data[np.isinf(data)] = 0
489+
return data

tests/test_recon/test_algorithm.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import numpy as np
33
from httomolibgpu.prep.normalize import normalize as normalize_cupy
44
from httomolibgpu.recon.algorithm import (
5+
FBP2d_astra,
56
FBP3d_tomobar,
67
LPRec3d_tomobar,
78
SIRT3d_tomobar,
@@ -13,6 +14,26 @@
1314
from cupy.cuda import nvtx
1415

1516

17+
def test_reconstruct_FBP_2d_astra(data, flats, darks, ensure_clean_memory):
18+
normalised_data = normalize_cupy(data, flats, darks, cutoff=10, minus_log=True)
19+
recon_size = 150
20+
21+
recon_data = FBP2d_astra(
22+
cp.asnumpy(normalised_data),
23+
np.linspace(0.0 * np.pi / 180.0, 180.0 * np.pi / 180.0, data.shape[0]),
24+
79.5,
25+
filter_type="shepp-logan",
26+
filter_parameter=None,
27+
filter_d=2.0,
28+
recon_size=recon_size,
29+
recon_mask_radius=0.9,
30+
)
31+
assert recon_data.flags.c_contiguous
32+
assert_allclose(np.mean(recon_data), 0.0020, atol=1e-04)
33+
assert_allclose(np.mean(recon_data, axis=(0, 2)).sum(), 0.265129, rtol=1e-05)
34+
assert recon_data.dtype == np.float32
35+
assert recon_data.shape == (recon_size, 128, recon_size)
36+
1637
def test_reconstruct_FBP3d_tomobar_1(data, flats, darks, ensure_clean_memory):
1738
recon_data = FBP3d_tomobar(
1839
normalize_cupy(data, flats, darks, cutoff=10, minus_log=True),

zenodo-tests/test_recon/test_algorithm.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from httomolibgpu.prep.normalize import normalize
99
from httomolibgpu.recon.algorithm import (
10+
FBP2d_astra,
1011
FBP3d_tomobar,
1112
LPRec3d_tomobar,
1213
)
@@ -17,6 +18,32 @@
1718
from cupy.cuda import nvtx
1819
from conftest import force_clean_gpu_memory
1920

21+
def test_reconstruct_FBP2d_astra_i12_dataset1(i12_dataset1):
22+
force_clean_gpu_memory()
23+
projdata = i12_dataset1[0]
24+
angles = i12_dataset1[1]
25+
flats = i12_dataset1[2]
26+
darks = i12_dataset1[3]
27+
del i12_dataset1
28+
29+
data_normalised = normalize(projdata, flats, darks, minus_log=True)
30+
del flats, darks, projdata
31+
force_clean_gpu_memory()
32+
33+
recon_data = FBP2d_astra(
34+
cp.asnumpy(data_normalised),
35+
np.deg2rad(angles),
36+
center=1253.75,
37+
filter_type="shepp-logan",
38+
filter_parameter=None,
39+
filter_d=2.0,
40+
recon_mask_radius=0.9,
41+
)
42+
assert recon_data.flags.c_contiguous
43+
assert_allclose(np.sum(recon_data), 84673.68, rtol=1e-07, atol=1e-6)
44+
assert recon_data.dtype == np.float32
45+
assert recon_data.shape == (2560, 50, 2560)
46+
2047

2148
def test_reconstruct_FBP3d_tomobar_i12_dataset1(i12_dataset1):
2249
force_clean_gpu_memory()

0 commit comments

Comments
 (0)