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.
Files changed (37) hide show
  1. {httomolibgpu-5.6/httomolibgpu.egg-info → httomolibgpu-5.8}/PKG-INFO +2 -1
  2. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/denoise.py +3 -1
  3. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/morph.py +115 -21
  4. httomolibgpu-5.8/httomolibgpu/misc/sorting.py +54 -0
  5. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/stripe.py +20 -34
  6. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/recon/algorithm.py +142 -9
  7. {httomolibgpu-5.6 → httomolibgpu-5.8/httomolibgpu.egg-info}/PKG-INFO +2 -1
  8. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu.egg-info/SOURCES.txt +1 -0
  9. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu.egg-info/requires.txt +1 -0
  10. {httomolibgpu-5.6 → httomolibgpu-5.8}/pyproject.toml +2 -1
  11. {httomolibgpu-5.6 → httomolibgpu-5.8}/LICENSE +0 -0
  12. {httomolibgpu-5.6 → httomolibgpu-5.8}/MANIFEST.in +0 -0
  13. {httomolibgpu-5.6 → httomolibgpu-5.8}/README.rst +0 -0
  14. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/__init__.py +0 -0
  15. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/__init__.py +0 -0
  16. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/calc_metrics.cu +0 -0
  17. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/center_360_shifts.cu +0 -0
  18. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/generate_mask.cu +0 -0
  19. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/median_kernel.cu +0 -0
  20. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/raven_filter.cu +0 -0
  21. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/remove_nan_inf.cu +0 -0
  22. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cuda_kernels/remove_stripe_fw.cu +0 -0
  23. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/cupywrapper.py +0 -0
  24. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/__init__.py +0 -0
  25. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/corr.py +0 -0
  26. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/rescale.py +0 -0
  27. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/misc/utils.py +0 -0
  28. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/__init__.py +0 -0
  29. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/alignment.py +0 -0
  30. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/normalize.py +0 -0
  31. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/prep/phase.py +0 -0
  32. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/recon/__init__.py +0 -0
  33. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/recon/_phase_cross_correlation.py +0 -0
  34. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu/recon/rotation.py +0 -0
  35. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu.egg-info/dependency_links.txt +0 -0
  36. {httomolibgpu-5.6 → httomolibgpu-5.8}/httomolibgpu.egg-info/top_level.txt +0 -0
  37. {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.6
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(lipschitz_const, [float, int], "lipschitz_const", [], methods_name)
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
- weights = cp.linspace(0, 1.0, overlap, dtype=cp.float32)
101
- out[:, :, -dz + overlap :] = data[:n, :, overlap:]
102
- out[:, :, : dz - overlap] = data[n : 2 * n, :, overlap:][:, :, ::-1]
103
- out[:, :, dz - overlap : dz] = (
104
- weights * data[:n, :, :overlap]
105
- + (weights * data[n : 2 * n, :, :overlap])[:, :, ::-1]
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
- weights = cp.linspace(1.0, 0, overlap, dtype=cp.float32)
109
- out[:, :, : dz - overlap] = data[:n, :, :-overlap]
110
- out[:, :, -dz + overlap :] = data[n : 2 * n, :, :-overlap][:, :, ::-1]
111
- out[:, :, dz - overlap : dz] = (
112
- weights * data[:n, :, -overlap:]
113
- + (weights * data[n : 2 * n, :, -overlap:])[:, :, ::-1]
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
- (overlap // 2, overlap // 2),
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 = cp.transpose(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
- #: Sort each column of the sinogram by its grayscale values
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, (size, 1) if dim == 1 else (size, size))
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=1)
129
+ sino_corrected = cp.take_along_axis(sino_sort, sortvals_reverse, axis=0)
132
130
 
133
- return cp.transpose(sino_corrected)
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, matindex)
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, matindex, drop_ratio=0.1, norm=True):
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
- sinosort = cp.sort(sinogram, axis=0)
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
- # Normalize
931
- if norm is True:
933
+ if normalize:
932
934
  sinogram = sinogram / matfact
933
- sinogram1 = cp.transpose(sinogram)
934
- matcombine = cp.asarray(cp.dstack((matindex, sinogram1)))
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, matindex, norm=True):
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
- ["none", "ram-lak", "shepp-logan", "tukey", "gaussian", "blackman", "kaiser"],
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), 'PWLS' (Penalised Weightes LS).
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 a very minimal sacrifice in quality.
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: str = "PD_TV",
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), 'PWLS' (Penalised Weightes LS).
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: Relaxation parameter which can lead to acceleration of the algorithm, keep it in the range between 1.5 and 1.8 to avoid divergence. regularisation_type: str
690
- A method to use for regularisation. Currently PD_TV and ROF_TV are available.
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 a very minimal sacrifice in quality.
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.6
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
@@ -22,6 +22,7 @@ httomolibgpu/misc/corr.py
22
22
  httomolibgpu/misc/denoise.py
23
23
  httomolibgpu/misc/morph.py
24
24
  httomolibgpu/misc/rescale.py
25
+ httomolibgpu/misc/sorting.py
25
26
  httomolibgpu/misc/utils.py
26
27
  httomolibgpu/prep/__init__.py
27
28
  httomolibgpu/prep/alignment.py
@@ -20,3 +20,4 @@ imageio
20
20
  h5py
21
21
  pre-commit
22
22
  pyfftw
23
+ tomophantom
@@ -60,7 +60,8 @@ dev = [
60
60
  "imageio",
61
61
  "h5py",
62
62
  "pre-commit",
63
- "pyfftw"
63
+ "pyfftw",
64
+ "tomophantom"
64
65
  ]
65
66
 
66
67
 
File without changes
File without changes
File without changes
File without changes