nabu 2024.1.9__py3-none-any.whl → 2024.2.0__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.
Files changed (151) hide show
  1. nabu/__init__.py +1 -1
  2. nabu/app/bootstrap.py +2 -3
  3. nabu/app/cast_volume.py +4 -2
  4. nabu/app/cli_configs.py +5 -0
  5. nabu/app/composite_cor.py +1 -1
  6. nabu/app/create_distortion_map_from_poly.py +5 -6
  7. nabu/app/diag_to_pix.py +7 -19
  8. nabu/app/diag_to_rot.py +14 -29
  9. nabu/app/double_flatfield.py +32 -44
  10. nabu/app/parse_reconstruction_log.py +3 -0
  11. nabu/app/reconstruct.py +53 -15
  12. nabu/app/reconstruct_helical.py +2 -2
  13. nabu/app/stitching.py +27 -13
  14. nabu/app/tests/test_reduce_dark_flat.py +4 -1
  15. nabu/cuda/kernel.py +11 -2
  16. nabu/cuda/processing.py +2 -2
  17. nabu/cuda/src/cone.cu +77 -0
  18. nabu/cuda/src/hierarchical_backproj.cu +271 -0
  19. nabu/cuda/utils.py +0 -6
  20. nabu/estimation/alignment.py +5 -19
  21. nabu/estimation/cor.py +173 -599
  22. nabu/estimation/cor_sino.py +356 -26
  23. nabu/estimation/focus.py +63 -11
  24. nabu/estimation/tests/test_cor.py +124 -58
  25. nabu/estimation/tests/test_focus.py +6 -6
  26. nabu/estimation/tilt.py +2 -1
  27. nabu/estimation/utils.py +5 -33
  28. nabu/io/__init__.py +1 -1
  29. nabu/io/cast_volume.py +1 -1
  30. nabu/io/reader.py +416 -21
  31. nabu/io/tests/test_readers.py +422 -0
  32. nabu/io/tests/test_writers.py +1 -102
  33. nabu/io/writer.py +4 -433
  34. nabu/opencl/kernel.py +14 -3
  35. nabu/opencl/processing.py +8 -0
  36. nabu/pipeline/config_validators.py +5 -2
  37. nabu/pipeline/datadump.py +12 -5
  38. nabu/pipeline/estimators.py +162 -188
  39. nabu/pipeline/fullfield/chunked.py +168 -92
  40. nabu/pipeline/fullfield/chunked_cuda.py +7 -3
  41. nabu/pipeline/fullfield/computations.py +2 -7
  42. nabu/pipeline/fullfield/dataset_validator.py +0 -4
  43. nabu/pipeline/fullfield/nabu_config.py +37 -13
  44. nabu/pipeline/fullfield/processconfig.py +22 -13
  45. nabu/pipeline/fullfield/reconstruction.py +13 -9
  46. nabu/pipeline/helical/helical_chunked_regridded.py +1 -1
  47. nabu/pipeline/helical/helical_chunked_regridded_cuda.py +1 -0
  48. nabu/pipeline/helical/helical_reconstruction.py +1 -1
  49. nabu/pipeline/params.py +21 -1
  50. nabu/pipeline/processconfig.py +1 -12
  51. nabu/pipeline/reader.py +146 -0
  52. nabu/pipeline/tests/test_estimators.py +44 -72
  53. nabu/pipeline/utils.py +4 -2
  54. nabu/pipeline/writer.py +10 -2
  55. nabu/preproc/ccd_cuda.py +1 -1
  56. nabu/preproc/ctf.py +14 -7
  57. nabu/preproc/ctf_cuda.py +2 -3
  58. nabu/preproc/double_flatfield.py +5 -12
  59. nabu/preproc/double_flatfield_cuda.py +2 -2
  60. nabu/preproc/flatfield.py +5 -1
  61. nabu/preproc/flatfield_cuda.py +5 -1
  62. nabu/preproc/phase.py +24 -73
  63. nabu/preproc/phase_cuda.py +5 -8
  64. nabu/preproc/tests/test_ctf.py +11 -7
  65. nabu/preproc/tests/test_flatfield.py +67 -122
  66. nabu/preproc/tests/test_paganin.py +54 -30
  67. nabu/processing/azim.py +206 -0
  68. nabu/processing/convolution_cuda.py +1 -1
  69. nabu/processing/fft_cuda.py +15 -17
  70. nabu/processing/histogram.py +2 -0
  71. nabu/processing/histogram_cuda.py +2 -1
  72. nabu/processing/kernel_base.py +3 -0
  73. nabu/processing/muladd_cuda.py +1 -0
  74. nabu/processing/padding_opencl.py +1 -1
  75. nabu/processing/roll_opencl.py +1 -0
  76. nabu/processing/rotation_cuda.py +2 -2
  77. nabu/processing/tests/test_fft.py +17 -10
  78. nabu/processing/unsharp_cuda.py +1 -1
  79. nabu/reconstruction/cone.py +104 -40
  80. nabu/reconstruction/fbp.py +3 -0
  81. nabu/reconstruction/fbp_base.py +7 -2
  82. nabu/reconstruction/filtering.py +20 -7
  83. nabu/reconstruction/filtering_cuda.py +7 -1
  84. nabu/reconstruction/hbp.py +424 -0
  85. nabu/reconstruction/mlem.py +99 -0
  86. nabu/reconstruction/reconstructor.py +2 -0
  87. nabu/reconstruction/rings_cuda.py +19 -19
  88. nabu/reconstruction/sinogram_cuda.py +1 -0
  89. nabu/reconstruction/sinogram_opencl.py +3 -1
  90. nabu/reconstruction/tests/test_cone.py +10 -5
  91. nabu/reconstruction/tests/test_deringer.py +7 -6
  92. nabu/reconstruction/tests/test_fbp.py +124 -10
  93. nabu/reconstruction/tests/test_filtering.py +13 -11
  94. nabu/reconstruction/tests/test_halftomo.py +30 -4
  95. nabu/reconstruction/tests/test_mlem.py +91 -0
  96. nabu/reconstruction/tests/test_reconstructor.py +8 -3
  97. nabu/resources/dataset_analyzer.py +142 -92
  98. nabu/resources/gpu.py +1 -0
  99. nabu/resources/nxflatfield.py +134 -125
  100. nabu/resources/templates/id16a_fluo.conf +42 -0
  101. nabu/resources/tests/test_extract.py +10 -0
  102. nabu/resources/tests/test_nxflatfield.py +2 -2
  103. nabu/stitching/alignment.py +80 -24
  104. nabu/stitching/config.py +105 -68
  105. nabu/stitching/definitions.py +1 -0
  106. nabu/stitching/frame_composition.py +68 -60
  107. nabu/stitching/overlap.py +91 -51
  108. nabu/stitching/single_axis_stitching.py +32 -0
  109. nabu/stitching/slurm_utils.py +6 -6
  110. nabu/stitching/stitcher/__init__.py +0 -0
  111. nabu/stitching/stitcher/base.py +124 -0
  112. nabu/stitching/stitcher/dumper/__init__.py +3 -0
  113. nabu/stitching/stitcher/dumper/base.py +94 -0
  114. nabu/stitching/stitcher/dumper/postprocessing.py +356 -0
  115. nabu/stitching/stitcher/dumper/preprocessing.py +60 -0
  116. nabu/stitching/stitcher/post_processing.py +555 -0
  117. nabu/stitching/stitcher/pre_processing.py +1068 -0
  118. nabu/stitching/stitcher/single_axis.py +484 -0
  119. nabu/stitching/stitcher/stitcher.py +0 -0
  120. nabu/stitching/stitcher/y_stitcher.py +13 -0
  121. nabu/stitching/stitcher/z_stitcher.py +45 -0
  122. nabu/stitching/stitcher_2D.py +278 -0
  123. nabu/stitching/tests/test_config.py +12 -37
  124. nabu/stitching/tests/test_frame_composition.py +33 -59
  125. nabu/stitching/tests/test_overlap.py +149 -7
  126. nabu/stitching/tests/test_utils.py +1 -1
  127. nabu/stitching/tests/test_y_preprocessing_stitching.py +132 -0
  128. nabu/stitching/tests/{test_z_stitching.py → test_z_postprocessing_stitching.py} +167 -561
  129. nabu/stitching/tests/test_z_preprocessing_stitching.py +431 -0
  130. nabu/stitching/utils/__init__.py +1 -0
  131. nabu/stitching/utils/post_processing.py +281 -0
  132. nabu/stitching/utils/tests/test_post-processing.py +21 -0
  133. nabu/stitching/{utils.py → utils/utils.py} +79 -52
  134. nabu/stitching/y_stitching.py +27 -0
  135. nabu/stitching/z_stitching.py +32 -2263
  136. nabu/testutils.py +1 -152
  137. nabu/thirdparty/tomocupy_remove_stripe.py +43 -9
  138. nabu/utils.py +158 -61
  139. {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/METADATA +10 -3
  140. {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/RECORD +144 -121
  141. nabu/io/tiffwriter_zmm.py +0 -99
  142. nabu/pipeline/fallback_utils.py +0 -149
  143. nabu/pipeline/helical/tests/test_accumulator.py +0 -158
  144. nabu/pipeline/helical/tests/test_pipeline_elements_full.py +0 -355
  145. nabu/pipeline/helical/tests/test_strategy.py +0 -61
  146. nabu/pipeline/helical/utils.py +0 -51
  147. nabu/pipeline/tests/test_chunk_reader.py +0 -74
  148. {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/LICENSE +0 -0
  149. {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/WHEEL +0 -0
  150. {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/entry_points.txt +0 -0
  151. {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,10 @@
1
+ from math import sqrt
1
2
  import numpy as np
2
- from ..utils import calc_padding_lengths1D, nextpow2
3
+
4
+ from ..cuda.kernel import CudaKernel
5
+ from ..cuda.processing import CudaProcessing
6
+ from ..reconstruction.filtering_cuda import CudaSinoFilter
7
+ from ..utils import get_cuda_srcfile
3
8
 
4
9
  try:
5
10
  import astra
@@ -7,8 +12,6 @@ try:
7
12
  __have_astra__ = True
8
13
  except ImportError:
9
14
  __have_astra__ = False
10
- from ..cuda.processing import CudaProcessing
11
- from ..processing.padding_cuda import CudaPadding
12
15
 
13
16
 
14
17
  class ConebeamReconstructor:
@@ -16,6 +19,16 @@ class ConebeamReconstructor:
16
19
  A reconstructor for cone-beam geometry using the astra toolbox.
17
20
  """
18
21
 
22
+ default_extra_options = {
23
+ "axis_correction": None,
24
+ "clip_outer_circle": False,
25
+ "scale_factor": None,
26
+ "filter_cutoff": 1.0,
27
+ "outer_circle_value": 0.0,
28
+ # "use_astra_fdk": True,
29
+ "use_astra_fdk": False,
30
+ }
31
+
19
32
  def __init__(
20
33
  self,
21
34
  sinos_shape,
@@ -25,12 +38,12 @@ class ConebeamReconstructor:
25
38
  volume_shape=None,
26
39
  rot_center=None,
27
40
  relative_z_position=None,
28
- axis_correction=None,
29
41
  pixel_size=None,
30
- scale_factor=None,
31
42
  padding_mode="zeros",
43
+ filter_name=None,
32
44
  slice_roi=None,
33
45
  cuda_options=None,
46
+ extra_options=None,
34
47
  ):
35
48
  """
36
49
  Initialize a cone beam reconstructor. This reconstructor works on slabs of data,
@@ -78,6 +91,10 @@ class ConebeamReconstructor:
78
91
  This parameter must be in the form (start_x, end_x, start_y, end_y) with no negative values.
79
92
  Note that the current implementation just crops the final reconstructed volume,
80
93
  i.e there is no speed or memory benefit.
94
+ use_astra_fdk: bool
95
+ Whether to use the native Astra Toolbox FDK implementation.
96
+ If set to False, the cone-beam pre-weighting and projections padding/filtering is done by nabu.
97
+ Note that this parameter is automatically set to False if padding_mode != "zeros".
81
98
 
82
99
  Notes
83
100
  ------
@@ -106,10 +123,10 @@ class ConebeamReconstructor:
106
123
  Fast and flexible X-ray tomography using the ASTRA toolbox.
107
124
  Optics Express. 24. 25129-25147. 10.1364/OE.24.025129.
108
125
  """
126
+ self._configure_extra_options(extra_options)
109
127
  self._init_cuda(cuda_options)
110
- self.scale_factor = scale_factor
111
128
  self._set_sino_shape(sinos_shape)
112
- self._init_padding(padding_mode)
129
+ self._orig_prog_geom = None
113
130
  self._init_geometry(
114
131
  source_origin_dist,
115
132
  origin_detector_dist,
@@ -118,13 +135,17 @@ class ConebeamReconstructor:
118
135
  volume_shape,
119
136
  rot_center,
120
137
  relative_z_position,
121
- axis_correction,
122
138
  slice_roi,
123
139
  )
140
+ self._init_fdk(padding_mode, filter_name)
124
141
  self._alg_id = None
125
142
  self._vol_id = None
126
143
  self._proj_id = None
127
144
 
145
+ def _configure_extra_options(self, extra_options):
146
+ self.extra_options = self.default_extra_options.copy()
147
+ self.extra_options.update(extra_options or {})
148
+
128
149
  def _init_cuda(self, cuda_options):
129
150
  cuda_options = cuda_options or {}
130
151
  self.cuda = CudaProcessing(**cuda_options)
@@ -135,19 +156,25 @@ class ConebeamReconstructor:
135
156
  self.sinos_shape = sinos_shape
136
157
  self.n_sinos, self.n_angles, self.prj_width = sinos_shape
137
158
 
138
- def _init_padding(self, padding_mode):
139
- self._pad_data = False
159
+ def _init_fdk(self, padding_mode, filter_name):
140
160
  self.padding_mode = padding_mode
141
- if padding_mode == "zeros":
161
+ self._use_astra_fdk = bool(self.extra_options.get("use_astra_fdk", True))
162
+ self._use_astra_fdk &= padding_mode in ["zeros", "constant", None, "none"]
163
+ if self._use_astra_fdk:
142
164
  return
143
- self._pad_data = True
144
- n_x = self.prj_width
145
- x_pad_lens = calc_padding_lengths1D(n_x, nextpow2(n_x * 2))
146
- self.padder = CudaPadding(
147
- (self.n_angles, n_x), ((0, 0),) + (x_pad_lens,), mode=padding_mode, cuda_options={"ctx": self.cuda.ctx}
165
+ self.sino_filter = CudaSinoFilter(
166
+ self.sinos_shape[1:],
167
+ filter_name=filter_name,
168
+ padding_mode=self.padding_mode,
169
+ # TODO (?) configure FFT backend
170
+ extra_options={"cutoff": self.extra_options.get("filter_cutoff", 1.0)},
171
+ cuda_options={"ctx": self.cuda.ctx},
148
172
  )
149
- self._sinos_padded_shape = (self.n_sinos, self.n_angles, self.padder.padded_shape[-1])
150
- self.prj_width = self.padder.padded_shape[-1] # will impact translations
173
+ # In astra, FDK pre-weighting does the "n_a/(pi/2) multiplication"
174
+ # TODO not sure where this "magnification **2" factor comes from ?
175
+ mult_factor = self.n_angles / 3.141592 * 2 / (self.magnification**2)
176
+ self.sino_filter.set_filter(self.sino_filter.filter_f * mult_factor, normalize=False)
177
+ #
151
178
 
152
179
  def _set_pixel_size(self, pixel_size):
153
180
  if pixel_size is None:
@@ -173,12 +200,6 @@ class ConebeamReconstructor:
173
200
  self._vol_geom_n_x = self.n_x - start_x * 2
174
201
  self._vol_geom_n_y = self.n_y - start_y * 2
175
202
  else:
176
- # self._crop_data = True
177
- # self._output_cropped_shape = (
178
- # self.n_z,
179
- # np.arange(self.n_y)[start_x:end_x].size,
180
- # np.arange(self.n_x)[start_y:end_y].size,
181
- # )
182
203
  raise NotImplementedError(
183
204
  "Cone-beam geometry supports only slice_roi centered around origin (got slice_roi=%s with n_x=%d, n_y=%d)"
184
205
  % (str(slice_roi), self.n_x, self.n_y)
@@ -193,7 +214,6 @@ class ConebeamReconstructor:
193
214
  volume_shape,
194
215
  rot_center,
195
216
  relative_z_position,
196
- axis_correction,
197
217
  slice_roi,
198
218
  ):
199
219
  if angles is None:
@@ -215,7 +235,7 @@ class ConebeamReconstructor:
215
235
  if rot_center is not None:
216
236
  self._cor_shift = (self.sinos_shape[-1] - 1) / 2.0 - rot_center
217
237
  self._set_pixel_size(pixel_size)
218
- self._axis_corrections = axis_correction
238
+ self._axis_corrections = self.extra_options.get("axis_correction", None)
219
239
  self._create_astra_proj_geometry(relative_z_position)
220
240
 
221
241
  def _create_astra_proj_geometry(self, relative_z_position):
@@ -234,6 +254,8 @@ class ConebeamReconstructor:
234
254
  )
235
255
  self.relative_z_position = relative_z_position or 0.0
236
256
  # This will turn the geometry of type "cone" into a geometry of type "cone_vec"
257
+ if self._orig_prog_geom is None:
258
+ self._orig_prog_geom = self.proj_geom
237
259
  self.proj_geom = astra.geom_postalignment(self.proj_geom, (self._cor_shift, 0))
238
260
  # (src, detector_center, u, v) = (srcX, srcY, srcZ, dX, dY, dZ, uX, uY, uZ, vX, vY, vZ)
239
261
  vecs = self.proj_geom["Vectors"]
@@ -274,23 +296,29 @@ class ConebeamReconstructor:
274
296
  # But it seems Astra modifies the input sinogram while doing FDK, so this might be not relevant
275
297
  d_sinos = self.cuda.get_array("sinos")
276
298
 
277
- if self._pad_data:
278
- sinos_padded = self.cuda.allocate_array("sinos_padded", self._sinos_padded_shape, dtype="f")
279
- for i in range(self.n_sinos):
280
- self.padder.pad(self.cuda.sinos[i], output=sinos_padded[i])
281
- d_sinos = sinos_padded
282
-
283
299
  # self._proj_data_link = astra.data3d.GPULink(d_sinos.ptr, self.prj_width, self.n_angles, self.n_z, sinos.strides[-2])
284
300
  self._proj_data_link = astra.data3d.GPULink(
285
301
  d_sinos.ptr, self.prj_width, self.n_angles, self.n_sinos, d_sinos.strides[-2]
286
302
  )
287
303
  self._proj_id = astra.data3d.link("-sino", self.proj_geom, self._proj_data_link)
288
304
 
305
+ def _preprocess_data(self):
306
+ if self._use_astra_fdk:
307
+ return
308
+ d_sinos = self.cuda.sinos
309
+ fdk_preweighting(
310
+ d_sinos, self._orig_prog_geom, relative_z_position=self.relative_z_position, cor_shift=self._cor_shift
311
+ )
312
+ for i in range(d_sinos.shape[0]):
313
+ self.sino_filter.filter_sino(d_sinos[i], output=d_sinos[i])
314
+
289
315
  def _update_reconstruction(self):
290
- cfg = astra.astra_dict("FDK_CUDA")
316
+ if self._use_astra_fdk:
317
+ cfg = astra.astra_dict("FDK_CUDA")
318
+ else:
319
+ cfg = astra.astra_dict("BP3D_CUDA")
291
320
  cfg["ReconstructionDataId"] = self._vol_id
292
321
  cfg["ProjectionDataId"] = self._proj_id
293
- # TODO more options "eg. filter" ?
294
322
  if self._alg_id is not None:
295
323
  astra.algorithm.delete(self._alg_id)
296
324
  self._alg_id = astra.algorithm.create(cfg)
@@ -308,17 +336,20 @@ class ConebeamReconstructor:
308
336
  self._create_astra_proj_geometry(relative_z_position)
309
337
  self._set_input(sinos)
310
338
  self._set_output(output)
339
+ self._preprocess_data()
311
340
  self._update_reconstruction()
312
341
  astra.algorithm.run(self._alg_id)
342
+ #
343
+ # NB: Could also be done with
344
+ # from astra.experimental import direct_BP3D
345
+ # projector_id = astra.create_projector("cuda3d", self.proj_geom, self.vol_geom, options=None)
346
+ # direct_BP3D(projector_id, self._vol_link, self._proj_data_link)
347
+ #
313
348
  result = self.cuda.get_array("output")
314
349
  if output is None:
315
350
  result = result.get()
316
- if self.scale_factor is not None:
317
- result *= np.float32(self.scale_factor) # in-place for pycuda
318
- # if self._crop_data:
319
- # self.cuda.allocate_array("output_cropped", self._output_cropped_shape, dtype=np.float32)
320
- # for i in range(self.n_z):
321
- # output
351
+ if self.extra_options.get("scale_factor", None) is not None:
352
+ result *= np.float32(self.extra_options["scale_factor"]) # in-place for pycuda
322
353
  self.cuda.recover_arrays_references(["sinos", "output"])
323
354
  return result
324
355
 
@@ -345,3 +376,36 @@ def roi_is_centered(shape, slice_):
345
376
  Return True if "slice_" define a selection that is centered on the middle of the array.
346
377
  """
347
378
  return all([selection_is_centered(shp, s.start, s.stop) for shp, s in zip(shape, slice_)])
379
+
380
+
381
+ def fdk_preweighting(d_sinos, proj_geom, relative_z_position=0.0, cor_shift=0.0):
382
+
383
+ preweight_kernel = CudaKernel(
384
+ "devFDK_preweight",
385
+ filename=get_cuda_srcfile("cone.cu"),
386
+ signature="Piiifffffiii",
387
+ )
388
+
389
+ # n_angles, n_z, n_x = d_sinos.shape
390
+ n_z, n_angles, n_x = d_sinos.shape
391
+ det_origin = sqrt(proj_geom["DistanceOriginDetector"] ** 2 + cor_shift**2)
392
+
393
+ block = (32, 16, 1)
394
+ grid = (((n_x + 32 - 1) // 32) * ((n_z + 32 - 1) // 32), (n_angles + 16 - 1) // 16, 1)
395
+
396
+ preweight_kernel(
397
+ d_sinos,
398
+ np.uint32(n_x), # unsigned int projPitch,
399
+ np.uint32(0), # unsigned int startAngle,
400
+ np.uint32(n_angles), # unsigned int endAngle,
401
+ np.float32(proj_geom["DistanceOriginSource"]), # float fSrcOrigin,
402
+ np.float32(det_origin), # float fDetOrigin,
403
+ np.float32(relative_z_position), # float fZShift,
404
+ np.float32(proj_geom["DetectorSpacingX"]), # float fDetUSize,
405
+ np.float32(proj_geom["DetectorSpacingY"]), # float fDetVSize,
406
+ np.int32(n_angles), # dims.iProjAngles;
407
+ np.int32(n_x), # dims.iProjU; // number of detectors in the U direction
408
+ np.int32(n_z), # dims.iProjV // number of detectors in the V direction
409
+ block=block,
410
+ grid=grid,
411
+ )
@@ -73,12 +73,15 @@ class CudaBackprojector(BackprojectorBase):
73
73
  self._kernel_options["kernel_name"],
74
74
  filename=self._kernel_options["file_name"],
75
75
  options=self._kernel_options["sourcemodule_options"],
76
+ silent_compilation_warnings=True, # textures and Cuda 11
76
77
  )
77
78
  if self.halftomo and self.rot_center < self.dwidth:
78
79
  self.sino_mult = CudaSinoMult(self.sino_shape, self.rot_center, ctx=self._processing.ctx)
79
80
  self._prepare_textures() # has to be done after compilation for Cuda (to bind texture to built kernel)
80
81
 
81
82
  def _transfer_to_texture(self, sino, do_checks=True):
83
+ if do_checks and not (sino.flags.c_contiguous):
84
+ raise ValueError("Expected C-Contiguous array")
82
85
  if self._use_textures:
83
86
  copy_array(self._d_sino_cua, sino, check=do_checks)
84
87
  else:
@@ -21,6 +21,7 @@ class BackprojectorBase:
21
21
  "clip_outer_circle": False,
22
22
  "scale_factor": None,
23
23
  "filter_cutoff": 1.0,
24
+ "outer_circle_value": 0.0,
24
25
  }
25
26
 
26
27
  kernel_filename = None
@@ -335,13 +336,17 @@ class BackprojectorBase:
335
336
  self._output_is_ndarray = isinstance(output, np.ndarray)
336
337
  if output is None or self._output_is_ndarray:
337
338
  self._processing.allocate_array("_d_slice", self.slice_shape, dtype=np.float32)
338
- return self._processing._d_slice # pylint: disable=E1101
339
- if check:
339
+ output = self._processing._d_slice # pylint: disable=E1101
340
+ elif check:
340
341
  assert output.dtype == np.float32
341
342
  assert output.shape == self.slice_shape, "Expected output shape %s but got %s" % (
342
343
  self.slice_shape,
343
344
  output.shape,
344
345
  )
346
+ if self.extra_options.get("clip_outer_circle", False):
347
+ out_circle_val = self.extra_options.get("outer_circle_value", 0)
348
+ if out_circle_val != 0:
349
+ output.fill(out_circle_val)
345
350
  return output
346
351
 
347
352
  def _set_kernel_slice_arg(self, d_slice):
@@ -162,9 +162,10 @@ class SinoFilter:
162
162
  in self.d_sino_padded.
163
163
  """
164
164
  self._check_array(sino)
165
- sino_padded = np.pad(
166
- sino, ((0, 0), (0, self.dwidth_padded - self.dwidth)), mode=self.padding_mode
167
- ) # pad with a FFT-friendly layout
165
+ # sino_padded = np.pad(
166
+ # sino, ((0, 0), (0, self.dwidth_padded - self.dwidth)), mode=self.padding_mode
167
+ # ) # pad with a FFT-friendly layout
168
+ sino_padded = np.pad(sino, ((0, 0), (self.pad_left, self.pad_right)), mode=self.padding_mode)
168
169
  sino_padded_f = rfft(sino_padded, axis=1, workers=get_num_threads(self.extra_options["fft_threads"]))
169
170
  sino_padded_f *= self.filter_f
170
171
  sino_filtered = irfft(sino_padded_f, axis=1, workers=get_num_threads(self.extra_options["fft_threads"]))
@@ -173,9 +174,11 @@ class SinoFilter:
173
174
  else:
174
175
  res = output
175
176
  if self.ndim == 2:
176
- res[:] = sino_filtered[:, : self.dwidth] # pylint: disable=E1126 # ?!
177
+ # res[:] = sino_filtered[:, : self.dwidth] # pylint: disable=E1126 # ?!
178
+ res[:] = sino_filtered[:, self.pad_left : -self.pad_right] # pylint: disable=E1126 # ?!
177
179
  else:
178
- res[:] = sino_filtered[:, :, : self.dwidth] # pylint: disable=E1126 # ?!
180
+ # res[:] = sino_filtered[:, :, : self.dwidth] # pylint: disable=E1126 # ?!
181
+ res[:] = sino_filtered[:, :, self.pad_left : -self.pad_right] # pylint: disable=E1126 # ?!
179
182
  return res
180
183
 
181
184
  __call__ = filter_sino
@@ -209,12 +212,22 @@ def filter_sinogram(
209
212
  frequency cutoff for filter
210
213
  """
211
214
  n_angles, width = sinogram.shape
212
- sinogram_padded = np.pad(sinogram, ((0, 0), (0, padded_width - width)), mode=padding_mode, **padding_kwargs)
215
+
216
+ # Initially, padding was done this way
217
+ # sinogram_padded = np.pad(sinogram, ((0, 0), (0, padded_width - width)), mode=padding_mode, **padding_kwargs)
218
+
219
+ #
220
+ pad_left = (padded_width - width) // 2
221
+ pad_right = padded_width - width - pad_left
222
+ sinogram_padded = np.pad(sinogram, ((0, 0), (pad_left, pad_right)), mode=padding_mode, **padding_kwargs)
223
+ #
224
+
213
225
  fourier_filter = compute_fourier_filter(padded_width, filter_name, cutoff=filter_cutoff)
214
226
  if normalize:
215
227
  fourier_filter *= np.pi / n_angles
216
228
  fourier_filter = fourier_filter[: padded_width // 2 + 1] # R2C
217
229
  sino_f = rfft(sinogram_padded, axis=1)
218
230
  sino_f *= fourier_filter
219
- sino_filtered = irfft(sino_f, axis=1)[:, :width] # pylint: disable=E1126 # ?!
231
+ # sino_filtered = irfft(sino_f, axis=1)[:, :width] # pylint: disable=E1126 # ?!
232
+ sino_filtered = irfft(sino_f, axis=1)[:, pad_left:-pad_right] # pylint: disable=E1126 # ?!
220
233
  return sino_filtered
@@ -7,7 +7,7 @@ from .filtering import SinoFilter
7
7
 
8
8
 
9
9
  class CudaSinoFilter(SinoFilter):
10
- default_extra_options = {**SinoFilter.default_extra_options, **{"fft_backend": "skcuda"}}
10
+ default_extra_options = {**SinoFilter.default_extra_options, **{"fft_backend": "vkfft"}}
11
11
 
12
12
  def __init__(
13
13
  self,
@@ -76,6 +76,12 @@ class CudaSinoFilter(SinoFilter):
76
76
  self._check_array(sino)
77
77
  if not (isinstance(sino, self.cuda.array_class)):
78
78
  sino = self.cuda.set_array("sino", sino)
79
+ elif not (sino.flags.c_contiguous):
80
+ # Transfer the device array into another, c-contiguous, device array
81
+ # We can throw an error as well in this case, but often we so something like fbp(radios[:, i, :])
82
+ sino_tmp = self.cuda.allocate_array("sino_contig", sino.shape)
83
+ sino_tmp.set(sino)
84
+ sino = sino_tmp
79
85
 
80
86
  # Padding
81
87
  self.padding_kernel(sino, output=self.d_sino_padded)