|
29 | 29 | from unittest.mock import Mock |
30 | 30 |
|
31 | 31 | if cupy_run: |
32 | | - from httomolibgpu.cuda_kernels import load_cuda_module |
33 | 32 | from cupyx.scipy.fft import fft2, ifft2, fftshift |
34 | 33 | else: |
35 | | - load_cuda_module = Mock() |
36 | 34 | fft2 = Mock() |
37 | 35 | ifft2 = Mock() |
38 | 36 | fftshift = Mock() |
|
44 | 42 | from httomolibgpu.misc.supp_func import data_checker |
45 | 43 |
|
46 | 44 | __all__ = [ |
47 | | - "paganin_filter_savu", |
48 | 45 | "paganin_filter_tomopy", |
49 | 46 | ] |
50 | 47 |
|
51 | 48 |
|
52 | | -## %%%%%%%%%%%%%%%%%%%%%%% paganin_filter %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ## |
53 | | -#: CuPy implementation of Paganin filter from Savu |
54 | | -def paganin_filter_savu( |
55 | | - data: cp.ndarray, |
56 | | - ratio: float = 250.0, |
57 | | - energy: float = 53.0, |
58 | | - distance: float = 1.0, |
59 | | - resolution: float = 1.28, |
60 | | - pad_y: int = 100, |
61 | | - pad_x: int = 100, |
62 | | - pad_method: str = "edge", |
63 | | - increment: float = 0.0, |
64 | | -) -> cp.ndarray: |
65 | | - """ |
66 | | - Apply Paganin filter (for denoising or contrast enhancement) to |
67 | | - projections. |
68 | | -
|
69 | | - Parameters |
70 | | - ---------- |
71 | | - data : cp.ndarray |
72 | | - The stack of projections to filter. |
73 | | -
|
74 | | - ratio : float, optional |
75 | | - Ratio of delta/beta. |
76 | | -
|
77 | | - energy : float, optional |
78 | | - Beam energy in keV. |
79 | | -
|
80 | | - distance : float, optional |
81 | | - Distance from sample to detector in metres. |
82 | | -
|
83 | | - resolution : float, optional |
84 | | - Pixel size in microns. |
85 | | -
|
86 | | - pad_y : int, optional |
87 | | - Pad the top and bottom of projections. |
88 | | -
|
89 | | - pad_x : int, optional |
90 | | - Pad the left and right of projections. |
91 | | -
|
92 | | - pad_method : str, optional |
93 | | - Numpy pad method to use. |
94 | | -
|
95 | | - increment : float, optional |
96 | | - Increment all values by this amount before taking the log. |
97 | | -
|
98 | | - Returns |
99 | | - ------- |
100 | | - cp.ndarray |
101 | | - The stack of filtered projections. |
102 | | - """ |
103 | | - # Check the input data is valid |
104 | | - if data.ndim != 3: |
105 | | - raise ValueError( |
106 | | - f"Invalid number of dimensions in data: {data.ndim}," |
107 | | - " please provide a stack of 2D projections." |
108 | | - ) |
109 | | - |
110 | | - data = data_checker(data, verbosity=True, method_name="paganin_filter_savu") |
111 | | - |
112 | | - # Setup various values for the filter |
113 | | - _, height, width = data.shape |
114 | | - micron = 1e-6 |
115 | | - keV = 1000.0 |
116 | | - energy *= keV |
117 | | - resolution *= micron |
118 | | - wavelength = (1240.0 / energy) * 1e-9 |
119 | | - |
120 | | - height1 = height + 2 * pad_y |
121 | | - width1 = width + 2 * pad_x |
122 | | - |
123 | | - # Define the paganin filter, taking into account the padding that will be |
124 | | - # applied to the projections (if any) |
125 | | - |
126 | | - # Using raw kernel her as indexing is direct and it avoids a lot of temporaries |
127 | | - # and tiny kernels |
128 | | - module = load_cuda_module("paganin_filter_gen") |
129 | | - kernel = module.get_function("paganin_filter_gen") |
130 | | - |
131 | | - # Apply padding to all the 2D projections |
132 | | - # Note: this takes considerable time on GPU... |
133 | | - data = cp.pad(data, ((0, 0), (pad_y, pad_y), (pad_x, pad_x)), mode=pad_method) |
134 | | - |
135 | | - precond_kernel_float = cp.ElementwiseKernel( |
136 | | - "T data", |
137 | | - "T out", |
138 | | - """ |
139 | | - if (isnan(data)) { |
140 | | - out = T(0); |
141 | | - } else if (isinf(data)) { |
142 | | - out = data < 0.0 ? -3.402823e38f : 3.402823e38f; // FLT_MAX, not available in cupy |
143 | | - } else if (data == 0.0) { |
144 | | - out = 1.0; |
145 | | - } else { |
146 | | - out = data; |
147 | | - } |
148 | | - """, |
149 | | - name="paganin_precond_float", |
150 | | - no_return=True, |
151 | | - ) |
152 | | - precond_kernel_int = cp.ElementwiseKernel( |
153 | | - "T data", |
154 | | - "T out", |
155 | | - """out = data == 0 ? 1 : data""", |
156 | | - name="paganin_precond_int", |
157 | | - no_return=True, |
158 | | - ) |
159 | | - |
160 | | - if data.dtype in (cp.float32, cp.float64): |
161 | | - precond_kernel_float(data, data) |
162 | | - else: |
163 | | - precond_kernel_int(data, data) |
164 | | - |
165 | | - # avoid normalising in both directions - we include multiplier in the post_kernel |
166 | | - data = cp.asarray(data, dtype=cp.complex64) |
167 | | - data = fft2(data, axes=(-2, -1), overwrite_x=True, norm="backward") |
168 | | - |
169 | | - # prepare filter here, while the GPU is busy with the FFT |
170 | | - filtercomplex = cp.empty((height1, width1), dtype=cp.complex64) |
171 | | - bx = 16 |
172 | | - by = 8 |
173 | | - gx = (width1 + bx - 1) // bx |
174 | | - gy = (height1 + by - 1) // by |
175 | | - kernel( |
176 | | - grid=(gx, gy, 1), |
177 | | - block=(bx, by, 1), |
178 | | - args=( |
179 | | - cp.int32(width1), |
180 | | - cp.int32(height1), |
181 | | - cp.float32(resolution), |
182 | | - cp.float32(wavelength), |
183 | | - cp.float32(distance), |
184 | | - cp.float32(ratio), |
185 | | - filtercomplex, |
186 | | - ), |
187 | | - ) |
188 | | - data *= filtercomplex |
189 | | - |
190 | | - data = ifft2(data, axes=(-2, -1), overwrite_x=True, norm="forward") |
191 | | - |
192 | | - post_kernel = cp.ElementwiseKernel( |
193 | | - "C pci1, raw float32 increment, raw float32 ratio, raw float32 fft_scale", |
194 | | - "T out", |
195 | | - "out = -0.5 * ratio * log(abs(pci1) * fft_scale + increment)", |
196 | | - name="paganin_post_proc", |
197 | | - no_return=True, |
198 | | - ) |
199 | | - fft_scale = 1.0 / (data.shape[1] * data.shape[2]) |
200 | | - res = cp.empty((data.shape[0], height, width), dtype=cp.float32) |
201 | | - post_kernel( |
202 | | - data[:, pad_y : pad_y + height, pad_x : pad_x + width], |
203 | | - np.float32(increment), |
204 | | - np.float32(ratio), |
205 | | - np.float32(fft_scale), |
206 | | - res, |
207 | | - ) |
208 | | - return res |
209 | | - |
210 | | - |
211 | | -def _wavelength(energy: float) -> float: |
212 | | - SPEED_OF_LIGHT = 299792458e2 # [cm/s] |
213 | | - PLANCK_CONSTANT = 6.58211928e-19 # [keV*s] |
214 | | - return 2 * math.pi * PLANCK_CONSTANT * SPEED_OF_LIGHT / energy |
215 | | - |
216 | | - |
217 | | -def _reciprocal_grid(pixel_size: float, shape_proj: tuple) -> cp.ndarray: |
218 | | - """ |
219 | | - Calculate reciprocal grid. |
220 | | -
|
221 | | - Parameters |
222 | | - ---------- |
223 | | - pixel_size : float |
224 | | - Detector pixel size in cm. |
225 | | - shape_proj : tuple |
226 | | - Shape of the reciprocal grid along x and y axes. |
227 | | -
|
228 | | - Returns |
229 | | - ------- |
230 | | - ndarray |
231 | | - Grid coordinates. |
232 | | - """ |
233 | | - # Sampling in reciprocal space. |
234 | | - indx = _reciprocal_coord(pixel_size, shape_proj[0]) |
235 | | - indy = _reciprocal_coord(pixel_size, shape_proj[1]) |
236 | | - indx_sq = cp.square(indx) |
237 | | - indy_sq = cp.square(indy) |
238 | | - |
239 | | - return cp.add.outer(indx_sq, indy_sq) |
240 | | - |
241 | | - |
242 | | -def _reciprocal_coord(pixel_size: float, num_grid: int) -> cp.ndarray: |
243 | | - """ |
244 | | - Calculate reciprocal grid coordinates for a given pixel size |
245 | | - and discretization. |
246 | | -
|
247 | | - Parameters |
248 | | - ---------- |
249 | | - pixel_size : float |
250 | | - Detector pixel size in cm. |
251 | | - num_grid : int |
252 | | - Size of the reciprocal grid. |
253 | | -
|
254 | | - Returns |
255 | | - ------- |
256 | | - ndarray |
257 | | - Grid coordinates. |
258 | | - """ |
259 | | - n = num_grid - 1 |
260 | | - rc = cp.arange(-n, num_grid, 2, dtype=cp.float32) |
261 | | - rc *= 2 * math.pi / (n * pixel_size) |
262 | | - return rc |
263 | | - |
264 | | - |
265 | | -##-------------------------------------------------------------## |
266 | 49 | ##-------------------------------------------------------------## |
267 | 50 | # Adaptation of retrieve_phase (Paganin filter) from TomoPy |
268 | 51 | def paganin_filter_tomopy( |
@@ -412,3 +195,57 @@ def _pad_projections_to_second_power( |
412 | 195 | def _paganin_filter_factor2(energy, dist, alpha, w2): |
413 | 196 | # Alpha represents the ratio of delta/beta. |
414 | 197 | return 1 / (_wavelength(energy) * dist * w2 / (4 * math.pi) + alpha) |
| 198 | + |
| 199 | + |
| 200 | +def _wavelength(energy: float) -> float: |
| 201 | + SPEED_OF_LIGHT = 299792458e2 # [cm/s] |
| 202 | + PLANCK_CONSTANT = 6.58211928e-19 # [keV*s] |
| 203 | + return 2 * math.pi * PLANCK_CONSTANT * SPEED_OF_LIGHT / energy |
| 204 | + |
| 205 | + |
| 206 | +def _reciprocal_grid(pixel_size: float, shape_proj: tuple) -> cp.ndarray: |
| 207 | + """ |
| 208 | + Calculate reciprocal grid. |
| 209 | +
|
| 210 | + Parameters |
| 211 | + ---------- |
| 212 | + pixel_size : float |
| 213 | + Detector pixel size in cm. |
| 214 | + shape_proj : tuple |
| 215 | + Shape of the reciprocal grid along x and y axes. |
| 216 | +
|
| 217 | + Returns |
| 218 | + ------- |
| 219 | + ndarray |
| 220 | + Grid coordinates. |
| 221 | + """ |
| 222 | + # Sampling in reciprocal space. |
| 223 | + indx = _reciprocal_coord(pixel_size, shape_proj[0]) |
| 224 | + indy = _reciprocal_coord(pixel_size, shape_proj[1]) |
| 225 | + indx_sq = cp.square(indx) |
| 226 | + indy_sq = cp.square(indy) |
| 227 | + |
| 228 | + return cp.add.outer(indx_sq, indy_sq) |
| 229 | + |
| 230 | + |
| 231 | +def _reciprocal_coord(pixel_size: float, num_grid: int) -> cp.ndarray: |
| 232 | + """ |
| 233 | + Calculate reciprocal grid coordinates for a given pixel size |
| 234 | + and discretization. |
| 235 | +
|
| 236 | + Parameters |
| 237 | + ---------- |
| 238 | + pixel_size : float |
| 239 | + Detector pixel size in cm. |
| 240 | + num_grid : int |
| 241 | + Size of the reciprocal grid. |
| 242 | +
|
| 243 | + Returns |
| 244 | + ------- |
| 245 | + ndarray |
| 246 | + Grid coordinates. |
| 247 | + """ |
| 248 | + n = num_grid - 1 |
| 249 | + rc = cp.arange(-n, num_grid, 2, dtype=cp.float32) |
| 250 | + rc *= 2 * math.pi / (n * pixel_size) |
| 251 | + return rc |
0 commit comments