diffct 2.0.1__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.
- {diffct-2.0.1 → diffct-2.0.2}/CHANGELOG.md +22 -0
- {diffct-2.0.1 → diffct-2.0.2}/CITATION.cff +1 -1
- {diffct-2.0.1 → diffct-2.0.2}/PKG-INFO +32 -27
- {diffct-2.0.1 → diffct-2.0.2}/README.md +31 -26
- {diffct-2.0.1 → diffct-2.0.2}/diffct/__init__.py +1 -1
- {diffct-2.0.1 → diffct-2.0.2}/diffct/utils.py +12 -9
- diffct-2.0.2/docs/video/README.md +47 -0
- {diffct-2.0.1 → diffct-2.0.2}/examples/README.md +6 -4
- {diffct-2.0.1 → diffct-2.0.2}/pyproject.toml +1 -1
- diffct-2.0.2/tests/test_cuda_exit_deallocations.py +83 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_multi_gpu_exit.py +5 -0
- diffct-2.0.1/docs/video/README.md +0 -23
- {diffct-2.0.1 → diffct-2.0.2}/.gitignore +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/LICENSE +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/NOTICE +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/analytical.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/constants.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/differentiable.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/geometry.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/kernels/__init__.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/kernels/cone_beam.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/kernels/fan_beam.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/kernels/geometry_vjp.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/kernels/parallel_beam.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/operators.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/diffct/projectors.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/examples/data/NOTICE +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/formal/README.md +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/pytest.ini +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/__init__.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/_phantoms.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/benchmarks/README.md +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/benchmarks/__init__.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/benchmarks/_common.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/benchmarks/test_bench_cone.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/benchmarks/test_bench_fan.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/benchmarks/test_bench_parallel.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/distributed_projector_check.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_adjoint_inner_product.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_analytical_regressions.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_cone_projector_autograd.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_cuda_smoke.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_fbp_fan_accuracy.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_fbp_fan_offsets.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_fbp_parallel_accuracy.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_fdk_cone_accuracy.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_fdk_cone_offsets.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_geometry_autograd.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_geometry_gradients.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_gradcheck.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_projector_api.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_projector_degenerate_geometry.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_projector_geometry_devices.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_ramp_filter_windows.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_release_2_0_1.py +0 -0
- {diffct-2.0.1 → diffct-2.0.2}/tests/test_weights.py +0 -0
|
@@ -5,6 +5,25 @@ 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
|
+
|
|
8
27
|
## [2.0.1] - 2026-10-09
|
|
9
28
|
|
|
10
29
|
### Fixed
|
|
@@ -13,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
32
|
weighted backprojections run in the CUDA context of their input. Before,
|
|
14
33
|
inputs on another GPU than the current device could use the wrong context
|
|
15
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.
|
|
16
38
|
- ``custom_trajectory_3d`` and ``custom_trajectory_2d_fan`` raise
|
|
17
39
|
``ValueError`` for a source at the isocenter or a non-finite source,
|
|
18
40
|
instead of returning NaN geometry. The figure-8 example in the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: diffct
|
|
3
|
-
Version: 2.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
|
-
> **
|
|
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
|
|
@@ -77,16 +79,19 @@ To our knowledge, diffct is the only open-source GPU CT library that combines ar
|
|
|
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
81
|
|
|
80
|
-
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).
|
|
81
83
|
|
|
82
84
|
## Install
|
|
83
85
|
|
|
84
|
-
You need
|
|
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:
|
|
85
87
|
|
|
86
88
|
```bash
|
|
87
|
-
pip install "diffct[cu12]" #
|
|
89
|
+
pip install "diffct[cu12]" # PyTorch built for CUDA 12
|
|
90
|
+
pip install "diffct[cu13]" # PyTorch built for CUDA 13
|
|
88
91
|
```
|
|
89
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
|
+
|
|
90
95
|
From source, with the examples:
|
|
91
96
|
|
|
92
97
|
```bash
|
|
@@ -129,11 +134,11 @@ loss = 0.5 * (A.project(x) - sinogram).square().sum()
|
|
|
129
134
|
loss.backward() # x.grad = A^T (A x - y)
|
|
130
135
|
```
|
|
131
136
|
|
|
132
|
-
**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).
|
|
133
138
|
|
|
134
|
-
**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).
|
|
135
140
|
|
|
136
|
-
**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).
|
|
137
142
|
|
|
138
143
|
```python
|
|
139
144
|
A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, devices=[0, 1, 2, 3])
|
|
@@ -143,8 +148,8 @@ A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, dev
|
|
|
143
148
|
|
|
144
149
|
<p align="center">
|
|
145
150
|
<picture>
|
|
146
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/scaling_dark.png">
|
|
147
|
-
<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">
|
|
148
153
|
</picture>
|
|
149
154
|
</p>
|
|
150
155
|
|
|
@@ -154,7 +159,7 @@ A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, dev
|
|
|
154
159
|
| Adjoint (backprojection) | 20.66 ms | 5.89 ms (3.5×) | 4.62 ms (4.5×) |
|
|
155
160
|
| CGLS iteration | 30.08 ms | 8.08 ms (3.7×) | 5.59 ms (5.4×) |
|
|
156
161
|
|
|
157
|
-
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](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).
|
|
158
163
|
|
|
159
164
|
To measure one against several GPUs on your machine (default scan of the script, not the table setup):
|
|
160
165
|
|
|
@@ -167,7 +172,7 @@ python examples/benchmark_projector.py --devices 0 1
|
|
|
167
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.
|
|
168
173
|
|
|
169
174
|
<p align="center">
|
|
170
|
-
<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">
|
|
171
176
|
</p>
|
|
172
177
|
|
|
173
178
|
```bash
|
|
@@ -177,7 +182,7 @@ python examples/walnut_reconstruction.py --figure out.png
|
|
|
177
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.
|
|
178
183
|
|
|
179
184
|
<p align="center">
|
|
180
|
-
<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">
|
|
181
186
|
</p>
|
|
182
187
|
|
|
183
188
|
```bash
|
|
@@ -185,22 +190,22 @@ python examples/walnut_reconstruction.py --algorithms --save-volume walnut256.np
|
|
|
185
190
|
python examples/iterative_reconstruction.py --size 256 --views 720 --trajectory helical --noise 0.01 --phantom walnut256.npy --figure out.png
|
|
186
191
|
```
|
|
187
192
|
|
|
188
|
-
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).
|
|
189
194
|
|
|
190
195
|
## Examples
|
|
191
196
|
|
|
192
|
-
`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
|
|
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).
|
|
193
198
|
|
|
194
199
|
## Documentation
|
|
195
200
|
|
|
196
201
|
| Document | Contents |
|
|
197
202
|
|---|---|
|
|
198
|
-
| [Trajectory guide](docs/source/trajectories.rst) | Trajectory tuples and `Projector` usage |
|
|
199
|
-
| [docs/REFERENCE.md](docs/REFERENCE.md) | Capabilities and limits, custom trajectories, geometry gradients, multi-GPU details, geometry and units |
|
|
200
|
-
| [docs/DISTRIBUTED.md](docs/DISTRIBUTED.md) | Execution/memory choices, distributed loss rules, Slurm and cross-node checks |
|
|
201
|
-
| [docs/MIGRATION.md](docs/MIGRATION.md) | Moving from the circular-only API |
|
|
202
|
-
| [docs/video/README.md](docs/video/README.md) | How the intro video is rendered |
|
|
203
|
-
| [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 |
|
|
204
209
|
|
|
205
210
|
Run the test suite on a CUDA host with `python -m pytest tests/ -q`.
|
|
206
211
|
|
|
@@ -232,7 +237,7 @@ If you use this library in your research, please cite the software and the techn
|
|
|
232
237
|
|
|
233
238
|
## License and acknowledgements
|
|
234
239
|
|
|
235
|
-
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
|
|
236
241
|
[PYRO-NN](https://github.com/csyben/PYRO-NN) and
|
|
237
242
|
[geometry_gradients_CT](https://github.com/mareikethies/geometry_gradients_CT).
|
|
238
|
-
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
|
-
> **
|
|
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
|
|
@@ -46,16 +48,19 @@ To our knowledge, diffct is the only open-source GPU CT library that combines ar
|
|
|
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
50
|
|
|
49
|
-
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).
|
|
50
52
|
|
|
51
53
|
## Install
|
|
52
54
|
|
|
53
|
-
You need
|
|
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:
|
|
54
56
|
|
|
55
57
|
```bash
|
|
56
|
-
pip install "diffct[cu12]" #
|
|
58
|
+
pip install "diffct[cu12]" # PyTorch built for CUDA 12
|
|
59
|
+
pip install "diffct[cu13]" # PyTorch built for CUDA 13
|
|
57
60
|
```
|
|
58
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
|
+
|
|
59
64
|
From source, with the examples:
|
|
60
65
|
|
|
61
66
|
```bash
|
|
@@ -98,11 +103,11 @@ loss = 0.5 * (A.project(x) - sinogram).square().sum()
|
|
|
98
103
|
loss.backward() # x.grad = A^T (A x - y)
|
|
99
104
|
```
|
|
100
105
|
|
|
101
|
-
**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).
|
|
102
107
|
|
|
103
|
-
**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).
|
|
104
109
|
|
|
105
|
-
**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).
|
|
106
111
|
|
|
107
112
|
```python
|
|
108
113
|
A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, devices=[0, 1, 2, 3])
|
|
@@ -112,8 +117,8 @@ A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, dev
|
|
|
112
117
|
|
|
113
118
|
<p align="center">
|
|
114
119
|
<picture>
|
|
115
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/scaling_dark.png">
|
|
116
|
-
<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">
|
|
117
122
|
</picture>
|
|
118
123
|
</p>
|
|
119
124
|
|
|
@@ -123,7 +128,7 @@ A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, dev
|
|
|
123
128
|
| Adjoint (backprojection) | 20.66 ms | 5.89 ms (3.5×) | 4.62 ms (4.5×) |
|
|
124
129
|
| CGLS iteration | 30.08 ms | 8.08 ms (3.7×) | 5.59 ms (5.4×) |
|
|
125
130
|
|
|
126
|
-
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](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).
|
|
127
132
|
|
|
128
133
|
To measure one against several GPUs on your machine (default scan of the script, not the table setup):
|
|
129
134
|
|
|
@@ -136,7 +141,7 @@ python examples/benchmark_projector.py --devices 0 1
|
|
|
136
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.
|
|
137
142
|
|
|
138
143
|
<p align="center">
|
|
139
|
-
<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">
|
|
140
145
|
</p>
|
|
141
146
|
|
|
142
147
|
```bash
|
|
@@ -146,7 +151,7 @@ python examples/walnut_reconstruction.py --figure out.png
|
|
|
146
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.
|
|
147
152
|
|
|
148
153
|
<p align="center">
|
|
149
|
-
<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">
|
|
150
155
|
</p>
|
|
151
156
|
|
|
152
157
|
```bash
|
|
@@ -154,22 +159,22 @@ python examples/walnut_reconstruction.py --algorithms --save-volume walnut256.np
|
|
|
154
159
|
python examples/iterative_reconstruction.py --size 256 --views 720 --trajectory helical --noise 0.01 --phantom walnut256.npy --figure out.png
|
|
155
160
|
```
|
|
156
161
|
|
|
157
|
-
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).
|
|
158
163
|
|
|
159
164
|
## Examples
|
|
160
165
|
|
|
161
|
-
`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
|
|
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).
|
|
162
167
|
|
|
163
168
|
## Documentation
|
|
164
169
|
|
|
165
170
|
| Document | Contents |
|
|
166
171
|
|---|---|
|
|
167
|
-
| [Trajectory guide](docs/source/trajectories.rst) | Trajectory tuples and `Projector` usage |
|
|
168
|
-
| [docs/REFERENCE.md](docs/REFERENCE.md) | Capabilities and limits, custom trajectories, geometry gradients, multi-GPU details, geometry and units |
|
|
169
|
-
| [docs/DISTRIBUTED.md](docs/DISTRIBUTED.md) | Execution/memory choices, distributed loss rules, Slurm and cross-node checks |
|
|
170
|
-
| [docs/MIGRATION.md](docs/MIGRATION.md) | Moving from the circular-only API |
|
|
171
|
-
| [docs/video/README.md](docs/video/README.md) | How the intro video is rendered |
|
|
172
|
-
| [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 |
|
|
173
178
|
|
|
174
179
|
Run the test suite on a CUDA host with `python -m pytest tests/ -q`.
|
|
175
180
|
|
|
@@ -201,7 +206,7 @@ If you use this library in your research, please cite the software and the techn
|
|
|
201
206
|
|
|
202
207
|
## License and acknowledgements
|
|
203
208
|
|
|
204
|
-
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
|
|
205
210
|
[PYRO-NN](https://github.com/csyben/PYRO-NN) and
|
|
206
211
|
[geometry_gradients_CT](https://github.com/mareikethies/geometry_gradients_CT).
|
|
207
|
-
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).
|
|
@@ -10,7 +10,6 @@ import contextlib
|
|
|
10
10
|
import functools
|
|
11
11
|
import inspect
|
|
12
12
|
import math
|
|
13
|
-
import sys
|
|
14
13
|
import weakref
|
|
15
14
|
import numpy as np
|
|
16
15
|
import torch
|
|
@@ -191,16 +190,20 @@ def _on_device_of(name):
|
|
|
191
190
|
def _keep_cuda_modules_at_exit():
|
|
192
191
|
"""Stop numba-cuda from unloading CUDA modules while the interpreter exits.
|
|
193
192
|
|
|
194
|
-
numba-cuda queues module unloads and
|
|
195
|
-
|
|
196
|
-
GPUs, a flush during interpreter shutdown can unload the same
|
|
197
|
-
and corrupt the heap, so the process crashes after all work
|
|
198
|
-
process exit releases these resources anyway.
|
|
193
|
+
numba-cuda queues module unloads and other deallocations, and flushes a
|
|
194
|
+
queue once it passes a count or byte limit. After kernels were loaded on
|
|
195
|
+
several GPUs, a flush during interpreter shutdown can unload the same
|
|
196
|
+
library twice and corrupt the heap, so the process crashes after all work
|
|
197
|
+
is done. The process exit releases these resources anyway.
|
|
198
|
+
|
|
199
|
+
Raising the limits is not enough: the byte limit is
|
|
200
|
+
``int(capacity * CUDA_DEALLOCS_RATIO)``, and queues with no capacity set
|
|
201
|
+
have a limit of 0 bytes for any finite ratio. numba-cuda skips the flush
|
|
202
|
+
while a queue is disabled, so disable every queue for the rest of the exit.
|
|
199
203
|
"""
|
|
200
|
-
from numba.cuda.
|
|
204
|
+
from numba.cuda.cudadrv.driver import _PendingDeallocs
|
|
201
205
|
|
|
202
|
-
|
|
203
|
-
cuda_config.CUDA_DEALLOCS_RATIO = float("inf")
|
|
206
|
+
_PendingDeallocs.is_disabled = property(lambda self: True)
|
|
204
207
|
|
|
205
208
|
|
|
206
209
|
class _ExitOrderAnchor:
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Intro video
|
|
2
|
+
|
|
3
|
+
`diffct_intro.py` is the Manim Community (v0.21) source of `docs/assets/diffct_intro.gif`
|
|
4
|
+
and `docs/assets/diffct_intro.mp4`. Rendering needs Manim, LaTeX and FFmpeg.
|
|
5
|
+
The scenes read three files in `docs/video/`:
|
|
6
|
+
|
|
7
|
+
- `walnut_measured.npz`: measured projections and reconstruction slices.
|
|
8
|
+
- `data2d.npz`: a walnut slice and a display sinogram made with SciPy.
|
|
9
|
+
- `calib_history.json`: saved results from a separate geometry calibration run.
|
|
10
|
+
|
|
11
|
+
These inputs are included in the checkout. Rendering them does not need CUDA.
|
|
12
|
+
The scenes do not read `recon_slices.npz` or `recon_psnr.json`.
|
|
13
|
+
|
|
14
|
+
To rebuild the inputs, install SciPy in the environment that runs `make_inputs.py`.
|
|
15
|
+
SciPy is not a core diffct dependency.
|
|
16
|
+
Run these commands from the repository root:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python -m pip install scipy
|
|
20
|
+
python docs/video/make_inputs.py --gpu
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`--gpu` needs a CUDA-enabled diffct installation. It writes `walnut_measured.npz`,
|
|
24
|
+
`recon_slices.npz`, `recon_psnr.json` and `data2d.npz`.
|
|
25
|
+
Without `--gpu`, the script writes only `data2d.npz` and reads the existing `walnut_measured.npz`.
|
|
26
|
+
Neither mode writes `calib_history.json`. The calibration example does not export this JSON file.
|
|
27
|
+
|
|
28
|
+
Render and join the scenes:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cd docs/video
|
|
32
|
+
rm -f scenes.txt
|
|
33
|
+
for s in S00Intro S01Trajectory S02Autograd S03Geometry S04MultiGPU S05Measured S06End; do
|
|
34
|
+
manim -qh diffct_intro.py $s
|
|
35
|
+
echo "file 'media/videos/diffct_intro/1080p60/$s.mp4'" >> scenes.txt
|
|
36
|
+
done
|
|
37
|
+
ffmpeg -f concat -safe 0 -i scenes.txt -c:v libx264 -preset slow -crf 26 -pix_fmt yuv420p -r 30 \
|
|
38
|
+
-movflags +faststart -an ../assets/diffct_intro.mp4
|
|
39
|
+
ffmpeg -i ../assets/diffct_intro.mp4 -vf "fps=8,scale=800:-1:flags=lanczos,palettegen=max_colors=64:stats_mode=full" palette.png
|
|
40
|
+
ffmpeg -i ../assets/diffct_intro.mp4 -i palette.png \
|
|
41
|
+
-lavfi "fps=8,scale=800:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=none" ../assets/diffct_intro.gif
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The reconstructions come from the measured walnut in `examples/data/walnut_cone.npz`
|
|
45
|
+
(Meaney 2022, CC BY 4.0; see `examples/data/NOTICE`). The timings in the multi-GPU scene
|
|
46
|
+
are measured diffct results on A100 64 GB GPUs; see `docs/assets/scaling.json`.
|
|
47
|
+
The scene stores these timings as constants. Rebuilding the inputs does not update them.
|
|
@@ -21,10 +21,12 @@ python examples/iterative_reconstruction.py --size 32 --views 32 \
|
|
|
21
21
|
--trajectory helical --algorithms cgls --iterations 5
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
This still computes an FDK baseline
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
iterations
|
|
24
|
+
This still computes an FDK baseline. Five CGLS steps show how to use the script.
|
|
25
|
+
The default iterative run uses a 128³ volume, 360 views, and CGLS/SIRT/TV with 30/200/200
|
|
26
|
+
iterations respectively. Its TV weight is 1.0.
|
|
27
|
+
The measured walnut script uses SIRT/CGLS/TV with 200/20/300 iterations and TV weight 0.3.
|
|
28
|
+
These weights are example settings. Choose the TV weight for your geometry,
|
|
29
|
+
voxel spacing, resolution and data scale.
|
|
28
30
|
|
|
29
31
|
To inspect a helical trajectory without a GPU (PyTorch and matplotlib required):
|
|
30
32
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "diffct"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.2"
|
|
8
8
|
description = "Differentiable CUDA projectors for CT: arbitrary trajectories, multi-GPU and multi-node execution, geometry gradients"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""CPU regression checks for CUDA deallocation queues during shutdown."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.parametrize("capacity", [None, 80 * 1024**3], ids=["unset", "80-GiB"])
|
|
9
|
+
@pytest.mark.parametrize("with_size", [False, True], ids=["unsized", "sized"])
|
|
10
|
+
def test_exit_hook_keeps_cuda_deallocations_pending(monkeypatch, capacity, with_size):
|
|
11
|
+
try:
|
|
12
|
+
from numba.cuda.cudadrv import driver
|
|
13
|
+
from numba.cuda.cudadrv.driver import _PendingDeallocs
|
|
14
|
+
except ImportError as error:
|
|
15
|
+
pytest.skip(f"CUDA deallocation queue cannot be imported: {error}")
|
|
16
|
+
|
|
17
|
+
from numba.core import config
|
|
18
|
+
from diffct.utils import _keep_cuda_modules_at_exit
|
|
19
|
+
|
|
20
|
+
# Queue logging normally starts when a CUDA driver is initialized.
|
|
21
|
+
monkeypatch.setattr(driver, "_logger", logging.getLogger(__name__), raising=False)
|
|
22
|
+
# Capture all shared state before the hook can replace or add attributes.
|
|
23
|
+
class_attributes = dict(vars(_PendingDeallocs))
|
|
24
|
+
configurations = {id(configuration): configuration for configuration in (config, driver.config)}
|
|
25
|
+
configuration_attributes = {
|
|
26
|
+
identifier: {
|
|
27
|
+
name: value
|
|
28
|
+
for name, value in vars(configuration).items()
|
|
29
|
+
if name.startswith("CUDA_DEALLOCS_")
|
|
30
|
+
}
|
|
31
|
+
for identifier, configuration in configurations.items()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
queue = _PendingDeallocs() if capacity is None else _PendingDeallocs(capacity)
|
|
35
|
+
destroyed = []
|
|
36
|
+
|
|
37
|
+
def destructor(handle):
|
|
38
|
+
destroyed.append(handle)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
_keep_cuda_modules_at_exit()
|
|
42
|
+
|
|
43
|
+
# Sizes are bookkeeping values; this test does not allocate CUDA memory.
|
|
44
|
+
size = (80 * 1024**3) // 32
|
|
45
|
+
for handle in range(64):
|
|
46
|
+
if with_size:
|
|
47
|
+
queue.add_item(destructor, handle, size)
|
|
48
|
+
else:
|
|
49
|
+
queue.add_item(destructor, handle)
|
|
50
|
+
assert destroyed == []
|
|
51
|
+
assert len(queue) == handle + 1
|
|
52
|
+
finally:
|
|
53
|
+
for name in set(vars(_PendingDeallocs)) - class_attributes.keys():
|
|
54
|
+
delattr(_PendingDeallocs, name)
|
|
55
|
+
for name, value in class_attributes.items():
|
|
56
|
+
if name not in vars(_PendingDeallocs) or vars(_PendingDeallocs)[name] is not value:
|
|
57
|
+
setattr(_PendingDeallocs, name, value)
|
|
58
|
+
for identifier, configuration in configurations.items():
|
|
59
|
+
saved = configuration_attributes[identifier]
|
|
60
|
+
for name in tuple(vars(configuration)):
|
|
61
|
+
if name.startswith("CUDA_DEALLOCS_") and name not in saved:
|
|
62
|
+
delattr(configuration, name)
|
|
63
|
+
for name, value in saved.items():
|
|
64
|
+
setattr(configuration, name, value)
|
|
65
|
+
|
|
66
|
+
assert dict(vars(_PendingDeallocs)) == class_attributes
|
|
67
|
+
for identifier, configuration in configurations.items():
|
|
68
|
+
assert {
|
|
69
|
+
name: value
|
|
70
|
+
for name, value in vars(configuration).items()
|
|
71
|
+
if name.startswith("CUDA_DEALLOCS_")
|
|
72
|
+
} == configuration_attributes[identifier]
|
|
73
|
+
|
|
74
|
+
# A fresh queue must regain the stock count-triggered flush after restoration.
|
|
75
|
+
stock_queue = _PendingDeallocs()
|
|
76
|
+
stock_destroyed = []
|
|
77
|
+
count_limit = driver.config.CUDA_DEALLOCS_COUNT
|
|
78
|
+
for handle in range(count_limit):
|
|
79
|
+
stock_queue.add_item(stock_destroyed.append, handle)
|
|
80
|
+
assert stock_destroyed == []
|
|
81
|
+
stock_queue.add_item(stock_destroyed.append, count_limit)
|
|
82
|
+
assert stock_destroyed == list(range(count_limit + 1))
|
|
83
|
+
assert len(stock_queue) == 0
|
|
@@ -81,3 +81,8 @@ def test_multi_gpu_exit():
|
|
|
81
81
|
f"CUDA subprocess exited with return code {child.returncode}\n"
|
|
82
82
|
f"Child stderr tail:\n{child.stderr[-8000:]}"
|
|
83
83
|
)
|
|
84
|
+
for marker in ("Exception ignored", "Traceback", "ValueError", "OverflowError"):
|
|
85
|
+
assert marker not in child.stderr, (
|
|
86
|
+
f"CUDA subprocess reported {marker!r} during cleanup\n"
|
|
87
|
+
f"Child stderr tail:\n{child.stderr[-8000:]}"
|
|
88
|
+
)
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Intro video
|
|
2
|
-
|
|
3
|
-
`diffct_intro.py` is the Manim Community (v0.21) source of `docs/assets/diffct_intro.gif`
|
|
4
|
-
and `docs/assets/diffct_intro.mp4`. Make the inputs on a CUDA GPU, then render and join the scenes:
|
|
5
|
-
|
|
6
|
-
```bash
|
|
7
|
-
python docs/video/make_inputs.py --gpu # walnut reconstructions; without --gpu only data2d.npz
|
|
8
|
-
cd docs/video
|
|
9
|
-
rm -f scenes.txt
|
|
10
|
-
for s in S00Intro S01Trajectory S02Autograd S03Geometry S04MultiGPU S05Measured S06End; do
|
|
11
|
-
manim -qh diffct_intro.py $s
|
|
12
|
-
echo "file 'media/videos/diffct_intro/1080p60/$s.mp4'" >> scenes.txt
|
|
13
|
-
done
|
|
14
|
-
ffmpeg -f concat -safe 0 -i scenes.txt -c:v libx264 -preset slow -crf 26 -pix_fmt yuv420p -r 30 \
|
|
15
|
-
-movflags +faststart -an ../assets/diffct_intro.mp4
|
|
16
|
-
ffmpeg -i ../assets/diffct_intro.mp4 -vf "fps=8,scale=800:-1:flags=lanczos,palettegen=max_colors=64:stats_mode=full" palette.png
|
|
17
|
-
ffmpeg -i ../assets/diffct_intro.mp4 -i palette.png \
|
|
18
|
-
-lavfi "fps=8,scale=800:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=none" ../assets/diffct_intro.gif
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
The reconstructions come from the measured walnut in `examples/data/walnut_cone.npz`
|
|
22
|
-
(Meaney 2022, CC BY 4.0; see `examples/data/NOTICE`). The timings in the multi-GPU scene
|
|
23
|
-
are measured diffct results on A100 64 GB GPUs; see `docs/assets/scaling.json`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|