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 +32 -0
- nabu/__init__.py +1 -1
- nabu/cuda/src/cone.cu +19 -9
- nabu/pipeline/fullfield/reconstruction.py +7 -2
- nabu/reconstruction/astra.py +245 -0
- nabu/reconstruction/cone.py +9 -4
- nabu/reconstruction/tests/test_cone.py +74 -1
- nabu/resources/dataset_analyzer.py +6 -1
- nabu/stitching/utils/utils.py +2 -2
- {nabu-2024.2.4.dist-info → nabu-2024.2.6.dist-info}/METADATA +25 -25
- {nabu-2024.2.4.dist-info → nabu-2024.2.6.dist-info}/RECORD +15 -14
- {nabu-2024.2.4.dist-info → nabu-2024.2.6.dist-info}/WHEEL +1 -1
- nabu/resources/tests/test_extract.py +0 -9
- {nabu-2024.2.4.dist-info → nabu-2024.2.6.dist-info}/LICENSE +0 -0
- {nabu-2024.2.4.dist-info → nabu-2024.2.6.dist-info}/entry_points.txt +0 -0
- {nabu-2024.2.4.dist-info → nabu-2024.2.6.dist-info}/top_level.txt +0 -0
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
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
|
-
|
37
|
+
|
38
|
+
const uint angle = blockDim.y * blockIdx.y + threadIdx.y + startAngle;
|
39
|
+
|
38
40
|
if (angle >= endAngle)
|
39
41
|
return;
|
40
42
|
|
41
|
-
|
42
|
-
const int
|
43
|
-
int
|
44
|
-
|
45
|
-
|
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
|
-
|
135
|
-
|
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)
|
nabu/reconstruction/cone.py
CHANGED
@@ -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
|
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 = ((
|
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,
|
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
|
-
|
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
|
nabu/stitching/utils/utils.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from
|
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
|
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
|
+
Metadata-Version: 2.2
|
2
2
|
Name: nabu
|
3
|
-
Version: 2024.2.
|
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
|
52
|
+
Requires-Dist: numpy<2,>1.9.0
|
53
53
|
Requires-Dist: scipy
|
54
|
-
Requires-Dist: h5py
|
55
|
-
Requires-Dist: silx
|
56
|
-
Requires-Dist: tomoscan
|
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
|
68
|
-
Requires-Dist: PyWavelets
|
69
|
-
Requires-Dist: glymur
|
70
|
-
Requires-Dist: pycuda
|
71
|
-
Requires-Dist: scikit-cuda
|
72
|
-
Requires-Dist: pycudwt
|
73
|
-
Requires-Dist: sluurp
|
74
|
-
Requires-Dist: pyvkfft
|
75
|
-
Provides-Extra:
|
76
|
-
Requires-Dist: scikit-image
|
77
|
-
Requires-Dist: PyWavelets
|
78
|
-
Requires-Dist: glymur
|
79
|
-
Requires-Dist: sluurp
|
80
|
-
Requires-Dist: pyvkfft
|
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=
|
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=
|
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=
|
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/
|
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=
|
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=
|
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=
|
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.
|
314
|
-
nabu-2024.2.
|
315
|
-
nabu-2024.2.
|
316
|
-
nabu-2024.2.
|
317
|
-
nabu-2024.2.
|
318
|
-
nabu-2024.2.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|