Skip to content

Commit bea0855

Browse files
committed
savu paganin work
1 parent dd49ae6 commit bea0855

File tree

1 file changed

+68
-4
lines changed

1 file changed

+68
-4
lines changed

httomolibgpu/prep/phase.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ def _calculate_alpha(energy, distance_micron, ratio_delta_beta):
145145
) * ratio_delta_beta
146146

147147

148+
# the scaling is different here and doesn't follow the original formula
149+
def _calculate_alpha_savu(energy, distance_micron, ratio_delta_beta):
150+
return (
151+
((1240.0 / energy) * 10.0 ** (-9))
152+
* distance_micron
153+
* ratio_delta_beta
154+
* math.pi
155+
)
156+
157+
148158
def _shift_bit_length(x: int) -> int:
149159
return 1 << (x - 1).bit_length()
150160

@@ -264,10 +274,64 @@ def paganin_filter_savu_legacy(
264274
cp.ndarray
265275
The 3D array of Paganin phase-filtered projection images.
266276
"""
277+
# Check the input data is valid
278+
if tomo.ndim != 3:
279+
raise ValueError(
280+
f"Invalid number of dimensions in data: {tomo.ndim},"
281+
" please provide a stack of 2D projections."
282+
)
283+
284+
dz_orig, dy_orig, dx_orig = tomo.shape
267285

268-
# the scaling is different here and doesn't follow the original formula
269-
# ((1240.0 / energy) * 10.0 ** (-9)) * distance_micron * ratio_delta_beta * math.pi
286+
# Perform padding to the power of 2 as FFT is O(n*log(n)) complexity
287+
# TODO: adding other options of padding?
288+
padded_tomo, pad_tup = _pad_projections_to_second_power(tomo)
270289

271-
return paganin_filter(
272-
tomo, pixel_size, distance * 1e-03, energy, ratio_delta_beta / (4 * 2 * np.pi)
290+
dz, dy, dx = padded_tomo.shape
291+
292+
# 3D FFT of tomo data
293+
padded_tomo = cp.asarray(padded_tomo, dtype=cp.complex64)
294+
fft_tomo = fft2(padded_tomo, axes=(-2, -1), overwrite_x=True)
295+
296+
# calculate alpha constant as Savu does
297+
micron = 10 ** (-6)
298+
KeV = 1000.0
299+
alpha = _calculate_alpha_savu(energy * KeV, distance * micron, ratio_delta_beta)
300+
301+
# Compute the reciprocal grid
302+
# NOTE: pixel_size is ALREADY in microns, why it rescaled to micron again in Savu?
303+
indx = _reciprocal_coord(pixel_size * micron, dy)
304+
indy = _reciprocal_coord(pixel_size * micron, dx)
305+
306+
# Build Lorentzian-type filter
307+
phase_filter = fftshift(
308+
1.0 / (1.0 + alpha * (cp.add.outer(cp.square(indx), cp.square(indy))))
273309
)
310+
311+
phase_filter = phase_filter / phase_filter.max() # normalisation
312+
313+
# Filter projections
314+
fft_tomo *= phase_filter
315+
316+
# Apply filter and take inverse FFT
317+
ifft_filtered_tomo = ifft2(fft_tomo, axes=(-2, -1), overwrite_x=True).real
318+
319+
# slicing indices for cropping
320+
slc_indices = (
321+
slice(pad_tup[0][0], pad_tup[0][0] + dz_orig, 1),
322+
slice(pad_tup[1][0], pad_tup[1][0] + dy_orig, 1),
323+
slice(pad_tup[2][0], pad_tup[2][0] + dx_orig, 1),
324+
)
325+
326+
# crop the padded filtered data:
327+
tomo = ifft_filtered_tomo[slc_indices].astype(cp.float32)
328+
329+
# taking the negative log
330+
_log_kernel = cp.ElementwiseKernel(
331+
"C tomo",
332+
"C out",
333+
"out = -log(tomo)",
334+
name="log_kernel",
335+
)
336+
337+
return _log_kernel(tomo)

0 commit comments

Comments
 (0)