nabu 2024.2.4__py3-none-any.whl → 2024.2.6__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.
doc/doc_config.py ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python
2
+
3
+ from nabu.resources.nabu_config import nabu_config
4
+
5
+
6
+ def generate(file_):
7
+ def write(content):
8
+ print(content, file=file_)
9
+ for section, values in nabu_config.items():
10
+ if section == "about":
11
+ continue
12
+ write("## %s\n" % section)
13
+ for key, val in values.items():
14
+ if val["type"] == "unsupported":
15
+ continue
16
+ write(val["help"] + "\n")
17
+ write(
18
+ "```ini\n%s = %s\n```"
19
+ % (key, val["default"])
20
+ )
21
+
22
+
23
+
24
+ if __name__ == "__main__":
25
+
26
+ import sys, os
27
+ print(os.path.abspath(__file__))
28
+ exit(0)
29
+
30
+ fname = "/tmp/test.md"
31
+ with open(fname, "w") as f:
32
+ generate(f)
nabu/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "2024.2.4"
1
+ __version__ = "2024.2.6"
2
2
  __nabu_modules__ = [
3
3
  "app",
4
4
  "cuda",
nabu/cuda/src/cone.cu CHANGED
@@ -26,23 +26,33 @@ along with the ASTRA Toolbox. If not, see <http://www.gnu.org/licenses/>.
26
26
  */
27
27
 
28
28
 
29
- static const unsigned int g_anglesPerWeightBlock = 16;
30
- static const unsigned int g_detBlockU = 32;
31
- static const unsigned int g_detBlockV = 32;
29
+ // static const unsigned int g_anglesPerWeightBlock = 16;
30
+ // static const unsigned int g_detBlockU = 32;
31
+ // static const unsigned int g_detBlockV = 32;
32
32
 
33
33
 
34
34
  __global__ void devFDK_preweight(void* D_projData, unsigned int projPitch, unsigned int startAngle, unsigned int endAngle, float fSrcOrigin, float fDetOrigin, float fZShift, float fDetUSize, float fDetVSize, unsigned int iProjAngles, unsigned int iProjU, unsigned int iProjV)
35
35
  {
36
36
  float* projData = (float*)D_projData;
37
- int angle = startAngle + blockIdx.y * g_anglesPerWeightBlock + threadIdx.y;
37
+
38
+ const uint angle = blockDim.y * blockIdx.y + threadIdx.y + startAngle;
39
+
38
40
  if (angle >= endAngle)
39
41
  return;
40
42
 
41
- const int detectorU = (blockIdx.x%((iProjU+g_detBlockU-1)/g_detBlockU)) * g_detBlockU + threadIdx.x;
42
- const int startDetectorV = (blockIdx.x/((iProjU+g_detBlockU-1)/g_detBlockU)) * g_detBlockV;
43
- int endDetectorV = startDetectorV + g_detBlockV;
44
- if (endDetectorV > iProjV)
45
- endDetectorV = iProjV;
43
+ // Astra FDK kernel used this indexing (with the appropriate grid)
44
+ // const int detectorU = (blockIdx.x%((iProjU+g_detBlockU-1)/g_detBlockU)) * g_detBlockU + threadIdx.x;
45
+ // const int startDetectorV = (blockIdx.x/((iProjU+g_detBlockU-1)/g_detBlockU)) * g_detBlockV;
46
+ // Instead we choose a simpler scheme which does not assume pitch memory allocation
47
+
48
+ const uint detectorU = blockDim.x * blockIdx.x + threadIdx.x;
49
+ const int startDetectorV = 0;
50
+
51
+ int endDetectorV = iProjV; // startDetectorV + g_detBlockV;
52
+ // if (endDetectorV > iProjV)
53
+ // endDetectorV = iProjV;
54
+ if (detectorU >= iProjU) return;
55
+
46
56
 
47
57
  // We need the length of the central ray and the length of the ray(s) to
48
58
  // our detector pixel(s).
@@ -131,8 +131,12 @@ class FullFieldReconstructor:
131
131
  self.resources["gpus"] = avail_gpus
132
132
  if len(avail_gpus) == 0:
133
133
  return
134
- # pick first GPU by default. TODO: handle user's nabu_config["resources"]["gpu_id"]
135
- self.resources["gpu_id"] = self._gpu_id = list(avail_gpus.keys())[0]
134
+ user_gpus = self.process_config.nabu_config.get("resources", {}).get("gpu_id", [])
135
+ if len(user_gpus) == 0:
136
+ user_gpus = [0]
137
+ # For now nabu does not support multi-GPU reconstruction. Take the first one.
138
+ user_gpu_idx = user_gpus[0]
139
+ self.resources["gpu_id"] = self._gpu_id = list(avail_gpus.keys())[user_gpu_idx]
136
140
 
137
141
  def _get_backend(self, backend, cuda_options):
138
142
  self._pipeline_cls = ChunkedPipeline
@@ -145,6 +149,7 @@ class FullFieldReconstructor:
145
149
  backend = "numpy"
146
150
  else:
147
151
  self.gpu_mem = self.resources["gpus"][self._gpu_id]["memory_GB"] * self.gpu_mem_fraction
152
+ self.cuda_options = {"device_id": self._gpu_id}
148
153
  if backend == "cuda":
149
154
  if not (__has_pycuda__):
150
155
  raise RuntimeError("pycuda not avilable")
@@ -0,0 +1,245 @@
1
+ # ruff: noqa
2
+ try:
3
+ import astra
4
+
5
+ __have_astra__ = True
6
+ except ImportError:
7
+ __have_astra__ = False
8
+ astra = None
9
+
10
+
11
+ class AstraReconstructor:
12
+ """
13
+ Base class for reconstructors based on the Astra toolbox
14
+ """
15
+
16
+ default_extra_options = {
17
+ "axis_correction": None,
18
+ "clip_outer_circle": False,
19
+ "scale_factor": None,
20
+ "filter_cutoff": 1.0,
21
+ "outer_circle_value": 0.0,
22
+ }
23
+
24
+ def __init__(
25
+ self,
26
+ sinos_shape,
27
+ angles=None,
28
+ volume_shape=None,
29
+ rot_center=None,
30
+ pixel_size=None,
31
+ padding_mode="zeros",
32
+ filter_name=None,
33
+ slice_roi=None,
34
+ cuda_options=None,
35
+ extra_options=None,
36
+ ):
37
+ self._configure_extra_options(extra_options)
38
+ self._init_cuda(cuda_options)
39
+ self._set_sino_shape(sinos_shape)
40
+ self._orig_prog_geom = None
41
+ self._init_geometry(
42
+ source_origin_dist,
43
+ origin_detector_dist,
44
+ pixel_size,
45
+ angles,
46
+ volume_shape,
47
+ rot_center,
48
+ relative_z_position,
49
+ slice_roi,
50
+ )
51
+ self._init_fdk(padding_mode, filter_name)
52
+ self._alg_id = None
53
+ self._vol_id = None
54
+ self._proj_id = None
55
+
56
+ def _configure_extra_options(self, extra_options):
57
+ self.extra_options = self.default_extra_options.copy()
58
+ self.extra_options.update(extra_options or {})
59
+
60
+ def _init_cuda(self, cuda_options):
61
+ cuda_options = cuda_options or {}
62
+ self.cuda = CudaProcessing(**cuda_options)
63
+
64
+ def _set_sino_shape(self, sinos_shape):
65
+ if len(sinos_shape) != 3:
66
+ raise ValueError("Expected a 3D shape")
67
+ self.sinos_shape = sinos_shape
68
+ self.n_sinos, self.n_angles, self.prj_width = sinos_shape
69
+
70
+ def _set_pixel_size(self, pixel_size):
71
+ if pixel_size is None:
72
+ det_spacing_y = det_spacing_x = 1
73
+ elif np.iterable(pixel_size):
74
+ det_spacing_y, det_spacing_x = pixel_size
75
+ else:
76
+ # assuming scalar
77
+ det_spacing_y = det_spacing_x = pixel_size
78
+ self._det_spacing_y = det_spacing_y
79
+ self._det_spacing_x = det_spacing_x
80
+
81
+ def _set_slice_roi(self, slice_roi):
82
+ self.slice_roi = slice_roi
83
+ self._vol_geom_n_x = self.n_x
84
+ self._vol_geom_n_y = self.n_y
85
+ self._crop_data = True
86
+ if slice_roi is None:
87
+ return
88
+ start_x, end_x, start_y, end_y = slice_roi
89
+ if roi_is_centered(self.volume_shape[1:], (slice(start_y, end_y), slice(start_x, end_x))):
90
+ # Astra can only reconstruct subregion centered around the origin
91
+ self._vol_geom_n_x = self.n_x - start_x * 2
92
+ self._vol_geom_n_y = self.n_y - start_y * 2
93
+ else:
94
+ raise NotImplementedError(
95
+ "Astra supports only slice_roi centered around origin (got slice_roi=%s with n_x=%d, n_y=%d)"
96
+ % (str(slice_roi), self.n_x, self.n_y)
97
+ )
98
+
99
+ def _init_geometry(
100
+ self,
101
+ source_origin_dist,
102
+ origin_detector_dist,
103
+ pixel_size,
104
+ angles,
105
+ volume_shape,
106
+ rot_center,
107
+ relative_z_position,
108
+ slice_roi,
109
+ ):
110
+ if angles is None:
111
+ self.angles = np.linspace(0, 2 * np.pi, self.n_angles, endpoint=True)
112
+ else:
113
+ self.angles = angles
114
+ if volume_shape is None:
115
+ volume_shape = (self.sinos_shape[0], self.sinos_shape[2], self.sinos_shape[2])
116
+ self.volume_shape = volume_shape
117
+ self.n_z, self.n_y, self.n_x = self.volume_shape
118
+ self.source_origin_dist = source_origin_dist
119
+ self.origin_detector_dist = origin_detector_dist
120
+ self.magnification = 1 + origin_detector_dist / source_origin_dist
121
+ self._set_slice_roi(slice_roi)
122
+ self.vol_geom = astra.create_vol_geom(self._vol_geom_n_y, self._vol_geom_n_x, self.n_z)
123
+ self.vol_shape = astra.geom_size(self.vol_geom)
124
+ self._cor_shift = 0.0
125
+ self.rot_center = rot_center
126
+ if rot_center is not None:
127
+ self._cor_shift = (self.sinos_shape[-1] - 1) / 2.0 - rot_center
128
+ self._set_pixel_size(pixel_size)
129
+ self._axis_corrections = self.extra_options.get("axis_correction", None)
130
+ self._create_astra_proj_geometry(relative_z_position)
131
+
132
+ def _create_astra_proj_geometry(self, relative_z_position):
133
+ # This object has to be re-created each time, because once the modifications below are done,
134
+ # it is no more a "cone" geometry but a "cone_vec" geometry, and cannot be updated subsequently
135
+ # (see astra/functions.py:271)
136
+ self.proj_geom = astra.create_proj_geom(
137
+ "cone",
138
+ self._det_spacing_x,
139
+ self._det_spacing_y,
140
+ self.n_sinos,
141
+ self.prj_width,
142
+ self.angles,
143
+ self.source_origin_dist,
144
+ self.origin_detector_dist,
145
+ )
146
+ self.relative_z_position = relative_z_position or 0.0
147
+ # This will turn the geometry of type "cone" into a geometry of type "cone_vec"
148
+ if self._orig_prog_geom is None:
149
+ self._orig_prog_geom = self.proj_geom
150
+ self.proj_geom = astra.geom_postalignment(self.proj_geom, (self._cor_shift, 0))
151
+ # (src, detector_center, u, v) = (srcX, srcY, srcZ, dX, dY, dZ, uX, uY, uZ, vX, vY, vZ)
152
+ vecs = self.proj_geom["Vectors"]
153
+
154
+ # To adapt the center of rotation:
155
+ # dX = cor_shift * cos(theta) - origin_detector_dist * sin(theta)
156
+ # dY = origin_detector_dist * cos(theta) + cor_shift * sin(theta)
157
+ if self._axis_corrections is not None:
158
+ # should we check that dX and dY match the above formulas ?
159
+ cor_shifts = self._cor_shift + self._axis_corrections
160
+ vecs[:, 3] = cor_shifts * np.cos(self.angles) - self.origin_detector_dist * np.sin(self.angles)
161
+ vecs[:, 4] = self.origin_detector_dist * np.cos(self.angles) + cor_shifts * np.sin(self.angles)
162
+
163
+ # To adapt the z position:
164
+ # Component 2 of vecs is the z coordinate of the source, component 5 is the z component of the detector position
165
+ # We need to re-create the same inclination of the cone beam, thus we need to keep the inclination of the two z positions.
166
+ # The detector is centered on the rotation axis, thus moving it up or down, just moves it out of the reconstruction volume.
167
+ # We can bring back the detector in the correct volume position, by applying a rigid translation of both the detector and the source.
168
+ # The translation is exactly the amount that brought the detector up or down, but in the opposite direction.
169
+ vecs[:, 2] = -self.relative_z_position
170
+
171
+ def _set_output(self, volume):
172
+ if volume is not None:
173
+ expected_shape = self.vol_shape # if not (self._crop_data) else self._output_cropped_shape
174
+ self.cuda.check_array(volume, expected_shape)
175
+ self.cuda.set_array("output", volume)
176
+ if volume is None:
177
+ self.cuda.allocate_array("output", self.vol_shape)
178
+ d_volume = self.cuda.get_array("output")
179
+ z, y, x = d_volume.shape
180
+ self._vol_link = astra.data3d.GPULink(d_volume.ptr, x, y, z, d_volume.strides[-2])
181
+ self._vol_id = astra.data3d.link("-vol", self.vol_geom, self._vol_link)
182
+
183
+ def _set_input(self, sinos):
184
+ self.cuda.check_array(sinos, self.sinos_shape)
185
+ self.cuda.set_array("sinos", sinos) # self.cuda.sinos is now a GPU array
186
+ # TODO don't create new link/proj_id if ptr is the same ?
187
+ # But it seems Astra modifies the input sinogram while doing FDK, so this might be not relevant
188
+ d_sinos = self.cuda.get_array("sinos")
189
+
190
+ # self._proj_data_link = astra.data3d.GPULink(d_sinos.ptr, self.prj_width, self.n_angles, self.n_z, sinos.strides[-2])
191
+ self._proj_data_link = astra.data3d.GPULink(
192
+ d_sinos.ptr, self.prj_width, self.n_angles, self.n_sinos, d_sinos.strides[-2]
193
+ )
194
+ self._proj_id = astra.data3d.link("-sino", self.proj_geom, self._proj_data_link)
195
+
196
+ def _preprocess_data(self):
197
+ d_sinos = self.cuda.sinos
198
+ for i in range(d_sinos.shape[0]):
199
+ self.sino_filter.filter_sino(d_sinos[i], output=d_sinos[i])
200
+
201
+ def _update_reconstruction(self):
202
+ cfg = astra.astra_dict("BP3D_CUDA")
203
+ cfg["ReconstructionDataId"] = self._vol_id
204
+ cfg["ProjectionDataId"] = self._proj_id
205
+ if self._alg_id is not None:
206
+ astra.algorithm.delete(self._alg_id)
207
+ self._alg_id = astra.algorithm.create(cfg)
208
+
209
+ def reconstruct(self, sinos, output=None, relative_z_position=None):
210
+ """
211
+ sinos: numpy.ndarray or pycuda.gpuarray
212
+ Sinograms, with shape (n_sinograms, n_angles, width)
213
+ output: pycuda.gpuarray, optional
214
+ Output array. If not provided, a new numpy array is returned
215
+ relative_z_position: int, optional
216
+ Position of the central slice of the slab, with respect to the full stack of slices.
217
+ By default it is set to zero, meaning that the current slab is assumed in the middle of the stack
218
+ """
219
+ self._create_astra_proj_geometry(relative_z_position)
220
+ self._set_input(sinos)
221
+ self._set_output(output)
222
+ self._preprocess_data()
223
+ self._update_reconstruction()
224
+ astra.algorithm.run(self._alg_id)
225
+ #
226
+ # NB: Could also be done with
227
+ # from astra.experimental import direct_BP3D
228
+ # projector_id = astra.create_projector("cuda3d", self.proj_geom, self.vol_geom, options=None)
229
+ # direct_BP3D(projector_id, self._vol_link, self._proj_data_link)
230
+ #
231
+ result = self.cuda.get_array("output")
232
+ if output is None:
233
+ result = result.get()
234
+ if self.extra_options.get("scale_factor", None) is not None:
235
+ result *= np.float32(self.extra_options["scale_factor"]) # in-place for pycuda
236
+ self.cuda.recover_arrays_references(["sinos", "output"])
237
+ return result
238
+
239
+ def __del__(self):
240
+ if getattr(self, "_alg_id", None) is not None:
241
+ astra.algorithm.delete(self._alg_id)
242
+ if getattr(self, "_vol_id", None) is not None:
243
+ astra.data3d.delete(self._vol_id)
244
+ if getattr(self, "_proj_id", None) is not None:
245
+ astra.data3d.delete(self._proj_id)
@@ -1,10 +1,11 @@
1
+ import logging
1
2
  from math import sqrt
2
3
  import numpy as np
3
4
 
4
5
  from ..cuda.kernel import CudaKernel
5
6
  from ..cuda.processing import CudaProcessing
6
7
  from ..reconstruction.filtering_cuda import CudaSinoFilter
7
- from ..utils import get_cuda_srcfile
8
+ from ..utils import get_cuda_srcfile, updiv
8
9
 
9
10
  try:
10
11
  import astra
@@ -14,6 +15,9 @@ except ImportError:
14
15
  __have_astra__ = False
15
16
 
16
17
 
18
+ _logger = logging.getLogger(__name__)
19
+
20
+
17
21
  class ConebeamReconstructor:
18
22
  """
19
23
  A reconstructor for cone-beam geometry using the astra toolbox.
@@ -159,7 +163,9 @@ class ConebeamReconstructor:
159
163
  def _init_fdk(self, padding_mode, filter_name):
160
164
  self.padding_mode = padding_mode
161
165
  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"]
166
+ if self._use_astra_fdk and padding_mode not in ["zeros", "constant", None, "none"]:
167
+ self._use_astra_fdk = False
168
+ _logger.warning("padding_mode was set to %s, cannot use native astra FDK" % padding_mode)
163
169
  if self._use_astra_fdk:
164
170
  return
165
171
  self.sino_filter = CudaSinoFilter(
@@ -386,12 +392,11 @@ def fdk_preweighting(d_sinos, proj_geom, relative_z_position=0.0, cor_shift=0.0)
386
392
  signature="Piiifffffiii",
387
393
  )
388
394
 
389
- # n_angles, n_z, n_x = d_sinos.shape
390
395
  n_z, n_angles, n_x = d_sinos.shape
391
396
  det_origin = sqrt(proj_geom["DistanceOriginDetector"] ** 2 + cor_shift**2)
392
397
 
393
398
  block = (32, 16, 1)
394
- grid = (((n_x + 32 - 1) // 32) * ((n_z + 32 - 1) // 32), (n_angles + 16 - 1) // 16, 1)
399
+ grid = (updiv(n_x, block[0]), updiv(n_angles, block[1]), 1)
395
400
 
396
401
  preweight_kernel(
397
402
  d_sinos,
@@ -1,7 +1,9 @@
1
+ import logging
1
2
  import pytest
2
3
  import numpy as np
3
4
  from scipy.ndimage import gaussian_filter, shift
4
5
  from nabu.utils import subdivide_into_overlapping_segment, clip_circle
6
+ from nabu.testutils import __do_long_tests__, generate_tests_scenarios
5
7
 
6
8
  try:
7
9
  import astra
@@ -387,6 +389,77 @@ class TestCone:
387
389
  str(roi)
388
390
  )
389
391
 
392
+ def test_fdk_preweight(self, caplog):
393
+ """
394
+ Check that nabu's FDK pre-weighting give the same results as astra
395
+ """
396
+ shapes = [
397
+ {"n_z": 256, "n_x": 256, "n_a": 500},
398
+ # {"n_z": 250, "n_x": 340, "n_a": 250}, # Astra reconstruction is incorrect in this case!
399
+ ]
400
+ src_orig_dist = 1000
401
+ orig_det_dist = 50
402
+
403
+ rot_centers_from_middle = [0]
404
+ if __do_long_tests__:
405
+ rot_centers_from_middle.extend([10, -15])
406
+
407
+ params_list = generate_tests_scenarios({"shape": shapes, "rot_center": rot_centers_from_middle})
408
+
409
+ for params in params_list:
410
+ n_z = params["shape"]["n_z"]
411
+ n_x = n_y = params["shape"]["n_x"]
412
+ n_a = params["shape"]["n_a"]
413
+ rc = params["rot_center"]
414
+ volume, cone_data = generate_hollow_cube_cone_sinograms(
415
+ vol_shape=(n_z, n_y, n_x),
416
+ n_angles=n_a,
417
+ src_orig_dist=src_orig_dist,
418
+ orig_det_dist=orig_det_dist,
419
+ apply_filter=False,
420
+ rot_center_shift=rc,
421
+ )
422
+
423
+ reconstructor_args = [(n_z, n_a, n_x), src_orig_dist, orig_det_dist]
424
+ reconstructor_kwargs_base = {
425
+ "volume_shape": volume.shape,
426
+ "rot_center": (n_x - 1) / 2 + rc,
427
+ "cuda_options": {"ctx": self.ctx},
428
+ }
429
+ reconstructor_kwargs_astra = {"padding_mode": "zeros", "extra_options": {"use_astra_fdk": True}}
430
+ reconstructor_kwargs_nabu = {"padding_mode": "zeros", "extra_options": {"use_astra_fdk": False}}
431
+ reconstructor_astra = ConebeamReconstructor(
432
+ *reconstructor_args, **{**reconstructor_kwargs_base, **reconstructor_kwargs_astra}
433
+ )
434
+ assert reconstructor_astra._use_astra_fdk is True, "reconstructor_astra should use native astra FDK"
435
+ reconstructor_nabu = ConebeamReconstructor(
436
+ *reconstructor_args, **{**reconstructor_kwargs_base, **reconstructor_kwargs_nabu}
437
+ )
438
+ ref = reconstructor_astra.reconstruct(cone_data)
439
+ res = reconstructor_nabu.reconstruct(cone_data)
440
+
441
+ reconstructor_kwargs_nabu = {"padding_mode": "edges", "extra_options": {"use_astra_fdk": False}}
442
+ cb_ep = ConebeamReconstructor(
443
+ *reconstructor_args, **{**reconstructor_kwargs_base, **reconstructor_kwargs_nabu}
444
+ )
445
+ res_ep = cb_ep.reconstruct(cone_data) # noqa: F841
446
+
447
+ assert np.max(np.abs(res - ref)) < 2e-3, "Wrong FDK results for parameters: %s" % (str(params))
448
+
449
+ # Test with edges padding - only nabu can do that
450
+ reconstructor_kwargs_nabu["padding_mode"] = "edges"
451
+ reconstructor_nabu = ConebeamReconstructor(
452
+ *reconstructor_args, **{**reconstructor_kwargs_base, **reconstructor_kwargs_nabu}
453
+ )
454
+ reconstructor_nabu.reconstruct(cone_data)
455
+ # result is slightly different than "res" in the borders, which is expected
456
+ # it would be good to test it as well, but it's outside of the scope of this test
457
+
458
+ with caplog.at_level(logging.WARNING):
459
+ reconstructor_kwargs_nabu = {"padding_mode": "edges", "extra_options": {"use_astra_fdk": True}}
460
+ ConebeamReconstructor(*reconstructor_args, **{**reconstructor_kwargs_base, **reconstructor_kwargs_nabu})
461
+ assert "cannot use native astra FDK" in caplog.text
462
+
390
463
 
391
464
  def generate_hollow_cube_cone_sinograms(
392
465
  vol_shape,
@@ -406,7 +479,7 @@ def generate_hollow_cube_cone_sinograms(
406
479
  prj_height = n_z
407
480
  angles = np.linspace(0, 2 * np.pi, n_angles, True)
408
481
 
409
- proj_geom = astra.create_proj_geom("cone", 1.0, 1.0, prj_width, prj_width, angles, src_orig_dist, orig_det_dist)
482
+ proj_geom = astra.create_proj_geom("cone", 1.0, 1.0, n_z, prj_width, angles, src_orig_dist, orig_det_dist)
410
483
  if rot_center_shift is not None:
411
484
  proj_geom = astra.geom_postalignment(proj_geom, (-rot_center_shift, 0))
412
485
  magnification = 1 + orig_det_dist / src_orig_dist
@@ -2,8 +2,10 @@ import os
2
2
  import numpy as np
3
3
  from silx.io.url import DataUrl
4
4
  from silx.io import get_data
5
+ from tomoscan import __version__ as __tomoscan_version__
5
6
  from tomoscan.esrf.scan.edfscan import EDFTomoScan
6
7
  from tomoscan.esrf.scan.nxtomoscan import NXtomoScan
8
+ from packaging.version import parse as parse_version
7
9
 
8
10
  from ..utils import check_supported, indices_to_slices
9
11
  from ..io.reader import EDFStackReader, NXDarksFlats, NXTomoReader
@@ -143,7 +145,10 @@ class DatasetAnalyzer:
143
145
  Return the sample-detector distance in meters.
144
146
  """
145
147
  if self._distance is None:
146
- self._distance = abs(self.dataset_scanner.distance)
148
+ if parse_version(__tomoscan_version__) < parse_version("2.0"):
149
+ self._distance = abs(self.dataset_scanner.distance)
150
+ else:
151
+ self._distance = abs(self.dataset_scanner.sample_detector_distance)
147
152
  return self._distance
148
153
 
149
154
  @distance.setter
@@ -1,4 +1,4 @@
1
- from distutils.version import StrictVersion
1
+ from packaging.version import parse as parse_version
2
2
  from typing import Optional, Union
3
3
  import logging
4
4
  import functools
@@ -523,7 +523,7 @@ def find_shift_with_itk(img1: numpy.ndarray, img2: numpy.ndarray) -> tuple:
523
523
  _logger.warning("itk is not installed. Please install it to find shift with it")
524
524
  return (0, 0)
525
525
 
526
- if StrictVersion(itk.Version.GetITKVersion()) < StrictVersion("4.9.0"):
526
+ if parse_version(itk.Version.GetITKVersion()) < parse_version("4.9.0"):
527
527
  _logger.error("ITK 4.9.0 is required to find shift with it.")
528
528
  return (0, 0)
529
529
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: nabu
3
- Version: 2024.2.4
3
+ Version: 2024.2.6
4
4
  Summary: Nabu - Tomography software
5
5
  Author-email: Pierre Paleo <pierre.paleo@esrf.fr>, Henri Payno <henri.payno@esrf.fr>, Alessandro Mirone <mirone@esrf.fr>, Jérôme Lesaint <jerome.lesaint@esrf.fr>
6
6
  Maintainer-email: Pierre Paleo <pierre.paleo@esrf.fr>
@@ -49,35 +49,35 @@ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
49
49
  Requires-Python: >=3.7
50
50
  Description-Content-Type: text/markdown
51
51
  License-File: LICENSE
52
- Requires-Dist: numpy <2,>1.9.0
52
+ Requires-Dist: numpy<2,>1.9.0
53
53
  Requires-Dist: scipy
54
- Requires-Dist: h5py >=3.0
55
- Requires-Dist: silx >=0.15.0
56
- Requires-Dist: tomoscan >=2.1.0
54
+ Requires-Dist: h5py>=3.0
55
+ Requires-Dist: silx>=0.15.0
56
+ Requires-Dist: tomoscan>=2.1.0
57
57
  Requires-Dist: psutil
58
58
  Requires-Dist: pytest
59
59
  Requires-Dist: tifffile
60
60
  Requires-Dist: tqdm
61
- Provides-Extra: doc
62
- Requires-Dist: sphinx ; extra == 'doc'
63
- Requires-Dist: cloud-sptheme ; extra == 'doc'
64
- Requires-Dist: myst-parser ; extra == 'doc'
65
- Requires-Dist: nbsphinx ; extra == 'doc'
66
61
  Provides-Extra: full
67
- Requires-Dist: scikit-image ; extra == 'full'
68
- Requires-Dist: PyWavelets ; extra == 'full'
69
- Requires-Dist: glymur ; extra == 'full'
70
- Requires-Dist: pycuda !=2024.1.1 ; extra == 'full'
71
- Requires-Dist: scikit-cuda ; extra == 'full'
72
- Requires-Dist: pycudwt ; extra == 'full'
73
- Requires-Dist: sluurp >=0.3 ; extra == 'full'
74
- Requires-Dist: pyvkfft ; extra == 'full'
75
- Provides-Extra: full_nocuda
76
- Requires-Dist: scikit-image ; extra == 'full_nocuda'
77
- Requires-Dist: PyWavelets ; extra == 'full_nocuda'
78
- Requires-Dist: glymur ; extra == 'full_nocuda'
79
- Requires-Dist: sluurp >=0.3 ; extra == 'full_nocuda'
80
- Requires-Dist: pyvkfft ; extra == 'full_nocuda'
62
+ Requires-Dist: scikit-image; extra == "full"
63
+ Requires-Dist: PyWavelets; extra == "full"
64
+ Requires-Dist: glymur; extra == "full"
65
+ Requires-Dist: pycuda!=2024.1.1; extra == "full"
66
+ Requires-Dist: scikit-cuda; extra == "full"
67
+ Requires-Dist: pycudwt; extra == "full"
68
+ Requires-Dist: sluurp>=0.3; extra == "full"
69
+ Requires-Dist: pyvkfft; extra == "full"
70
+ Provides-Extra: full-nocuda
71
+ Requires-Dist: scikit-image; extra == "full-nocuda"
72
+ Requires-Dist: PyWavelets; extra == "full-nocuda"
73
+ Requires-Dist: glymur; extra == "full-nocuda"
74
+ Requires-Dist: sluurp>=0.3; extra == "full-nocuda"
75
+ Requires-Dist: pyvkfft; extra == "full-nocuda"
76
+ Provides-Extra: doc
77
+ Requires-Dist: sphinx; extra == "doc"
78
+ Requires-Dist: cloud_sptheme; extra == "doc"
79
+ Requires-Dist: myst-parser; extra == "doc"
80
+ Requires-Dist: nbsphinx; extra == "doc"
81
81
 
82
82
  # Nabu
83
83
 
@@ -1,7 +1,8 @@
1
1
  doc/conf.py,sha256=3xtCarCHrXPr50GbeRDuH-o3Jzojw7mpr7vpGfZPLAE,3787
2
2
  doc/create_conf_doc.py,sha256=IVOdP70KvbW9WS_UQu3Iyd0YfS60E2fJ5IDtQ_s4cDw,1143
3
+ doc/doc_config.py,sha256=anqeOVjqE2e7eVzg7yuh9dvIneTkrA5doGl1cVBqT7Q,730
3
4
  doc/get_mathjax.py,sha256=VIvKRCdDuF2VoY8JD3mSey9XX13AZMmwTJBHdt1tUs4,1012
4
- nabu/__init__.py,sha256=c6RQ-mRwrAgEkoyeON7u8FYClLvqty1oogHOSMgxIRw,270
5
+ nabu/__init__.py,sha256=EVuU--KTOdCVd-SsAeXYWg4ZcflIHGPc1zOOSai-Qw4,270
5
6
  nabu/tests.py,sha256=cew9OY2uTyncHI_HM32W8CP6B1GTGKaOW65XtMEqs7o,1417
6
7
  nabu/testutils.py,sha256=VkSL9tbY0XEH49Z5OjDFFhzkSxrCv4UIuvSVFgegSUY,7632
7
8
  nabu/utils.py,sha256=V1B_sD54XGNKn5pORa2yNCATyswOzybey3sv8BuIYWY,26355
@@ -44,7 +45,7 @@ nabu/cuda/src/ElementOp.cu,sha256=PhebQQgeF0V7MDNzeYiRXIeNq3tE2PsBx00XhanBmvg,71
44
45
  nabu/cuda/src/backproj.cu,sha256=Zq9w8TP9zSYCHH_91dfrTVLOSEmY0y4hzm7J2qdCdJ8,6257
45
46
  nabu/cuda/src/backproj_polar.cu,sha256=sZbtw3KMfN69XyubJQSSLC87d5IPOyzbPBoRSNC1Cek,1851
46
47
  nabu/cuda/src/boundary.h,sha256=eQhfKZm-o0kj88BvkUwzqfqxYfRde4Tuj8AhQvRK16Y,2898
47
- nabu/cuda/src/cone.cu,sha256=Q-mKNBnx0pXzpOUGKi0Kq4POp7VqaQrLCDgqTf1pdkk,3071
48
+ nabu/cuda/src/cone.cu,sha256=IuuVpHPnGekQmLq7_FyJ3pN4Ntjs7k5yeUiPKty7H9w,3399
48
49
  nabu/cuda/src/convolution.cu,sha256=O6r8qPpQjpaqnNivMRX0LK3dEACKk9xyNYO9u_JysU0,7353
49
50
  nabu/cuda/src/dfi_fftshift.cu,sha256=ElgNXy8H14mff2hmyjxCq7CnFK_DSY2Z564QGytRO1o,2220
50
51
  nabu/cuda/src/flatfield.cu,sha256=ZVVmEd-jcsK03VWv42uugYw6LeUnDEVY8qo_G07doHs,2058
@@ -143,7 +144,7 @@ nabu/pipeline/fullfield/computations.py,sha256=AEp3qvwyY-l8-GzjH1E6kmcmU6OgDp6sB
143
144
  nabu/pipeline/fullfield/dataset_validator.py,sha256=Iy6oOnXnBldDcg0ifm_zzrzMQ6YdkR_hkHFySZgxbno,2943
144
145
  nabu/pipeline/fullfield/nabu_config.py,sha256=rvHmCNHx2y-GKBidRWCAjJd0IynvolPTTmBqNoOSvEA,31877
145
146
  nabu/pipeline/fullfield/processconfig.py,sha256=2eE9W0KjsHq8aSCXlczsISpWM9SMjWL03fN1RqZWyxg,36815
146
- nabu/pipeline/fullfield/reconstruction.py,sha256=fMHKR8LnpQ2_asIO5bJB3pRixABrKLi53KzJuB83NZ4,37422
147
+ nabu/pipeline/fullfield/reconstruction.py,sha256=LpIMyl8LLJoJLhayHXT3VyOwRpEL9tdjPhIegFUS388,37678
147
148
  nabu/pipeline/helical/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
148
149
  nabu/pipeline/helical/dataset_validator.py,sha256=0YQc0hdYdpaXznFaKmlj9SIu7mNs0xXMejcRkhOZHaI,640
149
150
  nabu/pipeline/helical/fbp.py,sha256=0fAz-Fb0Rn_FzellvcS2cy-Wvm-5dxEf494YFt4pLws,5845
@@ -220,7 +221,8 @@ nabu/processing/tests/test_rotation.py,sha256=vedRXV9RePJywBKoyBkGANP1dhZCjphbYO
220
221
  nabu/processing/tests/test_transpose.py,sha256=hTG17wTaB5Wv6twbW3ZFhBv6BYfqJY7DTQPoO0-KdkM,2760
221
222
  nabu/processing/tests/test_unsharp.py,sha256=R3ovbwDDp3ccy2A8t6CcUVELXRWkED5EnQdN2FQOfQM,4391
222
223
  nabu/reconstruction/__init__.py,sha256=EmKVvx_-FJvzJngG4ielIC7FhMCpI1Waaflg_lF44tk,163
223
- nabu/reconstruction/cone.py,sha256=WObFcHvv7NkaZhUoC_xTlvl95f38AjsAJkePSOzngVk,18870
224
+ nabu/reconstruction/astra.py,sha256=qnFYabU-Bzgys8hXjIBcwO2NazrvhNXUYFIkMHc6BmM,10444
225
+ nabu/reconstruction/cone.py,sha256=RZj1JoxqskShvglgxUnsbYw7qbm6Rralt7hJ96eU-8c,19015
224
226
  nabu/reconstruction/fbp.py,sha256=uwEniGdEOn1atc9mTAHEDeF1y-ZqneifCKVr-ieHZss,5015
225
227
  nabu/reconstruction/fbp_base.py,sha256=DwZCilPXgGMRPV8_XfkWiaXUzWFM8rNBa8IyMdy5nno,17092
226
228
  nabu/reconstruction/fbp_opencl.py,sha256=coEGLq65PCuvWnhAbIyLPHACkWjMB0XOceMp9ZIDWtc,3274
@@ -238,7 +240,7 @@ nabu/reconstruction/sinogram.py,sha256=KTSGP_JJABf4Yr9l628HPbyWsBnpbnyGKyPEq3ZrP
238
240
  nabu/reconstruction/sinogram_cuda.py,sha256=DmTWdI9JhINjBpBuPiEt5mSqFmqu2FitIV94g3hSTAI,10659
239
241
  nabu/reconstruction/sinogram_opencl.py,sha256=vxJa5BeOd2NVdUayXYfQGAfO1AEbJfTGotuijT8qgCs,1486
240
242
  nabu/reconstruction/tests/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
241
- nabu/reconstruction/tests/test_cone.py,sha256=2BsR1kHlnlronghckifGmcrtROWF43lilYo8TMDUTjo,17846
243
+ nabu/reconstruction/tests/test_cone.py,sha256=Vb31LX_KXYU8FPlco9heFRHIftk0-RI5_LSbhX9OMIA,21453
242
244
  nabu/reconstruction/tests/test_deringer.py,sha256=XE97waf6TKFm-Kxe9PGQ-Vs_Pldn-OzvOolhuy7a5k4,8340
243
245
  nabu/reconstruction/tests/test_fbp.py,sha256=jj2eSRB56-xs4SM_pK3Q9EupKQWFTzbtraXPD165rcU,15291
244
246
  nabu/reconstruction/tests/test_filtering.py,sha256=I5H1KSfd4iXTJWSqeMPR5Q7v5rFTXO6Vp2jYU4ugl58,5247
@@ -249,7 +251,7 @@ nabu/reconstruction/tests/test_reconstructor.py,sha256=3p2Wk_OqgZqkNOkhK_NJWlHkO
249
251
  nabu/reconstruction/tests/test_sino_normalization.py,sha256=fGv5Dlidxgm8ZC70Nk6oqVgpY2jzOW9NJaGlo44IJOo,3692
250
252
  nabu/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
251
253
  nabu/resources/cor.py,sha256=-mcrTbj3G7o4PP5E_gIRo2j6_-ADmMkkOc_0CyQv84c,170
252
- nabu/resources/dataset_analyzer.py,sha256=e3AqYR9ORW9Q03mGYCiC66nXld-X0LPnx9E8X3zFIGU,18568
254
+ nabu/resources/dataset_analyzer.py,sha256=nye5a2QgbeZZeRSMl2UEdR1gx9_IQf6rYMvfDJWm1QE,18859
253
255
  nabu/resources/gpu.py,sha256=GgpMb5umRQAUsEDEAefb4wSA5qm4JSMhkWmCEpW3X9g,5702
254
256
  nabu/resources/logger.py,sha256=-lOzhN_sU4R3BIfC69aMj2O8S_ocsvXsmwkhWlcxVEc,3758
255
257
  nabu/resources/nxflatfield.py,sha256=FhPqiXe6ZKu5eGI6aVfyTgcHhZF9kQUNX0cOxDPD73k,9324
@@ -262,7 +264,6 @@ nabu/resources/templates/id16_holo.conf,sha256=sDd_rEJGZjOGVAsGub5sT2arfXDnc_sxy
262
264
  nabu/resources/templates/id16a_fluo.conf,sha256=Nz1etzO2fSwksi7CThWJ5T1kZEdyBe8rMO7puNJ93Hc,542
263
265
  nabu/resources/templates/id19_pag.conf,sha256=u4fFPEBprzOW9_5_ChkIgowQcYpLhjmA8Gwm5XgC4Jc,384
264
266
  nabu/resources/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
265
- nabu/resources/tests/test_extract.py,sha256=msYWFP96LjpEntq1ucTWgKZMhUvZcLpJ-AQB3oGOh2E,206
266
267
  nabu/resources/tests/test_nxflatfield.py,sha256=XRGbYwqJv0NYAVQnAB224TwTZP_W2Bs3-yu0zQnDzEM,4179
267
268
  nabu/resources/tests/test_units.py,sha256=F2jFTck-1UwYET1MwTtX6ntzYUosfwOJkugSencGgz8,2155
268
269
  nabu/stitching/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -302,7 +303,7 @@ nabu/stitching/tests/test_z_postprocessing_stitching.py,sha256=kKRb3PvMOnCW84S58
302
303
  nabu/stitching/tests/test_z_preprocessing_stitching.py,sha256=3EhX161S2JVz6z3vXnYgBlUUQVb1weXBqIR0KAXLeUg,16157
303
304
  nabu/stitching/utils/__init__.py,sha256=alIDGBnxWH4JvP-UW-7N99seBBi0r1GV1h8f1ERFBec,21
304
305
  nabu/stitching/utils/post_processing.py,sha256=aow36shbYhFgAnbCoq6mtXVpjBNCKm7VSLIvv3EYVdA,11303
305
- nabu/stitching/utils/utils.py,sha256=EssaIs7eUL1vrImJIi_Y5OzHHdEaoVedHAGWcOT8M6k,24650
306
+ nabu/stitching/utils/utils.py,sha256=Z_JcVrUdykYFzeHJD8J-3y7kQb4Ef3_FM7M0zjQTPXg,24659
306
307
  nabu/stitching/utils/tests/test_post-processing.py,sha256=Xy0UW8sL7ERtrxN7lEZhm8gJukDeL8vxkliMT8Agvas,753
307
308
  nabu/thirdparty/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
308
309
  nabu/thirdparty/algotom_convert_sino.py,sha256=iZVRilvyqMyLTu8RKgbqDFUjObWW5X69rCARrnNizuI,13872
@@ -310,9 +311,9 @@ nabu/thirdparty/pore3d_deringer_munch.py,sha256=o4bisnFc-wMjuohWBT8wgWmfNehPQGtC
310
311
  nabu/thirdparty/tomocupy_remove_stripe.py,sha256=Khe4zFf0kRzu65Yxnvq58gt1ljOztqJGdMDhVAiM7lM,24363
311
312
  nabu/thirdparty/tomopy_phase.py,sha256=hK4oPpkogLOhv23XzzEXQY2u3r8fJvASY_bINVs6ERE,8634
312
313
  nabu/thirdparty/tomwer_load_flats_darks.py,sha256=ZNoVAinUb_wGYbfvs_4BVnWsjsQmNxSvCh1bWhR2WWg,5611
313
- nabu-2024.2.4.dist-info/LICENSE,sha256=1eAIPSnEsnSFNUODnLtNtQTs76exG3ZxJ1DJR6zoUBA,1066
314
- nabu-2024.2.4.dist-info/METADATA,sha256=8aKl-Zw_YMikzn33XIqdUzSbSqI4x3bUwhxL_wZi8Dc,5538
315
- nabu-2024.2.4.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
316
- nabu-2024.2.4.dist-info/entry_points.txt,sha256=cJKGkBeykVL7uK3E4R0RLRqMXifTL2qdO573syPAvJc,1288
317
- nabu-2024.2.4.dist-info/top_level.txt,sha256=fsm_N3eXLRZk2QXF9OSKPNDPFXOz8FAQjHh5avT3dok,9
318
- nabu-2024.2.4.dist-info/RECORD,,
314
+ nabu-2024.2.6.dist-info/LICENSE,sha256=1eAIPSnEsnSFNUODnLtNtQTs76exG3ZxJ1DJR6zoUBA,1066
315
+ nabu-2024.2.6.dist-info/METADATA,sha256=5dkZbelyWSeWdpeFuTiuLAfLGkkWVmzeDIbie1bjmk4,5514
316
+ nabu-2024.2.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
317
+ nabu-2024.2.6.dist-info/entry_points.txt,sha256=cJKGkBeykVL7uK3E4R0RLRqMXifTL2qdO573syPAvJc,1288
318
+ nabu-2024.2.6.dist-info/top_level.txt,sha256=fsm_N3eXLRZk2QXF9OSKPNDPFXOz8FAQjHh5avT3dok,9
319
+ nabu-2024.2.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.1)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,9 +0,0 @@
1
- from nabu.utils import list_match_queries
2
-
3
-
4
- def test_list_match_queries():
5
-
6
- # entry0000 .... entry0099
7
- avail = ["entry%04d" % i for i in range(100)]
8
- query = "entry0000"
9
- list_match_queries()