httomolibgpu 5.6__tar.gz → 5.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {httomolibgpu-5.6/httomolibgpu.egg-info → httomolibgpu-5.8}/PKG-INFO +2 -1
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/denoise.py +3 -1
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/morph.py +115 -21
- httomolibgpu-5.8/httomolibgpu/misc/sorting.py +54 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/stripe.py +20 -34
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/recon/algorithm.py +142 -9
- {httomolibgpu-5.6 → httomolibgpu-5.8/httomolibgpu.egg-info}/PKG-INFO +2 -1
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu.egg-info/SOURCES.txt +1 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu.egg-info/requires.txt +1 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/pyproject.toml +2 -1
- {httomolibgpu-5.6 → httomolibgpu-5.8}/LICENSE +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/MANIFEST.in +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/README.rst +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/__init__.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/__init__.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/calc_metrics.cu +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/center_360_shifts.cu +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/generate_mask.cu +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/median_kernel.cu +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/raven_filter.cu +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/remove_nan_inf.cu +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/remove_stripe_fw.cu +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cupywrapper.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/__init__.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/corr.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/rescale.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/utils.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/__init__.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/alignment.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/normalize.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/phase.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/recon/__init__.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/recon/_phase_cross_correlation.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/recon/rotation.py +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu.egg-info/dependency_links.txt +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu.egg-info/top_level.txt +0 -0
- {httomolibgpu-5.6 → httomolibgpu-5.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httomolibgpu
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.8
|
|
4
4
|
Summary: Commonly used tomography data processing methods at DLS.
|
|
5
5
|
Author-email: Daniil Kazantsev <daniil.kazantsev@diamond.ac.uk>, Yousef Moazzam <yousef.moazzam@diamond.ac.uk>, Naman Gera <naman.gera@diamond.ac.uk>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -33,6 +33,7 @@ Requires-Dist: imageio; extra == "dev"
|
|
|
33
33
|
Requires-Dist: h5py; extra == "dev"
|
|
34
34
|
Requires-Dist: pre-commit; extra == "dev"
|
|
35
35
|
Requires-Dist: pyfftw; extra == "dev"
|
|
36
|
+
Requires-Dist: tomophantom; extra == "dev"
|
|
36
37
|
Dynamic: license-file
|
|
37
38
|
|
|
38
39
|
HTTomolibGPU is a library of GPU accelerated methods for tomography
|
|
@@ -175,7 +175,9 @@ def total_variation_PD(
|
|
|
175
175
|
__check_variable_type(iterations, [int], "iterations", [], methods_name)
|
|
176
176
|
__check_variable_type(isotropic, [bool], "isotropic", [], methods_name)
|
|
177
177
|
__check_variable_type(nonnegativity, [bool], "nonnegativity", [], methods_name)
|
|
178
|
-
__check_variable_type(
|
|
178
|
+
__check_variable_type(
|
|
179
|
+
lipschitz_const, [float, int], "lipschitz_const", [], methods_name
|
|
180
|
+
)
|
|
179
181
|
__check_variable_type(gpu_id, [int], "gpu_id", [], methods_name)
|
|
180
182
|
__check_variable_type(half_precision, [bool], "half_precision", [], methods_name)
|
|
181
183
|
###################################
|
|
@@ -45,6 +45,7 @@ from httomolibgpu.misc.utils import (
|
|
|
45
45
|
__all__ = [
|
|
46
46
|
"sino_360_to_180",
|
|
47
47
|
"data_resampler",
|
|
48
|
+
"average_projection_frames",
|
|
48
49
|
]
|
|
49
50
|
|
|
50
51
|
|
|
@@ -80,45 +81,76 @@ def sino_360_to_180(
|
|
|
80
81
|
)
|
|
81
82
|
__check_variable_type(overlap, [int, float], "overlap", [], methods_name)
|
|
82
83
|
__check_variable_type(side, [str], "side", ["left", "right"], methods_name)
|
|
83
|
-
__check_if_positive_nonzero(
|
|
84
|
-
overlap, "overlap", positive=True, nonzero=True, methods_name=methods_name
|
|
85
|
-
)
|
|
86
84
|
###################################
|
|
87
85
|
#
|
|
88
86
|
dx, dy, dz = data.shape
|
|
89
87
|
|
|
90
88
|
overlap = int(np.round(overlap))
|
|
91
|
-
if overlap >= dz - 1:
|
|
92
|
-
raise ValueError("Overlap must be less than size of the horizontal detector")
|
|
93
89
|
if overlap % 2 != 0:
|
|
94
90
|
overlap += 1
|
|
95
|
-
|
|
91
|
+
overlap_pos = abs(overlap)
|
|
92
|
+
if overlap_pos >= dz - 1:
|
|
93
|
+
raise ValueError("Overlap must be less than size of the horizontal detector")
|
|
96
94
|
n = dx // 2
|
|
97
95
|
out = cp.empty((n, dy, 2 * dz - overlap), dtype=data.dtype)
|
|
98
96
|
|
|
99
97
|
if side == "left":
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
98
|
+
if overlap == 0:
|
|
99
|
+
out[:, :, -dz:] = data[:n, :, :]
|
|
100
|
+
out[:, :, :dz] = data[n : 2 * n, :, :][:, :, ::-1]
|
|
101
|
+
return out
|
|
102
|
+
elif overlap < 0:
|
|
103
|
+
out[:, :, -dz:] = data[:n, :, :]
|
|
104
|
+
out[:, :, : (dz + overlap_pos)] = cp.pad(
|
|
105
|
+
data[n : 2 * n, :, :][:, :, ::-1],
|
|
106
|
+
pad_width=(
|
|
107
|
+
(0, 0),
|
|
108
|
+
(0, 0),
|
|
109
|
+
(0, overlap_pos),
|
|
110
|
+
),
|
|
111
|
+
mode="edge",
|
|
112
|
+
)
|
|
113
|
+
return out
|
|
114
|
+
else:
|
|
115
|
+
weights = cp.linspace(0, 1.0, overlap, dtype=cp.float32)
|
|
116
|
+
out[:, :, -dz + overlap :] = data[:n, :, overlap:]
|
|
117
|
+
out[:, :, : dz - overlap] = data[n : 2 * n, :, overlap:][:, :, ::-1]
|
|
118
|
+
out[:, :, dz - overlap : dz] = (
|
|
119
|
+
weights * data[:n, :, :overlap]
|
|
120
|
+
+ (weights * data[n : 2 * n, :, :overlap])[:, :, ::-1]
|
|
121
|
+
)
|
|
107
122
|
if side == "right":
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
if overlap == 0:
|
|
124
|
+
out[:, :, :dz] = data[:n, :, :]
|
|
125
|
+
out[:, :, dz::] = data[n : 2 * n, :, :][:, :, ::-1]
|
|
126
|
+
return out
|
|
127
|
+
elif overlap < 0:
|
|
128
|
+
out[:, :, : (dz + overlap_pos)] = cp.pad(
|
|
129
|
+
data[:n, :, :],
|
|
130
|
+
pad_width=(
|
|
131
|
+
(0, 0),
|
|
132
|
+
(0, 0),
|
|
133
|
+
(0, overlap_pos),
|
|
134
|
+
),
|
|
135
|
+
mode="edge",
|
|
136
|
+
)
|
|
137
|
+
out[:, :, (dz + overlap_pos) : :] = data[n : 2 * n, :, :][:, :, ::-1]
|
|
138
|
+
return out
|
|
139
|
+
else:
|
|
140
|
+
weights = cp.linspace(1.0, 0, overlap, dtype=cp.float32)
|
|
141
|
+
out[:, :, : dz - overlap] = data[:n, :, :-overlap]
|
|
142
|
+
out[:, :, -dz + overlap :] = data[n : 2 * n, :, :-overlap][:, :, ::-1]
|
|
143
|
+
out[:, :, dz - overlap : dz] = (
|
|
144
|
+
weights * data[:n, :, -overlap:]
|
|
145
|
+
+ (weights * data[n : 2 * n, :, -overlap:])[:, :, ::-1]
|
|
146
|
+
)
|
|
115
147
|
|
|
116
148
|
return cp.pad(
|
|
117
149
|
out,
|
|
118
150
|
pad_width=(
|
|
119
151
|
(0, 0),
|
|
120
152
|
(0, 0),
|
|
121
|
-
(
|
|
153
|
+
(overlap_pos // 2, overlap_pos // 2),
|
|
122
154
|
),
|
|
123
155
|
mode="edge",
|
|
124
156
|
)
|
|
@@ -274,3 +306,65 @@ def data_resampler(
|
|
|
274
306
|
if expanded:
|
|
275
307
|
scaled_data = cp.squeeze(scaled_data, axis=axis)
|
|
276
308
|
return scaled_data
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def average_projection_frames(
|
|
312
|
+
data: cp.ndarray,
|
|
313
|
+
projection_averaging_factor: int = 2,
|
|
314
|
+
) -> cp.ndarray:
|
|
315
|
+
"""
|
|
316
|
+
This method averages/downsamples by averaging data along the angular direction based on the provided projection_averaging_factor.
|
|
317
|
+
|
|
318
|
+
Parameters
|
|
319
|
+
----------
|
|
320
|
+
data : cp.ndarray
|
|
321
|
+
3d cupy array given as (angles, detY, detX).
|
|
322
|
+
projection_averaging_factor : int
|
|
323
|
+
to average every (defined by the provided factor) consecutive projections into one effective projection.
|
|
324
|
+
|
|
325
|
+
Raises
|
|
326
|
+
----------
|
|
327
|
+
ValueError: When data is not 3D
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
cp.ndarray: 3D cupy array with averaged projection data
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
### Data and parameters checks ###
|
|
335
|
+
methods_name = "average_projection_frames"
|
|
336
|
+
__check_if_data_3D_array(data, methods_name)
|
|
337
|
+
__check_if_data_correct_type(
|
|
338
|
+
data, accepted_type=["float32", "uint16"], methods_name=methods_name
|
|
339
|
+
)
|
|
340
|
+
__check_if_positive_nonzero(
|
|
341
|
+
projection_averaging_factor,
|
|
342
|
+
"projection_averaging_factor",
|
|
343
|
+
True,
|
|
344
|
+
True,
|
|
345
|
+
methods_name,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
###################################
|
|
349
|
+
if projection_averaging_factor > 1:
|
|
350
|
+
k = projection_averaging_factor
|
|
351
|
+
n_proj = data.shape[0]
|
|
352
|
+
|
|
353
|
+
n_full = n_proj // k
|
|
354
|
+
remainder = n_proj % k
|
|
355
|
+
|
|
356
|
+
n_out = n_full + (remainder > 0)
|
|
357
|
+
|
|
358
|
+
averaged = cp.empty((n_out, *data.shape[1:]), dtype=data.dtype)
|
|
359
|
+
|
|
360
|
+
if n_full:
|
|
361
|
+
averaged[:n_full] = (
|
|
362
|
+
data[: n_full * k].reshape(n_full, k, *data.shape[1:]).mean(axis=1)
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
if remainder:
|
|
366
|
+
averaged[-1] = data[n_full * k :].mean(axis=0)
|
|
367
|
+
|
|
368
|
+
return averaged
|
|
369
|
+
else:
|
|
370
|
+
return data
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ---------------------------------------------------------------------------
|
|
4
|
+
# Copyright 2026 Diamond Light Source Ltd.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ecpress or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# Created By : Tomography Team at DLS <scientificsoftware@diamond.ac.uk>
|
|
19
|
+
# Created Date: 22 April 2026
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
from typing import Tuple
|
|
23
|
+
import cupy as cp
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def argsort_with_reverse(
|
|
27
|
+
data: cp.ndarray, axis: int = -1
|
|
28
|
+
) -> Tuple[cp.ndarray, cp.ndarray]:
|
|
29
|
+
"""
|
|
30
|
+
Compute sorting indices for an 1D or 2D array, and efficiently compute the indices to revert the sort.
|
|
31
|
+
"""
|
|
32
|
+
dim = len(data.shape)
|
|
33
|
+
if 1 <= dim <= 2:
|
|
34
|
+
pass
|
|
35
|
+
else:
|
|
36
|
+
raise ValueError("only 1D and 2D arrays are supported")
|
|
37
|
+
if axis < 0:
|
|
38
|
+
axis = dim + axis
|
|
39
|
+
if axis >= dim:
|
|
40
|
+
raise ValueError("invalid axis")
|
|
41
|
+
sort_indices = cp.argsort(data, axis=axis)
|
|
42
|
+
reverse_sort_indices = cp.empty_like(sort_indices)
|
|
43
|
+
if dim == 1:
|
|
44
|
+
reverse_sort_indices[sort_indices] = cp.arange(0, data.size)
|
|
45
|
+
elif axis == 0: # sort rows
|
|
46
|
+
nrows, ncols = data.shape
|
|
47
|
+
cols = cp.arange(ncols)[None, :]
|
|
48
|
+
reverse_sort_indices[sort_indices, cols] = cp.arange(nrows)[:, None]
|
|
49
|
+
else: # sort columns
|
|
50
|
+
nrows, ncols = data.shape
|
|
51
|
+
rows = cp.arange(nrows)[:, None]
|
|
52
|
+
reverse_sort_indices[rows, sort_indices] = cp.arange(ncols)
|
|
53
|
+
|
|
54
|
+
return sort_indices, reverse_sort_indices
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import numpy as np
|
|
24
24
|
import pywt
|
|
25
25
|
from httomolibgpu import cupywrapper
|
|
26
|
+
from httomolibgpu.misc.sorting import argsort_with_reverse
|
|
26
27
|
|
|
27
28
|
cp = cupywrapper.cp
|
|
28
29
|
cupy_run = cupywrapper.cupy_run
|
|
@@ -116,21 +117,18 @@ def _rs_sort(sinogram, size, dim):
|
|
|
116
117
|
"""
|
|
117
118
|
Remove stripes using the sorting technique.
|
|
118
119
|
"""
|
|
119
|
-
sinogram
|
|
120
|
+
#: Sort each row of the sinogram by its grayscale values
|
|
121
|
+
sortvals, sortvals_reverse = argsort_with_reverse(sinogram, axis=0)
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
#: Keep track of the sorting indices so we can reverse it below
|
|
123
|
-
sortvals = cp.argsort(sinogram, axis=1)
|
|
124
|
-
sortvals_reverse = cp.argsort(sortvals, axis=1)
|
|
125
|
-
sino_sort = cp.take_along_axis(sinogram, sortvals, axis=1)
|
|
123
|
+
sino_sort = cp.take_along_axis(sinogram, sortvals, axis=0)
|
|
126
124
|
|
|
127
125
|
#: Now apply the median filter on the sorted image along each row
|
|
128
|
-
sino_sort = median_filter(sino_sort, (
|
|
126
|
+
sino_sort = median_filter(sino_sort, (1, size) if dim == 1 else (size, size))
|
|
129
127
|
|
|
130
128
|
#: step 3: re-sort the smoothed image columns to the original rows
|
|
131
|
-
sino_corrected = cp.take_along_axis(sino_sort, sortvals_reverse, axis=
|
|
129
|
+
sino_corrected = cp.take_along_axis(sino_sort, sortvals_reverse, axis=0)
|
|
132
130
|
|
|
133
|
-
return
|
|
131
|
+
return sino_corrected
|
|
134
132
|
|
|
135
133
|
|
|
136
134
|
def remove_stripe_ti(
|
|
@@ -821,6 +819,7 @@ def remove_all_stripe(
|
|
|
821
819
|
la_size: int = 61,
|
|
822
820
|
sm_size: int = 21,
|
|
823
821
|
dim: Literal[1, 2] = 1,
|
|
822
|
+
normalize: bool = False,
|
|
824
823
|
) -> cp.ndarray:
|
|
825
824
|
"""
|
|
826
825
|
Remove all types of stripe artifacts from sinogram using Nghia Vo's
|
|
@@ -839,6 +838,8 @@ def remove_all_stripe(
|
|
|
839
838
|
Window size of the median filter to remove small-to-medium stripes.
|
|
840
839
|
dim : {1, 2},
|
|
841
840
|
Dimension of the window.
|
|
841
|
+
normalize : bool,
|
|
842
|
+
Controls whether to normalize while removing large stripes
|
|
842
843
|
|
|
843
844
|
Returns
|
|
844
845
|
-------
|
|
@@ -859,10 +860,10 @@ def remove_all_stripe(
|
|
|
859
860
|
__check_variable_type(dim, [int], "dim", [1, 2], methods_name)
|
|
860
861
|
###################################
|
|
861
862
|
|
|
862
|
-
matindex = _create_matindex(data.shape[2], data.shape[0])
|
|
863
863
|
for m in range(data.shape[1]):
|
|
864
864
|
sino = data[:, m, :]
|
|
865
|
-
sino = _rs_dead(sino, snr, la_size
|
|
865
|
+
sino = _rs_dead(sino, snr, la_size)
|
|
866
|
+
sino = _rs_large(sino, snr, la_size, normalize)
|
|
866
867
|
sino = _rs_sort(sino, sm_size, dim)
|
|
867
868
|
sino = cp.nan_to_num(sino)
|
|
868
869
|
data[:, m, :] = sino
|
|
@@ -910,15 +911,17 @@ def _detect_stripe(listdata, snr):
|
|
|
910
911
|
return listmask
|
|
911
912
|
|
|
912
913
|
|
|
913
|
-
def _rs_large(sinogram, snr, size,
|
|
914
|
+
def _rs_large(sinogram, snr, size, normalize, drop_ratio=0.1):
|
|
914
915
|
"""
|
|
915
916
|
Remove large stripes.
|
|
916
917
|
"""
|
|
917
918
|
drop_ratio = max(min(drop_ratio, 0.8), 0) # = cp.clip(drop_ratio, 0.0, 0.8)
|
|
918
919
|
nrow, ncol = sinogram.shape
|
|
919
920
|
ndrop = int(0.5 * drop_ratio * nrow)
|
|
920
|
-
|
|
921
|
+
sort_indices, sort_indices_reverse = argsort_with_reverse(sinogram, axis=0)
|
|
922
|
+
sinosort = cp.take_along_axis(sinogram, sort_indices, axis=0)
|
|
921
923
|
sinosmooth = median_filter(sinosort, (1, size))
|
|
924
|
+
|
|
922
925
|
list1 = cp.mean(sinosort[ndrop : nrow - ndrop], axis=0)
|
|
923
926
|
list2 = cp.mean(sinosmooth[ndrop : nrow - ndrop], axis=0)
|
|
924
927
|
listfact = list1 / list2
|
|
@@ -927,30 +930,16 @@ def _rs_large(sinogram, snr, size, matindex, drop_ratio=0.1, norm=True):
|
|
|
927
930
|
listmask = _detect_stripe(listfact, snr)
|
|
928
931
|
listmask = binary_dilation(listmask, iterations=1).astype(listmask.dtype)
|
|
929
932
|
matfact = cp.tile(listfact, (nrow, 1))
|
|
930
|
-
|
|
931
|
-
if norm is True:
|
|
933
|
+
if normalize:
|
|
932
934
|
sinogram = sinogram / matfact
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
ids = cp.argsort(matcombine[:, :, 1], axis=1)
|
|
937
|
-
matsort = matcombine.copy()
|
|
938
|
-
matsort[:, :, 0] = cp.take_along_axis(matsort[:, :, 0], ids, axis=1)
|
|
939
|
-
matsort[:, :, 1] = cp.take_along_axis(matsort[:, :, 1], ids, axis=1)
|
|
940
|
-
|
|
941
|
-
matsort[:, :, 1] = cp.transpose(sinosmooth)
|
|
942
|
-
ids = cp.argsort(matsort[:, :, 0], axis=1)
|
|
943
|
-
matsortback = matsort.copy()
|
|
944
|
-
matsortback[:, :, 0] = cp.take_along_axis(matsortback[:, :, 0], ids, axis=1)
|
|
945
|
-
matsortback[:, :, 1] = cp.take_along_axis(matsortback[:, :, 1], ids, axis=1)
|
|
946
|
-
|
|
947
|
-
sino_corrected = cp.transpose(matsortback[:, :, 1])
|
|
935
|
+
sort_indices, sort_indices_reverse = argsort_with_reverse(sinogram, axis=0)
|
|
936
|
+
sino_corrected = cp.take_along_axis(sinosmooth, sort_indices_reverse, axis=0)
|
|
948
937
|
listxmiss = cp.where(listmask > 0.0)[0]
|
|
949
938
|
sinogram[:, listxmiss] = sino_corrected[:, listxmiss]
|
|
950
939
|
return sinogram
|
|
951
940
|
|
|
952
941
|
|
|
953
|
-
def _rs_dead(sinogram, snr, size
|
|
942
|
+
def _rs_dead(sinogram, snr, size):
|
|
954
943
|
"""remove unresponsive and fluctuating stripes"""
|
|
955
944
|
sinogram = cp.copy(sinogram) # Make it mutable
|
|
956
945
|
nrow, _ = sinogram.shape
|
|
@@ -979,9 +968,6 @@ def _rs_dead(sinogram, snr, size, matindex, norm=True):
|
|
|
979
968
|
sinogram[:, listx[ids]] - sinogram[:, listx[ids - 1]]
|
|
980
969
|
)
|
|
981
970
|
|
|
982
|
-
# Remove residual stripes
|
|
983
|
-
if norm is True:
|
|
984
|
-
sinogram = _rs_large(sinogram, snr, size, matindex)
|
|
985
971
|
return sinogram
|
|
986
972
|
|
|
987
973
|
|
|
@@ -56,6 +56,7 @@ __all__ = [
|
|
|
56
56
|
"CGLS3d_tomobar",
|
|
57
57
|
"FISTA3d_tomobar",
|
|
58
58
|
"ADMM3d_tomobar",
|
|
59
|
+
"OSEM3d_tomobar",
|
|
59
60
|
]
|
|
60
61
|
|
|
61
62
|
input_data_axis_labels = ["angles", "detY", "detX"] # set the labels of the input data
|
|
@@ -126,7 +127,18 @@ def FBP2d_astra(
|
|
|
126
127
|
filter_type,
|
|
127
128
|
[str],
|
|
128
129
|
"filter_type",
|
|
129
|
-
[
|
|
130
|
+
[
|
|
131
|
+
"none",
|
|
132
|
+
"ram-lak",
|
|
133
|
+
"cosine",
|
|
134
|
+
"shepp-logan",
|
|
135
|
+
"tukey",
|
|
136
|
+
"gaussian",
|
|
137
|
+
"blackman",
|
|
138
|
+
"kaiser",
|
|
139
|
+
"lanczos",
|
|
140
|
+
"hann",
|
|
141
|
+
],
|
|
130
142
|
methods_name,
|
|
131
143
|
)
|
|
132
144
|
__check_variable_type(
|
|
@@ -552,15 +564,15 @@ def FISTA3d_tomobar(
|
|
|
552
564
|
subsets_number: int
|
|
553
565
|
The number of the ordered subsets to accelerate convergence. Keep the value bellow 10 to avoid divergence.
|
|
554
566
|
data_fidelity: str
|
|
555
|
-
Data fidelity given as 'LS' (Least Squares)
|
|
567
|
+
Data fidelity given as 'LS' (Least Squares)
|
|
556
568
|
regularisation_type: str
|
|
557
|
-
A method to use for regularisation. Currently PD_TV and ROF_TV are available.
|
|
569
|
+
A method to use for regularisation. Currently PD_TV and ROF_TV penalties are available.
|
|
558
570
|
regularisation_parameter: float
|
|
559
571
|
The main regularisation parameter to control the amount of smoothing/noise removal. Larger values lead to stronger smoothing.
|
|
560
572
|
regularisation_iterations: int
|
|
561
573
|
The number of iterations for regularisers (aka INNER iterations).
|
|
562
574
|
regularisation_half_precision: bool
|
|
563
|
-
Perform faster regularisation computation in half-precision with
|
|
575
|
+
Perform faster regularisation computation in half-precision with some sacrifice in quality (can be used to find the best settings).
|
|
564
576
|
nonnegativity : bool
|
|
565
577
|
Impose nonnegativity constraint on the reconstructed image.
|
|
566
578
|
gpu_id : int
|
|
@@ -644,7 +656,7 @@ def ADMM3d_tomobar(
|
|
|
644
656
|
initialisation: Literal["FBP", "CGLS", "SIRT", None] = "FBP",
|
|
645
657
|
ADMM_rho_const: float = 1.0,
|
|
646
658
|
ADMM_relax_par: float = 1.7,
|
|
647
|
-
regularisation_type:
|
|
659
|
+
regularisation_type: Literal["ROF_TV", "PD_TV"] = "PD_TV",
|
|
648
660
|
regularisation_parameter: float = 0.0025,
|
|
649
661
|
regularisation_iterations: int = 40,
|
|
650
662
|
regularisation_half_precision: bool = True,
|
|
@@ -679,21 +691,23 @@ def ADMM3d_tomobar(
|
|
|
679
691
|
subsets_number: int
|
|
680
692
|
The number of the ordered subsets to accelerate convergence. The recommended range is between 12 to 24.
|
|
681
693
|
data_fidelity: str
|
|
682
|
-
Data fidelity given as 'LS' (Least Squares)
|
|
694
|
+
Data fidelity given as 'LS' (Least Squares)
|
|
683
695
|
initialisation: str, optional
|
|
684
696
|
Initialise ADMM with the reconstructed image to reduce the number of iterations and accelerate. Choose between 'CGLS' or 'SIRT' when data
|
|
685
697
|
is noisy and/or undersampled. Choose 'FBP' when the data is of better quality (default) or None.
|
|
686
698
|
ADMM_rho_const: float
|
|
687
699
|
Convergence related parameter for ADMM, higher values lead to slower convergence, but too small values can destabilise the iterations.
|
|
688
700
|
Recommended range is between 0.9 and 2.0.
|
|
689
|
-
ADMM_relax_par:
|
|
690
|
-
|
|
701
|
+
ADMM_relax_par: float
|
|
702
|
+
Relaxation parameter which can lead to acceleration of the algorithm, keep it in the range between 1.5 and 1.8 to avoid divergence.
|
|
703
|
+
regularisation_type: str
|
|
704
|
+
A method to use for regularisation. Currently PD_TV and ROF_TV penalties are available.
|
|
691
705
|
regularisation_parameter: float
|
|
692
706
|
The main regularisation parameter to control the amount of smoothing/noise removal. Larger values lead to stronger smoothing.
|
|
693
707
|
regularisation_iterations: int
|
|
694
708
|
The number of iterations for regularisers (aka INNER iterations).
|
|
695
709
|
regularisation_half_precision: bool
|
|
696
|
-
Perform faster regularisation computation in half-precision with
|
|
710
|
+
Perform faster regularisation computation in half-precision with some sacrifice in quality (can be used to find the best settings).
|
|
697
711
|
nonnegativity : bool
|
|
698
712
|
Impose nonnegativity constraint (set to True) on the reconstructed image. Default False.
|
|
699
713
|
gpu_id : int
|
|
@@ -835,6 +849,125 @@ def ADMM3d_tomobar(
|
|
|
835
849
|
return cp.require(cp.swapaxes(reconstruction, 0, 1), requirements="C")
|
|
836
850
|
|
|
837
851
|
|
|
852
|
+
## %%%%%%%%%%%%%%%%%%%%%%% OSEM reconstruction %%%%%%%%%%%%%%%%%%%%%%%%%%%% ##
|
|
853
|
+
def OSEM3d_tomobar(
|
|
854
|
+
data: cp.ndarray,
|
|
855
|
+
angles: np.ndarray,
|
|
856
|
+
center: Optional[float] = None,
|
|
857
|
+
detector_pad: Union[bool, int] = False,
|
|
858
|
+
recon_size: Optional[int] = None,
|
|
859
|
+
recon_mask_radius: Optional[float] = 0.95,
|
|
860
|
+
iterations: int = 20,
|
|
861
|
+
subsets_number: int = 12,
|
|
862
|
+
regularisation_type: Literal["ROF_TV", "PD_TV"] = "PD_TV",
|
|
863
|
+
regularisation_parameter: float = 1.0,
|
|
864
|
+
regularisation_iterations: int = 30,
|
|
865
|
+
regularisation_half_precision: bool = True,
|
|
866
|
+
nonnegativity: bool = True,
|
|
867
|
+
gpu_id: int = 0,
|
|
868
|
+
) -> cp.ndarray:
|
|
869
|
+
"""
|
|
870
|
+
Ordered-Subsets Expectation-Maximisation method is the accelerated Maximum Likelihood Expectation-Maximisation (MLEM) algorithm.
|
|
871
|
+
Can be coupled with various types of regularisation or denoising operations :cite:`kazantsev2019ccpi` (currently accepts ROF_TV and PD_TV regularisations only).
|
|
872
|
+
Should be applied to reconstruct emission-type measurements, e.g., XRF tomography measurements.
|
|
873
|
+
|
|
874
|
+
Parameters
|
|
875
|
+
----------
|
|
876
|
+
data : cp.ndarray
|
|
877
|
+
Projection data as a CuPy array.
|
|
878
|
+
angles : np.ndarray
|
|
879
|
+
An array of angles given in radians.
|
|
880
|
+
center : float, optional
|
|
881
|
+
The center of rotation (CoR).
|
|
882
|
+
detector_pad : bool, int
|
|
883
|
+
Detector width padding with edge values to remove circle/arc type artifacts in the reconstruction. Set to True to perform
|
|
884
|
+
an automated padding or specify a certain value as an integer.
|
|
885
|
+
recon_size : int, optional
|
|
886
|
+
The squared size of the reconstructed slice. By default (None), the reconstructed size will be the dimension of the horizontal detector.
|
|
887
|
+
recon_mask_radius: float, optional
|
|
888
|
+
The radius of the circular mask that applies to the reconstructed slice in order to crop
|
|
889
|
+
out some undesirable artifacts. The values outside the given diameter will be set to zero.
|
|
890
|
+
To implement the cropping one can use the range [0.7-1.0] or set to None (2.0) when no cropping is needed.
|
|
891
|
+
iterations : int
|
|
892
|
+
The number of OSEM algorithm iterations. For OS method 20 interations is normally sufficient, while for MLEM one should run 300-500 iterations.
|
|
893
|
+
subsets_number: int
|
|
894
|
+
The number of the ordered subsets to accelerate convergence. One can set 'subsets_number' to 1 to achieve MLEM, but the number of 'iterations' should be also increased.
|
|
895
|
+
data_fidelity: str
|
|
896
|
+
Data fidelity given as 'LS' (Least Squares)
|
|
897
|
+
regularisation_type: str
|
|
898
|
+
A method to use for regularisation. Currently PD_TV and ROF_TV are available.
|
|
899
|
+
regularisation_parameter: float
|
|
900
|
+
The main regularisation parameter to control the amount of smoothing/noise removal. Larger values lead to stronger smoothing.
|
|
901
|
+
regularisation_iterations: int
|
|
902
|
+
The number of iterations for regularisers (aka INNER iterations).
|
|
903
|
+
regularisation_half_precision: bool
|
|
904
|
+
Perform faster regularisation computation in half-precision with a very minimal sacrifice in quality.
|
|
905
|
+
nonnegativity : bool
|
|
906
|
+
Impose nonnegativity constraint on the reconstructed image.
|
|
907
|
+
gpu_id : int
|
|
908
|
+
A GPU device index to perform operation on.
|
|
909
|
+
|
|
910
|
+
Returns
|
|
911
|
+
-------
|
|
912
|
+
cp.ndarray
|
|
913
|
+
The OSEM reconstructed volume as a CuPy array.
|
|
914
|
+
"""
|
|
915
|
+
### Data and parameters checks ###
|
|
916
|
+
methods_name = "OSEM3d_tomobar"
|
|
917
|
+
__common_data_parameters_check(
|
|
918
|
+
data,
|
|
919
|
+
angles,
|
|
920
|
+
methods_name,
|
|
921
|
+
center,
|
|
922
|
+
detector_pad,
|
|
923
|
+
recon_size,
|
|
924
|
+
recon_mask_radius,
|
|
925
|
+
gpu_id,
|
|
926
|
+
)
|
|
927
|
+
__common_iterative_basic_parameters_check(methods_name, iterations, nonnegativity)
|
|
928
|
+
__common_iterative_parameters_check(
|
|
929
|
+
methods_name,
|
|
930
|
+
subsets_number,
|
|
931
|
+
regularisation_type,
|
|
932
|
+
regularisation_parameter,
|
|
933
|
+
regularisation_iterations,
|
|
934
|
+
regularisation_half_precision,
|
|
935
|
+
)
|
|
936
|
+
###################################
|
|
937
|
+
|
|
938
|
+
RecToolsCP = _instantiate_iterative_recon_class(
|
|
939
|
+
data,
|
|
940
|
+
angles,
|
|
941
|
+
center,
|
|
942
|
+
detector_pad,
|
|
943
|
+
recon_size,
|
|
944
|
+
subsets_number,
|
|
945
|
+
gpu_id,
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
_data_ = {
|
|
949
|
+
"projection_data": data,
|
|
950
|
+
"data_axes_labels_order": input_data_axis_labels,
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
_algorithm_ = {
|
|
954
|
+
"iterations": iterations,
|
|
955
|
+
"nonnegativity": nonnegativity,
|
|
956
|
+
"recon_mask_radius": recon_mask_radius,
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
_regularisation_ = {
|
|
960
|
+
"method": regularisation_type, # Selected regularisation method
|
|
961
|
+
"regul_param": regularisation_parameter, # Regularisation parameter
|
|
962
|
+
"iterations": regularisation_iterations, # The number of regularisation iterations
|
|
963
|
+
"half_precision": regularisation_half_precision, # enabling half-precision calculation
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
reconstruction = RecToolsCP.OSEM(_data_, _algorithm_, _regularisation_)
|
|
967
|
+
cp._default_memory_pool.free_all_blocks()
|
|
968
|
+
return cp.require(cp.swapaxes(reconstruction, 0, 1), requirements="C")
|
|
969
|
+
|
|
970
|
+
|
|
838
971
|
## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ##
|
|
839
972
|
def _instantiate_direct_recon_class(
|
|
840
973
|
data: cp.ndarray | Tuple[int, int, int],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httomolibgpu
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.8
|
|
4
4
|
Summary: Commonly used tomography data processing methods at DLS.
|
|
5
5
|
Author-email: Daniil Kazantsev <daniil.kazantsev@diamond.ac.uk>, Yousef Moazzam <yousef.moazzam@diamond.ac.uk>, Naman Gera <naman.gera@diamond.ac.uk>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -33,6 +33,7 @@ Requires-Dist: imageio; extra == "dev"
|
|
|
33
33
|
Requires-Dist: h5py; extra == "dev"
|
|
34
34
|
Requires-Dist: pre-commit; extra == "dev"
|
|
35
35
|
Requires-Dist: pyfftw; extra == "dev"
|
|
36
|
+
Requires-Dist: tomophantom; extra == "dev"
|
|
36
37
|
Dynamic: license-file
|
|
37
38
|
|
|
38
39
|
HTTomolibGPU is a library of GPU accelerated methods for tomography
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|