httomolibgpu 2.7.1__py3-none-any.whl → 3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
httomolibgpu/__init__.py CHANGED
@@ -4,11 +4,12 @@ from httomolibgpu.misc.morph import sino_360_to_180, data_resampler
4
4
  from httomolibgpu.misc.rescale import rescale_to_int
5
5
  from httomolibgpu.prep.alignment import distortion_correction_proj_discorpy
6
6
  from httomolibgpu.prep.normalize import normalize
7
- from httomolibgpu.prep.phase import paganin_filter_savu, paganin_filter_tomopy
7
+ from httomolibgpu.prep.phase import paganin_filter_tomopy
8
8
  from httomolibgpu.prep.stripe import (
9
9
  remove_stripe_based_sorting,
10
10
  remove_stripe_ti,
11
11
  remove_all_stripe,
12
+ raven_filter,
12
13
  )
13
14
 
14
15
  from httomolibgpu.recon.algorithm import (
@@ -17,6 +18,7 @@ from httomolibgpu.recon.algorithm import (
17
18
  LPRec3d_tomobar,
18
19
  SIRT3d_tomobar,
19
20
  CGLS3d_tomobar,
21
+ FISTA3d_tomobar,
20
22
  )
21
23
 
22
24
  from httomolibgpu.recon.rotation import find_center_vo, find_center_360, find_center_pc
httomolibgpu/misc/corr.py CHANGED
@@ -114,7 +114,7 @@ def median_filter(
114
114
 
115
115
 
116
116
  def remove_outlier(
117
- data: cp.ndarray, kernel_size: int = 3, dif: float = 0.1
117
+ data: cp.ndarray, kernel_size: int = 3, dif: float = 1000
118
118
  ) -> cp.ndarray:
119
119
  """Selectively applies 3D median filter to a 3D CuPy array to remove outliers. Also called a dezinger.
120
120
  For more detailed information, see :ref:`method_outlier_removal`.
@@ -126,8 +126,8 @@ def remove_outlier(
126
126
  kernel_size : int, optional
127
127
  The size of the filter's kernel (a diameter).
128
128
  dif : float, optional
129
- Expected difference value between outlier value and the
130
- median value of the array.
129
+ Expected difference value between the outlier value (central voxel) and the
130
+ median value of the neighbourhood. Lower values lead to median filtering.
131
131
 
132
132
  Returns
133
133
  -------
@@ -21,7 +21,6 @@
21
21
  """Module for data denoising. For more detailed information see :ref:`data_denoising_module`."""
22
22
 
23
23
  import numpy as np
24
- from typing import Union, Optional
25
24
 
26
25
  from httomolibgpu import cupywrapper
27
26
 
@@ -33,7 +32,7 @@ from unittest.mock import Mock
33
32
  from httomolibgpu.misc.supp_func import data_checker
34
33
 
35
34
  if cupy_run:
36
- from ccpi.filters.regularisersCuPy import ROF_TV, PD_TV
35
+ from tomobar.regularisersCuPy import ROF_TV_cupy, PD_TV_cupy
37
36
  else:
38
37
  ROF_TV = Mock()
39
38
  PD_TV = Mock()
@@ -47,10 +46,11 @@ __all__ = [
47
46
 
48
47
  def total_variation_ROF(
49
48
  data: cp.ndarray,
50
- regularisation_parameter: Optional[float] = 1e-05,
51
- iterations: Optional[int] = 3000,
52
- time_marching_parameter: Optional[float] = 0.001,
53
- gpu_id: Optional[int] = 0,
49
+ regularisation_parameter: float = 1e-05,
50
+ iterations: int = 3000,
51
+ time_marching_parameter: float = 0.001,
52
+ gpu_id: int = 0,
53
+ half_precision: bool = False,
54
54
  ) -> cp.ndarray:
55
55
  """
56
56
  Total Variation using Rudin-Osher-Fatemi (ROF) :cite:`rudin1992nonlinear` explicit iteration scheme to perform edge-preserving image denoising.
@@ -62,14 +62,16 @@ def total_variation_ROF(
62
62
  ----------
63
63
  data : cp.ndarray
64
64
  Input CuPy 3D array of float32 data type.
65
- regularisation_parameter : float, optional
65
+ regularisation_parameter : float
66
66
  Regularisation parameter to control the level of smoothing. Defaults to 1e-05.
67
- iterations : int, optional
67
+ iterations : int
68
68
  The number of iterations. Defaults to 3000.
69
- time_marching_parameter : float, optional
69
+ time_marching_parameter : float
70
70
  Time marching parameter, needs to be small to ensure convergence. Defaults to 0.001.
71
- gpu_id : int, optional
71
+ gpu_id : int
72
72
  GPU device index to perform processing on. Defaults to 0.
73
+ half_precision : bool
74
+ Perform faster computation in half-precision with a very minimal sacrifice in quality. Defaults to False.
73
75
 
74
76
  Returns
75
77
  -------
@@ -84,19 +86,25 @@ def total_variation_ROF(
84
86
 
85
87
  data = data_checker(data, verbosity=True, method_name="total_variation_ROF")
86
88
 
87
- return ROF_TV(
88
- data, regularisation_parameter, iterations, time_marching_parameter, gpu_id
89
+ return ROF_TV_cupy(
90
+ data,
91
+ regularisation_parameter,
92
+ iterations,
93
+ time_marching_parameter,
94
+ gpu_id,
95
+ half_precision,
89
96
  )
90
97
 
91
98
 
92
99
  def total_variation_PD(
93
100
  data: cp.ndarray,
94
- regularisation_parameter: Optional[float] = 1e-05,
95
- iterations: Optional[int] = 1000,
96
- isotropic: Optional[bool] = True,
97
- nonnegativity: Optional[bool] = False,
98
- lipschitz_const: Optional[float] = 8.0,
99
- gpu_id: Optional[int] = 0,
101
+ regularisation_parameter: float = 1e-05,
102
+ iterations: int = 1000,
103
+ isotropic: bool = True,
104
+ nonnegativity: bool = False,
105
+ lipschitz_const: float = 8.0,
106
+ gpu_id: int = 0,
107
+ half_precision: bool = False,
100
108
  ) -> cp.ndarray:
101
109
  """
102
110
  Primal Dual algorithm for non-smooth convex Total Variation functional :cite:`chan1999nonlinear`. See more in :ref:`method_total_variation_PD`.
@@ -117,6 +125,8 @@ def total_variation_PD(
117
125
  Lipschitz constant to control convergence. Defaults to 8.
118
126
  gpu_id : int
119
127
  GPU device index to perform processing on. Defaults to 0.
128
+ half_precision : bool
129
+ Perform faster computation in half-precision with a very minimal sacrifice in quality. Defaults to False.
120
130
 
121
131
  Returns
122
132
  -------
@@ -139,7 +149,7 @@ def total_variation_PD(
139
149
  if nonnegativity:
140
150
  nonneg = 1
141
151
 
142
- return PD_TV(
152
+ return PD_TV_cupy(
143
153
  data,
144
154
  regularisation_parameter,
145
155
  iterations,
@@ -147,4 +157,5 @@ def total_variation_PD(
147
157
  nonneg,
148
158
  lipschitz_const,
149
159
  gpu_id,
160
+ half_precision,
150
161
  )
@@ -24,32 +24,32 @@ import numpy as np
24
24
  from httomolibgpu import cupywrapper
25
25
 
26
26
  cp = cupywrapper.cp
27
- cupy_run = cupywrapper.cupy_run
28
27
 
29
28
  from typing import Literal, Optional, Tuple, Union
30
29
 
31
30
  from httomolibgpu.misc.supp_func import data_checker
32
31
 
32
+
33
33
  __all__ = [
34
34
  "rescale_to_int",
35
35
  ]
36
36
 
37
37
 
38
38
  def rescale_to_int(
39
- data: Union[np.ndarray, cp.ndarray],
39
+ data: cp.ndarray,
40
40
  perc_range_min: float = 0.0,
41
41
  perc_range_max: float = 100.0,
42
42
  bits: Literal[8, 16, 32] = 8,
43
43
  glob_stats: Optional[Tuple[float, float, float, int]] = None,
44
- ) -> Union[np.ndarray, cp.ndarray]:
44
+ ) -> cp.ndarray:
45
45
  """
46
46
  Rescales the data given as float32 type and converts it into the range of an unsigned integer type
47
47
  with the given number of bits. For more detailed information and examples, see :ref:`method_rescale_to_int`.
48
48
 
49
49
  Parameters
50
50
  ----------
51
- data : Union[np.ndarray, cp.ndarray]
52
- Input data as a numpy or cupy array (the function is cpu-gpu agnostic)
51
+ data : cp.ndarray
52
+ Input data as a cupy array
53
53
  perc_range_min: float, optional
54
54
  The lower cutoff point in the input data, in percent of the data range (defaults to 0).
55
55
  The lower bound is computed as min + perc_range_min/100*(max-min)
@@ -69,7 +69,7 @@ def rescale_to_int(
69
69
 
70
70
  Returns
71
71
  -------
72
- Union[np.ndarray, cp.ndarray]
72
+ cp.ndarray
73
73
  The original data, clipped to the range specified with the perc_range_min and
74
74
  perc_range_max, and scaled to the full range of the output integer type
75
75
  """
@@ -82,18 +82,13 @@ def rescale_to_int(
82
82
 
83
83
  data = data_checker(data, verbosity=True, method_name="rescale_to_int")
84
84
 
85
- if cupy_run:
86
- xp = cp.get_array_module(data)
87
- else:
88
- import numpy as xp
89
-
90
85
  # get the min and max integer values of the output type
91
- output_min = xp.iinfo(output_dtype).min
92
- output_max = xp.iinfo(output_dtype).max
86
+ output_min = cp.iinfo(output_dtype).min
87
+ output_max = cp.iinfo(output_dtype).max
93
88
 
94
89
  if not isinstance(glob_stats, tuple):
95
- min_value = float(xp.min(data))
96
- max_value = float(xp.max(data))
90
+ min_value = float(cp.min(data))
91
+ max_value = float(cp.max(data))
97
92
  else:
98
93
  min_value = glob_stats[0]
99
94
  max_value = glob_stats[1]
@@ -102,32 +97,21 @@ def rescale_to_int(
102
97
  input_min = (perc_range_min * (range_intensity) / 100) + min_value
103
98
  input_max = (perc_range_max * (range_intensity) / 100) + min_value
104
99
 
100
+ factor = cp.float32(1.0)
105
101
  if (input_max - input_min) != 0.0:
106
- factor = xp.float32((output_max - output_min) / (input_max - input_min))
107
- else:
108
- factor = 1.0
109
-
110
- res = xp.empty(data.shape, dtype=output_dtype)
111
- if xp.__name__ == "numpy":
112
- if input_max == pow(2, 32):
113
- input_max -= 1
114
- res = np.copy(data.astype(float))
115
- res[data.astype(float) < input_min] = int(input_min)
116
- res[data.astype(float) > input_max] = int(input_max)
117
- res -= input_min
118
- res *= factor
119
- res = output_dtype(res)
120
- else:
121
- rescale_kernel = cp.ElementwiseKernel(
122
- "T x, raw T input_min, raw T input_max, raw T factor",
123
- "O out",
124
- """
125
- T x_clean = isnan(x) || isinf(x) ? T(0) : x;
126
- T x_clipped = x_clean < input_min ? input_min : (x_clean > input_max ? input_max : x_clean);
127
- T x_rebased = x_clipped - input_min;
128
- out = O(x_rebased * factor);
129
- """,
130
- "rescale_to_int",
131
- )
132
- rescale_kernel(data, input_min, input_max, factor, res)
102
+ factor = cp.float32((output_max - output_min) / (input_max - input_min))
103
+
104
+ res = cp.empty(data.shape, dtype=output_dtype)
105
+ rescale_kernel = cp.ElementwiseKernel(
106
+ "T x, raw T input_min, raw T input_max, raw T factor",
107
+ "O out",
108
+ """
109
+ T x_clean = isnan(x) || isinf(x) ? T(0) : x;
110
+ T x_clipped = x_clean < input_min ? input_min : (x_clean > input_max ? input_max : x_clean);
111
+ T x_rebased = x_clipped - input_min;
112
+ out = O(x_rebased * factor);
113
+ """,
114
+ "rescale_to_int",
115
+ )
116
+ rescale_kernel(data, input_min, input_max, factor, res)
133
117
  return res
@@ -159,10 +159,10 @@ def data_checker(
159
159
  data: cp.ndarray,
160
160
  verbosity: bool = True,
161
161
  method_name: Optional[str] = None,
162
- ) -> bool:
162
+ ) -> cp.ndarray:
163
163
  """
164
164
  Function that performs the variety of checks on input data, in some cases also correct the data and prints warnings.
165
- Currently it checks for: the presence of infs and nans in data.
165
+ Currently it checks for: the presence of infs and nans in data.
166
166
 
167
167
  Parameters
168
168
  ----------
@@ -29,10 +29,8 @@ cupy_run = cupywrapper.cupy_run
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,225 +42,10 @@ import math
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(
@@ -287,7 +70,7 @@ def paganin_filter_tomopy(
287
70
  energy : float, optional
288
71
  Energy of incident wave in keV.
289
72
  alpha : float, optional
290
- Regularization parameter, the ratio of delta/beta. Larger values lead to more smoothing.
73
+ Regularization parameter, the ratio of delta/beta. Smaller values lead to less noise and more blur.
291
74
 
292
75
  Returns
293
76
  -------
@@ -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
@@ -143,8 +143,8 @@ def remove_stripe_ti(
143
143
 
144
144
  _, _, dx_orig = data.shape
145
145
  if (dx_orig % 2) != 0:
146
- # the horizontal detector size is odd, data needs to be padded/cropped, for now raising the error
147
- raise ValueError("The horizontal detector size must be even")
146
+ # if the horizontal detector size is odd, the data needs to be padded
147
+ data = cp.pad(data, ((0, 0), (0, 0), (0, 1)), mode="edge")
148
148
 
149
149
  gamma = beta * ((1 - beta) / (1 + beta)) ** cp.abs(
150
150
  cp.fft.fftfreq(data.shape[-1]) * data.shape[-1]
@@ -154,7 +154,11 @@ def remove_stripe_ti(
154
154
  v = v - v[:, 0:1]
155
155
  v = cp.fft.irfft(cp.fft.rfft(v) * cp.fft.rfft(gamma)).astype(data.dtype)
156
156
  data[:] += v
157
- return data
157
+ if (dx_orig % 2) != 0:
158
+ # unpad
159
+ return data[:, :, :-1]
160
+ else:
161
+ return data
158
162
 
159
163
 
160
164
  ######## Optimized version for Vo-all ring removal in tomopy########
@@ -18,7 +18,7 @@
18
18
  # Created By : Tomography Team at DLS <scientificsoftware@diamond.ac.uk>
19
19
  # Changes relative to ToMoBAR 2024.01 version
20
20
  # ---------------------------------------------------------------------------
21
- """Module for tomographic reconstruction"""
21
+ """Module for tomographic reconstruction. For more detailed information see :ref:`image_reconstruction_module`"""
22
22
 
23
23
  import numpy as np
24
24
  from httomolibgpu import cupywrapper
@@ -37,8 +37,8 @@ else:
37
37
  RecToolsDIRCuPy = Mock()
38
38
  RecToolsIRCuPy = Mock()
39
39
 
40
- from numpy import float32, complex64
41
- from typing import Optional, Type
40
+ from numpy import float32
41
+ from typing import Optional, Type, Union
42
42
 
43
43
  from httomolibgpu.misc.supp_func import data_checker
44
44
 
@@ -49,6 +49,7 @@ __all__ = [
49
49
  "LPRec3d_tomobar",
50
50
  "SIRT3d_tomobar",
51
51
  "CGLS3d_tomobar",
52
+ "FISTA3d_tomobar",
52
53
  ]
53
54
 
54
55
  input_data_axis_labels = ["angles", "detY", "detX"] # set the labels of the input data
@@ -59,6 +60,7 @@ def FBP2d_astra(
59
60
  data: np.ndarray,
60
61
  angles: np.ndarray,
61
62
  center: Optional[float] = None,
63
+ detector_pad: Union[bool, int] = False,
62
64
  filter_type: str = "ram-lak",
63
65
  filter_parameter: Optional[float] = None,
64
66
  filter_d: Optional[float] = None,
@@ -70,8 +72,7 @@ def FBP2d_astra(
70
72
  """
71
73
  Perform Filtered Backprojection (FBP) reconstruction slice-by-slice (2d) using ASTRA toolbox :cite:`van2016fast` and
72
74
  ToMoBAR :cite:`kazantsev2020tomographic` wrappers.
73
- This is a 2D recon using ASTRA's API for the FBP method, see for more parameters ASTRA's documentation here:
74
- https://astra-toolbox.com/docs/algs/FBP_CUDA.html.
75
+ This is a 2D recon using ASTRA's API for the FBP_CUDA method, see more in :ref:`method_FBP2d_astra`.
75
76
 
76
77
  Parameters
77
78
  ----------
@@ -81,6 +82,9 @@ def FBP2d_astra(
81
82
  An array of angles given in radians.
82
83
  center : float, optional
83
84
  The center of rotation (CoR).
85
+ detector_pad : bool, int
86
+ Detector width padding with edge values to remove circle/arc type artifacts in the reconstruction. Set to True to perform
87
+ an automated padding or specify a certain value as an integer.
84
88
  filter_type: str
85
89
  Type of projection filter, see ASTRA's API for all available options for filters.
86
90
  filter_parameter: float, optional
@@ -93,7 +97,7 @@ def FBP2d_astra(
93
97
  recon_mask_radius: float
94
98
  The radius of the circular mask that applies to the reconstructed slice in order to crop
95
99
  out some undesirable artifacts. The values outside the given diameter will be set to zero.
96
- It is recommended to keep the value in the range [0.7-1.0].
100
+ To implement the cropping one can use the range [0.7-1.0] or set to 2.0 when no cropping required.
97
101
  neglog: bool
98
102
  Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
99
103
  assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
@@ -112,12 +116,12 @@ def FBP2d_astra(
112
116
  recon_size = data_shape[2]
113
117
 
114
118
  RecTools = _instantiate_direct_recon2d_class(
115
- data, angles, center, recon_size, gpu_id
119
+ data, angles, center, detector_pad, recon_size, gpu_id
116
120
  )
117
121
 
118
122
  detY_size = data_shape[1]
119
123
  reconstruction = np.empty(
120
- (recon_size, detY_size, recon_size), dtype=np.float32(), order="C"
124
+ (recon_size, detY_size, recon_size), dtype=float32, order="C"
121
125
  )
122
126
  _take_neg_log_np(data) if neglog else data
123
127
 
@@ -140,6 +144,7 @@ def FBP3d_tomobar(
140
144
  data: cp.ndarray,
141
145
  angles: np.ndarray,
142
146
  center: Optional[float] = None,
147
+ detector_pad: Union[bool, int] = False,
143
148
  filter_freq_cutoff: float = 0.35,
144
149
  recon_size: Optional[int] = None,
145
150
  recon_mask_radius: Optional[float] = 0.95,
@@ -149,7 +154,8 @@ def FBP3d_tomobar(
149
154
  """
150
155
  Perform Filtered Backprojection (FBP) reconstruction using ASTRA toolbox :cite:`van2016fast` and
151
156
  ToMoBAR :cite:`kazantsev2020tomographic` wrappers.
152
- This is a 3D recon from the CuPy array directly and using a custom built SINC filter for filtration in Fourier space.
157
+ This is a 3D recon from the CuPy array directly and using a custom built SINC filter for filtration in Fourier space,
158
+ see more in :ref:`method_FBP3d_tomobar`.
153
159
 
154
160
  Parameters
155
161
  ----------
@@ -159,15 +165,18 @@ def FBP3d_tomobar(
159
165
  An array of angles given in radians.
160
166
  center : float, optional
161
167
  The center of rotation (CoR).
168
+ detector_pad : bool, int
169
+ Detector width padding with edge values to remove circle/arc type artifacts in the reconstruction. Set to True to perform
170
+ an automated padding or specify a certain value as an integer.
162
171
  filter_freq_cutoff : float
163
- Cutoff frequency parameter for the SINC filter, the lower values produce better contrast but noisy reconstruction.
172
+ Cutoff frequency parameter for the SINC filter, the lower values may produce better contrast but noisy reconstruction. The filter change will also affect the dynamic range of the reconstructed image.
164
173
  recon_size : int, optional
165
174
  The [recon_size, recon_size] shape of the reconstructed slice in pixels.
166
175
  By default (None), the reconstructed size will be the dimension of the horizontal detector.
167
176
  recon_mask_radius: float, optional
168
177
  The radius of the circular mask that applies to the reconstructed slice in order to crop
169
178
  out some undesirable artifacts. The values outside the given diameter will be set to zero.
170
- It is recommended to keep the value in the range [0.7-1.0].
179
+ To implement the cropping one can use the range [0.7-1.0] or set to 2.0 when no cropping required.
171
180
  neglog: bool
172
181
  Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
173
182
  assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
@@ -182,7 +191,7 @@ def FBP3d_tomobar(
182
191
  data = data_checker(data, verbosity=True, method_name="FBP3d_tomobar")
183
192
 
184
193
  RecToolsCP = _instantiate_direct_recon_class(
185
- data, angles, center, recon_size, gpu_id
194
+ data, angles, center, detector_pad, recon_size, gpu_id
186
195
  )
187
196
 
188
197
  reconstruction = RecToolsCP.FBP(
@@ -200,16 +209,20 @@ def LPRec3d_tomobar(
200
209
  data: cp.ndarray,
201
210
  angles: np.ndarray,
202
211
  center: Optional[float] = None,
212
+ detector_pad: Union[bool, int] = False,
203
213
  filter_type: str = "shepp",
204
214
  filter_freq_cutoff: float = 1.0,
205
215
  recon_size: Optional[int] = None,
206
- recon_mask_radius: Optional[float] = 0.95,
216
+ recon_mask_radius: float = 0.95,
217
+ power_of_2_oversampling: Optional[bool] = True,
218
+ min_mem_usage_filter: Optional[bool] = False,
219
+ min_mem_usage_ifft2: Optional[bool] = False,
207
220
  neglog: bool = False,
208
221
  ) -> cp.ndarray:
209
222
  """
210
223
  Fourier direct inversion in 3D on unequally spaced (also called as Log-Polar) grids using
211
224
  CuPy array as an input. This implementation follows V. Nikitin's CUDA-C implementation and TomoCuPy package.
212
- :cite:`andersson2016fast`.
225
+ :cite:`andersson2016fast`, see more in :ref:`method_LPRec3d_tomobar`.
213
226
 
214
227
  Parameters
215
228
  ----------
@@ -219,17 +232,20 @@ def LPRec3d_tomobar(
219
232
  An array of angles given in radians.
220
233
  center : float, optional
221
234
  The center of rotation (CoR).
235
+ detector_pad : bool, int
236
+ Detector width padding with edge values to remove circle/arc type artifacts in the reconstruction. Set to True to perform
237
+ an automated padding or specify a certain value as an integer.
222
238
  filter_type : str
223
239
  Filter type, the accepted strings are: none, ramp, shepp, cosine, cosine2, hamming, hann, parzen.
224
240
  filter_freq_cutoff : float
225
- Cutoff frequency parameter for a filter. The higher values increase the resolution but also amplify the noise.
241
+ Cutoff frequency parameter for a filter.
226
242
  recon_size : int, optional
227
243
  The [recon_size, recon_size] shape of the reconstructed slice in pixels.
228
244
  By default (None), the reconstructed size will be the dimension of the horizontal detector.
229
- recon_mask_radius: float, optional
245
+ recon_mask_radius: float
230
246
  The radius of the circular mask that applies to the reconstructed slice in order to crop
231
247
  out some undesirable artifacts. The values outside the given diameter will be set to zero.
232
- It is recommended to keep the value in the range [0.7-1.0].
248
+ To implement the cropping one can use the range [0.7-1.0] or set to 2.0 when no cropping required.
233
249
  neglog: bool
234
250
  Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
235
251
  assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
@@ -242,7 +258,9 @@ def LPRec3d_tomobar(
242
258
 
243
259
  data = data_checker(data, verbosity=True, method_name="LPRec3d_tomobar")
244
260
 
245
- RecToolsCP = _instantiate_direct_recon_class(data, angles, center, recon_size, 0)
261
+ RecToolsCP = _instantiate_direct_recon_class(
262
+ data, angles, center, detector_pad, recon_size, 0
263
+ )
246
264
 
247
265
  reconstruction = RecToolsCP.FOURIER_INV(
248
266
  _take_neg_log(data) if neglog else data,
@@ -250,6 +268,9 @@ def LPRec3d_tomobar(
250
268
  data_axes_labels_order=input_data_axis_labels,
251
269
  filter_type=filter_type,
252
270
  cutoff_freq=filter_freq_cutoff,
271
+ power_of_2_oversampling=power_of_2_oversampling,
272
+ min_mem_usage_filter=min_mem_usage_filter,
273
+ min_mem_usage_ifft2=min_mem_usage_ifft2,
253
274
  )
254
275
  cp._default_memory_pool.free_all_blocks()
255
276
  return cp.require(cp.swapaxes(reconstruction, 0, 1), requirements="C")
@@ -260,9 +281,11 @@ def SIRT3d_tomobar(
260
281
  data: cp.ndarray,
261
282
  angles: np.ndarray,
262
283
  center: Optional[float] = None,
284
+ detector_pad: Union[bool, int] = False,
263
285
  recon_size: Optional[int] = None,
264
- iterations: Optional[int] = 300,
265
- nonnegativity: Optional[bool] = True,
286
+ recon_mask_radius: float = 0.95,
287
+ iterations: int = 300,
288
+ nonnegativity: bool = True,
266
289
  neglog: bool = False,
267
290
  gpu_id: int = 0,
268
291
  ) -> cp.ndarray:
@@ -280,17 +303,24 @@ def SIRT3d_tomobar(
280
303
  An array of angles given in radians.
281
304
  center : float, optional
282
305
  The center of rotation (CoR).
306
+ detector_pad : bool, int
307
+ Detector width padding with edge values to remove circle/arc type artifacts in the reconstruction. Set to True to perform
308
+ an automated padding or specify a certain value as an integer.
283
309
  recon_size : int, optional
284
310
  The [recon_size, recon_size] shape of the reconstructed slice in pixels.
285
311
  By default (None), the reconstructed size will be the dimension of the horizontal detector.
286
- iterations : int, optional
312
+ recon_mask_radius: float
313
+ The radius of the circular mask that applies to the reconstructed slice in order to crop
314
+ out some undesirable artifacts. The values outside the given diameter will be set to zero.
315
+ To implement the cropping one can use the range [0.7-1.0] or set to 2.0 when no cropping required.
316
+ iterations : int
287
317
  The number of SIRT iterations.
288
- nonnegativity : bool, optional
318
+ nonnegativity : bool
289
319
  Impose nonnegativity constraint on reconstructed image.
290
320
  neglog: bool
291
321
  Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
292
322
  assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
293
- gpu_id : int, optional
323
+ gpu_id : int
294
324
  A GPU device index to perform operation on.
295
325
 
296
326
  Returns
@@ -304,6 +334,7 @@ def SIRT3d_tomobar(
304
334
  data,
305
335
  angles,
306
336
  center,
337
+ detector_pad,
307
338
  recon_size,
308
339
  gpu_id,
309
340
  datafidelity="LS",
@@ -316,6 +347,7 @@ def SIRT3d_tomobar(
316
347
  _algorithm_ = {
317
348
  "iterations": iterations,
318
349
  "nonnegativity": nonnegativity,
350
+ "recon_mask_radius": recon_mask_radius,
319
351
  }
320
352
  reconstruction = RecToolsCP.SIRT(_data_, _algorithm_)
321
353
  cp._default_memory_pool.free_all_blocks()
@@ -327,9 +359,11 @@ def CGLS3d_tomobar(
327
359
  data: cp.ndarray,
328
360
  angles: np.ndarray,
329
361
  center: Optional[float] = None,
362
+ detector_pad: Union[bool, int] = False,
330
363
  recon_size: Optional[int] = None,
331
- iterations: Optional[int] = 20,
332
- nonnegativity: Optional[bool] = True,
364
+ recon_mask_radius: float = 0.95,
365
+ iterations: int = 20,
366
+ nonnegativity: bool = True,
333
367
  neglog: bool = False,
334
368
  gpu_id: int = 0,
335
369
  ) -> cp.ndarray:
@@ -347,12 +381,19 @@ def CGLS3d_tomobar(
347
381
  An array of angles given in radians.
348
382
  center : float, optional
349
383
  The center of rotation (CoR).
384
+ detector_pad : bool, int
385
+ Detector width padding with edge values to remove circle/arc type artifacts in the reconstruction. Set to True to perform
386
+ an automated padding or specify a certain value as an integer.
350
387
  recon_size : int, optional
351
388
  The [recon_size, recon_size] shape of the reconstructed slice in pixels.
352
389
  By default (None), the reconstructed size will be the dimension of the horizontal detector.
353
- iterations : int, optional
390
+ recon_mask_radius: float
391
+ The radius of the circular mask that applies to the reconstructed slice in order to crop
392
+ out some undesirable artifacts. The values outside the given diameter will be set to zero.
393
+ To implement the cropping one can use the range [0.7-1.0] or set to 2.0 when no cropping required.
394
+ iterations : int
354
395
  The number of CGLS iterations.
355
- nonnegativity : bool, optional
396
+ nonnegativity : bool
356
397
  Impose nonnegativity constraint on reconstructed image.
357
398
  neglog: bool
358
399
  Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
@@ -368,24 +409,126 @@ def CGLS3d_tomobar(
368
409
  data = data_checker(data, verbosity=True, method_name="CGLS3d_tomobar")
369
410
 
370
411
  RecToolsCP = _instantiate_iterative_recon_class(
371
- data, angles, center, recon_size, gpu_id, datafidelity="LS"
412
+ data, angles, center, detector_pad, recon_size, gpu_id, datafidelity="LS"
372
413
  )
373
414
 
374
415
  _data_ = {
375
416
  "projection_norm_data": _take_neg_log(data) if neglog else data,
376
417
  "data_axes_labels_order": input_data_axis_labels,
377
418
  } # data dictionary
378
- _algorithm_ = {"iterations": iterations, "nonnegativity": nonnegativity}
419
+ _algorithm_ = {
420
+ "iterations": iterations,
421
+ "nonnegativity": nonnegativity,
422
+ "recon_mask_radius": recon_mask_radius,
423
+ }
379
424
  reconstruction = RecToolsCP.CGLS(_data_, _algorithm_)
380
425
  cp._default_memory_pool.free_all_blocks()
381
426
  return cp.require(cp.swapaxes(reconstruction, 0, 1), requirements="C")
382
427
 
383
428
 
429
+ ## %%%%%%%%%%%%%%%%%%%%%%% FISTA reconstruction %%%%%%%%%%%%%%%%%%%%%%%%%%%% ##
430
+ def FISTA3d_tomobar(
431
+ data: cp.ndarray,
432
+ angles: np.ndarray,
433
+ center: Optional[float] = None,
434
+ detector_pad: Union[bool, int] = False,
435
+ recon_size: Optional[int] = None,
436
+ recon_mask_radius: float = 0.95,
437
+ iterations: int = 20,
438
+ subsets_number: int = 6,
439
+ regularisation_type: str = "PD_TV",
440
+ regularisation_parameter: float = 0.000001,
441
+ regularisation_iterations: int = 50,
442
+ regularisation_half_precision: bool = True,
443
+ nonnegativity: bool = True,
444
+ neglog: bool = False,
445
+ gpu_id: int = 0,
446
+ ) -> cp.ndarray:
447
+ """
448
+ A Fast Iterative Shrinkage-Thresholding Algorithm :cite:`beck2009fast` with various types of regularisation or
449
+ denoising operations :cite:`kazantsev2019ccpi` (currently accepts ROF_TV and PD_TV regularisations only).
450
+
451
+ Parameters
452
+ ----------
453
+ data : cp.ndarray
454
+ Projection data as a CuPy array.
455
+ angles : np.ndarray
456
+ An array of angles given in radians.
457
+ center : float, optional
458
+ The center of rotation (CoR).
459
+ detector_pad : bool, int
460
+ Detector width padding with edge values to remove circle/arc type artifacts in the reconstruction. Set to True to perform
461
+ an automated padding or specify a certain value as an integer.
462
+ recon_size : int, optional
463
+ The [recon_size, recon_size] shape of the reconstructed slice in pixels.
464
+ By default (None), the reconstructed size will be the dimension of the horizontal detector.
465
+ recon_mask_radius: float
466
+ The radius of the circular mask that applies to the reconstructed slice in order to crop
467
+ out some undesirable artifacts. The values outside the given diameter will be set to zero.
468
+ To implement the cropping one can use the range [0.7-1.0] or set to 2.0 when no cropping required.
469
+ iterations : int
470
+ The number of FISTA algorithm iterations.
471
+ subsets_number: int
472
+ The number of the ordered subsets to accelerate convergence. Keep the value bellow 10 to avoid divergence.
473
+ regularisation_type: str
474
+ A method to use for regularisation. Currently PD_TV and ROF_TV are available.
475
+ regularisation_parameter: float
476
+ The main regularisation parameter to control the amount of smoothing/noise removal. Larger values lead to stronger smoothing.
477
+ regularisation_iterations: int
478
+ The number of iterations for regularisers (aka INNER iterations).
479
+ regularisation_half_precision: bool
480
+ Perform faster regularisation computation in half-precision with a very minimal sacrifice in quality.
481
+ nonnegativity : bool
482
+ Impose nonnegativity constraint on the reconstructed image.
483
+ neglog: bool
484
+ Take negative logarithm on input data to convert to attenuation coefficient or a density of the scanned object. Defaults to False,
485
+ assuming that the negative log is taken either in normalisation procedure on with Paganin filter application.
486
+ gpu_id : int
487
+ A GPU device index to perform operation on.
488
+
489
+ Returns
490
+ -------
491
+ cp.ndarray
492
+ The FISTA reconstructed volume as a CuPy array.
493
+ """
494
+ data = data_checker(data, verbosity=True, method_name="FISTA3d_tomobar")
495
+
496
+ RecToolsCP = _instantiate_iterative_recon_class(
497
+ data, angles, center, detector_pad, recon_size, gpu_id, datafidelity="LS"
498
+ )
499
+
500
+ _data_ = {
501
+ "projection_norm_data": _take_neg_log(data) if neglog else data,
502
+ "OS_number": subsets_number,
503
+ "data_axes_labels_order": input_data_axis_labels,
504
+ }
505
+ lc = RecToolsCP.powermethod(_data_) # calculate Lipschitz constant (run once)
506
+
507
+ _algorithm_ = {
508
+ "iterations": iterations,
509
+ "lipschitz_const": lc.get(),
510
+ "nonnegativity": nonnegativity,
511
+ "recon_mask_radius": recon_mask_radius,
512
+ }
513
+
514
+ _regularisation_ = {
515
+ "method": regularisation_type, # Selected regularisation method
516
+ "regul_param": regularisation_parameter, # Regularisation parameter
517
+ "iterations": regularisation_iterations, # The number of regularisation iterations
518
+ "half_precision": regularisation_half_precision, # enabling half-precision calculation
519
+ }
520
+
521
+ reconstruction = RecToolsCP.FISTA(_data_, _algorithm_, _regularisation_)
522
+ cp._default_memory_pool.free_all_blocks()
523
+ return cp.require(cp.swapaxes(reconstruction, 0, 1), requirements="C")
524
+
525
+
384
526
  ## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ##
385
527
  def _instantiate_direct_recon_class(
386
528
  data: cp.ndarray,
387
529
  angles: np.ndarray,
388
530
  center: Optional[float] = None,
531
+ detector_pad: Union[bool, int] = False,
389
532
  recon_size: Optional[int] = None,
390
533
  gpu_id: int = 0,
391
534
  ) -> Type:
@@ -395,6 +538,7 @@ def _instantiate_direct_recon_class(
395
538
  data (cp.ndarray): data array
396
539
  angles (np.ndarray): angles
397
540
  center (Optional[float], optional): center of recon. Defaults to None.
541
+ detector_pad : (Union[bool, int]) : Detector width padding. Defaults to False.
398
542
  recon_size (Optional[int], optional): recon_size. Defaults to None.
399
543
  gpu_id (int, optional): gpu ID. Defaults to 0.
400
544
 
@@ -405,8 +549,13 @@ def _instantiate_direct_recon_class(
405
549
  center = data.shape[2] // 2 # making a crude guess
406
550
  if recon_size is None:
407
551
  recon_size = data.shape[2]
552
+ if detector_pad is True:
553
+ detector_pad = __estimate_detectorHoriz_padding(data.shape[2])
554
+ elif detector_pad is False:
555
+ detector_pad = 0
408
556
  RecToolsCP = RecToolsDIRCuPy(
409
557
  DetectorsDimH=data.shape[2], # Horizontal detector dimension
558
+ DetectorsDimH_pad=detector_pad, # padding for horizontal detector
410
559
  DetectorsDimV=data.shape[1], # Vertical detector dimension (3D case)
411
560
  CenterRotOffset=data.shape[2] / 2
412
561
  - center
@@ -423,6 +572,7 @@ def _instantiate_direct_recon2d_class(
423
572
  data: np.ndarray,
424
573
  angles: np.ndarray,
425
574
  center: Optional[float] = None,
575
+ detector_pad: Union[bool, int] = False,
426
576
  recon_size: Optional[int] = None,
427
577
  gpu_id: int = 0,
428
578
  ) -> Type:
@@ -432,6 +582,7 @@ def _instantiate_direct_recon2d_class(
432
582
  data (cp.ndarray): data array
433
583
  angles (np.ndarray): angles
434
584
  center (Optional[float], optional): center of recon. Defaults to None.
585
+ detector_pad : (Union[bool, int]) : Detector width padding. Defaults to False.
435
586
  recon_size (Optional[int], optional): recon_size. Defaults to None.
436
587
  gpu_id (int, optional): gpu ID. Defaults to 0.
437
588
 
@@ -442,8 +593,13 @@ def _instantiate_direct_recon2d_class(
442
593
  center = data.shape[2] // 2 # making a crude guess
443
594
  if recon_size is None:
444
595
  recon_size = data.shape[2]
596
+ if detector_pad is True:
597
+ detector_pad = __estimate_detectorHoriz_padding(data.shape[2])
598
+ elif detector_pad is False:
599
+ detector_pad = 0
445
600
  RecTools = RecToolsDIR(
446
601
  DetectorsDimH=data.shape[2], # Horizontal detector dimension
602
+ DetectorsDimH_pad=detector_pad, # padding for horizontal detector
447
603
  DetectorsDimV=None, # 2d case
448
604
  CenterRotOffset=data.shape[2] / 2
449
605
  - center
@@ -459,6 +615,7 @@ def _instantiate_iterative_recon_class(
459
615
  data: cp.ndarray,
460
616
  angles: np.ndarray,
461
617
  center: Optional[float] = None,
618
+ detector_pad: Union[bool, int] = False,
462
619
  recon_size: Optional[int] = None,
463
620
  gpu_id: int = 0,
464
621
  datafidelity: str = "LS",
@@ -469,6 +626,7 @@ def _instantiate_iterative_recon_class(
469
626
  data (cp.ndarray): data array
470
627
  angles (np.ndarray): angles
471
628
  center (Optional[float], optional): center of recon. Defaults to None.
629
+ detector_pad : (Union[bool, int]) : Detector width padding. Defaults to False.
472
630
  recon_size (Optional[int], optional): recon_size. Defaults to None.
473
631
  datafidelity (str, optional): Data fidelity
474
632
  gpu_id (int, optional): gpu ID. Defaults to 0.
@@ -480,8 +638,13 @@ def _instantiate_iterative_recon_class(
480
638
  center = data.shape[2] // 2 # making a crude guess
481
639
  if recon_size is None:
482
640
  recon_size = data.shape[2]
641
+ if detector_pad is True:
642
+ detector_pad = __estimate_detectorHoriz_padding(data.shape[2])
643
+ elif detector_pad is False:
644
+ detector_pad = 0
483
645
  RecToolsCP = RecToolsIRCuPy(
484
646
  DetectorsDimH=data.shape[2], # Horizontal detector dimension
647
+ DetectorsDimH_pad=detector_pad, # padding for horizontal detector
485
648
  DetectorsDimV=data.shape[1], # Vertical detector dimension (3D case)
486
649
  CenterRotOffset=data.shape[2] / 2
487
650
  - center
@@ -510,3 +673,10 @@ def _take_neg_log_np(data: np.ndarray) -> np.ndarray:
510
673
  data[np.isnan(data)] = 6.0
511
674
  data[np.isinf(data)] = 0
512
675
  return data
676
+
677
+
678
+ def __estimate_detectorHoriz_padding(detX_size) -> int:
679
+ det_half = detX_size // 2
680
+ padded_value_exact = int(np.sqrt(2 * (det_half**2))) - det_half
681
+ padded_add_margin = int(0.1 * padded_value_exact)
682
+ return padded_value_exact + padded_add_margin
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httomolibgpu
3
- Version: 2.7.1
3
+ Version: 3.1
4
4
  Summary: Commonly used tomography data processing methods at DLS.
5
5
  Author-email: Daniil Kazantsev <daniil.kazantsev@diamond.ac.uk>, Yousef Moazzam <yousef.moazzam@diamond.ac.uk>, Naman Gera <naman.gera@diamond.ac.uk>
6
6
  License: BSD-3-Clause
@@ -19,7 +19,6 @@ Requires-Dist: scipy
19
19
  Requires-Dist: pillow
20
20
  Requires-Dist: scikit-image
21
21
  Requires-Dist: tomobar
22
- Requires-Dist: ccpi-regularisation-cupy
23
22
  Provides-Extra: dev
24
23
  Requires-Dist: pytest; extra == "dev"
25
24
  Requires-Dist: pytest-cov; extra == "dev"
@@ -71,7 +70,7 @@ Conda environment
71
70
 
72
71
  $ conda create --name httomolibgpu # create a fresh conda environment
73
72
  $ conda activate httomolibgpu # activate the environment
74
- $ conda install -c conda-forge cupy==12.3.0 # for linux users
73
+ $ conda install conda-forge::cupy==12.3.0
75
74
  $ pip install httomolibgpu
76
75
 
77
76
  Setup the development environment:
@@ -1,29 +1,28 @@
1
- httomolibgpu/__init__.py,sha256=nLXdPpzb6smp80bCciANQHJq0o-mmMJkuga-PitGmzw,820
1
+ httomolibgpu/__init__.py,sha256=sz3ia5rSjC4lj6Xw4VZekh3OxmeVB2E2747bzyHqQdY,838
2
2
  httomolibgpu/cupywrapper.py,sha256=6ITGJ2Jw5I5kVmKEL5LlsnLRniEqqBLsHiAjvLtk0Xk,493
3
3
  httomolibgpu/cuda_kernels/__init__.py,sha256=VQNMaGcVDwiE-C64FfLtubHpLriLG0Y3_QnjHBSHrN0,884
4
4
  httomolibgpu/cuda_kernels/calc_metrics.cu,sha256=oV7ZPcwjWafmZjbNsUkBYPvOViJ_nX3zBoOAuPCmIrA,11335
5
5
  httomolibgpu/cuda_kernels/center_360_shifts.cu,sha256=Ya_8hxjXGtPBsPY3qfGJaugwnYrTFjFFretRcLiUfFQ,1631
6
6
  httomolibgpu/cuda_kernels/generate_mask.cu,sha256=3il3r1J2cnTCd3UXO4GWGfBgGxj4pvrZnXviW_SXpO0,2650
7
7
  httomolibgpu/cuda_kernels/median_kernel.cu,sha256=EECLUCoJkT9GQ9Db_FF6fYOG6cDSiAePTRZNxE4VZ68,1692
8
- httomolibgpu/cuda_kernels/paganin_filter_gen.cu,sha256=REvnVqsg-Frev7S_SBi8jKVFHPwWlIxGfCV4NUJaO58,1099
9
8
  httomolibgpu/cuda_kernels/raven_filter.cu,sha256=KX2TM_9tMpvoGCHezDNWYABCnv2cT9mlMo4IhxRUac0,1437
10
9
  httomolibgpu/cuda_kernels/remove_nan_inf.cu,sha256=gv0ihkf6A_D_po9x7pmgFsQFhwZ1dB_HYc_0Tu-bpUU,630
11
10
  httomolibgpu/misc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- httomolibgpu/misc/corr.py,sha256=ZRkDv_RmrRbL-Mr3uNaj2joEjcq6ImRlTDrWD75EtXw,4597
13
- httomolibgpu/misc/denoise.py,sha256=6dpsEjnkew-hG6BxR2crTQLfPLhqs2jvrCiJ3XJolYw,4728
11
+ httomolibgpu/misc/corr.py,sha256=1tUjwMku-MAPiLaH9IFe3zmF0p6rJFLruoObXSZelXY,4665
12
+ httomolibgpu/misc/denoise.py,sha256=l5FVdpur1I6YQcJJBfYTKjEsiDNyRYtpdOQZ7ZHicJw,4997
14
13
  httomolibgpu/misc/morph.py,sha256=AlLk_kGFHF6vNrdICMpsXmTUDnCc7ey97-_DqwZb3Wc,7475
15
- httomolibgpu/misc/rescale.py,sha256=ODO-WI3jmfyVIcfMsD_Pb39hUt4YiHqIWncpemuFhks,5058
16
- httomolibgpu/misc/supp_func.py,sha256=g68-YLg8sHcq7qZEC5LbgbTF2iHslQnU1BUg6mLxvjg,6228
14
+ httomolibgpu/misc/rescale.py,sha256=K4VQ1AdxOAhe8tTSVb9VXVZsjBap5VlOtxHVdf9MU08,4416
15
+ httomolibgpu/misc/supp_func.py,sha256=yDzNmRlIlIikQ4sKd2y9trQ9yQtblECmQ2JM5vmIY5I,6233
17
16
  httomolibgpu/prep/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
17
  httomolibgpu/prep/alignment.py,sha256=BuFTfLZD5_THQAKP_ikQ3fRE8JpN-JItGllZgrHRU5s,5657
19
18
  httomolibgpu/prep/normalize.py,sha256=ozVUAs4UY2DY7MQtJKllUgahp_4wRFKPuc_3iQl6bCE,4879
20
- httomolibgpu/prep/phase.py,sha256=KyzLJKq6ft1WexvjojpDiVwyCSGkar6DMOQEawW_olo,12043
21
- httomolibgpu/prep/stripe.py,sha256=-KRZHMSs2xkfCzPQGOl2RbLtV3VAr7tulMui36brdP8,15093
19
+ httomolibgpu/prep/phase.py,sha256=zIuAVqlnHVqcDUBtj40NRBoHaO7O2KqGZ5CCUABRXBQ,7282
20
+ httomolibgpu/prep/stripe.py,sha256=YgOVb7Z5H7NFM3d2Y1jfNNwwj7ZMDv9nMZMMCvdBnVM,15150
22
21
  httomolibgpu/recon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- httomolibgpu/recon/algorithm.py,sha256=Lqp3o8e_P-PyOTssqlmdFO0k0fArc5pNM_0i8rQFT2E,19460
22
+ httomolibgpu/recon/algorithm.py,sha256=EL0Y5xVkXR4tau5BYkEesekLxfSneIWmDdgEjfHDQ0o,27978
24
23
  httomolibgpu/recon/rotation.py,sha256=k_E0lBRprJz6AGclagIkrzk_9dipADxPtL5BxrggSwM,27729
25
- httomolibgpu-2.7.1.dist-info/licenses/LICENSE,sha256=bXeLsgelPUUXw8HCIYiVC97Dpjhm2nB54m7TACdH8ng,48032
26
- httomolibgpu-2.7.1.dist-info/METADATA,sha256=2TTgpJesuIdRsJ-6cG36ZrpbLN7IxiulljVWN3MBZo4,3401
27
- httomolibgpu-2.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- httomolibgpu-2.7.1.dist-info/top_level.txt,sha256=nV0Ty_YvSPVd1O6MNWuIplD0w1nwk5hT76YgBZ-bzUw,13
29
- httomolibgpu-2.7.1.dist-info/RECORD,,
24
+ httomolibgpu-3.1.dist-info/licenses/LICENSE,sha256=bXeLsgelPUUXw8HCIYiVC97Dpjhm2nB54m7TACdH8ng,48032
25
+ httomolibgpu-3.1.dist-info/METADATA,sha256=fVcG2CipC75a2sSwXNFl9OPxNLmZHTC24VDCcnhX7gI,3339
26
+ httomolibgpu-3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
+ httomolibgpu-3.1.dist-info/top_level.txt,sha256=nV0Ty_YvSPVd1O6MNWuIplD0w1nwk5hT76YgBZ-bzUw,13
28
+ httomolibgpu-3.1.dist-info/RECORD,,
@@ -1,37 +0,0 @@
1
- #include <cupy/complex.cuh>
2
-
3
- #ifndef M_PI
4
- #define M_PI 3.1415926535897932384626433832795f
5
- #endif
6
-
7
- extern "C" __global__ void
8
- paganin_filter_gen(int width1, int height1, float resolution, float wavelength,
9
- float distance, float ratio, complex<float> *filtercomplex) {
10
- int px = threadIdx.x + blockIdx.x * blockDim.x;
11
- int py = threadIdx.y + blockIdx.y * blockDim.y;
12
- if (px >= width1)
13
- return;
14
- if (py >= height1)
15
- return;
16
-
17
- float dpx = 1.0f / (width1 * resolution);
18
- float dpy = 1.0f / (height1 * resolution);
19
- int centerx = (width1 + 1) / 2 - 1;
20
- int centery = (height1 + 1) / 2 - 1;
21
-
22
- float pxx = (px - centerx) * dpx;
23
- float pyy = (py - centery) * dpy;
24
- float pd = (pxx * pxx + pyy * pyy) * wavelength * distance * M_PI;
25
- ;
26
- float filter1 = 1.0f + ratio * pd;
27
-
28
- complex<float> value = 1.0f / complex<float>(filter1, filter1);
29
-
30
- // ifftshifting positions
31
- int xshift = (width1 + 1) / 2;
32
- int yshift = (height1 + 1) / 2;
33
- int outX = (px + xshift) % width1;
34
- int outY = (py + yshift) % height1;
35
-
36
- filtercomplex[outY * width1 + outX] = value;
37
- }