Skip to content

Commit 8a06299

Browse files
authored
Merge pull request #196 from DiamondLightSource/neglog
adding neglog as a parameter of each reconstruction module
2 parents 5be3b20 + 621a479 commit 8a06299

File tree

3 files changed

+62
-13
lines changed

3 files changed

+62
-13
lines changed

docs/source/doc-conda-requirements.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ channels:
33
- conda-forge
44
dependencies:
55
- python>=3.10
6-
- sphinx
6+
- sphinx>=8.0,<8.2.0
77
- sphinx-book-theme
88
- nbsphinx
99
- pandoc
@@ -13,4 +13,4 @@ dependencies:
1313
- sphinxcontrib-bibtex
1414
- pyyaml
1515
- ipython
16-
- ghp-import
16+
- ghp-import

httomolibgpu/recon/algorithm.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ def FBP(
5454
data: cp.ndarray,
5555
angles: np.ndarray,
5656
center: Optional[float] = None,
57-
filter_freq_cutoff: Optional[float] = 0.35,
57+
filter_freq_cutoff: float = 0.35,
5858
recon_size: Optional[int] = None,
59-
recon_mask_radius: Optional[float] = 0.95,
59+
recon_mask_radius: float = 0.95,
60+
neglog: bool = False,
6061
gpu_id: int = 0,
6162
) -> cp.ndarray:
6263
"""
@@ -81,6 +82,9 @@ def FBP(
8182
The radius of the circular mask that applies to the reconstructed slice in order to crop
8283
out some undesirable artifacts. The values outside the given diameter will be set to zero.
8384
It is recommended to keep the value in the range [0.7-1.0].
85+
neglog: bool
86+
Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
87+
assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
8488
gpu_id : int
8589
A GPU device index to perform operation on.
8690
@@ -94,7 +98,7 @@ def FBP(
9498
)
9599

96100
reconstruction = RecToolsCP.FBP(
97-
data,
101+
_take_neg_log(data) if neglog else data,
98102
cutoff_freq=filter_freq_cutoff,
99103
recon_mask_radius=recon_mask_radius,
100104
data_axes_labels_order=input_data_axis_labels,
@@ -110,6 +114,7 @@ def LPRec(
110114
center: Optional[float] = None,
111115
recon_size: Optional[int] = None,
112116
recon_mask_radius: Optional[float] = 0.95,
117+
neglog: bool = False,
113118
) -> cp.ndarray:
114119
"""
115120
Fourier direct inversion in 3D on unequally spaced (also called as Log-Polar) grids using
@@ -131,6 +136,9 @@ def LPRec(
131136
The radius of the circular mask that applies to the reconstructed slice in order to crop
132137
out some undesirable artifacts. The values outside the given diameter will be set to zero.
133138
It is recommended to keep the value in the range [0.7-1.0].
139+
neglog: bool
140+
Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
141+
assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
134142
135143
Returns
136144
-------
@@ -140,7 +148,7 @@ def LPRec(
140148
RecToolsCP = _instantiate_direct_recon_class(data, angles, center, recon_size, 0)
141149

142150
reconstruction = RecToolsCP.FOURIER_INV(
143-
data,
151+
_take_neg_log(data) if neglog else data,
144152
recon_mask_radius=recon_mask_radius,
145153
data_axes_labels_order=input_data_axis_labels,
146154
)
@@ -156,12 +164,14 @@ def SIRT(
156164
recon_size: Optional[int] = None,
157165
iterations: Optional[int] = 300,
158166
nonnegativity: Optional[bool] = True,
167+
neglog: bool = False,
159168
gpu_id: int = 0,
160169
) -> cp.ndarray:
161170
"""
162171
Perform Simultaneous Iterative Recostruction Technique (SIRT) using ASTRA toolbox :cite:`van2016fast` and
163172
ToMoBAR :cite:`kazantsev2020tomographic` wrappers.
164-
This is 3D recon directly from a CuPy array while using ASTRA GPUlink capability.
173+
This is 3D recon directly from a CuPy array while using ASTRA GPUlink capability to avoid host-device
174+
transactions for projection and backprojection.
165175
166176
Parameters
167177
----------
@@ -178,6 +188,9 @@ def SIRT(
178188
The number of SIRT iterations.
179189
nonnegativity : bool, optional
180190
Impose nonnegativity constraint on reconstructed image.
191+
neglog: bool
192+
Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
193+
assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
181194
gpu_id : int, optional
182195
A GPU device index to perform operation on.
183196
@@ -187,11 +200,16 @@ def SIRT(
187200
The SIRT reconstructed volume as a CuPy array.
188201
"""
189202
RecToolsCP = _instantiate_iterative_recon_class(
190-
data, angles, center, recon_size, gpu_id, datafidelity="LS"
203+
data,
204+
angles,
205+
center,
206+
recon_size,
207+
gpu_id,
208+
datafidelity="LS",
191209
)
192210

193211
_data_ = {
194-
"projection_norm_data": data,
212+
"projection_norm_data": _take_neg_log(data) if neglog else data,
195213
"data_axes_labels_order": input_data_axis_labels,
196214
} # data dictionary
197215
_algorithm_ = {
@@ -211,12 +229,14 @@ def CGLS(
211229
recon_size: Optional[int] = None,
212230
iterations: Optional[int] = 20,
213231
nonnegativity: Optional[bool] = True,
232+
neglog: bool = False,
214233
gpu_id: int = 0,
215234
) -> cp.ndarray:
216235
"""
217-
Perform Congugate Gradient Least Squares (CGLS) using ASTRA toolbox :cite:`van2016fast` and
236+
Perform Conjugate Gradient Least Squares (CGLS) using ASTRA toolbox :cite:`van2016fast` and
218237
ToMoBAR :cite:`kazantsev2020tomographic` wrappers.
219-
This is 3D recon directly from a CuPy array while using ASTRA GPUlink capability.
238+
This is 3D recon directly from a CuPy array while using ASTRA GPUlink capability to avoid host-device
239+
transactions for projection and backprojection.
220240
221241
Parameters
222242
----------
@@ -233,6 +253,9 @@ def CGLS(
233253
The number of CGLS iterations.
234254
nonnegativity : bool, optional
235255
Impose nonnegativity constraint on reconstructed image.
256+
neglog: bool
257+
Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
258+
assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
236259
gpu_id : int, optional
237260
A GPU device index to perform operation on.
238261
@@ -246,15 +269,14 @@ def CGLS(
246269
)
247270

248271
_data_ = {
249-
"projection_norm_data": data,
272+
"projection_norm_data": _take_neg_log(data) if neglog else data,
250273
"data_axes_labels_order": input_data_axis_labels,
251274
} # data dictionary
252275
_algorithm_ = {"iterations": iterations, "nonnegativity": nonnegativity}
253276
reconstruction = RecToolsCP.CGLS(_data_, _algorithm_)
254277
cp._default_memory_pool.free_all_blocks()
255278
return cp.require(cp.swapaxes(reconstruction, 0, 1), requirements="C")
256279

257-
258280
## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ##
259281
def _instantiate_direct_recon_class(
260282
data: cp.ndarray,
@@ -329,3 +351,11 @@ def _instantiate_iterative_recon_class(
329351
device_projector=gpu_id,
330352
)
331353
return RecToolsCP
354+
355+
def _take_neg_log(data: cp.ndarray) -> cp.ndarray:
356+
"""Taking negative log"""
357+
data[data<=0] = 1
358+
data = -cp.log(data)
359+
data[cp.isnan(data)] = 6.0
360+
data[cp.isinf(data)] = 0
361+
return data

tests/test_recon/test_algorithm.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@ def test_reconstruct_FBP_1(data, flats, darks, ensure_clean_memory):
3131
assert recon_data.shape == (160, 128, 160)
3232

3333

34+
def test_reconstruct_FBP_1_neglog(data, flats, darks, ensure_clean_memory):
35+
recon_data = FBP(
36+
normalize_cupy(data, flats, darks, cutoff=10, minus_log=False),
37+
np.linspace(0.0 * np.pi / 180.0, 180.0 * np.pi / 180.0, data.shape[0]),
38+
79.5,
39+
filter_freq_cutoff=1.1,
40+
recon_mask_radius=None,
41+
neglog=True,
42+
)
43+
assert recon_data.flags.c_contiguous
44+
recon_data = recon_data.get()
45+
assert_allclose(np.mean(recon_data), 0.000798, rtol=1e-07, atol=1e-6)
46+
assert_allclose(np.mean(recon_data, axis=(0, 2)).sum(), 0.102106, rtol=1e-05)
47+
assert_allclose(np.std(recon_data), 0.006293, rtol=1e-07, atol=1e-6)
48+
assert_allclose(np.median(recon_data), -0.000555, rtol=1e-07, atol=1e-6)
49+
assert recon_data.dtype == np.float32
50+
assert recon_data.shape == (160, 128, 160)
51+
52+
3453
def test_reconstruct_FBP_2(data, flats, darks, ensure_clean_memory):
3554
recon_data = FBP(
3655
normalize_cupy(data, flats, darks, cutoff=20.5, minus_log=False),

0 commit comments

Comments
 (0)