diffct 2.0.0__tar.gz → 2.0.2__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 (56) hide show
  1. {diffct-2.0.0 → diffct-2.0.2}/CHANGELOG.md +51 -1
  2. {diffct-2.0.0 → diffct-2.0.2}/CITATION.cff +1 -1
  3. {diffct-2.0.0 → diffct-2.0.2}/PKG-INFO +32 -29
  4. {diffct-2.0.0 → diffct-2.0.2}/README.md +31 -28
  5. {diffct-2.0.0 → diffct-2.0.2}/diffct/__init__.py +1 -1
  6. {diffct-2.0.0 → diffct-2.0.2}/diffct/analytical.py +4 -0
  7. {diffct-2.0.0 → diffct-2.0.2}/diffct/geometry.py +15 -2
  8. {diffct-2.0.0 → diffct-2.0.2}/diffct/operators.py +9 -0
  9. {diffct-2.0.0 → diffct-2.0.2}/diffct/projectors.py +49 -48
  10. {diffct-2.0.0 → diffct-2.0.2}/diffct/utils.py +69 -0
  11. diffct-2.0.2/docs/video/README.md +47 -0
  12. {diffct-2.0.0 → diffct-2.0.2}/examples/README.md +6 -72
  13. {diffct-2.0.0 → diffct-2.0.2}/examples/data/NOTICE +4 -4
  14. {diffct-2.0.0 → diffct-2.0.2}/formal/README.md +1 -1
  15. {diffct-2.0.0 → diffct-2.0.2}/pyproject.toml +1 -1
  16. {diffct-2.0.0 → diffct-2.0.2}/tests/benchmarks/README.md +1 -1
  17. diffct-2.0.2/tests/test_cuda_exit_deallocations.py +83 -0
  18. diffct-2.0.2/tests/test_multi_gpu_exit.py +88 -0
  19. diffct-2.0.2/tests/test_release_2_0_1.py +326 -0
  20. diffct-2.0.0/docs/video/README.md +0 -22
  21. {diffct-2.0.0 → diffct-2.0.2}/.gitignore +0 -0
  22. {diffct-2.0.0 → diffct-2.0.2}/LICENSE +0 -0
  23. {diffct-2.0.0 → diffct-2.0.2}/NOTICE +0 -0
  24. {diffct-2.0.0 → diffct-2.0.2}/diffct/constants.py +0 -0
  25. {diffct-2.0.0 → diffct-2.0.2}/diffct/differentiable.py +0 -0
  26. {diffct-2.0.0 → diffct-2.0.2}/diffct/kernels/__init__.py +0 -0
  27. {diffct-2.0.0 → diffct-2.0.2}/diffct/kernels/cone_beam.py +0 -0
  28. {diffct-2.0.0 → diffct-2.0.2}/diffct/kernels/fan_beam.py +0 -0
  29. {diffct-2.0.0 → diffct-2.0.2}/diffct/kernels/geometry_vjp.py +0 -0
  30. {diffct-2.0.0 → diffct-2.0.2}/diffct/kernels/parallel_beam.py +0 -0
  31. {diffct-2.0.0 → diffct-2.0.2}/pytest.ini +0 -0
  32. {diffct-2.0.0 → diffct-2.0.2}/tests/__init__.py +0 -0
  33. {diffct-2.0.0 → diffct-2.0.2}/tests/_phantoms.py +0 -0
  34. {diffct-2.0.0 → diffct-2.0.2}/tests/benchmarks/__init__.py +0 -0
  35. {diffct-2.0.0 → diffct-2.0.2}/tests/benchmarks/_common.py +0 -0
  36. {diffct-2.0.0 → diffct-2.0.2}/tests/benchmarks/test_bench_cone.py +0 -0
  37. {diffct-2.0.0 → diffct-2.0.2}/tests/benchmarks/test_bench_fan.py +0 -0
  38. {diffct-2.0.0 → diffct-2.0.2}/tests/benchmarks/test_bench_parallel.py +0 -0
  39. {diffct-2.0.0 → diffct-2.0.2}/tests/distributed_projector_check.py +0 -0
  40. {diffct-2.0.0 → diffct-2.0.2}/tests/test_adjoint_inner_product.py +0 -0
  41. {diffct-2.0.0 → diffct-2.0.2}/tests/test_analytical_regressions.py +0 -0
  42. {diffct-2.0.0 → diffct-2.0.2}/tests/test_cone_projector_autograd.py +0 -0
  43. {diffct-2.0.0 → diffct-2.0.2}/tests/test_cuda_smoke.py +0 -0
  44. {diffct-2.0.0 → diffct-2.0.2}/tests/test_fbp_fan_accuracy.py +0 -0
  45. {diffct-2.0.0 → diffct-2.0.2}/tests/test_fbp_fan_offsets.py +0 -0
  46. {diffct-2.0.0 → diffct-2.0.2}/tests/test_fbp_parallel_accuracy.py +0 -0
  47. {diffct-2.0.0 → diffct-2.0.2}/tests/test_fdk_cone_accuracy.py +0 -0
  48. {diffct-2.0.0 → diffct-2.0.2}/tests/test_fdk_cone_offsets.py +0 -0
  49. {diffct-2.0.0 → diffct-2.0.2}/tests/test_geometry_autograd.py +0 -0
  50. {diffct-2.0.0 → diffct-2.0.2}/tests/test_geometry_gradients.py +0 -0
  51. {diffct-2.0.0 → diffct-2.0.2}/tests/test_gradcheck.py +0 -0
  52. {diffct-2.0.0 → diffct-2.0.2}/tests/test_projector_api.py +0 -0
  53. {diffct-2.0.0 → diffct-2.0.2}/tests/test_projector_degenerate_geometry.py +0 -0
  54. {diffct-2.0.0 → diffct-2.0.2}/tests/test_projector_geometry_devices.py +0 -0
  55. {diffct-2.0.0 → diffct-2.0.2}/tests/test_ramp_filter_windows.py +0 -0
  56. {diffct-2.0.0 → diffct-2.0.2}/tests/test_weights.py +0 -0
@@ -5,6 +5,56 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.2] - 2026-10-09
9
+
10
+ ### Fixed
11
+
12
+ - The exit-time cleanup from 2.0.1 set numba-cuda's pending-deallocation
13
+ ratio to infinity. numba-cuda converts ``capacity * ratio`` to ``int``, so
14
+ each deallocation queued during shutdown raised ``ValueError`` or
15
+ ``OverflowError``. The exit code stayed 0, but stderr showed the error. The
16
+ exit hook now disables numba-cuda's pending-deallocation queues instead of
17
+ changing the limits, so no queue is flushed at exit and no error is raised.
18
+ The hook no longer imports ``numba.cuda.core.config``, which older
19
+ numba-cuda releases do not have.
20
+
21
+ ### Documentation
22
+
23
+ - The install instructions state that ``pip install diffct`` without a CUDA
24
+ extra needs a system CUDA Toolkit. Use ``diffct[cu12]`` or ``diffct[cu13]``.
25
+ - README images and links use absolute URLs, so they also work on PyPI.
26
+
27
+ ## [2.0.1] - 2026-10-09
28
+
29
+ ### Fixed
30
+
31
+ - The low-level projector and backprojector Functions and the analytical
32
+ weighted backprojections run in the CUDA context of their input. Before,
33
+ inputs on another GPU than the current device could use the wrong context
34
+ and stream.
35
+ - A process that ran diffct kernels on several GPUs no longer crashes at
36
+ interpreter exit: numba-cuda's flush of pending CUDA module unloads during
37
+ shutdown is skipped, and process exit releases the modules.
38
+ - ``custom_trajectory_3d`` and ``custom_trajectory_2d_fan`` raise
39
+ ``ValueError`` for a source at the isocenter or a non-finite source,
40
+ instead of returning NaN geometry. The figure-8 example in the
41
+ ``custom_trajectory_3d`` docstring no longer puts a source at the origin.
42
+ - The class docstring examples of the six projector Functions use the
43
+ per-view trajectory API.
44
+ - ``_trig_tables`` accepts NumPy dtypes for tensor input.
45
+ - Walnut example: ``preprocess_walnut.py`` raises when projection archives or
46
+ views are missing or an air level is not positive, instead of writing NaN
47
+ data. A non-default crop keeps the detector centre offset, and
48
+ ``walnut_reconstruction.py`` applies it to the geometry and the FDK
49
+ weights. The default crop has no offset.
50
+ - Examples: ``psnr`` returns infinity for identical images; distributed
51
+ reductions in CGLS, TV and the noise peak use the operator's process
52
+ group.
53
+
54
+ ### Added
55
+
56
+ - ``Projector.process_group``: the process group of a distributed operator.
57
+
8
58
  ## [2.0.0] - 2026-10-09
9
59
 
10
60
  Version 2.0.0 replaces the circular-orbit API of 1.x with arbitrary
@@ -28,7 +78,7 @@ per-view trajectories. It breaks the 1.x interface; see
28
78
  - Install extras ``diffct[cu12]`` and ``diffct[cu13]`` pull in the matching
29
79
  numba-cuda CUDA bindings, so one ``pip install`` gives a working import.
30
80
  - Measured walnut example (``examples/walnut_reconstruction.py``), intro video
31
- and README figures with provenance records in ``docs/assets``.
81
+ and README figures.
32
82
  - Geometry gradients. Trajectory tensors may require gradients; ``Projector``
33
83
  and the low-level Function classes return gradients for source, detector
34
84
  centre and detector axes (parallel: ray direction, detector origin and
@@ -13,7 +13,7 @@ authors:
13
13
  given-names: Yipeng
14
14
  email: yipeng.sun@fau.de
15
15
  alias: sypsyp97
16
- version: 2.0.0
16
+ version: 2.0.2
17
17
  date-released: "2026-10-09"
18
18
  license: Apache-2.0
19
19
  repository-code: "https://github.com/sypsyp97/diffct"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: diffct
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: Differentiable CUDA projectors for CT: arbitrary trajectories, multi-GPU and multi-node execution, geometry gradients
5
5
  Project-URL: Homepage, https://github.com/sypsyp97/diffct
6
6
  Project-URL: Documentation, https://sypsyp97.github.io/diffct/
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
31
31
 
32
32
  <h1 align="center">diffct</h1>
33
33
 
34
- <p align="center">English · <a href="README.zh.md">简体中文</a></p>
34
+ <p align="center">English · <a href="https://github.com/sypsyp97/diffct/blob/main/README.zh.md">简体中文</a></p>
35
35
 
36
36
  <p align="center">
37
37
  Differentiable CUDA projectors for CT: arbitrary trajectories, multi-GPU, multi-node, geometry gradients.
@@ -43,15 +43,15 @@ Description-Content-Type: text/markdown
43
43
  <a href="https://pypi.org/project/diffct/"><img src="https://img.shields.io/pypi/v/diffct.svg?style=flat-square&logo=pypi&logoColor=white" alt="PyPI version"></a>
44
44
  <a href="https://sypsyp97.github.io/diffct/"><img src="https://img.shields.io/github/actions/workflow/status/sypsyp97/diffct/docs.yml?branch=main&label=docs&style=flat-square" alt="Documentation"></a>
45
45
  <a href="https://github.com/sypsyp97/diffct/actions"><img src="https://img.shields.io/github/actions/workflow/status/sypsyp97/diffct/ci.yml?branch=main&label=CI&style=flat-square" alt="CI/CD"></a>
46
- <a href="https://deepwiki.com/sypsyp97/diffct"><img src="docs/assets/deepwiki-badge.svg" alt="Ask DeepWiki"></a>
46
+ <a href="https://deepwiki.com/sypsyp97/diffct"><img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/deepwiki-badge.svg" alt="Ask DeepWiki"></a>
47
47
  </p>
48
48
 
49
49
  <p align="center">
50
- <img src="docs/assets/diffct_intro.gif" width="100%" alt="diffct intro: projection, sinogram, trajectories, multi-GPU split, reconstruction and geometry calibration">
50
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/diffct_intro.gif" width="100%" alt="diffct intro: projection, sinogram, trajectories, multi-GPU split, reconstruction and geometry calibration">
51
51
  </p>
52
52
 
53
53
  <p align="center">
54
- <a href="docs/assets/diffct_intro.mp4">Intro video (MP4)</a> ·
54
+ <a href="https://github.com/sypsyp97/diffct/blob/main/docs/assets/diffct_intro.mp4">Intro video (MP4)</a> ·
55
55
  <a href="https://www.preprints.org/manuscript/202605.1446/v1">Technical report</a> ·
56
56
  <a href="https://doi.org/10.20944/preprints202605.1446.v1">DOI</a>
57
57
  </p>
@@ -65,7 +65,9 @@ Description-Content-Type: text/markdown
65
65
  <a href="#citation">Citation</a>
66
66
  </p>
67
67
 
68
- > **Version 2.0** replaces the circular-orbit API of 1.x with per-view trajectories. Code written for 1.x needs changes; see the [migration notes](docs/MIGRATION.md).
68
+ > **Install** with the CUDA extra, `pip install "diffct[cu12]"` or `pip install "diffct[cu13]"`, after PyTorch. A plain `pip install diffct` needs a system CUDA Toolkit.
69
+ >
70
+ > **Version 2.0** replaces the circular-orbit API of 1.x with per-view trajectories. Code written for 1.x needs changes; see the [migration notes](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md).
69
71
  > For Apple Silicon, use the [MLX port by Linda-Sophie Schneider](https://github.com/Linda-SophieSchneider/DiffCT-MLX).
70
72
 
71
73
  ## Why diffct
@@ -76,18 +78,20 @@ To our knowledge, diffct is the only open-source GPU CT library that combines ar
76
78
  - **Matched operators.** `project()` and `backproject()` form an exact adjoint pair for the cell-constant Siddon model. Both support PyTorch autograd, with volume/sinogram gradients and Hessian-vector products.
77
79
  - **Multi-GPU and multi-node.** Use `devices=[0, 1, 2, 3]` in one process, or one process per GPU with torchrun and NCCL. Views are partitioned and the volume is replicated. Speedup depends on workload and communication costs.
78
80
  - **Analytical helpers.** `diffct.analytical` provides ramp filters (ram-lak, shepp-logan, cosine, hamming, hann), fan, cone and Parker weights, and FBP and FDK backprojection.
79
- - **Validated.** FDK matches ASTRA 2.5.0 `FDK_CUDA`: PSNR within 0.3 dB. Geometry gradients match an independent float64 reference to about 1e-6. The [recorded A100 validation](docs/VALIDATION.md) reports 229 passing pytest tests. Full notes: [REFERENCE](docs/REFERENCE.md#validation-summary).
80
81
 
81
- Capabilities, limits and isocenter rules: [docs/REFERENCE.md](docs/REFERENCE.md#capabilities-and-limits).
82
+ Capabilities, limits and isocenter rules: [docs/REFERENCE.md](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#capabilities-and-limits).
82
83
 
83
84
  ## Install
84
85
 
85
- You need a CUDA GPU and PyTorch. Install PyTorch for your CUDA version first.
86
+ You need an NVIDIA GPU. Install PyTorch for your CUDA version first. Then install diffct with the extra for the same CUDA major version:
86
87
 
87
88
  ```bash
88
- pip install "diffct[cu12]" # [cu13] for CUDA 13; install PyTorch for your CUDA first
89
+ pip install "diffct[cu12]" # PyTorch built for CUDA 12
90
+ pip install "diffct[cu13]" # PyTorch built for CUDA 13
89
91
  ```
90
92
 
93
+ The extra installs the CUDA compiler libraries that Numba CUDA needs to compile the kernels. A plain `pip install diffct` does not install them. Use it only if a CUDA Toolkit is already installed on the system.
94
+
91
95
  From source, with the examples:
92
96
 
93
97
  ```bash
@@ -130,11 +134,11 @@ loss = 0.5 * (A.project(x) - sinogram).square().sum()
130
134
  loss.backward() # x.grad = A^T (A x - y)
131
135
  ```
132
136
 
133
- **Custom or calibrated trajectories.** Pass calibrated tensors directly instead of a generator; no circular fit is needed. See [REFERENCE](docs/REFERENCE.md#custom-or-calibrated-trajectories).
137
+ **Custom or calibrated trajectories.** Pass calibrated tensors directly instead of a generator; no circular fit is needed. See [REFERENCE](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#custom-or-calibrated-trajectories).
134
138
 
135
- **Geometry gradients.** Set `requires_grad=True` on trajectory tensors before you build `Projector`. See [REFERENCE](docs/REFERENCE.md#geometry-gradients).
139
+ **Geometry gradients.** Set `requires_grad=True` on trajectory tensors before you build `Projector`. See [REFERENCE](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#geometry-gradients).
136
140
 
137
- **Several GPUs and nodes.** Use `devices=[0, 1, 2, 3]` in one process, or one process per GPU with torchrun for one or more nodes. See [REFERENCE](docs/REFERENCE.md#several-gpus-and-nodes) and [DISTRIBUTED.md](docs/DISTRIBUTED.md).
141
+ **Several GPUs and nodes.** Use `devices=[0, 1, 2, 3]` in one process, or one process per GPU with torchrun for one or more nodes. See [REFERENCE](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#several-gpus-and-nodes) and [DISTRIBUTED.md](https://github.com/sypsyp97/diffct/blob/main/docs/DISTRIBUTED.md).
138
142
 
139
143
  ```python
140
144
  A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, devices=[0, 1, 2, 3])
@@ -144,8 +148,8 @@ A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, dev
144
148
 
145
149
  <p align="center">
146
150
  <picture>
147
- <source media="(prefers-color-scheme: dark)" srcset="docs/assets/scaling_dark.png">
148
- <img src="docs/assets/scaling_light.png" width="100%" alt="Time per forward, adjoint and CGLS iteration on 1, 4 and 8 GPUs, 64^3 and 128^3">
151
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png">
152
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png" width="100%" alt="Time per forward, adjoint and CGLS iteration on 1, 4 and 8 GPUs, 64^3 and 128^3">
149
153
  </picture>
150
154
  </p>
151
155
 
@@ -155,7 +159,7 @@ A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, dev
155
159
  | Adjoint (backprojection) | 20.66 ms | 5.89 ms (3.5×) | 4.62 ms (4.5×) |
156
160
  | CGLS iteration | 30.08 ms | 8.08 ms (3.7×) | 5.59 ms (5.4×) |
157
161
 
158
- Circular trajectory, 360 views, detector (2n, 1.5n) cells at pitch 1.25, A100 64 GB on Leonardo Booster. Small volumes scale less: at 64³, one CGLS iteration takes 4.63 ms on 1 GPU, 1.57 ms on 4 and 1.56 ms on 8. Raw data: [docs/assets/scaling.json](docs/assets/scaling.json).
162
+ Circular trajectory, 360 views, detector (2n, 1.5n) cells at pitch 1.25, A100 64 GB GPUs. Small volumes scale less: at 64³, one CGLS iteration takes 4.63 ms on 1 GPU, 1.57 ms on 4 and 1.56 ms on 8. Raw data: [docs/assets/scaling.json](https://github.com/sypsyp97/diffct/blob/main/docs/assets/scaling.json).
159
163
 
160
164
  To measure one against several GPUs on your machine (default scan of the script, not the table setup):
161
165
 
@@ -168,7 +172,7 @@ python examples/benchmark_projector.py --devices 0 1
168
172
  **Measured walnut.** 240 measured views, circular cone beam, 256³: FDK (Hann window), SIRT (200 iterations), CGLS (20), TV (300, weight 0.3); axial and coronal centre slices.
169
173
 
170
174
  <p align="center">
171
- <img src="docs/assets/walnut_measured.png" width="100%" alt="Measured walnut: FDK, SIRT, CGLS and TV reconstructions">
175
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/walnut_measured.png" width="100%" alt="Measured walnut: FDK, SIRT, CGLS and TV reconstructions">
172
176
  </p>
173
177
 
174
178
  ```bash
@@ -178,7 +182,7 @@ python examples/walnut_reconstruction.py --figure out.png
178
182
  **Simulated helical scan of the walnut.** The FDK walnut volume as ground truth, 720 views, 1% noise, 256³. PSNR: FDK 26.67 dB, CGLS (30 iterations) 34.13 dB, SIRT (200) 34.52 dB, TV (200, weight 1.0) 37.77 dB.
179
183
 
180
184
  <p align="center">
181
- <img src="docs/assets/walnut_helical.png" width="100%" alt="Simulated helical scan of the walnut: FDK, CGLS, SIRT and TV reconstructions">
185
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/walnut_helical.png" width="100%" alt="Simulated helical scan of the walnut: FDK, CGLS, SIRT and TV reconstructions">
182
186
  </p>
183
187
 
184
188
  ```bash
@@ -186,23 +190,22 @@ python examples/walnut_reconstruction.py --algorithms --save-volume walnut256.np
186
190
  python examples/iterative_reconstruction.py --size 256 --views 720 --trajectory helical --noise 0.01 --phantom walnut256.npy --figure out.png
187
191
  ```
188
192
 
189
- Walnut data: Meaney 2022, Zenodo 6986012, CC BY 4.0; see [examples/data/NOTICE](examples/data/NOTICE).
193
+ Walnut data: Meaney 2022, Zenodo 6986012, CC BY 4.0; see [examples/data/NOTICE](https://github.com/sypsyp97/diffct/blob/main/examples/data/NOTICE).
190
194
 
191
195
  ## Examples
192
196
 
193
- `quickstart.py`, `analytical_reconstruction.py`, `iterative_reconstruction.py`, `walnut_reconstruction.py`, `geometry_calibration.py`, `benchmark_projector.py`, `plot_trajectory.py`, and the Slurm template `slurm/multi_node.sbatch`. Launch modes, distributed-loss rules and measured results are in [examples/README.md](examples/README.md).
197
+ `quickstart.py`, `analytical_reconstruction.py`, `iterative_reconstruction.py`, `walnut_reconstruction.py`, `geometry_calibration.py`, `benchmark_projector.py`, `plot_trajectory.py`, and the Slurm template `slurm/multi_node.sbatch`. Launch modes and distributed-loss rules are in [examples/README.md](https://github.com/sypsyp97/diffct/blob/main/examples/README.md).
194
198
 
195
199
  ## Documentation
196
200
 
197
201
  | Document | Contents |
198
202
  |---|---|
199
- | [Trajectory guide](docs/source/trajectories.rst) | Trajectory tuples and `Projector` usage |
200
- | [docs/REFERENCE.md](docs/REFERENCE.md) | Capabilities and limits, custom trajectories, geometry gradients, multi-GPU details, geometry and units |
201
- | [docs/DISTRIBUTED.md](docs/DISTRIBUTED.md) | Execution/memory choices, distributed loss rules, Slurm and cross-node checks |
202
- | [docs/VALIDATION.md](docs/VALIDATION.md) | Test commands, validation details and the limits of each check |
203
- | [docs/MIGRATION.md](docs/MIGRATION.md) | Moving from the circular-only API |
204
- | [docs/video/README.md](docs/video/README.md) | How the intro video is rendered |
205
- | [CHANGELOG.md](CHANGELOG.md) | Release history |
203
+ | [Trajectory guide](https://github.com/sypsyp97/diffct/blob/main/docs/source/trajectories.rst) | Trajectory tuples and `Projector` usage |
204
+ | [docs/REFERENCE.md](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md) | Capabilities and limits, custom trajectories, geometry gradients, multi-GPU details, geometry and units |
205
+ | [docs/DISTRIBUTED.md](https://github.com/sypsyp97/diffct/blob/main/docs/DISTRIBUTED.md) | Execution/memory choices, distributed loss rules, Slurm and cross-node checks |
206
+ | [docs/MIGRATION.md](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md) | Moving from the circular-only API |
207
+ | [docs/video/README.md](https://github.com/sypsyp97/diffct/blob/main/docs/video/README.md) | How the intro video is rendered |
208
+ | [CHANGELOG.md](https://github.com/sypsyp97/diffct/blob/main/CHANGELOG.md) | Release history |
206
209
 
207
210
  Run the test suite on a CUDA host with `python -m pytest tests/ -q`.
208
211
 
@@ -234,7 +237,7 @@ If you use this library in your research, please cite the software and the techn
234
237
 
235
238
  ## License and acknowledgements
236
239
 
237
- Apache 2.0, see [LICENSE](LICENSE). The project draws on
240
+ Apache 2.0, see [LICENSE](https://github.com/sypsyp97/diffct/blob/main/LICENSE). The project draws on
238
241
  [PYRO-NN](https://github.com/csyben/PYRO-NN) and
239
242
  [geometry_gradients_CT](https://github.com/mareikethies/geometry_gradients_CT).
240
- Issues and pull requests are welcome. The walnut data are CC BY 4.0 (Meaney 2022); see [NOTICE](NOTICE).
243
+ Issues and pull requests are welcome. The walnut data are CC BY 4.0 (Meaney 2022); see [NOTICE](https://github.com/sypsyp97/diffct/blob/main/NOTICE).
@@ -1,6 +1,6 @@
1
1
  <h1 align="center">diffct</h1>
2
2
 
3
- <p align="center">English · <a href="README.zh.md">简体中文</a></p>
3
+ <p align="center">English · <a href="https://github.com/sypsyp97/diffct/blob/main/README.zh.md">简体中文</a></p>
4
4
 
5
5
  <p align="center">
6
6
  Differentiable CUDA projectors for CT: arbitrary trajectories, multi-GPU, multi-node, geometry gradients.
@@ -12,15 +12,15 @@
12
12
  <a href="https://pypi.org/project/diffct/"><img src="https://img.shields.io/pypi/v/diffct.svg?style=flat-square&logo=pypi&logoColor=white" alt="PyPI version"></a>
13
13
  <a href="https://sypsyp97.github.io/diffct/"><img src="https://img.shields.io/github/actions/workflow/status/sypsyp97/diffct/docs.yml?branch=main&label=docs&style=flat-square" alt="Documentation"></a>
14
14
  <a href="https://github.com/sypsyp97/diffct/actions"><img src="https://img.shields.io/github/actions/workflow/status/sypsyp97/diffct/ci.yml?branch=main&label=CI&style=flat-square" alt="CI/CD"></a>
15
- <a href="https://deepwiki.com/sypsyp97/diffct"><img src="docs/assets/deepwiki-badge.svg" alt="Ask DeepWiki"></a>
15
+ <a href="https://deepwiki.com/sypsyp97/diffct"><img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/deepwiki-badge.svg" alt="Ask DeepWiki"></a>
16
16
  </p>
17
17
 
18
18
  <p align="center">
19
- <img src="docs/assets/diffct_intro.gif" width="100%" alt="diffct intro: projection, sinogram, trajectories, multi-GPU split, reconstruction and geometry calibration">
19
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/diffct_intro.gif" width="100%" alt="diffct intro: projection, sinogram, trajectories, multi-GPU split, reconstruction and geometry calibration">
20
20
  </p>
21
21
 
22
22
  <p align="center">
23
- <a href="docs/assets/diffct_intro.mp4">Intro video (MP4)</a> ·
23
+ <a href="https://github.com/sypsyp97/diffct/blob/main/docs/assets/diffct_intro.mp4">Intro video (MP4)</a> ·
24
24
  <a href="https://www.preprints.org/manuscript/202605.1446/v1">Technical report</a> ·
25
25
  <a href="https://doi.org/10.20944/preprints202605.1446.v1">DOI</a>
26
26
  </p>
@@ -34,7 +34,9 @@
34
34
  <a href="#citation">Citation</a>
35
35
  </p>
36
36
 
37
- > **Version 2.0** replaces the circular-orbit API of 1.x with per-view trajectories. Code written for 1.x needs changes; see the [migration notes](docs/MIGRATION.md).
37
+ > **Install** with the CUDA extra, `pip install "diffct[cu12]"` or `pip install "diffct[cu13]"`, after PyTorch. A plain `pip install diffct` needs a system CUDA Toolkit.
38
+ >
39
+ > **Version 2.0** replaces the circular-orbit API of 1.x with per-view trajectories. Code written for 1.x needs changes; see the [migration notes](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md).
38
40
  > For Apple Silicon, use the [MLX port by Linda-Sophie Schneider](https://github.com/Linda-SophieSchneider/DiffCT-MLX).
39
41
 
40
42
  ## Why diffct
@@ -45,18 +47,20 @@ To our knowledge, diffct is the only open-source GPU CT library that combines ar
45
47
  - **Matched operators.** `project()` and `backproject()` form an exact adjoint pair for the cell-constant Siddon model. Both support PyTorch autograd, with volume/sinogram gradients and Hessian-vector products.
46
48
  - **Multi-GPU and multi-node.** Use `devices=[0, 1, 2, 3]` in one process, or one process per GPU with torchrun and NCCL. Views are partitioned and the volume is replicated. Speedup depends on workload and communication costs.
47
49
  - **Analytical helpers.** `diffct.analytical` provides ramp filters (ram-lak, shepp-logan, cosine, hamming, hann), fan, cone and Parker weights, and FBP and FDK backprojection.
48
- - **Validated.** FDK matches ASTRA 2.5.0 `FDK_CUDA`: PSNR within 0.3 dB. Geometry gradients match an independent float64 reference to about 1e-6. The [recorded A100 validation](docs/VALIDATION.md) reports 229 passing pytest tests. Full notes: [REFERENCE](docs/REFERENCE.md#validation-summary).
49
50
 
50
- Capabilities, limits and isocenter rules: [docs/REFERENCE.md](docs/REFERENCE.md#capabilities-and-limits).
51
+ Capabilities, limits and isocenter rules: [docs/REFERENCE.md](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#capabilities-and-limits).
51
52
 
52
53
  ## Install
53
54
 
54
- You need a CUDA GPU and PyTorch. Install PyTorch for your CUDA version first.
55
+ You need an NVIDIA GPU. Install PyTorch for your CUDA version first. Then install diffct with the extra for the same CUDA major version:
55
56
 
56
57
  ```bash
57
- pip install "diffct[cu12]" # [cu13] for CUDA 13; install PyTorch for your CUDA first
58
+ pip install "diffct[cu12]" # PyTorch built for CUDA 12
59
+ pip install "diffct[cu13]" # PyTorch built for CUDA 13
58
60
  ```
59
61
 
62
+ The extra installs the CUDA compiler libraries that Numba CUDA needs to compile the kernels. A plain `pip install diffct` does not install them. Use it only if a CUDA Toolkit is already installed on the system.
63
+
60
64
  From source, with the examples:
61
65
 
62
66
  ```bash
@@ -99,11 +103,11 @@ loss = 0.5 * (A.project(x) - sinogram).square().sum()
99
103
  loss.backward() # x.grad = A^T (A x - y)
100
104
  ```
101
105
 
102
- **Custom or calibrated trajectories.** Pass calibrated tensors directly instead of a generator; no circular fit is needed. See [REFERENCE](docs/REFERENCE.md#custom-or-calibrated-trajectories).
106
+ **Custom or calibrated trajectories.** Pass calibrated tensors directly instead of a generator; no circular fit is needed. See [REFERENCE](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#custom-or-calibrated-trajectories).
103
107
 
104
- **Geometry gradients.** Set `requires_grad=True` on trajectory tensors before you build `Projector`. See [REFERENCE](docs/REFERENCE.md#geometry-gradients).
108
+ **Geometry gradients.** Set `requires_grad=True` on trajectory tensors before you build `Projector`. See [REFERENCE](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#geometry-gradients).
105
109
 
106
- **Several GPUs and nodes.** Use `devices=[0, 1, 2, 3]` in one process, or one process per GPU with torchrun for one or more nodes. See [REFERENCE](docs/REFERENCE.md#several-gpus-and-nodes) and [DISTRIBUTED.md](docs/DISTRIBUTED.md).
110
+ **Several GPUs and nodes.** Use `devices=[0, 1, 2, 3]` in one process, or one process per GPU with torchrun for one or more nodes. See [REFERENCE](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#several-gpus-and-nodes) and [DISTRIBUTED.md](https://github.com/sypsyp97/diffct/blob/main/docs/DISTRIBUTED.md).
107
111
 
108
112
  ```python
109
113
  A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, devices=[0, 1, 2, 3])
@@ -113,8 +117,8 @@ A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, dev
113
117
 
114
118
  <p align="center">
115
119
  <picture>
116
- <source media="(prefers-color-scheme: dark)" srcset="docs/assets/scaling_dark.png">
117
- <img src="docs/assets/scaling_light.png" width="100%" alt="Time per forward, adjoint and CGLS iteration on 1, 4 and 8 GPUs, 64^3 and 128^3">
120
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png">
121
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png" width="100%" alt="Time per forward, adjoint and CGLS iteration on 1, 4 and 8 GPUs, 64^3 and 128^3">
118
122
  </picture>
119
123
  </p>
120
124
 
@@ -124,7 +128,7 @@ A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, dev
124
128
  | Adjoint (backprojection) | 20.66 ms | 5.89 ms (3.5×) | 4.62 ms (4.5×) |
125
129
  | CGLS iteration | 30.08 ms | 8.08 ms (3.7×) | 5.59 ms (5.4×) |
126
130
 
127
- Circular trajectory, 360 views, detector (2n, 1.5n) cells at pitch 1.25, A100 64 GB on Leonardo Booster. Small volumes scale less: at 64³, one CGLS iteration takes 4.63 ms on 1 GPU, 1.57 ms on 4 and 1.56 ms on 8. Raw data: [docs/assets/scaling.json](docs/assets/scaling.json).
131
+ Circular trajectory, 360 views, detector (2n, 1.5n) cells at pitch 1.25, A100 64 GB GPUs. Small volumes scale less: at 64³, one CGLS iteration takes 4.63 ms on 1 GPU, 1.57 ms on 4 and 1.56 ms on 8. Raw data: [docs/assets/scaling.json](https://github.com/sypsyp97/diffct/blob/main/docs/assets/scaling.json).
128
132
 
129
133
  To measure one against several GPUs on your machine (default scan of the script, not the table setup):
130
134
 
@@ -137,7 +141,7 @@ python examples/benchmark_projector.py --devices 0 1
137
141
  **Measured walnut.** 240 measured views, circular cone beam, 256³: FDK (Hann window), SIRT (200 iterations), CGLS (20), TV (300, weight 0.3); axial and coronal centre slices.
138
142
 
139
143
  <p align="center">
140
- <img src="docs/assets/walnut_measured.png" width="100%" alt="Measured walnut: FDK, SIRT, CGLS and TV reconstructions">
144
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/walnut_measured.png" width="100%" alt="Measured walnut: FDK, SIRT, CGLS and TV reconstructions">
141
145
  </p>
142
146
 
143
147
  ```bash
@@ -147,7 +151,7 @@ python examples/walnut_reconstruction.py --figure out.png
147
151
  **Simulated helical scan of the walnut.** The FDK walnut volume as ground truth, 720 views, 1% noise, 256³. PSNR: FDK 26.67 dB, CGLS (30 iterations) 34.13 dB, SIRT (200) 34.52 dB, TV (200, weight 1.0) 37.77 dB.
148
152
 
149
153
  <p align="center">
150
- <img src="docs/assets/walnut_helical.png" width="100%" alt="Simulated helical scan of the walnut: FDK, CGLS, SIRT and TV reconstructions">
154
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/walnut_helical.png" width="100%" alt="Simulated helical scan of the walnut: FDK, CGLS, SIRT and TV reconstructions">
151
155
  </p>
152
156
 
153
157
  ```bash
@@ -155,23 +159,22 @@ python examples/walnut_reconstruction.py --algorithms --save-volume walnut256.np
155
159
  python examples/iterative_reconstruction.py --size 256 --views 720 --trajectory helical --noise 0.01 --phantom walnut256.npy --figure out.png
156
160
  ```
157
161
 
158
- Walnut data: Meaney 2022, Zenodo 6986012, CC BY 4.0; see [examples/data/NOTICE](examples/data/NOTICE).
162
+ Walnut data: Meaney 2022, Zenodo 6986012, CC BY 4.0; see [examples/data/NOTICE](https://github.com/sypsyp97/diffct/blob/main/examples/data/NOTICE).
159
163
 
160
164
  ## Examples
161
165
 
162
- `quickstart.py`, `analytical_reconstruction.py`, `iterative_reconstruction.py`, `walnut_reconstruction.py`, `geometry_calibration.py`, `benchmark_projector.py`, `plot_trajectory.py`, and the Slurm template `slurm/multi_node.sbatch`. Launch modes, distributed-loss rules and measured results are in [examples/README.md](examples/README.md).
166
+ `quickstart.py`, `analytical_reconstruction.py`, `iterative_reconstruction.py`, `walnut_reconstruction.py`, `geometry_calibration.py`, `benchmark_projector.py`, `plot_trajectory.py`, and the Slurm template `slurm/multi_node.sbatch`. Launch modes and distributed-loss rules are in [examples/README.md](https://github.com/sypsyp97/diffct/blob/main/examples/README.md).
163
167
 
164
168
  ## Documentation
165
169
 
166
170
  | Document | Contents |
167
171
  |---|---|
168
- | [Trajectory guide](docs/source/trajectories.rst) | Trajectory tuples and `Projector` usage |
169
- | [docs/REFERENCE.md](docs/REFERENCE.md) | Capabilities and limits, custom trajectories, geometry gradients, multi-GPU details, geometry and units |
170
- | [docs/DISTRIBUTED.md](docs/DISTRIBUTED.md) | Execution/memory choices, distributed loss rules, Slurm and cross-node checks |
171
- | [docs/VALIDATION.md](docs/VALIDATION.md) | Test commands, validation details and the limits of each check |
172
- | [docs/MIGRATION.md](docs/MIGRATION.md) | Moving from the circular-only API |
173
- | [docs/video/README.md](docs/video/README.md) | How the intro video is rendered |
174
- | [CHANGELOG.md](CHANGELOG.md) | Release history |
172
+ | [Trajectory guide](https://github.com/sypsyp97/diffct/blob/main/docs/source/trajectories.rst) | Trajectory tuples and `Projector` usage |
173
+ | [docs/REFERENCE.md](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md) | Capabilities and limits, custom trajectories, geometry gradients, multi-GPU details, geometry and units |
174
+ | [docs/DISTRIBUTED.md](https://github.com/sypsyp97/diffct/blob/main/docs/DISTRIBUTED.md) | Execution/memory choices, distributed loss rules, Slurm and cross-node checks |
175
+ | [docs/MIGRATION.md](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md) | Moving from the circular-only API |
176
+ | [docs/video/README.md](https://github.com/sypsyp97/diffct/blob/main/docs/video/README.md) | How the intro video is rendered |
177
+ | [CHANGELOG.md](https://github.com/sypsyp97/diffct/blob/main/CHANGELOG.md) | Release history |
175
178
 
176
179
  Run the test suite on a CUDA host with `python -m pytest tests/ -q`.
177
180
 
@@ -203,7 +206,7 @@ If you use this library in your research, please cite the software and the techn
203
206
 
204
207
  ## License and acknowledgements
205
208
 
206
- Apache 2.0, see [LICENSE](LICENSE). The project draws on
209
+ Apache 2.0, see [LICENSE](https://github.com/sypsyp97/diffct/blob/main/LICENSE). The project draws on
207
210
  [PYRO-NN](https://github.com/csyben/PYRO-NN) and
208
211
  [geometry_gradients_CT](https://github.com/mareikethies/geometry_gradients_CT).
209
- Issues and pull requests are welcome. The walnut data are CC BY 4.0 (Meaney 2022); see [NOTICE](NOTICE).
212
+ Issues and pull requests are welcome. The walnut data are CC BY 4.0 (Meaney 2022); see [NOTICE](https://github.com/sypsyp97/diffct/blob/main/NOTICE).
@@ -49,7 +49,7 @@ try:
49
49
  except ImportError:
50
50
  pass
51
51
 
52
- __version__ = '2.0.0'
52
+ __version__ = '2.0.2'
53
53
 
54
54
  __all__ = [
55
55
  'Projector',
@@ -34,6 +34,7 @@ from .utils import (
34
34
  _get_numba_external_stream_for,
35
35
  _grid_2d,
36
36
  _grid_3d,
37
+ _on_device_of,
37
38
  )
38
39
  from .kernels import (
39
40
  _parallel_2d_fbp_backproject_kernel,
@@ -365,6 +366,7 @@ def _as_contig_f32(t, device):
365
366
  return DeviceManager.ensure_device(t, device).to(dtype=torch.float32).contiguous()
366
367
 
367
368
 
369
+ @_on_device_of("sinogram")
368
370
  def parallel_weighted_backproject(sinogram, ray_dir, det_origin, det_u_vec,
369
371
  detector_spacing, H, W, voxel_spacing=1.0):
370
372
  """Voxel-driven parallel-beam FBP backprojection with analytical constant.
@@ -448,6 +450,7 @@ def _analytical_geometry(src_pos, det_center, n, isocenter):
448
450
  return isocenter, float(sid_n.mean().item()), float(sdd_n.mean().item())
449
451
 
450
452
 
453
+ @_on_device_of("sinogram")
451
454
  def fan_weighted_backproject(sinogram, src_pos, det_center, det_u_vec,
452
455
  detector_spacing, H, W, voxel_spacing=1.0, *, isocenter=None):
453
456
  """Voxel-driven fan-beam FBP backprojection with analytical constant.
@@ -503,6 +506,7 @@ def fan_weighted_backproject(sinogram, src_pos, det_center, det_u_vec,
503
506
  return reco * scale
504
507
 
505
508
 
509
+ @_on_device_of("sinogram")
506
510
  def cone_weighted_backproject(sinogram, src_pos, det_center, det_u_vec, det_v_vec,
507
511
  D, H, W, du, dv, voxel_spacing=1.0, *, isocenter=None):
508
512
  """Voxel-driven cone-beam FDK backprojection with analytical constant.
@@ -13,6 +13,17 @@ from typing import Callable, Tuple
13
13
  # Trajectory Generation Functions
14
14
  # ============================================================================
15
15
 
16
+ def _check_source_positions(src_pos):
17
+ """Reject non-finite sources and sources at the isocenter, which have no viewing direction."""
18
+ if not torch.isfinite(src_pos).all():
19
+ raise ValueError("source_path_fn returned non-finite source positions")
20
+ if (torch.linalg.vector_norm(src_pos.detach(), dim=1) == 0).any():
21
+ raise ValueError(
22
+ "source_path_fn returned a source at the isocenter; every source needs a "
23
+ "nonzero distance from the origin"
24
+ )
25
+
26
+
16
27
  def circular_trajectory_3d(n_views, sid, sdd, start_angle=0.0, end_angle=None, device='cuda', dtype=torch.float32):
17
28
  """Generate circular trajectory geometry for cone-beam CT.
18
29
 
@@ -500,11 +511,11 @@ def custom_trajectory_3d(n_views, sid, sdd,
500
511
 
501
512
  Examples
502
513
  --------
503
- >>> # Define a custom figure-8 trajectory
514
+ >>> # Circular orbit with a two-lobe (figure-8) vertical motion
504
515
  >>> def figure8_path(angles, sid):
505
516
  ... src_pos = torch.zeros((len(angles), 3), device=angles.device, dtype=angles.dtype)
506
517
  ... src_pos[:, 0] = -sid * torch.sin(angles)
507
- ... src_pos[:, 1] = sid * torch.cos(angles) * torch.sin(angles)
518
+ ... src_pos[:, 1] = sid * torch.cos(angles)
508
519
  ... src_pos[:, 2] = 50 * torch.sin(2 * angles)
509
520
  ... return src_pos
510
521
  >>>
@@ -523,6 +534,7 @@ def custom_trajectory_3d(n_views, sid, sdd,
523
534
  if src_pos.shape != (n_views, 3):
524
535
  raise ValueError(f"source_path_fn must return tensor of shape ({n_views}, 3), "
525
536
  f"got {src_pos.shape}")
537
+ _check_source_positions(src_pos)
526
538
 
527
539
  # Keep the entire frame in the source-path autograd graph. Constructing
528
540
  # tensors from scalar tensors detaches them; rewriting view slices in
@@ -750,6 +762,7 @@ def custom_trajectory_2d_fan(n_views, sid, sdd,
750
762
  if src_pos.shape != (n_views, 2):
751
763
  raise ValueError(f"source_path_fn must return tensor of shape ({n_views}, 2), "
752
764
  f"got {src_pos.shape}")
765
+ _check_source_positions(src_pos)
753
766
 
754
767
  # Preallocate remaining position matrices
755
768
  det_center = torch.zeros((n_views, 2), device=device, dtype=dtype)
@@ -523,6 +523,15 @@ class Projector:
523
523
  *self.detector_spacing, self.voxel_spacing,
524
524
  )
525
525
 
526
+ @property
527
+ def process_group(self):
528
+ """Process group of a distributed operator, or None for the default group.
529
+
530
+ Pass it as ``group=`` to collectives that combine per-rank results of this
531
+ operator, so they use the same ranks as the operator.
532
+ """
533
+ return self._process_group
534
+
526
535
  def project(self, volume):
527
536
  """Project a ``(H, W)`` or ``(D, H, W)`` CUDA tensor to float32.
528
537