diffct 1.3.0__tar.gz → 1.3.1__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 (59) hide show
  1. {diffct-1.3.0 → diffct-1.3.1}/CHANGELOG.md +363 -295
  2. diffct-1.3.1/PKG-INFO +382 -0
  3. diffct-1.3.1/README.md +365 -0
  4. diffct-1.3.1/README.zh.md +335 -0
  5. {diffct-1.3.0 → diffct-1.3.1}/diffct/differentiable.py +771 -48
  6. {diffct-1.3.0 → diffct-1.3.1}/docs/source/api.rst +291 -291
  7. {diffct-1.3.0 → diffct-1.3.1}/docs/source/fbp_fan_example.rst +158 -158
  8. {diffct-1.3.0 → diffct-1.3.1}/docs/source/fbp_parallel_example.rst +117 -117
  9. {diffct-1.3.0 → diffct-1.3.1}/docs/source/fdk_cone_example.rst +184 -184
  10. diffct-1.3.1/examples/data/NOTICE +60 -0
  11. diffct-1.3.1/examples/data/preprocess_walnut.py +173 -0
  12. diffct-1.3.1/examples/data/walnut_cone.npz +0 -0
  13. diffct-1.3.1/examples/data/walnut_reco.png +0 -0
  14. {diffct-1.3.0 → diffct-1.3.1}/examples/fbp_fan.py +354 -333
  15. {diffct-1.3.0 → diffct-1.3.1}/examples/fbp_parallel.py +225 -225
  16. {diffct-1.3.0 → diffct-1.3.1}/examples/fdk_cone.py +493 -477
  17. {diffct-1.3.0 → diffct-1.3.1}/examples/iterative_reco_cone.py +1 -1
  18. diffct-1.3.1/examples/realdata_fbp_fan.py +297 -0
  19. diffct-1.3.1/examples/realdata_fbp_parallel.py +267 -0
  20. diffct-1.3.1/examples/realdata_fdk_cone.py +343 -0
  21. diffct-1.3.1/examples/realdata_walnut_fdk.py +275 -0
  22. {diffct-1.3.0 → diffct-1.3.1}/pyproject.toml +1 -1
  23. {diffct-1.3.0 → diffct-1.3.1}/pytest.ini +14 -14
  24. {diffct-1.3.0 → diffct-1.3.1}/tests/benchmarks/README.md +60 -60
  25. {diffct-1.3.0 → diffct-1.3.1}/tests/benchmarks/_common.py +62 -62
  26. {diffct-1.3.0 → diffct-1.3.1}/tests/benchmarks/test_bench_cone.py +145 -145
  27. {diffct-1.3.0 → diffct-1.3.1}/tests/benchmarks/test_bench_fan.py +127 -127
  28. {diffct-1.3.0 → diffct-1.3.1}/tests/benchmarks/test_bench_parallel.py +115 -115
  29. {diffct-1.3.0 → diffct-1.3.1}/tests/test_adjoint_inner_product.py +286 -286
  30. {diffct-1.3.0 → diffct-1.3.1}/tests/test_cone_projector_autograd.py +91 -91
  31. {diffct-1.3.0 → diffct-1.3.1}/tests/test_fbp_fan_accuracy.py +147 -147
  32. {diffct-1.3.0 → diffct-1.3.1}/tests/test_fbp_fan_offsets.py +162 -162
  33. {diffct-1.3.0 → diffct-1.3.1}/tests/test_fbp_parallel_accuracy.py +164 -164
  34. {diffct-1.3.0 → diffct-1.3.1}/tests/test_fdk_cone_accuracy.py +197 -197
  35. {diffct-1.3.0 → diffct-1.3.1}/tests/test_fdk_cone_offsets.py +210 -210
  36. {diffct-1.3.0 → diffct-1.3.1}/tests/test_gradcheck.py +209 -209
  37. {diffct-1.3.0 → diffct-1.3.1}/tests/test_ramp_filter_windows.py +217 -217
  38. diffct-1.3.0/PKG-INFO +0 -173
  39. diffct-1.3.0/README.md +0 -156
  40. {diffct-1.3.0 → diffct-1.3.1}/.github/workflows/docs.yml +0 -0
  41. {diffct-1.3.0 → diffct-1.3.1}/.github/workflows/release.yml +0 -0
  42. {diffct-1.3.0 → diffct-1.3.1}/.gitignore +0 -0
  43. {diffct-1.3.0 → diffct-1.3.1}/LICENSE +0 -0
  44. {diffct-1.3.0 → diffct-1.3.1}/diffct/__init__.py +0 -0
  45. {diffct-1.3.0 → diffct-1.3.1}/docs/Makefile +0 -0
  46. {diffct-1.3.0 → diffct-1.3.1}/docs/source/_static/.gitkeep +0 -0
  47. {diffct-1.3.0 → diffct-1.3.1}/docs/source/conf.py +0 -0
  48. {diffct-1.3.0 → diffct-1.3.1}/docs/source/examples.rst +0 -0
  49. {diffct-1.3.0 → diffct-1.3.1}/docs/source/getting_started.rst +0 -0
  50. {diffct-1.3.0 → diffct-1.3.1}/docs/source/index.rst +0 -0
  51. {diffct-1.3.0 → diffct-1.3.1}/docs/source/iterative_reco_cone_example.rst +0 -0
  52. {diffct-1.3.0 → diffct-1.3.1}/docs/source/iterative_reco_fan_example.rst +0 -0
  53. {diffct-1.3.0 → diffct-1.3.1}/docs/source/iterative_reco_parallel_example.rst +0 -0
  54. {diffct-1.3.0 → diffct-1.3.1}/examples/iterative_reco_fan.py +0 -0
  55. {diffct-1.3.0 → diffct-1.3.1}/examples/iterative_reco_parallel.py +0 -0
  56. {diffct-1.3.0 → diffct-1.3.1}/requirements.txt +0 -0
  57. {diffct-1.3.0 → diffct-1.3.1}/tests/benchmarks/__init__.py +0 -0
  58. {diffct-1.3.0 → diffct-1.3.1}/tests/test_cuda_smoke.py +0 -0
  59. {diffct-1.3.0 → diffct-1.3.1}/tests/test_weights.py +0 -0
@@ -1,295 +1,363 @@
1
- # Changelog
2
-
3
- All notable changes to this project are documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ## [1.3.0] - 2026-04-14
11
-
12
- ### Added
13
- - **Separable-footprint (SF) projector backends** for 2D fan and 3D cone
14
- beam geometries, following Long-Fessler-Balter (IEEE TMI 2010).
15
- `FanProjectorFunction` / `FanBackprojectorFunction` now accept
16
- `backend="siddon"` (default, unchanged) or `backend="sf"`.
17
- `ConeProjectorFunction` / `ConeBackprojectorFunction` accept
18
- `backend="siddon"` (default), `"sf_tr"` (trapezoid in transaxial,
19
- rectangle in axial) or `"sf_tt"` (trapezoid in both directions with
20
- axial rise/fall from `U_near`/`U_far` across the voxel). Each SF
21
- backend is implemented as a matched scatter/gather pair of CUDA
22
- kernels so both forward and adjoint are available — autograd works
23
- end-to-end for every backend, and the standalone Backprojector
24
- Function can also be driven in SF mode for iterative reconstruction.
25
- - **Adjoint inner-product tests** for every new SF backend in
26
- `tests/test_adjoint_inner_product.py`, plus `torch.autograd.gradcheck`
27
- coverage in `tests/test_gradcheck.py`. Test count grew from 57 to 66.
28
-
29
- ### Changed
30
- - **Internal grid-point convention** is now uniformly `x_v = ix - cx`
31
- (matching `_cone_3d_fdk_backproject_kernel`). The SF kernels use the
32
- same convention so voxel centers align with where the Siddon/FDK
33
- paths interpret `d_vol[ix, iy, iz]`.
34
- - **`_TPB_SF_3D = (4, 4, 4)`** introduced for the 3D SF kernels. SF-TT
35
- in particular exceeds the 128-register-per-thread budget allowed by
36
- the default `(8, 8, 8)` launch; the smaller block gives each thread
37
- ~1024 registers and avoids `CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES`.
38
-
39
- ### Notes
40
- - **Raw forward accuracy vs analytical Radon**: on both 2D fan
41
- (512² Shepp-Logan) and 3D cone (96³ and 128³ 3D Shepp-Logan) phantoms,
42
- measured against the analytical ellipse / ellipsoid Radon transform,
43
- the existing Siddon projector with bilinear / trilinear interpolation
44
- is slightly more accurate (~3% better RMSE, ~15% better max error)
45
- than SF-TR / SF-TT at the thin-ray sampling level. The classic SF
46
- advantage reported in Long et al. is over the piecewise-constant DD
47
- projector; diffct's Siddon uses smooth interpolation, which covers
48
- most of the gap SF was designed to close.
49
- - **Full analytical reconstruction (forward + FBP/FDK)**: despite the
50
- raw-forward result above, in a complete analytical pipeline
51
- (`FanProjectorFunction` / `ConeProjectorFunction` -> `ramp_filter_1d`
52
- -> `fan_weighted_backproject` / `cone_weighted_backproject`) SF
53
- **measurably improves reconstruction quality**. On the 256²
54
- Shepp-Logan `fbp_fan.py` example the raw MSE drops from
55
- `0.00216` (Siddon) to `0.00178` (SF-TR), a ~17% improvement.
56
- On the 128³ `fdk_cone.py` example it drops from `0.00325` (Siddon)
57
- to `0.00271` (SF-TR), again ~17%. The mechanism is that SF's
58
- mass-conserving cell-integrated forward pairs more naturally with
59
- the voxel-driven FBP/FDK gather backprojector than Siddon's
60
- thin-ray sampling, which feeds extra high-frequency ringing into
61
- the ramp filter.
62
- - **Performance**: SF-TR is ~2× and SF-TT ~2.5–4× slower than Siddon for
63
- cone beam, and ~3× slower than Siddon for fan beam. The extra cost
64
- comes from voxel-driven scatter with atomic adds (forward) versus
65
- ray-driven parallelism in Siddon.
66
- - **Recommendation**: `backend="siddon"` remains the default for
67
- backward compatibility and raw speed. For **analytical FBP/FDK
68
- reconstruction** where reconstruction MSE matters more than forward
69
- runtime, `"sf"` / `"sf_tr"` is a quantifiably better choice — at
70
- 2–3× forward cost you get ~17% lower reconstruction MSE on standard
71
- phantoms. For **iterative reconstruction with autograd**, either
72
- backend is valid; SF has the theoretical advantage of exact voxel-
73
- scale mass conservation on both forward and adjoint, but in practice
74
- Siddon's smoothing usually helps optimizer convergence. SF-TT's
75
- axial-trapezoid refinement over SF-TR improves RMSE only by ~0.001
76
- and is not worth the extra cost for typical cone angles; use SF-TR
77
- by default and reserve SF-TT for extreme cone-angle research.
78
-
79
- ## [1.2.11] - 2026-04-14
80
-
81
- ### Added
82
- - **Parker short-scan demos** in `examples/fdk_cone.py` and `examples/fbp_fan.py`.
83
- Both examples now expose an `apply_parker` switch that not only multiplies
84
- by `parker_weights` but also samples angles on the minimal short-scan range
85
- `pi + 2*gamma_max`, where `gamma_max` is computed from the detector half-width
86
- and `sdd`. (Before this change the `apply_parker` flag in `fbp_fan.py` was
87
- silently a no-op because the angular range stayed at `2*pi`.)
88
- - **`tests/test_gradcheck.py`** — `torch.autograd.gradcheck` coverage for
89
- `ParallelProjectorFunction`, `FanProjectorFunction`, and `ConeProjectorFunction`.
90
- Compares the analytical backward Jacobian to a finite-difference numerical
91
- Jacobian over small inputs with float32-calibrated tolerances. Strongest
92
- autograd-correctness guard beyond the existing inner-product adjoint tests.
93
- - **`tests/test_ramp_filter_windows.py`** — 27 parametrised tests covering every
94
- `ramp_filter_1d` window option (`None`/`"ram-lak"`, `"hann"`, `"hanning"`,
95
- `"hamming"`, `"cosine"`, `"shepp-logan"`) at three layers: direct `_ramp_window`
96
- helper (DC gain, Nyquist value, monotonicity), full `ramp_filter_1d` end-to-end
97
- (shape, DC annihilation, pad correctness, rfft vs complex-fft parity,
98
- `sample_spacing` scaling), and a high-frequency attenuation sanity check on a
99
- step input.
100
- - **`CHANGELOG.md`** — new Keep-a-Changelog file at the repo root.
101
- - **`tests/benchmarks/`** — opt-in `pytest-benchmark` suite covering every CUDA
102
- kernel in the library (forward projector, pure-adjoint backprojector, and
103
- analytical FBP/FDK pipeline) across three sizes for each of the three
104
- geometries. 27 benchmarks total. Excluded from the default `pytest tests/`
105
- run via `--ignore=tests/benchmarks` in `pytest.ini` so the normal test suite
106
- stays fast; run explicitly with `pytest tests/benchmarks/ --benchmark-only`.
107
- Enables before/after perf comparisons via `--benchmark-save=` /
108
- `--benchmark-compare=`.
109
-
110
- ### Changed
111
- - **`_cone_3d_fdk_backproject_kernel`** memory-access layout. The kernel now
112
- unpacks `cuda.grid(3)` as `(iz, iy, ix)` instead of `(ix, iy, iz)`, and
113
- `cone_weighted_backproject` launches the grid as `_grid_3d(D, H, W)` instead
114
- of `_grid_3d(W, H, D)`. This makes `iz` warp-adjacent, which matches the
115
- innermost stride-1 axis of the WHD output buffer, so the final `d_vol` write
116
- becomes coalesced. Measured speedup on a 64^3–160^3 sweep: 1.0x–1.14x,
117
- within run-to-run noise (the kernel is read-bound on the sinogram bilinear
118
- samples, not write-bound, so L2 absorbed the old uncoalesced writes). The
119
- fix is kept as the right default and future-proofs the kernel against
120
- edits that shift work toward the write path. Matching coalescing comments
121
- were added to the 2D `_parallel_2d_fbp_backproject_kernel` and
122
- `_fan_2d_fbp_backproject_kernel` kernels, which were already coalesced
123
- (their first grid index is also warp-adjacent and matches the `Nx` innermost
124
- axis of their `(Ny, Nx)` output buffers).
125
-
126
- ### Notes
127
- - No public-API changes. Pure-adjoint kernels and autograd Function classes
128
- are untouched, so the autograd and iterative-reconstruction paths are
129
- byte-for-byte equivalent to 1.2.10.
130
-
131
- ## [1.2.10] - 2026-04-14
132
-
133
- This release is a correctness overhaul of every analytical reconstruction path
134
- (parallel / fan / cone) **and** of the autograd adjoint path shared by the
135
- iterative examples. It also ships a unified architecture where each geometry
136
- has its own dedicated voxel-driven FBP/FDK gather kernel, separate from the
137
- pure Siddon adjoint used by autograd.
138
-
139
- ### Fixed
140
-
141
- #### Analytical FBP / FDK amplitude bugs
142
-
143
- Prior to this release, all three analytical reconstruction examples produced
144
- results that were off by large constant factors:
145
-
146
- | Example | Pre-fix reco range | Post-fix reco range | MSE improvement |
147
- |---|---|---|---|
148
- | `fbp_parallel.py` | [0, 6.52] | [−0.02, 1.01] | ~580× |
149
- | `fbp_fan.py` | [0, 6.33] | [−0.08, 1.01] | ~900× |
150
- | `fdk_cone.py` | [0, 10.08] | [−0.08, 1.00] | ~600× |
151
-
152
- Root causes:
153
- - Cone and fan used `(sdd/U)^2` with `U = sdd + x·sin − y·cos` instead of
154
- the correct FDK weight `(sid/U)^2` with `U = sid + x·sin − y·cos`.
155
- - All three geometries were missing the `1/(2π)` Fourier-convention
156
- constant from the FBP/FDK reconstruction formula.
157
- - Cone and fan used a Siddon ray-driven scatter for the analytical path,
158
- which is neither a true adjoint nor a classical FBP/FDK gather.
159
-
160
- #### Autograd adjoint bug (cone + fan)
161
-
162
- `ConeProjectorFunction.backward`, `ConeBackprojectorFunction.forward`,
163
- `FanProjectorFunction.backward` and `FanBackprojectorFunction.forward`
164
- were all passing `distance_weight=1.0` to the shared Siddon backward
165
- kernel. This meant the autograd backward was **not the true adjoint** `P^T`
166
- of the forward projector — it had a `(sdd/U)^2` per-voxel factor baked in,
167
- biasing the gradient by ~2–3× depending on voxel position. Any iterative
168
- reconstruction that relied on autograd (including `iterative_reco_cone.py`
169
- and `iterative_reco_fan.py`) was running on a biased gradient flow.
170
-
171
- Parallel beam was unaffected (its backward kernel had no `distance_weight`
172
- parameter to begin with).
173
-
174
- This release removes the `distance_weight` parameter from both
175
- `_cone_3d_backward_kernel` and `_fan_2d_backward_kernel` entirely, so the
176
- dead code path cannot be accidentally reintroduced, and fixes the four
177
- autograd call sites to use the pure adjoint.
178
-
179
- ### Added
180
-
181
- #### Voxel-driven gather kernels for every geometry
182
-
183
- Three new CUDA kernels, all under a dedicated `fastmath=False` decorator
184
- for FDK-grade accuracy:
185
-
186
- - `_parallel_2d_fbp_backproject_kernel` — no distance weighting (no source).
187
- - `_fan_2d_fbp_backproject_kernel` — `(sid/U)^2` + linear detector interp.
188
- - `_cone_3d_fdk_backproject_kernel` — `(sid/U)^2` + bilinear detector interp.
189
-
190
- Each kernel is voxel-driven: one thread per output pixel/voxel, loops over
191
- views, computes the projected detector coordinate, interpolates the filtered
192
- sinogram, weights and accumulates.
193
-
194
- #### `parallel_weighted_backproject` (new public helper)
195
-
196
- Mirrors `fan_weighted_backproject` and `cone_weighted_backproject`. Applies
197
- the `1/(2π)` Fourier-convention constant so a unit-density disk reconstructs
198
- to amplitude 1. Exported from `diffct`.
199
-
200
- #### `ramp_filter_1d` — new backward-compatible kwargs
201
-
202
- - `sample_spacing` (default `1.0`): physical detector cell pitch. Output is
203
- rescaled by `1/sample_spacing` for physical-unit correctness.
204
- - `pad_factor` (default `1`): zero-pad to `pad_factor * N` before the FFT to
205
- suppress circular-convolution wrap-around. `2` is recommended for FBP/FDK.
206
- - `window` (default `None`): frequency-domain apodization. Options: `None` /
207
- `"ram-lak"`, `"hann"`, `"hamming"`, `"cosine"`, `"shepp-logan"`.
208
- - `use_rfft` (default `True`): faster real-FFT path for real-valued inputs.
209
-
210
- Existing `ramp_filter_1d(sino, dim=1)` calls keep working unchanged.
211
-
212
- #### Analytical FBP / FDK scale factors
213
-
214
- Each analytical helper now applies the correct analytical constant
215
- automatically so reconstructions are already amplitude-calibrated:
216
-
217
- - `parallel_weighted_backproject`: `1 / (2π)`.
218
- - `fan_weighted_backproject`: `sdd / (2π · sid)`.
219
- - `cone_weighted_backproject`: `sdd / (2π · sid)`.
220
-
221
- ### Tests
222
-
223
- Test suite expanded from 6 to **27 tests**:
224
-
225
- - `tests/test_adjoint_inner_product.py` — the definitive check. For random `x`
226
- and `y`, asserts `⟨A x, y⟩ = ⟨x, A^T y⟩` for parallel, fan and cone autograd
227
- pairs. Permanently guards against any regression that would reintroduce the
228
- `distance_weight=1.0` bug.
229
- - `tests/test_fdk_cone_accuracy.py` — 128³ Shepp-Logan RMSE / amplitude bounds.
230
- - `tests/test_fdk_cone_offsets.py` — detector, center and combined offsets with
231
- amplitude assertions.
232
- - `tests/test_cone_projector_autograd.py` — gradient finiteness and non-zero
233
- sanity.
234
- - `tests/test_fbp_fan_accuracy.py` — 256×256 Shepp-Logan RMSE.
235
- - `tests/test_fbp_fan_offsets.py` — three offset configurations.
236
- - `tests/test_fbp_parallel_accuracy.py` — parallel FBP RMSE + offset.
237
-
238
- ### Examples
239
-
240
- `examples/fdk_cone.py`, `examples/fbp_fan.py` and `examples/fbp_parallel.py`
241
- have been rewritten with a consistent 8-step layout and detailed inline
242
- comments documenting every geometry variable (what it is, units, typical
243
- values, available options). Each example prints raw MSE, clamped MSE, and the
244
- reconstruction / phantom data ranges.
245
-
246
- The iterative reconstruction examples (`iterative_reco_parallel.py`,
247
- `iterative_reco_fan.py`, `iterative_reco_cone.py`) are unchanged — they
248
- silently benefit from the fixed autograd adjoint.
249
-
250
- ### Documentation
251
-
252
- - `docs/source/api.rst`: adds autofunction entries for
253
- `parallel_weighted_backproject`, documents the new `ramp_filter_1d`
254
- options, and adds an "Analytical FBP / FDK architecture" section
255
- explaining the scale-factor derivation.
256
- - `docs/source/fdk_cone_example.rst`: formula updated to reflect the
257
- voxel-gather kernel and `(sid/U)^2` weight.
258
- - `docs/source/fbp_fan_example.rst` and `fbp_parallel_example.rst`: completely
259
- rewritten to reference the new analytical helpers and fix a sign-convention
260
- inconsistency in the math section.
261
-
262
- ### Compatibility
263
-
264
- This release is source-compatible with 1.2.9 for every public entry point. The
265
- `distance_weight` parameter was internal and is removed from the private
266
- backward kernels; no public API touched that argument.
267
-
268
- ## [1.2.9] - 2026-02-14
269
-
270
- Restored `main` to the state of 1.2.9 by reverting an accidental merge.
271
- See [GitHub release v1.2.9](https://github.com/sypsyp97/diffct/releases/tag/v1.2.9).
272
-
273
- ## [1.2.8] - 2026-02-14
274
-
275
- See [GitHub release v1.2.8](https://github.com/sypsyp97/diffct/releases/tag/v1.2.8)
276
- and the commit history for details.
277
-
278
- ## [1.2.7] - 2025-08-05
279
-
280
- See [GitHub release v1.2.7](https://github.com/sypsyp97/diffct/releases/tag/v1.2.7).
281
-
282
- ## Earlier versions
283
-
284
- Releases 1.2.0 through 1.2.6 and the 1.1.x / 1.0.x lines are tracked on
285
- [GitHub releases](https://github.com/sypsyp97/diffct/releases). This CHANGELOG
286
- was introduced in 1.2.10 and does not back-fill detailed notes for earlier
287
- versions beyond pointers to the GitHub release pages.
288
-
289
- [Unreleased]: https://github.com/sypsyp97/diffct/compare/v1.3.0...HEAD
290
- [1.3.0]: https://github.com/sypsyp97/diffct/releases/tag/v1.3.0
291
- [1.2.11]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.11
292
- [1.2.10]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.10
293
- [1.2.9]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.9
294
- [1.2.8]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.8
295
- [1.2.7]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.7
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.3.1] - 2026-04-15
11
+
12
+ ### Added
13
+ - **Real-data walnut FDK example** at `examples/realdata_walnut_fdk.py`,
14
+ running the full analytical FDK pipeline on a preprocessed subset of
15
+ the Helsinki walnut cone-beam CT dataset ([Meaney 2022, Zenodo
16
+ 10.5281/zenodo.6986012](https://doi.org/10.5281/zenodo.6986012),
17
+ CC-BY 4.0). The 241-view, 256×256-per-view sinogram is shipped at
18
+ `examples/data/walnut_cone.npz` (~25 MB, float16, 8× binned, 256²
19
+ center crop) together with a `preprocess_walnut.py` regenerator
20
+ script, an attribution `NOTICE`, and a `walnut_reco.png` sample
21
+ montage. Reconstructs at half the nominal voxel size on a 512³ grid
22
+ using `backend="sf_tr"` + Hamming ramp window.
23
+ - **Synthetic "real-data" pipeline examples** for all three geometries
24
+ (`examples/realdata_fbp_parallel.py`, `realdata_fbp_fan.py`,
25
+ `realdata_fdk_cone.py`) that run a Beer-Lambert + Poisson + `-log`
26
+ forward model on Shepp-Logan and reconstruct with the analytical
27
+ pipeline, showing the proper place for `-log` preprocessing.
28
+ - **Chinese README** at `README.zh.md`, mirroring `README.md` with
29
+ bidirectional language links in both files.
30
+ - **Core Algorithm section** in the READMEs (EN + ZH) explaining the
31
+ Siddon + bilinear/trilinear interpolation design choice, the
32
+ trade-off (slight blur at nominal voxel size), and when to pick
33
+ SF vs VD honestly (see "Changed" below).
34
+
35
+ ### Changed
36
+ - **SF FBP / FDK backprojection kernels rewritten in LEAP's
37
+ chord-weighted matched-adjoint form** (from `projectors_SF.cu`).
38
+ The three analytical SF gather kernels
39
+ (`_fan_2d_sf_fbp_backproject_kernel`,
40
+ `_cone_3d_sf_tr_fdk_backproject_kernel`,
41
+ `_cone_3d_sf_tt_fdk_backproject_kernel`) now use the per-voxel
42
+ chord through the unit voxel instead of a `(sid/U)²`-plus-footprint-
43
+ area form, with a matching `sqrt(1+(v/sdd)²)` axial chord correction
44
+ on the cone side. Amplitude is calibrated against Siddon VD at
45
+ every voxel / detector-pitch combination tested (nominal, 0.64×
46
+ sub-nominal, 0.40× sub-nominal, supra-nominal) via the new
47
+ `det_spacing/(voxel·2π)` fan scale and
48
+ `(du·dv·sid)/(2π·sdd·voxel²)` cone scale constants.
49
+ - **Honest README Core Algorithm section on SF vs VD.** The earlier
50
+ "SF lowers MSE by ~17 %" and "SF is measurably sharper at sub-
51
+ nominal" claims in 1.3.0 are walked back: on Shepp-Logan and the
52
+ walnut dataset, LEAP-form SF and Siddon VD produce visually
53
+ indistinguishable edge profiles at typical CBCT magnifications,
54
+ and MSE differences are fractions of a percent. SF's real value
55
+ is its **mass-conserving cell-integrated forward model**, which
56
+ matters for iterative reconstruction, learned priors, and
57
+ sinogram-level losses — not as a drop-in sharpness knob on
58
+ analytical FBP / FDK. The shipped `fbp_fan.py`, `fdk_cone.py`,
59
+ and `realdata_walnut_fdk.py` example comments are updated to
60
+ match.
61
+
62
+ ### Fixed
63
+ - **SF FBP / FDK weight normalization** when the voxel's trapezoidal
64
+ footprint straddles the default 4-corner sort. The 1.3.0 SF
65
+ backprojection kernels used `fdk_w / (span · v_span)` (bounding-box
66
+ area) which under-amplified oblique views by up to 2× per axis and
67
+ produced a dim, angularly-modulated reconstruction. Diagnosed by
68
+ inspecting a Shepp-Logan SF reconstruction, corrected first to
69
+ `fdk_w / ((span+plateau)/2 · v_span)` (true trapezoid area) and
70
+ then fully replaced by the LEAP chord-weighted form above.
71
+
72
+ ### Acknowledgements
73
+ - [LEAP](https://github.com/LLNL/LEAP) (LLNL / Hyojin Kim et al.)
74
+ added to `README.md` / `README.zh.md` Acknowledgements.
75
+ `projectors_SF.cu` is the reference implementation the new
76
+ analytical SF backprojection kernels are ported from.
77
+
78
+ ## [1.3.0] - 2026-04-14
79
+
80
+ ### Added
81
+ - **Separable-footprint (SF) projector backends** for 2D fan and 3D cone
82
+ beam geometries, following Long-Fessler-Balter (IEEE TMI 2010).
83
+ `FanProjectorFunction` / `FanBackprojectorFunction` now accept
84
+ `backend="siddon"` (default, unchanged) or `backend="sf"`.
85
+ `ConeProjectorFunction` / `ConeBackprojectorFunction` accept
86
+ `backend="siddon"` (default), `"sf_tr"` (trapezoid in transaxial,
87
+ rectangle in axial) or `"sf_tt"` (trapezoid in both directions with
88
+ axial rise/fall from `U_near`/`U_far` across the voxel). Each SF
89
+ backend is implemented as a matched scatter/gather pair of CUDA
90
+ kernels so both forward and adjoint are available — autograd works
91
+ end-to-end for every backend, and the standalone Backprojector
92
+ Function can also be driven in SF mode for iterative reconstruction.
93
+ - **Adjoint inner-product tests** for every new SF backend in
94
+ `tests/test_adjoint_inner_product.py`, plus `torch.autograd.gradcheck`
95
+ coverage in `tests/test_gradcheck.py`. Test count grew from 57 to 66.
96
+
97
+ ### Changed
98
+ - **Internal grid-point convention** is now uniformly `x_v = ix - cx`
99
+ (matching `_cone_3d_fdk_backproject_kernel`). The SF kernels use the
100
+ same convention so voxel centers align with where the Siddon/FDK
101
+ paths interpret `d_vol[ix, iy, iz]`.
102
+ - **`_TPB_SF_3D = (4, 4, 4)`** introduced for the 3D SF kernels. SF-TT
103
+ in particular exceeds the 128-register-per-thread budget allowed by
104
+ the default `(8, 8, 8)` launch; the smaller block gives each thread
105
+ ~1024 registers and avoids `CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES`.
106
+
107
+ ### Notes
108
+ - **Raw forward accuracy vs analytical Radon**: on both 2D fan
109
+ (512² Shepp-Logan) and 3D cone (96³ and 128³ 3D Shepp-Logan) phantoms,
110
+ measured against the analytical ellipse / ellipsoid Radon transform,
111
+ the existing Siddon projector with bilinear / trilinear interpolation
112
+ is slightly more accurate (~3% better RMSE, ~15% better max error)
113
+ than SF-TR / SF-TT at the thin-ray sampling level. The classic SF
114
+ advantage reported in Long et al. is over the piecewise-constant DD
115
+ projector; diffct's Siddon uses smooth interpolation, which covers
116
+ most of the gap SF was designed to close.
117
+ - **Full analytical reconstruction (forward + FBP/FDK)**: despite the
118
+ raw-forward result above, in a complete analytical pipeline
119
+ (`FanProjectorFunction` / `ConeProjectorFunction` -> `ramp_filter_1d`
120
+ -> `fan_weighted_backproject` / `cone_weighted_backproject`) SF
121
+ **measurably improves reconstruction quality**. On the 256²
122
+ Shepp-Logan `fbp_fan.py` example the raw MSE drops from
123
+ `0.00216` (Siddon) to `0.00178` (SF-TR), a ~17% improvement.
124
+ On the 128³ `fdk_cone.py` example it drops from `0.00325` (Siddon)
125
+ to `0.00271` (SF-TR), again ~17%. The mechanism is that SF's
126
+ mass-conserving cell-integrated forward pairs more naturally with
127
+ the voxel-driven FBP/FDK gather backprojector than Siddon's
128
+ thin-ray sampling, which feeds extra high-frequency ringing into
129
+ the ramp filter.
130
+ - **Performance**: SF-TR is ~2× and SF-TT ~2.5–4× slower than Siddon for
131
+ cone beam, and ~3× slower than Siddon for fan beam. The extra cost
132
+ comes from voxel-driven scatter with atomic adds (forward) versus
133
+ ray-driven parallelism in Siddon.
134
+ - **Recommendation**: `backend="siddon"` remains the default for
135
+ backward compatibility and raw speed. For **analytical FBP/FDK
136
+ reconstruction** where reconstruction MSE matters more than forward
137
+ runtime, `"sf"` / `"sf_tr"` is a quantifiably better choice — at
138
+ 2–3× forward cost you get ~17% lower reconstruction MSE on standard
139
+ phantoms. For **iterative reconstruction with autograd**, either
140
+ backend is valid; SF has the theoretical advantage of exact voxel-
141
+ scale mass conservation on both forward and adjoint, but in practice
142
+ Siddon's smoothing usually helps optimizer convergence. SF-TT's
143
+ axial-trapezoid refinement over SF-TR improves RMSE only by ~0.001
144
+ and is not worth the extra cost for typical cone angles; use SF-TR
145
+ by default and reserve SF-TT for extreme cone-angle research.
146
+
147
+ ## [1.2.11] - 2026-04-14
148
+
149
+ ### Added
150
+ - **Parker short-scan demos** in `examples/fdk_cone.py` and `examples/fbp_fan.py`.
151
+ Both examples now expose an `apply_parker` switch that not only multiplies
152
+ by `parker_weights` but also samples angles on the minimal short-scan range
153
+ `pi + 2*gamma_max`, where `gamma_max` is computed from the detector half-width
154
+ and `sdd`. (Before this change the `apply_parker` flag in `fbp_fan.py` was
155
+ silently a no-op because the angular range stayed at `2*pi`.)
156
+ - **`tests/test_gradcheck.py`** — `torch.autograd.gradcheck` coverage for
157
+ `ParallelProjectorFunction`, `FanProjectorFunction`, and `ConeProjectorFunction`.
158
+ Compares the analytical backward Jacobian to a finite-difference numerical
159
+ Jacobian over small inputs with float32-calibrated tolerances. Strongest
160
+ autograd-correctness guard beyond the existing inner-product adjoint tests.
161
+ - **`tests/test_ramp_filter_windows.py`** — 27 parametrised tests covering every
162
+ `ramp_filter_1d` window option (`None`/`"ram-lak"`, `"hann"`, `"hanning"`,
163
+ `"hamming"`, `"cosine"`, `"shepp-logan"`) at three layers: direct `_ramp_window`
164
+ helper (DC gain, Nyquist value, monotonicity), full `ramp_filter_1d` end-to-end
165
+ (shape, DC annihilation, pad correctness, rfft vs complex-fft parity,
166
+ `sample_spacing` scaling), and a high-frequency attenuation sanity check on a
167
+ step input.
168
+ - **`CHANGELOG.md`** — new Keep-a-Changelog file at the repo root.
169
+ - **`tests/benchmarks/`** — opt-in `pytest-benchmark` suite covering every CUDA
170
+ kernel in the library (forward projector, pure-adjoint backprojector, and
171
+ analytical FBP/FDK pipeline) across three sizes for each of the three
172
+ geometries. 27 benchmarks total. Excluded from the default `pytest tests/`
173
+ run via `--ignore=tests/benchmarks` in `pytest.ini` so the normal test suite
174
+ stays fast; run explicitly with `pytest tests/benchmarks/ --benchmark-only`.
175
+ Enables before/after perf comparisons via `--benchmark-save=` /
176
+ `--benchmark-compare=`.
177
+
178
+ ### Changed
179
+ - **`_cone_3d_fdk_backproject_kernel`** memory-access layout. The kernel now
180
+ unpacks `cuda.grid(3)` as `(iz, iy, ix)` instead of `(ix, iy, iz)`, and
181
+ `cone_weighted_backproject` launches the grid as `_grid_3d(D, H, W)` instead
182
+ of `_grid_3d(W, H, D)`. This makes `iz` warp-adjacent, which matches the
183
+ innermost stride-1 axis of the WHD output buffer, so the final `d_vol` write
184
+ becomes coalesced. Measured speedup on a 64^3–160^3 sweep: 1.0x–1.14x,
185
+ within run-to-run noise (the kernel is read-bound on the sinogram bilinear
186
+ samples, not write-bound, so L2 absorbed the old uncoalesced writes). The
187
+ fix is kept as the right default and future-proofs the kernel against
188
+ edits that shift work toward the write path. Matching coalescing comments
189
+ were added to the 2D `_parallel_2d_fbp_backproject_kernel` and
190
+ `_fan_2d_fbp_backproject_kernel` kernels, which were already coalesced
191
+ (their first grid index is also warp-adjacent and matches the `Nx` innermost
192
+ axis of their `(Ny, Nx)` output buffers).
193
+
194
+ ### Notes
195
+ - No public-API changes. Pure-adjoint kernels and autograd Function classes
196
+ are untouched, so the autograd and iterative-reconstruction paths are
197
+ byte-for-byte equivalent to 1.2.10.
198
+
199
+ ## [1.2.10] - 2026-04-14
200
+
201
+ This release is a correctness overhaul of every analytical reconstruction path
202
+ (parallel / fan / cone) **and** of the autograd adjoint path shared by the
203
+ iterative examples. It also ships a unified architecture where each geometry
204
+ has its own dedicated voxel-driven FBP/FDK gather kernel, separate from the
205
+ pure Siddon adjoint used by autograd.
206
+
207
+ ### Fixed
208
+
209
+ #### Analytical FBP / FDK amplitude bugs
210
+
211
+ Prior to this release, all three analytical reconstruction examples produced
212
+ results that were off by large constant factors:
213
+
214
+ | Example | Pre-fix reco range | Post-fix reco range | MSE improvement |
215
+ |---|---|---|---|
216
+ | `fbp_parallel.py` | [0, 6.52] | [−0.02, 1.01] | ~580× |
217
+ | `fbp_fan.py` | [0, 6.33] | [−0.08, 1.01] | ~900× |
218
+ | `fdk_cone.py` | [0, 10.08] | [−0.08, 1.00] | ~600× |
219
+
220
+ Root causes:
221
+ - Cone and fan used `(sdd/U)^2` with `U = sdd + x·sin − y·cos` instead of
222
+ the correct FDK weight `(sid/U)^2` with `U = sid + x·sin − y·cos`.
223
+ - All three geometries were missing the `1/(2π)` Fourier-convention
224
+ constant from the FBP/FDK reconstruction formula.
225
+ - Cone and fan used a Siddon ray-driven scatter for the analytical path,
226
+ which is neither a true adjoint nor a classical FBP/FDK gather.
227
+
228
+ #### Autograd adjoint bug (cone + fan)
229
+
230
+ `ConeProjectorFunction.backward`, `ConeBackprojectorFunction.forward`,
231
+ `FanProjectorFunction.backward` and `FanBackprojectorFunction.forward`
232
+ were all passing `distance_weight=1.0` to the shared Siddon backward
233
+ kernel. This meant the autograd backward was **not the true adjoint** `P^T`
234
+ of the forward projector — it had a `(sdd/U)^2` per-voxel factor baked in,
235
+ biasing the gradient by ~2–3× depending on voxel position. Any iterative
236
+ reconstruction that relied on autograd (including `iterative_reco_cone.py`
237
+ and `iterative_reco_fan.py`) was running on a biased gradient flow.
238
+
239
+ Parallel beam was unaffected (its backward kernel had no `distance_weight`
240
+ parameter to begin with).
241
+
242
+ This release removes the `distance_weight` parameter from both
243
+ `_cone_3d_backward_kernel` and `_fan_2d_backward_kernel` entirely, so the
244
+ dead code path cannot be accidentally reintroduced, and fixes the four
245
+ autograd call sites to use the pure adjoint.
246
+
247
+ ### Added
248
+
249
+ #### Voxel-driven gather kernels for every geometry
250
+
251
+ Three new CUDA kernels, all under a dedicated `fastmath=False` decorator
252
+ for FDK-grade accuracy:
253
+
254
+ - `_parallel_2d_fbp_backproject_kernel` — no distance weighting (no source).
255
+ - `_fan_2d_fbp_backproject_kernel` — `(sid/U)^2` + linear detector interp.
256
+ - `_cone_3d_fdk_backproject_kernel` — `(sid/U)^2` + bilinear detector interp.
257
+
258
+ Each kernel is voxel-driven: one thread per output pixel/voxel, loops over
259
+ views, computes the projected detector coordinate, interpolates the filtered
260
+ sinogram, weights and accumulates.
261
+
262
+ #### `parallel_weighted_backproject` (new public helper)
263
+
264
+ Mirrors `fan_weighted_backproject` and `cone_weighted_backproject`. Applies
265
+ the `1/(2π)` Fourier-convention constant so a unit-density disk reconstructs
266
+ to amplitude 1. Exported from `diffct`.
267
+
268
+ #### `ramp_filter_1d` — new backward-compatible kwargs
269
+
270
+ - `sample_spacing` (default `1.0`): physical detector cell pitch. Output is
271
+ rescaled by `1/sample_spacing` for physical-unit correctness.
272
+ - `pad_factor` (default `1`): zero-pad to `pad_factor * N` before the FFT to
273
+ suppress circular-convolution wrap-around. `2` is recommended for FBP/FDK.
274
+ - `window` (default `None`): frequency-domain apodization. Options: `None` /
275
+ `"ram-lak"`, `"hann"`, `"hamming"`, `"cosine"`, `"shepp-logan"`.
276
+ - `use_rfft` (default `True`): faster real-FFT path for real-valued inputs.
277
+
278
+ Existing `ramp_filter_1d(sino, dim=1)` calls keep working unchanged.
279
+
280
+ #### Analytical FBP / FDK scale factors
281
+
282
+ Each analytical helper now applies the correct analytical constant
283
+ automatically so reconstructions are already amplitude-calibrated:
284
+
285
+ - `parallel_weighted_backproject`: `1 / (2π)`.
286
+ - `fan_weighted_backproject`: `sdd / (2π · sid)`.
287
+ - `cone_weighted_backproject`: `sdd / (2π · sid)`.
288
+
289
+ ### Tests
290
+
291
+ Test suite expanded from 6 to **27 tests**:
292
+
293
+ - `tests/test_adjoint_inner_product.py` — the definitive check. For random `x`
294
+ and `y`, asserts `⟨A x, y⟩ = ⟨x, A^T y⟩` for parallel, fan and cone autograd
295
+ pairs. Permanently guards against any regression that would reintroduce the
296
+ `distance_weight=1.0` bug.
297
+ - `tests/test_fdk_cone_accuracy.py` — 128³ Shepp-Logan RMSE / amplitude bounds.
298
+ - `tests/test_fdk_cone_offsets.py` — detector, center and combined offsets with
299
+ amplitude assertions.
300
+ - `tests/test_cone_projector_autograd.py` — gradient finiteness and non-zero
301
+ sanity.
302
+ - `tests/test_fbp_fan_accuracy.py` — 256×256 Shepp-Logan RMSE.
303
+ - `tests/test_fbp_fan_offsets.py` — three offset configurations.
304
+ - `tests/test_fbp_parallel_accuracy.py` — parallel FBP RMSE + offset.
305
+
306
+ ### Examples
307
+
308
+ `examples/fdk_cone.py`, `examples/fbp_fan.py` and `examples/fbp_parallel.py`
309
+ have been rewritten with a consistent 8-step layout and detailed inline
310
+ comments documenting every geometry variable (what it is, units, typical
311
+ values, available options). Each example prints raw MSE, clamped MSE, and the
312
+ reconstruction / phantom data ranges.
313
+
314
+ The iterative reconstruction examples (`iterative_reco_parallel.py`,
315
+ `iterative_reco_fan.py`, `iterative_reco_cone.py`) are unchanged — they
316
+ silently benefit from the fixed autograd adjoint.
317
+
318
+ ### Documentation
319
+
320
+ - `docs/source/api.rst`: adds autofunction entries for
321
+ `parallel_weighted_backproject`, documents the new `ramp_filter_1d`
322
+ options, and adds an "Analytical FBP / FDK architecture" section
323
+ explaining the scale-factor derivation.
324
+ - `docs/source/fdk_cone_example.rst`: formula updated to reflect the
325
+ voxel-gather kernel and `(sid/U)^2` weight.
326
+ - `docs/source/fbp_fan_example.rst` and `fbp_parallel_example.rst`: completely
327
+ rewritten to reference the new analytical helpers and fix a sign-convention
328
+ inconsistency in the math section.
329
+
330
+ ### Compatibility
331
+
332
+ This release is source-compatible with 1.2.9 for every public entry point. The
333
+ `distance_weight` parameter was internal and is removed from the private
334
+ backward kernels; no public API touched that argument.
335
+
336
+ ## [1.2.9] - 2026-02-14
337
+
338
+ Restored `main` to the state of 1.2.9 by reverting an accidental merge.
339
+ See [GitHub release v1.2.9](https://github.com/sypsyp97/diffct/releases/tag/v1.2.9).
340
+
341
+ ## [1.2.8] - 2026-02-14
342
+
343
+ See [GitHub release v1.2.8](https://github.com/sypsyp97/diffct/releases/tag/v1.2.8)
344
+ and the commit history for details.
345
+
346
+ ## [1.2.7] - 2025-08-05
347
+
348
+ See [GitHub release v1.2.7](https://github.com/sypsyp97/diffct/releases/tag/v1.2.7).
349
+
350
+ ## Earlier versions
351
+
352
+ Releases 1.2.0 through 1.2.6 and the 1.1.x / 1.0.x lines are tracked on
353
+ [GitHub releases](https://github.com/sypsyp97/diffct/releases). This CHANGELOG
354
+ was introduced in 1.2.10 and does not back-fill detailed notes for earlier
355
+ versions beyond pointers to the GitHub release pages.
356
+
357
+ [Unreleased]: https://github.com/sypsyp97/diffct/compare/v1.3.0...HEAD
358
+ [1.3.0]: https://github.com/sypsyp97/diffct/releases/tag/v1.3.0
359
+ [1.2.11]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.11
360
+ [1.2.10]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.10
361
+ [1.2.9]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.9
362
+ [1.2.8]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.8
363
+ [1.2.7]: https://github.com/sypsyp97/diffct/releases/tag/v1.2.7