diffct 1.2.3__tar.gz → 1.2.4__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 (28) hide show
  1. {diffct-1.2.3 → diffct-1.2.4}/PKG-INFO +1 -1
  2. {diffct-1.2.3 → diffct-1.2.4}/diffct/differentiable.py +80 -80
  3. {diffct-1.2.3 → diffct-1.2.4}/examples/fbp_fan.py +3 -2
  4. {diffct-1.2.3 → diffct-1.2.4}/examples/fbp_parallel.py +3 -2
  5. {diffct-1.2.3 → diffct-1.2.4}/examples/fdk_cone.py +3 -2
  6. {diffct-1.2.3 → diffct-1.2.4}/pyproject.toml +1 -1
  7. {diffct-1.2.3 → diffct-1.2.4}/.github/workflows/docs.yml +0 -0
  8. {diffct-1.2.3 → diffct-1.2.4}/.gitignore +0 -0
  9. {diffct-1.2.3 → diffct-1.2.4}/LICENSE +0 -0
  10. {diffct-1.2.3 → diffct-1.2.4}/README.md +0 -0
  11. {diffct-1.2.3 → diffct-1.2.4}/diffct/__init__.py +0 -0
  12. {diffct-1.2.3 → diffct-1.2.4}/docs/Makefile +0 -0
  13. {diffct-1.2.3 → diffct-1.2.4}/docs/source/_static/.gitkeep +0 -0
  14. {diffct-1.2.3 → diffct-1.2.4}/docs/source/api.rst +0 -0
  15. {diffct-1.2.3 → diffct-1.2.4}/docs/source/conf.py +0 -0
  16. {diffct-1.2.3 → diffct-1.2.4}/docs/source/examples.rst +0 -0
  17. {diffct-1.2.3 → diffct-1.2.4}/docs/source/fbp_fan_example.rst +0 -0
  18. {diffct-1.2.3 → diffct-1.2.4}/docs/source/fbp_parallel_example.rst +0 -0
  19. {diffct-1.2.3 → diffct-1.2.4}/docs/source/fdk_cone_example.rst +0 -0
  20. {diffct-1.2.3 → diffct-1.2.4}/docs/source/getting_started.rst +0 -0
  21. {diffct-1.2.3 → diffct-1.2.4}/docs/source/index.rst +0 -0
  22. {diffct-1.2.3 → diffct-1.2.4}/docs/source/iterative_reco_cone_example.rst +0 -0
  23. {diffct-1.2.3 → diffct-1.2.4}/docs/source/iterative_reco_fan_example.rst +0 -0
  24. {diffct-1.2.3 → diffct-1.2.4}/docs/source/iterative_reco_parallel_example.rst +0 -0
  25. {diffct-1.2.3 → diffct-1.2.4}/examples/iterative_reco_cone.py +0 -0
  26. {diffct-1.2.3 → diffct-1.2.4}/examples/iterative_reco_fan.py +0 -0
  27. {diffct-1.2.3 → diffct-1.2.4}/examples/iterative_reco_parallel.py +0 -0
  28. {diffct-1.2.3 → diffct-1.2.4}/requirements.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffct
3
- Version: 1.2.3
3
+ Version: 1.2.4
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>
@@ -135,7 +135,7 @@ class TorchCUDABridge:
135
135
  return torch.as_tensor(cuda_array, device=tensor_template.device, dtype=tensor_template.dtype)
136
136
 
137
137
  # === GPU-aware Trigonometric Table Generation ===
138
- def _trig_tables(angles, dtype=_DTYPE):
138
+ def _trig_tables(angles, dtype=_DTYPE, device=None):
139
139
  """Compute cosine and sine tables for input angles.
140
140
 
141
141
  Precompute cosine and sine values and return as torch tensors on the
@@ -163,7 +163,7 @@ def _trig_tables(angles, dtype=_DTYPE):
163
163
  device(type='cuda', index=0)
164
164
  """
165
165
  if isinstance(angles, torch.Tensor):
166
- device = angles.device
166
+ device = angles.device if device is None else device
167
167
  cos = torch.cos(angles).to(dtype=dtype)
168
168
  sin = torch.sin(angles).to(dtype=dtype)
169
169
  return cos.to(device), sin.to(device)
@@ -182,7 +182,10 @@ def _trig_tables(angles, dtype=_DTYPE):
182
182
  angles_cpu = torch.tensor(angles, dtype=torch_dtype)
183
183
  cos_cpu = torch.cos(angles_cpu)
184
184
  sin_cpu = torch.sin(angles_cpu)
185
- return cos_cpu, sin_cpu
185
+ if device is not None:
186
+ return cos_cpu.to(device), sin_cpu.to(device)
187
+ else:
188
+ return cos_cpu, sin_cpu
186
189
 
187
190
 
188
191
  # ############################################################################
@@ -191,14 +194,14 @@ def _trig_tables(angles, dtype=_DTYPE):
191
194
 
192
195
  def _validate_3d_memory_layout(tensor, expected_order='DHW'):
193
196
  """Validate 3D tensor memory layout to prevent coordinate system inconsistencies.
194
-
197
+
195
198
  Parameters
196
199
  ----------
197
200
  tensor : torch.Tensor
198
201
  3D tensor to validate
199
202
  expected_order : str, optional
200
203
  Expected memory order ('DHW', 'VHW', etc.). Default is 'DHW'.
201
-
204
+
202
205
  Raises
203
206
  ------
204
207
  ValueError
@@ -206,53 +209,50 @@ def _validate_3d_memory_layout(tensor, expected_order='DHW'):
206
209
  """
207
210
  if len(tensor.shape) != 3:
208
211
  raise ValueError(f"Expected 3D tensor, got {len(tensor.shape)}D")
209
-
212
+
210
213
  # Check if tensor is contiguous to avoid memory duplication
211
214
  if not tensor.is_contiguous():
212
215
  raise ValueError(
213
216
  "Input tensor must be contiguous. Call .contiguous() before passing to "
214
217
  "cone beam functions to avoid memory duplication and ensure correct results."
215
218
  )
216
-
217
- # Validate expected memory order based on stride patterns
218
- strides = tensor.stride()
219
-
220
- # Map expected orders to stride patterns
221
- order_mapping = {
222
- 'DHW': (0, 1, 2), # Depth, Height, Width
223
- 'VHW': (0, 1, 2), # Views, Height, Width (for sinograms)
224
- 'WHD': (2, 1, 0), # Width, Height, Depth (internal WHD format)
225
- }
226
-
227
- if expected_order not in order_mapping:
228
- raise ValueError(f"Unsupported expected_order: {expected_order}")
229
-
230
- expected_stride_order = order_mapping[expected_order]
231
-
232
- # Check if actual strides match expected order
233
- sorted_strides = sorted(enumerate(strides), key=lambda x: x[1], reverse=True)
234
- actual_order = tuple(idx for idx, _ in sorted_strides)
235
-
236
- if actual_order != expected_stride_order:
237
- # Create appropriate error message based on context
238
- if expected_order == 'VHW':
239
- actual_str = f"({tensor.shape[0]}, {tensor.shape[1]}, {tensor.shape[2]})"
240
- expected_str = "(Views, Height, Width)"
241
- fix_str = "ensure your sinogram has shape (num_views, det_v, det_u)"
242
- elif expected_order == 'DHW':
243
- actual_str = f"({tensor.shape[0]}, {tensor.shape[1]}, {tensor.shape[2]})"
244
- expected_str = "(Depth, Height, Width)"
245
- fix_str = "ensure your volume has shape (D, H, W)"
246
- else:
247
- actual_str = str(tuple(tensor.shape))
248
- expected_str = expected_order
249
- fix_str = "check tensor dimensions"
250
-
251
- raise ValueError(
252
- f"Memory layout mismatch: expected {expected_str} order, "
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
- )
219
+
220
+ # Only check memory order for DHW and VHW, not for internal WHD layout
221
+ if expected_order in ('DHW', 'VHW'):
222
+ strides = tensor.stride()
223
+ order_mapping = {
224
+ 'DHW': (0, 1, 2), # Depth, Height, Width
225
+ 'VHW': (0, 1, 2), # Views, Height, Width (for sinograms)
226
+ }
227
+ if expected_order not in order_mapping:
228
+ raise ValueError(f"Unsupported expected_order: {expected_order}")
229
+
230
+ expected_stride_order = order_mapping[expected_order]
231
+ # Check if actual strides match expected order
232
+ sorted_strides = sorted(enumerate(strides), key=lambda x: x[1], reverse=True)
233
+ actual_order = tuple(idx for idx, _ in sorted_strides)
234
+
235
+ if actual_order != expected_stride_order:
236
+ # Create appropriate error message based on context
237
+ if expected_order == 'VHW':
238
+ actual_str = f"({tensor.shape[0]}, {tensor.shape[1]}, {tensor.shape[2]})"
239
+ expected_str = "(Views, Height, Width)"
240
+ fix_str = "ensure your sinogram has shape (num_views, det_v, det_u)"
241
+ elif expected_order == 'DHW':
242
+ actual_str = f"({tensor.shape[0]}, {tensor.shape[1]}, {tensor.shape[2]})"
243
+ expected_str = "(Depth, Height, Width)"
244
+ fix_str = "ensure your volume has shape (D, H, W)"
245
+ else:
246
+ actual_str = str(tuple(tensor.shape))
247
+ expected_str = expected_order
248
+ fix_str = "check tensor dimensions"
249
+
250
+ raise ValueError(
251
+ f"Memory layout mismatch: expected {expected_str} order, "
252
+ f"but tensor has shape {actual_str}. Please {fix_str} and ensure "
253
+ f"the tensor is contiguous (.contiguous()) before passing to the function."
254
+ )
255
+ # For 'WHD' (internal layout), skip stride check entirely
256
256
 
257
257
 
258
258
  def _grid_2d(n1, n2, tpb=_TPB_2D):
@@ -392,7 +392,7 @@ def _parallel_2d_forward_kernel(
392
392
  cos_a = d_cos[iang] # Precomputed cosine of projection angle
393
393
  sin_a = d_sin[iang] # Precomputed sine of projection angle
394
394
  # Normalize all physical distances to voxel units
395
- u = (idet - (n_det - 1) * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
395
+ u = (idet - n_det * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
396
396
 
397
397
  # Define ray direction and starting point for parallel beam geometry
398
398
  # Ray direction is perpendicular to detector array (cos_a, sin_a)
@@ -471,8 +471,8 @@ def _parallel_2d_forward_kernel(
471
471
  dx, dy = mid_x - ix0, mid_y - iy0 # Fractional parts: distance from base voxel center [0,1]
472
472
 
473
473
  # Clamp indices to stay in-bounds during interpolation
474
- ix0 = min(ix0, Nx - 2)
475
- iy0 = min(iy0, Ny - 2)
474
+ ix0 = max(0, min(ix0, Nx - 2))
475
+ iy0 = max(0, min(iy0, Ny - 2))
476
476
 
477
477
  # === BILINEAR INTERPOLATION WEIGHT CALCULATION ===
478
478
  # Mathematical basis: Bilinear interpolation formula f(x,y) = Σ f(xi,yi) * wi(x,y)
@@ -554,7 +554,7 @@ def _parallel_2d_backward_kernel(
554
554
  cos_a = d_cos[iang] # Precomputed cosine of projection angle
555
555
  sin_a = d_sin[iang] # Precomputed sine of projection angle
556
556
  # Normalize all physical distances to voxel units
557
- u = (idet - (n_det - 1) * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
557
+ u = (idet - n_det * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
558
558
 
559
559
  # Define ray direction and starting point for parallel beam geometry
560
560
  dir_x, dir_y = cos_a, sin_a
@@ -599,8 +599,8 @@ def _parallel_2d_backward_kernel(
599
599
  dx, dy = mid_x - ix0, mid_y - iy0
600
600
 
601
601
  # Clamp indices to stay in-bounds during interpolation
602
- ix0 = min(ix0, Nx - 2)
603
- iy0 = min(iy0, Ny - 2)
602
+ ix0 = max(0, min(ix0, Nx - 2))
603
+ iy0 = max(0, min(iy0, Ny - 2))
604
604
 
605
605
  # === ATOMIC BACKPROJECTION WITH BILINEAR WEIGHTS ===
606
606
  # Distribute contribution weighted by segment length and interpolation weights
@@ -686,7 +686,7 @@ def _fan_2d_forward_kernel(
686
686
  cos_a = d_cos[iang] # Precomputed cosine of projection angle
687
687
  sin_a = d_sin[iang] # Precomputed sine of projection angle
688
688
  # Normalize all physical distances to voxel units
689
- u = (idet - (n_det - 1) * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
689
+ u = (idet - n_det * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
690
690
  sid_v = sid / voxel_spacing # Source-to-isocenter distance in voxel units
691
691
  sdd_v = sdd / voxel_spacing # Source-to-detector distance in voxel units
692
692
 
@@ -757,8 +757,8 @@ def _fan_2d_forward_kernel(
757
757
  dx, dy = mid_x - ix0, mid_y - iy0
758
758
 
759
759
  # Clamp indices to stay in-bounds during interpolation
760
- ix0 = min(ix0, Nx - 2)
761
- iy0 = min(iy0, Ny - 2)
760
+ ix0 = max(0, min(ix0, Nx - 2))
761
+ iy0 = max(0, min(iy0, Ny - 2))
762
762
 
763
763
  # Bilinear interpolation (identical to parallel beam)
764
764
  val = (
@@ -839,7 +839,7 @@ def _fan_2d_backward_kernel(
839
839
  cos_a = d_cos[iang] # Precomputed cosine of projection angle
840
840
  sin_a = d_sin[iang] # Precomputed sine of projection angle
841
841
  # Normalize all physical distances to voxel units
842
- u = (idet - (n_det - 1) * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
842
+ u = (idet - n_det * 0.5) * det_spacing / voxel_spacing # Detector coordinate in voxel units
843
843
  sid_v = sid / voxel_spacing # Source-to-isocenter distance in voxel units
844
844
  sdd_v = sdd / voxel_spacing # Source-to-detector distance in voxel units
845
845
 
@@ -901,8 +901,8 @@ def _fan_2d_backward_kernel(
901
901
  dx, dy = mid_x - ix0, mid_y - iy0
902
902
 
903
903
  # Clamp indices to stay in-bounds during interpolation
904
- ix0 = min(ix0, Nx - 2)
905
- iy0 = min(iy0, Ny - 2)
904
+ ix0 = max(0, min(ix0, Nx - 2))
905
+ iy0 = max(0, min(iy0, Ny - 2))
906
906
 
907
907
  # === ATOMIC BACKPROJECTION WITH BILINEAR WEIGHTS ===
908
908
  # Distribute contribution weighted by segment length and interpolation weights
@@ -995,8 +995,8 @@ def _cone_3d_forward_kernel(
995
995
  # === 3D CONE BEAM GEOMETRY SETUP ===
996
996
  cos_a, sin_a = d_cos[iview], d_sin[iview] # Projection angle trigonometry
997
997
  # Normalize all physical distances to voxel units
998
- u = (iu - (n_u - 1) * 0.5) * du / voxel_spacing # Detector u-coordinate in voxel units
999
- v = (iv - (n_v - 1) * 0.5) * dv / voxel_spacing # Detector v-coordinate in voxel units
998
+ u = (iu - n_u * 0.5) * du / voxel_spacing # Detector u-coordinate in voxel units
999
+ v = (iv - n_v * 0.5) * dv / voxel_spacing # Detector v-coordinate in voxel units
1000
1000
  sid_v = sid / voxel_spacing # Source-to-isocenter distance in voxel units
1001
1001
  sdd_v = sdd / voxel_spacing # Source-to-detector distance in voxel units
1002
1002
 
@@ -1091,9 +1091,9 @@ def _cone_3d_forward_kernel(
1091
1091
  dx, dy, dz = mid_x - ix0, mid_y - iy0, mid_z - iz0 # Fractional parts: distance from base voxel center [0,1]
1092
1092
 
1093
1093
  # 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)
1094
+ ix0 = max(0, min(ix0, Nx - 2))
1095
+ iy0 = max(0, min(iy0, Ny - 2))
1096
+ iz0 = max(0, min(iz0, Nz - 2))
1097
1097
 
1098
1098
  # === TRILINEAR INTERPOLATION WEIGHT CALCULATION ===
1099
1099
  # Mathematical basis: Trilinear interpolation formula f(x,y,z) = Σ f(xi,yi,zi) * wi(x,y,z)
@@ -1196,8 +1196,8 @@ def _cone_3d_backward_kernel(
1196
1196
  g = d_sino[iview, iu, iv] # Sinogram value to backproject along this ray
1197
1197
  cos_a, sin_a = d_cos[iview], d_sin[iview] # Projection angle trigonometry
1198
1198
  # Normalize all physical distances to voxel units
1199
- u = (iu - (n_u - 1) * 0.5) * du / voxel_spacing # Detector u-coordinate in voxel units
1200
- v = (iv - (n_v - 1) * 0.5) * dv / voxel_spacing # Detector v-coordinate in voxel units
1199
+ u = (iu - n_u * 0.5) * du / voxel_spacing # Detector u-coordinate in voxel units
1200
+ v = (iv - n_v * 0.5) * dv / voxel_spacing # Detector v-coordinate in voxel units
1201
1201
  sid_v = sid / voxel_spacing # Source-to-isocenter distance in voxel units
1202
1202
  sdd_v = sdd / voxel_spacing # Source-to-detector distance in voxel units
1203
1203
 
@@ -1281,9 +1281,9 @@ def _cone_3d_backward_kernel(
1281
1281
  dx, dy, dz = mid_x - ix0, mid_y - iy0, mid_z - iz0 # Fractional parts for 3D weights
1282
1282
 
1283
1283
  # 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)
1284
+ ix0 = max(0, min(ix0, Nx - 2))
1285
+ iy0 = max(0, min(iy0, Ny - 2))
1286
+ iz0 = max(0, min(iz0, Nz - 2))
1287
1287
 
1288
1288
  # === ATOMIC BACKPROJECTION WITH TRILINEAR WEIGHTS ===
1289
1289
  # Distribute contribution weighted by segment length and interpolation weights
@@ -1408,7 +1408,7 @@ class ParallelProjectorFunction(torch.autograd.Function):
1408
1408
  sinogram = torch.zeros((n_angles, num_detectors), dtype=image.dtype, device=device)
1409
1409
 
1410
1410
  # Prepare trigonometric tables on the correct device
1411
- d_cos, d_sin = _trig_tables(angles, dtype=image.dtype)
1411
+ d_cos, d_sin = _trig_tables(angles, dtype=image.dtype, device=device)
1412
1412
 
1413
1413
  # Get Numba CUDA array views for kernel
1414
1414
  d_image = TorchCUDABridge.tensor_to_cuda_array(image)
@@ -1441,7 +1441,7 @@ class ParallelProjectorFunction(torch.autograd.Function):
1441
1441
 
1442
1442
  n_angles = angles.shape[0]
1443
1443
  grad_image = torch.zeros((Ny, Nx), dtype=grad_sinogram.dtype, device=device)
1444
- d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype)
1444
+ d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype, device=device)
1445
1445
 
1446
1446
  d_grad_sino = TorchCUDABridge.tensor_to_cuda_array(grad_sinogram)
1447
1447
  d_img_grad = TorchCUDABridge.tensor_to_cuda_array(grad_image)
@@ -1541,7 +1541,7 @@ class ParallelBackprojectorFunction(torch.autograd.Function):
1541
1541
  reco = torch.zeros((Ny, Nx), dtype=sinogram.dtype, device=device)
1542
1542
 
1543
1543
  # Prepare trigonometric tables on the correct device
1544
- d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype)
1544
+ d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype, device=device)
1545
1545
 
1546
1546
  # Get Numba CUDA array views for kernel
1547
1547
  d_sino = TorchCUDABridge.tensor_to_cuda_array(sinogram)
@@ -1578,7 +1578,7 @@ class ParallelBackprojectorFunction(torch.autograd.Function):
1578
1578
  grad_sino = torch.zeros((n_ang, n_det), dtype=grad_output.dtype, device=device)
1579
1579
 
1580
1580
  # Prepare trigonometric tables on the correct device
1581
- d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype)
1581
+ d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype, device=device)
1582
1582
 
1583
1583
  # Get Numba CUDA array views for kernel
1584
1584
  d_grad_out = TorchCUDABridge.tensor_to_cuda_array(grad_output)
@@ -1679,7 +1679,7 @@ class FanProjectorFunction(torch.autograd.Function):
1679
1679
  n_ang = angles.shape[0]
1680
1680
 
1681
1681
  sinogram = torch.zeros((n_ang, num_detectors), dtype=image.dtype, device=device)
1682
- d_cos, d_sin = _trig_tables(angles, dtype=image.dtype)
1682
+ d_cos, d_sin = _trig_tables(angles, dtype=image.dtype, device=device)
1683
1683
 
1684
1684
  d_image = TorchCUDABridge.tensor_to_cuda_array(image)
1685
1685
  d_sino = TorchCUDABridge.tensor_to_cuda_array(sinogram)
@@ -1713,7 +1713,7 @@ class FanProjectorFunction(torch.autograd.Function):
1713
1713
 
1714
1714
  n_ang = angles.shape[0]
1715
1715
  grad_img = torch.zeros((Ny, Nx), dtype=grad_sinogram.dtype, device=device)
1716
- d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype)
1716
+ d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype, device=device)
1717
1717
 
1718
1718
  d_grad_sino = TorchCUDABridge.tensor_to_cuda_array(grad_sinogram)
1719
1719
  d_img_grad = TorchCUDABridge.tensor_to_cuda_array(grad_img)
@@ -1817,7 +1817,7 @@ class FanBackprojectorFunction(torch.autograd.Function):
1817
1817
  Ny, Nx = H, W
1818
1818
 
1819
1819
  reco = torch.zeros((Ny, Nx), dtype=sinogram.dtype, device=device)
1820
- d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype)
1820
+ d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype, device=device)
1821
1821
 
1822
1822
  d_sino = TorchCUDABridge.tensor_to_cuda_array(sinogram)
1823
1823
  d_reco = TorchCUDABridge.tensor_to_cuda_array(reco)
@@ -1851,7 +1851,7 @@ class FanBackprojectorFunction(torch.autograd.Function):
1851
1851
  Ny, Nx = grad_output.shape
1852
1852
 
1853
1853
  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)
1854
+ d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype, device=device)
1855
1855
 
1856
1856
  d_grad_out = TorchCUDABridge.tensor_to_cuda_array(grad_output)
1857
1857
  d_sino_grad = TorchCUDABridge.tensor_to_cuda_array(grad_sino)
@@ -1959,7 +1959,7 @@ class ConeProjectorFunction(torch.autograd.Function):
1959
1959
  _validate_3d_memory_layout(volume, expected_order='DHW')
1960
1960
 
1961
1961
  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)
1962
+ d_cos, d_sin = _trig_tables(angles, dtype=volume.dtype, device=device)
1963
1963
 
1964
1964
  volume_perm = volume.permute(2, 1, 0).contiguous()
1965
1965
  d_vol = TorchCUDABridge.tensor_to_cuda_array(volume_perm)
@@ -1997,7 +1997,7 @@ class ConeProjectorFunction(torch.autograd.Function):
1997
1997
  n_views = angles.shape[0]
1998
1998
 
1999
1999
  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)
2000
+ d_cos, d_sin = _trig_tables(angles, dtype=grad_sinogram.dtype, device=device)
2001
2001
 
2002
2002
  d_grad_sino = TorchCUDABridge.tensor_to_cuda_array(grad_sinogram)
2003
2003
  d_vol_grad = TorchCUDABridge.tensor_to_cuda_array(grad_vol_perm)
@@ -2116,7 +2116,7 @@ class ConeBackprojectorFunction(torch.autograd.Function):
2116
2116
  _validate_3d_memory_layout(sinogram, expected_order='VHW')
2117
2117
 
2118
2118
  vol_perm = torch.zeros((W, H, D), dtype=sinogram.dtype, device=device)
2119
- d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype)
2119
+ d_cos, d_sin = _trig_tables(angles, dtype=sinogram.dtype, device=device)
2120
2120
 
2121
2121
  d_sino = TorchCUDABridge.tensor_to_cuda_array(sinogram)
2122
2122
  d_reco = TorchCUDABridge.tensor_to_cuda_array(vol_perm)
@@ -2153,7 +2153,7 @@ class ConeBackprojectorFunction(torch.autograd.Function):
2153
2153
  n_views = angles.shape[0]
2154
2154
 
2155
2155
  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)
2156
+ d_cos, d_sin = _trig_tables(angles, dtype=grad_output.dtype, device=device)
2157
2157
 
2158
2158
  grad_output_perm = grad_output.permute(2, 1, 0).contiguous()
2159
2159
  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.3"
7
+ version = "1.2.4"
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