diffct 1.2.3__tar.gz → 1.2.5__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.
- diffct-1.2.5/.github/workflows/release.yml +20 -0
- {diffct-1.2.3 → diffct-1.2.5}/PKG-INFO +1 -1
- {diffct-1.2.3 → diffct-1.2.5}/diffct/differentiable.py +85 -110
- {diffct-1.2.3 → diffct-1.2.5}/examples/fbp_fan.py +3 -2
- {diffct-1.2.3 → diffct-1.2.5}/examples/fbp_parallel.py +3 -2
- {diffct-1.2.3 → diffct-1.2.5}/examples/fdk_cone.py +3 -2
- {diffct-1.2.3 → diffct-1.2.5}/pyproject.toml +1 -1
- {diffct-1.2.3 → diffct-1.2.5}/.github/workflows/docs.yml +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/.gitignore +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/LICENSE +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/README.md +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/diffct/__init__.py +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/Makefile +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/_static/.gitkeep +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/api.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/conf.py +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/examples.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/fbp_fan_example.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/fbp_parallel_example.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/fdk_cone_example.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/getting_started.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/index.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/iterative_reco_cone_example.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/iterative_reco_fan_example.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/docs/source/iterative_reco_parallel_example.rst +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/examples/iterative_reco_cone.py +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/examples/iterative_reco_fan.py +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/examples/iterative_reco_parallel.py +0 -0
- {diffct-1.2.3 → diffct-1.2.5}/requirements.txt +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Create Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
name: Create Release
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout code
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
- name: Create Release
|
|
18
|
+
uses: softprops/action-gh-release@v2
|
|
19
|
+
with:
|
|
20
|
+
generate_release_notes: true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffct
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.5
|
|
4
4
|
Summary: A CUDA-based library for computed tomography (CT) projection and reconstruction with differentiable operators
|
|
5
5
|
Project-URL: Homepage, https://github.com/sypsyp97/diffct
|
|
6
6
|
Author-email: Yipeng Sun <yipeng.sun@fau.de>
|
|
@@ -19,6 +19,8 @@ _TPB_3D = (8, 8, 8)
|
|
|
19
19
|
# Trades numerical precision for performance in ray-tracing calculations
|
|
20
20
|
# Safe for CT reconstruction where slight precision loss is acceptable for speed gains
|
|
21
21
|
_FASTMATH_DECORATOR = cuda.jit(cache=True, fastmath=True)
|
|
22
|
+
# Disable fastmath for backward kernels to ensure gradient correctness
|
|
23
|
+
_NON_FASTMATH_DECORATOR = cuda.jit(cache=True, fastmath=False)
|
|
22
24
|
_INF = _DTYPE(np.inf)
|
|
23
25
|
_EPSILON = _DTYPE(1e-6)
|
|
24
26
|
# === Device Management Utilities ===
|
|
@@ -106,36 +108,9 @@ class TorchCUDABridge:
|
|
|
106
108
|
raise ValueError("Tensor must be on CUDA device")
|
|
107
109
|
return cuda.as_cuda_array(tensor.detach())
|
|
108
110
|
|
|
109
|
-
@staticmethod
|
|
110
|
-
def cuda_array_to_tensor(cuda_array, tensor_template):
|
|
111
|
-
"""Convert a Numba CUDA array to a PyTorch tensor.
|
|
112
|
-
|
|
113
|
-
Wrap a Numba CUDA DeviceNDArray as a PyTorch tensor with matching device
|
|
114
|
-
and dtype from a template tensor, sharing underlying memory.
|
|
115
|
-
|
|
116
|
-
Parameters
|
|
117
|
-
----------
|
|
118
|
-
cuda_array : numba.cuda.cudadrv.devicearray.DeviceNDArray
|
|
119
|
-
Numba CUDA array to wrap.
|
|
120
|
-
tensor_template : torch.Tensor
|
|
121
|
-
Template tensor specifying device and dtype.
|
|
122
|
-
|
|
123
|
-
Returns
|
|
124
|
-
-------
|
|
125
|
-
torch.Tensor
|
|
126
|
-
PyTorch tensor sharing data with the CUDA array on the template's
|
|
127
|
-
device and dtype.
|
|
128
|
-
|
|
129
|
-
Examples
|
|
130
|
-
--------
|
|
131
|
-
>>> arr = cuda.device_array((10,), dtype=np.float32)
|
|
132
|
-
>>> t = torch.zeros(10, device='cuda')
|
|
133
|
-
>>> new_t = TorchCUDABridge.cuda_array_to_tensor(arr, t)
|
|
134
|
-
"""
|
|
135
|
-
return torch.as_tensor(cuda_array, device=tensor_template.device, dtype=tensor_template.dtype)
|
|
136
111
|
|
|
137
112
|
# === GPU-aware Trigonometric Table Generation ===
|
|
138
|
-
def _trig_tables(angles, dtype=_DTYPE):
|
|
113
|
+
def _trig_tables(angles, dtype=_DTYPE, device=None):
|
|
139
114
|
"""Compute cosine and sine tables for input angles.
|
|
140
115
|
|
|
141
116
|
Precompute cosine and sine values and return as torch tensors on the
|
|
@@ -163,7 +138,7 @@ def _trig_tables(angles, dtype=_DTYPE):
|
|
|
163
138
|
device(type='cuda', index=0)
|
|
164
139
|
"""
|
|
165
140
|
if isinstance(angles, torch.Tensor):
|
|
166
|
-
device = angles.device
|
|
141
|
+
device = angles.device if device is None else device
|
|
167
142
|
cos = torch.cos(angles).to(dtype=dtype)
|
|
168
143
|
sin = torch.sin(angles).to(dtype=dtype)
|
|
169
144
|
return cos.to(device), sin.to(device)
|
|
@@ -182,7 +157,10 @@ def _trig_tables(angles, dtype=_DTYPE):
|
|
|
182
157
|
angles_cpu = torch.tensor(angles, dtype=torch_dtype)
|
|
183
158
|
cos_cpu = torch.cos(angles_cpu)
|
|
184
159
|
sin_cpu = torch.sin(angles_cpu)
|
|
185
|
-
|
|
160
|
+
if device is not None:
|
|
161
|
+
return cos_cpu.to(device), sin_cpu.to(device)
|
|
162
|
+
else:
|
|
163
|
+
return cos_cpu, sin_cpu
|
|
186
164
|
|
|
187
165
|
|
|
188
166
|
# ############################################################################
|
|
@@ -191,14 +169,14 @@ def _trig_tables(angles, dtype=_DTYPE):
|
|
|
191
169
|
|
|
192
170
|
def _validate_3d_memory_layout(tensor, expected_order='DHW'):
|
|
193
171
|
"""Validate 3D tensor memory layout to prevent coordinate system inconsistencies.
|
|
194
|
-
|
|
172
|
+
|
|
195
173
|
Parameters
|
|
196
174
|
----------
|
|
197
175
|
tensor : torch.Tensor
|
|
198
176
|
3D tensor to validate
|
|
199
177
|
expected_order : str, optional
|
|
200
178
|
Expected memory order ('DHW', 'VHW', etc.). Default is 'DHW'.
|
|
201
|
-
|
|
179
|
+
|
|
202
180
|
Raises
|
|
203
181
|
------
|
|
204
182
|
ValueError
|
|
@@ -206,53 +184,50 @@ def _validate_3d_memory_layout(tensor, expected_order='DHW'):
|
|
|
206
184
|
"""
|
|
207
185
|
if len(tensor.shape) != 3:
|
|
208
186
|
raise ValueError(f"Expected 3D tensor, got {len(tensor.shape)}D")
|
|
209
|
-
|
|
187
|
+
|
|
210
188
|
# Check if tensor is contiguous to avoid memory duplication
|
|
211
189
|
if not tensor.is_contiguous():
|
|
212
190
|
raise ValueError(
|
|
213
191
|
"Input tensor must be contiguous. Call .contiguous() before passing to "
|
|
214
192
|
"cone beam functions to avoid memory duplication and ensure correct results."
|
|
215
193
|
)
|
|
216
|
-
|
|
217
|
-
#
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
f"but tensor has shape {actual_str}. Please {fix_str} and ensure "
|
|
254
|
-
f"the tensor is contiguous (.contiguous()) before passing to the function."
|
|
255
|
-
)
|
|
194
|
+
|
|
195
|
+
# Only check memory order for DHW and VHW, not for internal WHD layout
|
|
196
|
+
if expected_order in ('DHW', 'VHW'):
|
|
197
|
+
strides = tensor.stride()
|
|
198
|
+
order_mapping = {
|
|
199
|
+
'DHW': (0, 1, 2), # Depth, Height, Width
|
|
200
|
+
'VHW': (0, 1, 2), # Views, Height, Width (for sinograms)
|
|
201
|
+
}
|
|
202
|
+
if expected_order not in order_mapping:
|
|
203
|
+
raise ValueError(f"Unsupported expected_order: {expected_order}")
|
|
204
|
+
|
|
205
|
+
expected_stride_order = order_mapping[expected_order]
|
|
206
|
+
# Check if actual strides match expected order
|
|
207
|
+
sorted_strides = sorted(enumerate(strides), key=lambda x: x[1], reverse=True)
|
|
208
|
+
actual_order = tuple(idx for idx, _ in sorted_strides)
|
|
209
|
+
|
|
210
|
+
if actual_order != expected_stride_order:
|
|
211
|
+
# Create appropriate error message based on context
|
|
212
|
+
if expected_order == 'VHW':
|
|
213
|
+
actual_str = f"({tensor.shape[0]}, {tensor.shape[1]}, {tensor.shape[2]})"
|
|
214
|
+
expected_str = "(Views, Height, Width)"
|
|
215
|
+
fix_str = "ensure your sinogram has shape (num_views, det_v, det_u)"
|
|
216
|
+
elif expected_order == 'DHW':
|
|
217
|
+
actual_str = f"({tensor.shape[0]}, {tensor.shape[1]}, {tensor.shape[2]})"
|
|
218
|
+
expected_str = "(Depth, Height, Width)"
|
|
219
|
+
fix_str = "ensure your volume has shape (D, H, W)"
|
|
220
|
+
else:
|
|
221
|
+
actual_str = str(tuple(tensor.shape))
|
|
222
|
+
expected_str = expected_order
|
|
223
|
+
fix_str = "check tensor dimensions"
|
|
224
|
+
|
|
225
|
+
raise ValueError(
|
|
226
|
+
f"Memory layout mismatch: expected {expected_str} order, "
|
|
227
|
+
f"but tensor has shape {actual_str}. Please {fix_str} and ensure "
|
|
228
|
+
f"the tensor is contiguous (.contiguous()) before passing to the function."
|
|
229
|
+
)
|
|
230
|
+
# For 'WHD' (internal layout), skip stride check entirely
|
|
256
231
|
|
|
257
232
|
|
|
258
233
|
def _grid_2d(n1, n2, tpb=_TPB_2D):
|
|
@@ -392,7 +367,7 @@ def _parallel_2d_forward_kernel(
|
|
|
392
367
|
cos_a = d_cos[iang] # Precomputed cosine of projection angle
|
|
393
368
|
sin_a = d_sin[iang] # Precomputed sine of projection angle
|
|
394
369
|
# Normalize all physical distances to voxel units
|
|
395
|
-
u = (idet -
|
|
370
|
+
u = (idet - n_det * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
|
|
396
371
|
|
|
397
372
|
# Define ray direction and starting point for parallel beam geometry
|
|
398
373
|
# Ray direction is perpendicular to detector array (cos_a, sin_a)
|
|
@@ -471,8 +446,8 @@ def _parallel_2d_forward_kernel(
|
|
|
471
446
|
dx, dy = mid_x - ix0, mid_y - iy0 # Fractional parts: distance from base voxel center [0,1]
|
|
472
447
|
|
|
473
448
|
# Clamp indices to stay in-bounds during interpolation
|
|
474
|
-
ix0 = min(ix0, Nx - 2)
|
|
475
|
-
iy0 = min(iy0, Ny - 2)
|
|
449
|
+
ix0 = max(0, min(ix0, Nx - 2))
|
|
450
|
+
iy0 = max(0, min(iy0, Ny - 2))
|
|
476
451
|
|
|
477
452
|
# === BILINEAR INTERPOLATION WEIGHT CALCULATION ===
|
|
478
453
|
# Mathematical basis: Bilinear interpolation formula f(x,y) = Σ f(xi,yi) * wi(x,y)
|
|
@@ -501,7 +476,7 @@ def _parallel_2d_forward_kernel(
|
|
|
501
476
|
|
|
502
477
|
d_sino[iang, idet] = accum
|
|
503
478
|
|
|
504
|
-
@
|
|
479
|
+
@_NON_FASTMATH_DECORATOR
|
|
505
480
|
def _parallel_2d_backward_kernel(
|
|
506
481
|
d_sino, n_ang, n_det,
|
|
507
482
|
d_image, Nx, Ny,
|
|
@@ -554,7 +529,7 @@ def _parallel_2d_backward_kernel(
|
|
|
554
529
|
cos_a = d_cos[iang] # Precomputed cosine of projection angle
|
|
555
530
|
sin_a = d_sin[iang] # Precomputed sine of projection angle
|
|
556
531
|
# Normalize all physical distances to voxel units
|
|
557
|
-
u = (idet -
|
|
532
|
+
u = (idet - n_det * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
|
|
558
533
|
|
|
559
534
|
# Define ray direction and starting point for parallel beam geometry
|
|
560
535
|
dir_x, dir_y = cos_a, sin_a
|
|
@@ -599,8 +574,8 @@ def _parallel_2d_backward_kernel(
|
|
|
599
574
|
dx, dy = mid_x - ix0, mid_y - iy0
|
|
600
575
|
|
|
601
576
|
# Clamp indices to stay in-bounds during interpolation
|
|
602
|
-
ix0 = min(ix0, Nx - 2)
|
|
603
|
-
iy0 = min(iy0, Ny - 2)
|
|
577
|
+
ix0 = max(0, min(ix0, Nx - 2))
|
|
578
|
+
iy0 = max(0, min(iy0, Ny - 2))
|
|
604
579
|
|
|
605
580
|
# === ATOMIC BACKPROJECTION WITH BILINEAR WEIGHTS ===
|
|
606
581
|
# Distribute contribution weighted by segment length and interpolation weights
|
|
@@ -686,7 +661,7 @@ def _fan_2d_forward_kernel(
|
|
|
686
661
|
cos_a = d_cos[iang] # Precomputed cosine of projection angle
|
|
687
662
|
sin_a = d_sin[iang] # Precomputed sine of projection angle
|
|
688
663
|
# Normalize all physical distances to voxel units
|
|
689
|
-
u = (idet -
|
|
664
|
+
u = (idet - n_det * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
|
|
690
665
|
sid_v = sid / voxel_spacing # Source-to-isocenter distance in voxel units
|
|
691
666
|
sdd_v = sdd / voxel_spacing # Source-to-detector distance in voxel units
|
|
692
667
|
|
|
@@ -757,8 +732,8 @@ def _fan_2d_forward_kernel(
|
|
|
757
732
|
dx, dy = mid_x - ix0, mid_y - iy0
|
|
758
733
|
|
|
759
734
|
# Clamp indices to stay in-bounds during interpolation
|
|
760
|
-
ix0 = min(ix0, Nx - 2)
|
|
761
|
-
iy0 = min(iy0, Ny - 2)
|
|
735
|
+
ix0 = max(0, min(ix0, Nx - 2))
|
|
736
|
+
iy0 = max(0, min(iy0, Ny - 2))
|
|
762
737
|
|
|
763
738
|
# Bilinear interpolation (identical to parallel beam)
|
|
764
739
|
val = (
|
|
@@ -781,7 +756,7 @@ def _fan_2d_forward_kernel(
|
|
|
781
756
|
|
|
782
757
|
d_sino[iang, idet] = accum
|
|
783
758
|
|
|
784
|
-
@
|
|
759
|
+
@_NON_FASTMATH_DECORATOR
|
|
785
760
|
def _fan_2d_backward_kernel(
|
|
786
761
|
d_sino, n_ang, n_det,
|
|
787
762
|
d_image, Nx, Ny,
|
|
@@ -839,7 +814,7 @@ def _fan_2d_backward_kernel(
|
|
|
839
814
|
cos_a = d_cos[iang] # Precomputed cosine of projection angle
|
|
840
815
|
sin_a = d_sin[iang] # Precomputed sine of projection angle
|
|
841
816
|
# Normalize all physical distances to voxel units
|
|
842
|
-
u = (idet -
|
|
817
|
+
u = (idet - n_det * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
|
|
843
818
|
sid_v = sid / voxel_spacing # Source-to-isocenter distance in voxel units
|
|
844
819
|
sdd_v = sdd / voxel_spacing # Source-to-detector distance in voxel units
|
|
845
820
|
|
|
@@ -901,8 +876,8 @@ def _fan_2d_backward_kernel(
|
|
|
901
876
|
dx, dy = mid_x - ix0, mid_y - iy0
|
|
902
877
|
|
|
903
878
|
# Clamp indices to stay in-bounds during interpolation
|
|
904
|
-
ix0 = min(ix0, Nx - 2)
|
|
905
|
-
iy0 = min(iy0, Ny - 2)
|
|
879
|
+
ix0 = max(0, min(ix0, Nx - 2))
|
|
880
|
+
iy0 = max(0, min(iy0, Ny - 2))
|
|
906
881
|
|
|
907
882
|
# === ATOMIC BACKPROJECTION WITH BILINEAR WEIGHTS ===
|
|
908
883
|
# Distribute contribution weighted by segment length and interpolation weights
|
|
@@ -995,8 +970,8 @@ def _cone_3d_forward_kernel(
|
|
|
995
970
|
# === 3D CONE BEAM GEOMETRY SETUP ===
|
|
996
971
|
cos_a, sin_a = d_cos[iview], d_sin[iview] # Projection angle trigonometry
|
|
997
972
|
# Normalize all physical distances to voxel units
|
|
998
|
-
u = (iu -
|
|
999
|
-
v = (iv -
|
|
973
|
+
u = (iu - n_u * 0.5) * du / voxel_spacing # Detector u-coordinate in voxel units
|
|
974
|
+
v = (iv - n_v * 0.5) * dv / voxel_spacing # Detector v-coordinate in voxel units
|
|
1000
975
|
sid_v = sid / voxel_spacing # Source-to-isocenter distance in voxel units
|
|
1001
976
|
sdd_v = sdd / voxel_spacing # Source-to-detector distance in voxel units
|
|
1002
977
|
|
|
@@ -1091,9 +1066,9 @@ def _cone_3d_forward_kernel(
|
|
|
1091
1066
|
dx, dy, dz = mid_x - ix0, mid_y - iy0, mid_z - iz0 # Fractional parts: distance from base voxel center [0,1]
|
|
1092
1067
|
|
|
1093
1068
|
# Clamp indices to stay in-bounds during interpolation
|
|
1094
|
-
ix0 = min(ix0, Nx - 2)
|
|
1095
|
-
iy0 = min(iy0, Ny - 2)
|
|
1096
|
-
iz0 = min(iz0, Nz - 2)
|
|
1069
|
+
ix0 = max(0, min(ix0, Nx - 2))
|
|
1070
|
+
iy0 = max(0, min(iy0, Ny - 2))
|
|
1071
|
+
iz0 = max(0, min(iz0, Nz - 2))
|
|
1097
1072
|
|
|
1098
1073
|
# === TRILINEAR INTERPOLATION WEIGHT CALCULATION ===
|
|
1099
1074
|
# Mathematical basis: Trilinear interpolation formula f(x,y,z) = Σ f(xi,yi,zi) * wi(x,y,z)
|
|
@@ -1131,7 +1106,7 @@ def _cone_3d_forward_kernel(
|
|
|
1131
1106
|
|
|
1132
1107
|
d_sino[iview, iu, iv] = accum
|
|
1133
1108
|
|
|
1134
|
-
@
|
|
1109
|
+
@_NON_FASTMATH_DECORATOR
|
|
1135
1110
|
def _cone_3d_backward_kernel(
|
|
1136
1111
|
d_sino, n_views, n_u, n_v,
|
|
1137
1112
|
d_vol, Nx, Ny, Nz,
|
|
@@ -1196,8 +1171,8 @@ def _cone_3d_backward_kernel(
|
|
|
1196
1171
|
g = d_sino[iview, iu, iv] # Sinogram value to backproject along this ray
|
|
1197
1172
|
cos_a, sin_a = d_cos[iview], d_sin[iview] # Projection angle trigonometry
|
|
1198
1173
|
# Normalize all physical distances to voxel units
|
|
1199
|
-
u = (iu -
|
|
1200
|
-
v = (iv -
|
|
1174
|
+
u = (iu - n_u * 0.5) * du / voxel_spacing # Detector u-coordinate in voxel units
|
|
1175
|
+
v = (iv - n_v * 0.5) * dv / voxel_spacing # Detector v-coordinate in voxel units
|
|
1201
1176
|
sid_v = sid / voxel_spacing # Source-to-isocenter distance in voxel units
|
|
1202
1177
|
sdd_v = sdd / voxel_spacing # Source-to-detector distance in voxel units
|
|
1203
1178
|
|
|
@@ -1281,9 +1256,9 @@ def _cone_3d_backward_kernel(
|
|
|
1281
1256
|
dx, dy, dz = mid_x - ix0, mid_y - iy0, mid_z - iz0 # Fractional parts for 3D weights
|
|
1282
1257
|
|
|
1283
1258
|
# Clamp indices to stay in-bounds during interpolation
|
|
1284
|
-
ix0 = min(ix0, Nx - 2)
|
|
1285
|
-
iy0 = min(iy0, Ny - 2)
|
|
1286
|
-
iz0 = min(iz0, Nz - 2)
|
|
1259
|
+
ix0 = max(0, min(ix0, Nx - 2))
|
|
1260
|
+
iy0 = max(0, min(iy0, Ny - 2))
|
|
1261
|
+
iz0 = max(0, min(iz0, Nz - 2))
|
|
1287
1262
|
|
|
1288
1263
|
# === ATOMIC BACKPROJECTION WITH TRILINEAR WEIGHTS ===
|
|
1289
1264
|
# Distribute contribution weighted by segment length and interpolation weights
|
|
@@ -1408,7 +1383,7 @@ class ParallelProjectorFunction(torch.autograd.Function):
|
|
|
1408
1383
|
sinogram = torch.zeros((n_angles, num_detectors), dtype=image.dtype, device=device)
|
|
1409
1384
|
|
|
1410
1385
|
# Prepare trigonometric tables on the correct device
|
|
1411
|
-
d_cos, d_sin = _trig_tables(angles, dtype=image.dtype)
|
|
1386
|
+
d_cos, d_sin = _trig_tables(angles, dtype=image.dtype, device=device)
|
|
1412
1387
|
|
|
1413
1388
|
# Get Numba CUDA array views for kernel
|
|
1414
1389
|
d_image = TorchCUDABridge.tensor_to_cuda_array(image)
|
|
@@ -1441,7 +1416,7 @@ class ParallelProjectorFunction(torch.autograd.Function):
|
|
|
1441
1416
|
|
|
1442
1417
|
n_angles = angles.shape[0]
|
|
1443
1418
|
grad_image = torch.zeros((Ny, Nx), dtype=grad_sinogram.dtype, device=device)
|
|
1444
|
-
d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype)
|
|
1419
|
+
d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype, device=device)
|
|
1445
1420
|
|
|
1446
1421
|
d_grad_sino = TorchCUDABridge.tensor_to_cuda_array(grad_sinogram)
|
|
1447
1422
|
d_img_grad = TorchCUDABridge.tensor_to_cuda_array(grad_image)
|
|
@@ -1541,7 +1516,7 @@ class ParallelBackprojectorFunction(torch.autograd.Function):
|
|
|
1541
1516
|
reco = torch.zeros((Ny, Nx), dtype=sinogram.dtype, device=device)
|
|
1542
1517
|
|
|
1543
1518
|
# Prepare trigonometric tables on the correct device
|
|
1544
|
-
d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype)
|
|
1519
|
+
d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype, device=device)
|
|
1545
1520
|
|
|
1546
1521
|
# Get Numba CUDA array views for kernel
|
|
1547
1522
|
d_sino = TorchCUDABridge.tensor_to_cuda_array(sinogram)
|
|
@@ -1578,7 +1553,7 @@ class ParallelBackprojectorFunction(torch.autograd.Function):
|
|
|
1578
1553
|
grad_sino = torch.zeros((n_ang, n_det), dtype=grad_output.dtype, device=device)
|
|
1579
1554
|
|
|
1580
1555
|
# Prepare trigonometric tables on the correct device
|
|
1581
|
-
d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype)
|
|
1556
|
+
d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype, device=device)
|
|
1582
1557
|
|
|
1583
1558
|
# Get Numba CUDA array views for kernel
|
|
1584
1559
|
d_grad_out = TorchCUDABridge.tensor_to_cuda_array(grad_output)
|
|
@@ -1679,7 +1654,7 @@ class FanProjectorFunction(torch.autograd.Function):
|
|
|
1679
1654
|
n_ang = angles.shape[0]
|
|
1680
1655
|
|
|
1681
1656
|
sinogram = torch.zeros((n_ang, num_detectors), dtype=image.dtype, device=device)
|
|
1682
|
-
d_cos, d_sin = _trig_tables(angles, dtype=image.dtype)
|
|
1657
|
+
d_cos, d_sin = _trig_tables(angles, dtype=image.dtype, device=device)
|
|
1683
1658
|
|
|
1684
1659
|
d_image = TorchCUDABridge.tensor_to_cuda_array(image)
|
|
1685
1660
|
d_sino = TorchCUDABridge.tensor_to_cuda_array(sinogram)
|
|
@@ -1713,7 +1688,7 @@ class FanProjectorFunction(torch.autograd.Function):
|
|
|
1713
1688
|
|
|
1714
1689
|
n_ang = angles.shape[0]
|
|
1715
1690
|
grad_img = torch.zeros((Ny, Nx), dtype=grad_sinogram.dtype, device=device)
|
|
1716
|
-
d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype)
|
|
1691
|
+
d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype, device=device)
|
|
1717
1692
|
|
|
1718
1693
|
d_grad_sino = TorchCUDABridge.tensor_to_cuda_array(grad_sinogram)
|
|
1719
1694
|
d_img_grad = TorchCUDABridge.tensor_to_cuda_array(grad_img)
|
|
@@ -1817,7 +1792,7 @@ class FanBackprojectorFunction(torch.autograd.Function):
|
|
|
1817
1792
|
Ny, Nx = H, W
|
|
1818
1793
|
|
|
1819
1794
|
reco = torch.zeros((Ny, Nx), dtype=sinogram.dtype, device=device)
|
|
1820
|
-
d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype)
|
|
1795
|
+
d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype, device=device)
|
|
1821
1796
|
|
|
1822
1797
|
d_sino = TorchCUDABridge.tensor_to_cuda_array(sinogram)
|
|
1823
1798
|
d_reco = TorchCUDABridge.tensor_to_cuda_array(reco)
|
|
@@ -1851,7 +1826,7 @@ class FanBackprojectorFunction(torch.autograd.Function):
|
|
|
1851
1826
|
Ny, Nx = grad_output.shape
|
|
1852
1827
|
|
|
1853
1828
|
grad_sino = torch.zeros((n_ang, n_det), dtype=grad_output.dtype, device=device)
|
|
1854
|
-
d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype)
|
|
1829
|
+
d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype, device=device)
|
|
1855
1830
|
|
|
1856
1831
|
d_grad_out = TorchCUDABridge.tensor_to_cuda_array(grad_output)
|
|
1857
1832
|
d_sino_grad = TorchCUDABridge.tensor_to_cuda_array(grad_sino)
|
|
@@ -1959,7 +1934,7 @@ class ConeProjectorFunction(torch.autograd.Function):
|
|
|
1959
1934
|
_validate_3d_memory_layout(volume, expected_order='DHW')
|
|
1960
1935
|
|
|
1961
1936
|
sino = torch.zeros((n_views, det_u, det_v), dtype=volume.dtype, device=device)
|
|
1962
|
-
d_cos, d_sin = _trig_tables(angles, dtype=volume.dtype)
|
|
1937
|
+
d_cos, d_sin = _trig_tables(angles, dtype=volume.dtype, device=device)
|
|
1963
1938
|
|
|
1964
1939
|
volume_perm = volume.permute(2, 1, 0).contiguous()
|
|
1965
1940
|
d_vol = TorchCUDABridge.tensor_to_cuda_array(volume_perm)
|
|
@@ -1997,7 +1972,7 @@ class ConeProjectorFunction(torch.autograd.Function):
|
|
|
1997
1972
|
n_views = angles.shape[0]
|
|
1998
1973
|
|
|
1999
1974
|
grad_vol_perm = torch.zeros((W, H, D), dtype=grad_sinogram.dtype, device=device)
|
|
2000
|
-
d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype)
|
|
1975
|
+
d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype, device=device)
|
|
2001
1976
|
|
|
2002
1977
|
d_grad_sino = TorchCUDABridge.tensor_to_cuda_array(grad_sinogram)
|
|
2003
1978
|
d_vol_grad = TorchCUDABridge.tensor_to_cuda_array(grad_vol_perm)
|
|
@@ -2116,7 +2091,7 @@ class ConeBackprojectorFunction(torch.autograd.Function):
|
|
|
2116
2091
|
_validate_3d_memory_layout(sinogram, expected_order='VHW')
|
|
2117
2092
|
|
|
2118
2093
|
vol_perm = torch.zeros((W, H, D), dtype=sinogram.dtype, device=device)
|
|
2119
|
-
d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype)
|
|
2094
|
+
d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype, device=device)
|
|
2120
2095
|
|
|
2121
2096
|
d_sino = TorchCUDABridge.tensor_to_cuda_array(sinogram)
|
|
2122
2097
|
d_reco = TorchCUDABridge.tensor_to_cuda_array(vol_perm)
|
|
@@ -2153,7 +2128,7 @@ class ConeBackprojectorFunction(torch.autograd.Function):
|
|
|
2153
2128
|
n_views = angles.shape[0]
|
|
2154
2129
|
|
|
2155
2130
|
grad_sino = torch.zeros((n_views, n_u, n_v), dtype=grad_output.dtype, device=device)
|
|
2156
|
-
d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype)
|
|
2131
|
+
d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype, device=device)
|
|
2157
2132
|
|
|
2158
2133
|
grad_output_perm = grad_output.permute(2, 1, 0).contiguous()
|
|
2159
2134
|
d_grad_out = TorchCUDABridge.tensor_to_cuda_array(grad_output_perm)
|
|
@@ -2,6 +2,7 @@ import math
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import torch
|
|
4
4
|
import matplotlib.pyplot as plt
|
|
5
|
+
import torch.nn.functional as F
|
|
5
6
|
from diffct.differentiable import FanProjectorFunction, FanBackprojectorFunction
|
|
6
7
|
|
|
7
8
|
|
|
@@ -76,9 +77,9 @@ def main():
|
|
|
76
77
|
sino_weighted = sinogram * weights
|
|
77
78
|
sinogram_filt = ramp_filter(sino_weighted)
|
|
78
79
|
|
|
79
|
-
reconstruction = FanBackprojectorFunction.apply(sinogram_filt, angles_torch,
|
|
80
|
+
reconstruction = F.relu(FanBackprojectorFunction.apply(sinogram_filt, angles_torch,
|
|
80
81
|
detector_spacing, Ny, Nx,
|
|
81
|
-
sdd, sid, voxel_spacing)
|
|
82
|
+
sdd, sid, voxel_spacing)) # ReLU to ensure non-negativity
|
|
82
83
|
|
|
83
84
|
# --- FBP normalization ---
|
|
84
85
|
# The backprojection is a sum over all angles. To approximate the integral,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import torch
|
|
3
3
|
import matplotlib.pyplot as plt
|
|
4
|
+
import torch.nn.functional as F
|
|
4
5
|
from diffct.differentiable import ParallelProjectorFunction, ParallelBackprojectorFunction
|
|
5
6
|
|
|
6
7
|
|
|
@@ -64,8 +65,8 @@ def main():
|
|
|
64
65
|
|
|
65
66
|
sinogram_filt = ramp_filter(sinogram)
|
|
66
67
|
|
|
67
|
-
reconstruction = ParallelBackprojectorFunction.apply(sinogram_filt, angles_torch,
|
|
68
|
-
detector_spacing, Ny, Nx, voxel_spacing)
|
|
68
|
+
reconstruction = F.relu(ParallelBackprojectorFunction.apply(sinogram_filt, angles_torch,
|
|
69
|
+
detector_spacing, Ny, Nx, voxel_spacing)) # ReLU to ensure non-negativity
|
|
69
70
|
|
|
70
71
|
# --- FBP normalization ---
|
|
71
72
|
# The backprojection is a sum over all angles. To approximate the integral,
|
|
@@ -2,6 +2,7 @@ import math
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import torch
|
|
4
4
|
import matplotlib.pyplot as plt
|
|
5
|
+
import torch.nn.functional as F
|
|
5
6
|
from diffct.differentiable import ConeProjectorFunction, ConeBackprojectorFunction
|
|
6
7
|
|
|
7
8
|
|
|
@@ -110,8 +111,8 @@ def main():
|
|
|
110
111
|
sino_weighted = sinogram * weights
|
|
111
112
|
sinogram_filt = ramp_filter_3d(sino_weighted).contiguous()
|
|
112
113
|
|
|
113
|
-
reconstruction = ConeBackprojectorFunction.apply(sinogram_filt, angles_torch, Nz, Ny, Nx,
|
|
114
|
-
du, dv, sdd, sid, voxel_spacing)
|
|
114
|
+
reconstruction = F.relu(ConeBackprojectorFunction.apply(sinogram_filt, angles_torch, Nz, Ny, Nx,
|
|
115
|
+
du, dv, sdd, sid, voxel_spacing)) # ReLU to ensure non-negativity
|
|
115
116
|
|
|
116
117
|
# --- FDK normalization ---
|
|
117
118
|
# The backprojection is a sum over all angles. To approximate the integral,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "diffct"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.5"
|
|
8
8
|
description = "A CUDA-based library for computed tomography (CT) projection and reconstruction with differentiable operators"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
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
|