@@ -97,20 +97,15 @@ def paganin_filter(
9797 fft_tomo = fft2 (padded_tomo , axes = (- 2 , - 1 ), overwrite_x = True )
9898
9999 # calculate alpha constant
100- alpha = _calculate_alpha (energy , distance , ratio_delta_beta )
100+ alpha = _calculate_alpha (energy , distance / 1e-6 , ratio_delta_beta )
101101
102102 # Compute the reciprocal grid
103- # pixel_size *= 1e6 # rescaling to meters
104103 indx = _reciprocal_coord (pixel_size , dy )
105104 indy = _reciprocal_coord (pixel_size , dx )
106105
107106 # Build Lorentzian-type filter
108107 phase_filter = fftshift (
109- 1.0
110- / (
111- 1.0
112- + alpha * (cp .add .outer (cp .square (indx ), cp .square (indy )) / (4 * math .pi ))
113- )
108+ 1.0 / (1.0 + alpha * (cp .add .outer (cp .square (indx ), cp .square (indy ))))
114109 )
115110
116111 phase_filter = phase_filter / phase_filter .max () # normalisation
@@ -142,18 +137,10 @@ def paganin_filter(
142137 return _log_kernel (tomo )
143138
144139
145- def _calculate_alpha (energy , distance , ratio_delta_beta ):
146- return _wavelength (energy ) * distance * ratio_delta_beta
147-
148-
149- # the scaling is different here and doesn't follow the original formula
150- def _calculate_alpha_savu (energy , distance_micron , ratio_delta_beta ):
140+ def _calculate_alpha (energy , distance_micron , ratio_delta_beta ):
151141 return (
152- ((1240.0 / energy ) * 10.0 ** (- 9 ))
153- * distance_micron
154- * ratio_delta_beta
155- * math .pi
156- )
142+ _wavelength_micron (energy ) * distance_micron / (4 * math .pi )
143+ ) * ratio_delta_beta
157144
158145
159146def _shift_bit_length (x : int ) -> int :
@@ -216,9 +203,10 @@ def _pad_projections_to_second_power(
216203 return padded_tomo , tuple (pad_list )
217204
218205
219- def _wavelength (energy : float ) -> float :
220- # for photons: E = 1keV -> 1.23984193 nm
221- return (1.23984193e-6 ) / energy
206+ def _wavelength_micron (energy : float ) -> float :
207+ SPEED_OF_LIGHT = 299792458e2 * 10000.0 # [microns/s]
208+ PLANCK_CONSTANT = 6.58211928e-19 # [keV*s]
209+ return 2 * math .pi * PLANCK_CONSTANT * SPEED_OF_LIGHT / energy
222210
223211
224212def _reciprocal_coord (pixel_size : float , num_grid : int ) -> cp .ndarray :
@@ -254,7 +242,7 @@ def paganin_filter_savu_legacy(
254242 """
255243 Perform single-material phase retrieval from flats/darks corrected tomographic measurements. For more detailed information, see :ref:`phase_contrast_module`.
256244 Also see :cite:`Paganin02` and :cite:`paganin2020boosting` for references. The ratio_delta_beta parameter here follows implementation in Savu software.
257- The module will be retired in future in favour of paganin_filter. One can rescale parameter ratio_delta_beta / 4*2*pi to achieve the same effect in paganin_filter.
245+ The module will be retired in future in favour of paganin_filter. One can rescale parameter ratio_delta_beta / 4 to achieve the same effect in paganin_filter.
258246
259247 Parameters
260248 ----------
@@ -274,64 +262,5 @@ def paganin_filter_savu_legacy(
274262 cp.ndarray
275263 The 3D array of Paganin phase-filtered projection images.
276264 """
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
285-
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 )
289-
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 ))))
309- )
310-
311- phase_filter = phase_filter / phase_filter .max () # normalisation
312265
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 )
266+ return paganin_filter (tomo , pixel_size , distance , energy , ratio_delta_beta / 4 )
0 commit comments