diffct 1.3.3__tar.gz → 2.0.0__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 (97) hide show
  1. {diffct-1.3.3 → diffct-2.0.0}/.gitignore +195 -187
  2. {diffct-1.3.3 → diffct-2.0.0}/CHANGELOG.md +294 -0
  3. diffct-2.0.0/CITATION.cff +51 -0
  4. {diffct-1.3.3 → diffct-2.0.0}/LICENSE +201 -201
  5. diffct-2.0.0/NOTICE +18 -0
  6. diffct-2.0.0/PKG-INFO +240 -0
  7. diffct-2.0.0/README.md +209 -0
  8. diffct-2.0.0/diffct/__init__.py +83 -0
  9. diffct-2.0.0/diffct/analytical.py +569 -0
  10. diffct-2.0.0/diffct/constants.py +72 -0
  11. diffct-2.0.0/diffct/differentiable.py +74 -0
  12. diffct-2.0.0/diffct/geometry.py +991 -0
  13. diffct-2.0.0/diffct/kernels/__init__.py +43 -0
  14. diffct-2.0.0/diffct/kernels/cone_beam.py +617 -0
  15. diffct-2.0.0/diffct/kernels/fan_beam.py +451 -0
  16. diffct-2.0.0/diffct/kernels/geometry_vjp.py +562 -0
  17. diffct-2.0.0/diffct/kernels/parallel_beam.py +400 -0
  18. diffct-2.0.0/diffct/operators.py +546 -0
  19. diffct-2.0.0/diffct/projectors.py +910 -0
  20. diffct-2.0.0/diffct/utils.py +357 -0
  21. diffct-2.0.0/docs/video/README.md +22 -0
  22. diffct-2.0.0/examples/README.md +209 -0
  23. {diffct-1.3.3 → diffct-2.0.0}/examples/data/NOTICE +45 -45
  24. diffct-2.0.0/formal/README.md +82 -0
  25. diffct-2.0.0/pyproject.toml +49 -0
  26. {diffct-1.3.3 → diffct-2.0.0}/pytest.ini +14 -14
  27. diffct-2.0.0/tests/_phantoms.py +81 -0
  28. {diffct-1.3.3 → diffct-2.0.0}/tests/benchmarks/README.md +56 -60
  29. {diffct-1.3.3 → diffct-2.0.0}/tests/benchmarks/_common.py +57 -62
  30. {diffct-1.3.3 → diffct-2.0.0}/tests/benchmarks/test_bench_cone.py +137 -145
  31. {diffct-1.3.3 → diffct-2.0.0}/tests/benchmarks/test_bench_fan.py +121 -127
  32. {diffct-1.3.3 → diffct-2.0.0}/tests/benchmarks/test_bench_parallel.py +113 -115
  33. diffct-2.0.0/tests/distributed_projector_check.py +709 -0
  34. diffct-2.0.0/tests/test_adjoint_inner_product.py +170 -0
  35. diffct-2.0.0/tests/test_analytical_regressions.py +182 -0
  36. {diffct-1.3.3 → diffct-2.0.0}/tests/test_cone_projector_autograd.py +76 -91
  37. diffct-2.0.0/tests/test_cuda_smoke.py +121 -0
  38. diffct-2.0.0/tests/test_fbp_fan_accuracy.py +92 -0
  39. diffct-2.0.0/tests/test_fbp_fan_offsets.py +117 -0
  40. diffct-2.0.0/tests/test_fbp_parallel_accuracy.py +72 -0
  41. diffct-2.0.0/tests/test_fdk_cone_accuracy.py +114 -0
  42. diffct-2.0.0/tests/test_fdk_cone_offsets.py +118 -0
  43. diffct-2.0.0/tests/test_geometry_autograd.py +51 -0
  44. diffct-2.0.0/tests/test_geometry_gradients.py +402 -0
  45. diffct-2.0.0/tests/test_gradcheck.py +125 -0
  46. diffct-2.0.0/tests/test_projector_api.py +796 -0
  47. diffct-2.0.0/tests/test_projector_degenerate_geometry.py +257 -0
  48. diffct-2.0.0/tests/test_projector_geometry_devices.py +69 -0
  49. diffct-2.0.0/tests/test_ramp_filter_windows.py +136 -0
  50. diffct-2.0.0/tests/test_weights.py +157 -0
  51. diffct-1.3.3/.git +0 -1
  52. diffct-1.3.3/.github/workflows/docs.yml +0 -37
  53. diffct-1.3.3/.github/workflows/release.yml +0 -20
  54. diffct-1.3.3/PKG-INFO +0 -385
  55. diffct-1.3.3/README.md +0 -368
  56. diffct-1.3.3/README.zh.md +0 -335
  57. diffct-1.3.3/diffct/__init__.py +0 -18
  58. diffct-1.3.3/diffct/differentiable.py +0 -5400
  59. diffct-1.3.3/docs/Makefile +0 -20
  60. diffct-1.3.3/docs/source/api.rst +0 -291
  61. diffct-1.3.3/docs/source/conf.py +0 -35
  62. diffct-1.3.3/docs/source/examples.rst +0 -30
  63. diffct-1.3.3/docs/source/fbp_fan_example.rst +0 -158
  64. diffct-1.3.3/docs/source/fbp_parallel_example.rst +0 -117
  65. diffct-1.3.3/docs/source/fdk_cone_example.rst +0 -184
  66. diffct-1.3.3/docs/source/getting_started.rst +0 -84
  67. diffct-1.3.3/docs/source/index.rst +0 -67
  68. diffct-1.3.3/docs/source/iterative_reco_cone_example.rst +0 -131
  69. diffct-1.3.3/docs/source/iterative_reco_fan_example.rst +0 -90
  70. diffct-1.3.3/docs/source/iterative_reco_parallel_example.rst +0 -96
  71. diffct-1.3.3/examples/data/preprocess_walnut.py +0 -303
  72. diffct-1.3.3/examples/data/walnut_cone.npz +0 -0
  73. diffct-1.3.3/examples/data/walnut_reco.png +0 -0
  74. diffct-1.3.3/examples/fbp_fan.py +0 -356
  75. diffct-1.3.3/examples/fbp_parallel.py +0 -227
  76. diffct-1.3.3/examples/fdk_cone.py +0 -495
  77. diffct-1.3.3/examples/iterative_reco_cone.py +0 -219
  78. diffct-1.3.3/examples/iterative_reco_fan.py +0 -188
  79. diffct-1.3.3/examples/iterative_reco_parallel.py +0 -135
  80. diffct-1.3.3/examples/realdata_fbp_fan.py +0 -300
  81. diffct-1.3.3/examples/realdata_fbp_parallel.py +0 -269
  82. diffct-1.3.3/examples/realdata_fdk_cone.py +0 -345
  83. diffct-1.3.3/examples/realdata_walnut_fdk.py +0 -363
  84. diffct-1.3.3/pyproject.toml +0 -39
  85. diffct-1.3.3/requirements.txt +0 -5
  86. diffct-1.3.3/tests/test_adjoint_inner_product.py +0 -286
  87. diffct-1.3.3/tests/test_cuda_smoke.py +0 -84
  88. diffct-1.3.3/tests/test_fbp_fan_accuracy.py +0 -147
  89. diffct-1.3.3/tests/test_fbp_fan_offsets.py +0 -162
  90. diffct-1.3.3/tests/test_fbp_parallel_accuracy.py +0 -164
  91. diffct-1.3.3/tests/test_fdk_cone_accuracy.py +0 -197
  92. diffct-1.3.3/tests/test_fdk_cone_offsets.py +0 -210
  93. diffct-1.3.3/tests/test_gradcheck.py +0 -209
  94. diffct-1.3.3/tests/test_ramp_filter_windows.py +0 -217
  95. diffct-1.3.3/tests/test_weights.py +0 -103
  96. {diffct-1.3.3/tests/benchmarks → diffct-2.0.0/tests}/__init__.py +0 -0
  97. /diffct-1.3.3/docs/source/_static/.gitkeep → /diffct-2.0.0/tests/benchmarks/__init__.py +0 -0
@@ -1,187 +1,195 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[cod]
4
- *$py.class
5
-
6
- # C extensions
7
- *.so
8
-
9
- # Distribution / packaging
10
- .Python
11
- build/
12
- develop-eggs/
13
- dist/
14
- downloads/
15
- eggs/
16
- .eggs/
17
- lib/
18
- lib64/
19
- parts/
20
- sdist/
21
- var/
22
- wheels/
23
- share/python-wheels/
24
- *.egg-info/
25
- .installed.cfg
26
- *.egg
27
- MANIFEST
28
-
29
- # PyInstaller
30
- # Usually these files are written by a python script from a template
31
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
- *.manifest
33
- *.spec
34
-
35
- # Installer logs
36
- pip-log.txt
37
- pip-delete-this-directory.txt
38
-
39
- # Unit test / coverage reports
40
- htmlcov/
41
- .tox/
42
- .nox/
43
- .coverage
44
- .coverage.*
45
- .cache
46
- nosetests.xml
47
- coverage.xml
48
- *.cover
49
- *.py,cover
50
- .hypothesis/
51
- .pytest_cache/
52
- cover/
53
-
54
- # Translations
55
- *.mo
56
- *.pot
57
-
58
- # Django stuff:
59
- *.log
60
- local_settings.py
61
- db.sqlite3
62
- db.sqlite3-journal
63
-
64
- # Flask stuff:
65
- instance/
66
- .webassets-cache
67
-
68
- # Scrapy stuff:
69
- .scrapy
70
-
71
- # Sphinx documentation
72
- docs/_build/
73
-
74
- # PyBuilder
75
- .pybuilder/
76
- target/
77
-
78
- # Jupyter Notebook
79
- .ipynb_checkpoints
80
-
81
- # IPython
82
- profile_default/
83
- ipython_config.py
84
-
85
- # pyenv
86
- # For a library or package, you might want to ignore these files since the code is
87
- # intended to run in multiple environments; otherwise, check them in:
88
- # .python-version
89
-
90
- # pipenv
91
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
- # install all needed dependencies.
95
- #Pipfile.lock
96
-
97
- # UV
98
- # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
- # This is especially recommended for binary packages to ensure reproducibility, and is more
100
- # commonly ignored for libraries.
101
- #uv.lock
102
-
103
- # poetry
104
- # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
- # This is especially recommended for binary packages to ensure reproducibility, and is more
106
- # commonly ignored for libraries.
107
- # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
- #poetry.lock
109
-
110
- # pdm
111
- # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
- #pdm.lock
113
- # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
- # in version control.
115
- # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
- .pdm.toml
117
- .pdm-python
118
- .pdm-build/
119
-
120
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
- __pypackages__/
122
-
123
- # Celery stuff
124
- celerybeat-schedule
125
- celerybeat.pid
126
-
127
- # SageMath parsed files
128
- *.sage.py
129
-
130
- # Environments
131
- .env
132
- .venv
133
- env/
134
- venv/
135
- ENV/
136
- env.bak/
137
- venv.bak/
138
-
139
- # Spyder project settings
140
- .spyderproject
141
- .spyproject
142
-
143
- # Rope project settings
144
- .ropeproject
145
-
146
- # mkdocs documentation
147
- /site
148
-
149
- # mypy
150
- .mypy_cache/
151
- .dmypy.json
152
- dmypy.json
153
-
154
- # Pyre type checker
155
- .pyre/
156
-
157
- # pytype static type analyzer
158
- .pytype/
159
-
160
- # Cython debug symbols
161
- cython_debug/
162
-
163
- # PyCharm
164
- # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
- # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
- # and can be added to the global gitignore or merged into this file. For a more nuclear
167
- # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
- #.idea/
169
-
170
- # PyPI configuration file
171
- .pypirc
172
-
173
- # Roo memory bank
174
- .vscode
175
- /memory-bank
176
- .rooignore
177
-
178
- # Cline configuration file
179
- .clinerules-architect
180
- .clinerules-ask
181
- .clinerules-code
182
- .clinerules-debug
183
- .clinerules-test
184
- .roomodes
185
- projectBrief.md
186
- .kiro
187
- .claude
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # PyPI configuration file
171
+ .pypirc
172
+
173
+ # Roo memory bank
174
+ .vscode
175
+ /memory-bank
176
+ .rooignore
177
+
178
+ # Cline configuration file
179
+ .clinerules-architect
180
+ .clinerules-ask
181
+ .clinerules-code
182
+ .clinerules-debug
183
+ .clinerules-test
184
+ .roomodes
185
+ projectBrief.md
186
+ .kiro
187
+ .claude
188
+
189
+ # Local GPU / cluster validation evidence
190
+ .validation/
191
+
192
+ # Manim render output
193
+ docs/video/media/
194
+ docs/video/scenes.txt
195
+ docs/video/palette.png
@@ -5,6 +5,300 @@ 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.0] - 2026-10-09
9
+
10
+ Version 2.0.0 replaces the circular-orbit API of 1.x with arbitrary
11
+ per-view trajectories. It breaks the 1.x interface; see
12
+ [docs/MIGRATION.md](docs/MIGRATION.md).
13
+
14
+ ### Breaking
15
+
16
+ - ``diffct.Projector`` is the main entry point. Geometry is a tuple of
17
+ per-view tensors (source, detector centre, detector axes) instead of
18
+ scalar ``angles``, ``sid`` and ``sdd``.
19
+ - The low-level ``*ProjectorFunction`` / ``*BackprojectorFunction`` classes
20
+ and the ``*_weighted_backproject`` helpers take per-view geometry tensors.
21
+ - The separable-footprint backends (``sf``, ``sf_tr``, ``sf_tt``) of 1.3.x
22
+ are removed. All projectors use the matched cell-constant Siddon model.
23
+
24
+ ### Added
25
+
26
+ - Multi-GPU execution in one process (``devices=[...]``) and one process per
27
+ GPU with torchrun and NCCL, on one or several nodes.
28
+ - Install extras ``diffct[cu12]`` and ``diffct[cu13]`` pull in the matching
29
+ numba-cuda CUDA bindings, so one ``pip install`` gives a working import.
30
+ - Measured walnut example (``examples/walnut_reconstruction.py``), intro video
31
+ and README figures with provenance records in ``docs/assets``.
32
+ - Geometry gradients. Trajectory tensors may require gradients; ``Projector``
33
+ and the low-level Function classes return gradients for source, detector
34
+ centre and detector axes (parallel: ray direction, detector origin and
35
+ axis). CUDA kernels compute the exact derivative of the cell-constant
36
+ Siddon model from the face crossings of each ray. Distributed mode sums
37
+ the gradients over ranks.
38
+ - Second derivatives for volumes and sinograms: backward passes are autograd
39
+ Functions themselves. Second derivatives with respect to the geometry
40
+ raise ``RuntimeError``.
41
+
42
+ ### Changed
43
+
44
+ - Detector arrays are centred: cell ``k`` lies at
45
+ ``(k - (N - 1) / 2) * pitch`` from ``det_center`` (or ``det_origin``), as in
46
+ ``main``. Earlier dev versions used ``(k - N / 2) * pitch``. The projector,
47
+ the FBP/FDK gathers and ``detector_coordinates_1d`` all changed.
48
+ - Siddon projections are line integrals in physical length. Earlier
49
+ versions returned voxel-length integrals, which differ by the factor
50
+ ``voxel_spacing``. Backprojection scales the same way, so the pair stays
51
+ matched. Results with ``voxel_spacing == 1`` do not change.
52
+ - FBP and FDK gather kernels sample at voxel centres,
53
+ ``(index + 0.5 - N / 2) * voxel_spacing``, as the cell-constant Siddon
54
+ projector does. Earlier reconstructions were half a voxel off.
55
+ - Siddon projector and backprojector kernels no longer skip ray pieces of
56
+ length ``<= 1e-6``. They skip only pieces of length exactly 0 or less.
57
+ Forward projection is now a continuous function of the geometry: each piece
58
+ changes a projection value by at most cell value x ``1e-6``.
59
+
60
+ ### Fixed
61
+
62
+ - Fan and cone rays integrate only from the source to the detector pixel.
63
+ A source or detector inside the volume box gave the full-line integral.
64
+ - Fan and cone ray setup measures the ray parameter from the endpoint
65
+ nearer the volume centre, and the traversal restarts at the volume entry
66
+ point. A distant source gave relative errors up to 5e-3 (source 1e6
67
+ voxels away) and could stop the traversal. Parallel-beam setup runs in
68
+ float64 for a distant detector origin. Projection speed does not change.
69
+ - Ray setup treats a direction component as axis-parallel only below
70
+ 1e-20, so a distant source with a small lateral component is no longer
71
+ rejected.
72
+ - Siddon kernels use a finite sentinel instead of infinity, because
73
+ ``fastmath`` lets LLVM assume that infinity never occurs.
74
+ - ``Projector`` rejects fan and cone views whose source coincides with the
75
+ detector centre or whose detector is edge-on to the source, and views where
76
+ both lie farther than 1e6 voxels from the volume centre.
77
+ - The traversal treats a direction component as axis-parallel only below
78
+ 1e-20. With the earlier 1e-6 limit, a ray that grazed a grid line was
79
+ assigned to the neighbouring row.
80
+ - ``tests/distributed_projector_check.py`` no longer issues a MAX
81
+ all-reduce after a local failure while other ranks wait in a SUM
82
+ all-reduce.
83
+
84
+ ## [1.3.0.dev0] - 2026-04-14 (dev branch, not released on PyPI)
85
+
86
+ First sync of the dev (arbitrary-trajectory) branch against the main
87
+ branch's 1.2.10 / 1.2.11 analytical reconstruction overhaul. Brings
88
+ dev up to functional parity with main 1.2.11 except for the 1.3.0
89
+ separable-footprint (SF) projector backends, which rely on closed-form
90
+ circular-orbit geometry and are not yet generalised to arbitrary
91
+ trajectories.
92
+
93
+ ### Added
94
+
95
+ #### Analytical reconstruction helpers (``diffct.analytical``)
96
+
97
+ A new module exposes the following helpers, all trajectory-agnostic so
98
+ they work with both the circular trajectories and the arbitrary
99
+ ``(src_pos, det_center, det_u_vec[, det_v_vec])`` trajectory arrays
100
+ that dev-branch kernels already accept:
101
+
102
+ - ``detector_coordinates_1d`` — detector cell centre coordinates.
103
+ - ``angular_integration_weights`` — trapezoidal per-view weights for
104
+ the analytical FBP/FDK sum. Optional ``redundant_full_scan`` flag
105
+ absorbs the ``1/2`` redundancy factor for full scans.
106
+ - ``fan_cosine_weights`` / ``cone_cosine_weights`` — per-detector-cell
107
+ ``cos(gamma)`` pre-weight for fan / cone FBP pipelines.
108
+ - ``parker_weights`` — Parker short-scan redundancy weights for
109
+ circular fan geometries.
110
+ - ``ramp_filter_1d`` — generic 1D ramp filter with ``sample_spacing``,
111
+ ``pad_factor``, ``window`` (``"ram-lak"``, ``"hann"``, ``"hamming"``,
112
+ ``"cosine"``, ``"shepp-logan"``), and ``use_rfft`` options, rescaled
113
+ by ``1 / sample_spacing`` so the output is in physical units.
114
+ - ``parallel_weighted_backproject`` / ``fan_weighted_backproject`` /
115
+ ``cone_weighted_backproject`` — voxel-driven FBP / FDK backprojection
116
+ wrappers that dispatch to new dedicated gather kernels and apply the
117
+ analytical Fourier-convention constant (``1/(2*pi)`` for parallel,
118
+ ``sdd_mean/(2*pi*sid_mean)`` for fan and cone). These are the
119
+ **recommended path for analytical reconstruction** and replace the
120
+ previous pattern of passing a filtered sinogram through
121
+ ``*BackprojectorFunction.apply``.
122
+
123
+ #### Voxel-driven FBP / FDK gather kernels
124
+
125
+ Three new CUDA kernels, each under the dedicated ``fastmath=False``
126
+ ``_FDK_ACCURACY_DECORATOR`` added to ``diffct.constants``:
127
+
128
+ - ``_parallel_2d_fbp_backproject_kernel`` — no distance weighting.
129
+ - ``_fan_2d_fbp_backproject_kernel`` — ``(|S|/U_n)^2`` weighted,
130
+ where ``U_n`` is the signed distance from the per-view source to
131
+ the voxel along the detector normal.
132
+ - ``_cone_3d_fdk_backproject_kernel`` — same pattern in 3D.
133
+
134
+ Every kernel is voxel-driven: one thread per output pixel/voxel, loops
135
+ over views inside, projects the voxel onto the detector using the
136
+ per-view ``(src_pos, det_center, det_u_vec[, det_v_vec])`` arrays,
137
+ bilinearly samples the filtered sinogram, applies the per-view weight
138
+ and accumulates. They are completely separate from the pure Siddon
139
+ adjoint kernels that back the autograd path; autograd is untouched.
140
+
141
+ #### Tests (``tests/``)
142
+
143
+ The dev branch had no test directory at all prior to this change.
144
+ ``pytest.ini`` and ``tests/__init__.py`` are new, plus 58 tests across
145
+ 11 files that mirror the main-branch test layout:
146
+
147
+ - ``tests/test_adjoint_inner_product.py`` — ``<A x, y> = <x, A^T y>``
148
+ identity for parallel / fan / cone autograd pairs, plus an extra
149
+ ``test_cone_autograd_backward_matches_backprojector_forward`` that
150
+ protects the autograd ``ConeProjectorFunction.backward`` from
151
+ drifting away from the standalone ``ConeBackprojectorFunction``.
152
+ - ``tests/test_gradcheck.py`` — ``torch.autograd.gradcheck`` for every
153
+ projector Function with float32-calibrated tolerances.
154
+ - ``tests/test_weights.py`` — unit tests for ``detector_coordinates_1d``,
155
+ ``angular_integration_weights``, ``fan_cosine_weights``,
156
+ ``cone_cosine_weights`` and ``parker_weights``.
157
+ - ``tests/test_cuda_smoke.py`` — end-to-end smoke tests for every
158
+ Projector / Backprojector Function pair + the analytical
159
+ ``*_weighted_backproject`` wrappers.
160
+ - ``tests/test_cone_projector_autograd.py`` — gradient finiteness and
161
+ sparsity guard for the cone projector Function (circular + spiral
162
+ trajectories).
163
+ - ``tests/test_fbp_parallel_accuracy.py`` /
164
+ ``tests/test_fbp_fan_accuracy.py`` /
165
+ ``tests/test_fdk_cone_accuracy.py`` — quantitative RMSE and
166
+ amplitude bounds for a full Shepp-Logan FBP / FDK pipeline per
167
+ geometry. These would have tripped on the old dev reconstruction
168
+ path, which silently produced wrong-amplitude volumes.
169
+ - ``tests/test_fbp_fan_offsets.py`` /
170
+ ``tests/test_fdk_cone_offsets.py`` — adapted from main's offset
171
+ tests: dev's arbitrary-trajectory kernels have no scalar
172
+ ``detector_offset`` / ``center_offset_*`` parameters, so offsets
173
+ are applied by shifting the trajectory arrays directly. Verifies
174
+ that the FBP / FDK gather kernels handle non-centred trajectories.
175
+ - ``tests/test_ramp_filter_windows.py`` — 29 parametrised tests covering
176
+ every ``_ramp_window`` option (DC gain, Nyquist value, non-negativity)
177
+ and the full ``ramp_filter_1d`` end-to-end (shape, DC annihilation,
178
+ rfft vs complex-fft parity, ``sample_spacing`` scaling, high-frequency
179
+ pass-through).
180
+
181
+ #### Benchmark suite (``tests/benchmarks/``)
182
+
183
+ Opt-in ``pytest-benchmark`` suite covering every CUDA kernel in the
184
+ library (forward projector, pure-adjoint backprojector, and the full
185
+ analytical FBP / FDK pipeline) across three sizes for each of the
186
+ three geometries. 27 benchmarks total, excluded from the default
187
+ ``pytest tests/`` run via ``--ignore=tests/benchmarks`` in
188
+ ``pytest.ini``. Run explicitly with
189
+ ``pytest tests/benchmarks/ --benchmark-only``.
190
+
191
+ #### Parker short-scan demos in examples
192
+
193
+ ``examples/circular_trajectory/fbp_fan.py`` and
194
+ ``examples/circular_trajectory/fdk_cone.py`` now expose an
195
+ ``apply_parker`` switch. When enabled, the example switches the
196
+ trajectory to a minimal ``pi + 2*gamma_max`` short scan and applies
197
+ ``parker_weights`` to the sinogram before the ramp filter; when
198
+ disabled, the pipeline runs a full ``2*pi`` scan with the ``1/2``
199
+ redundancy factor absorbed by ``angular_integration_weights``. Both
200
+ branches produce correctly amplitude-calibrated reconstructions.
201
+
202
+ #### Documentation (``docs/source/api.rst``)
203
+
204
+ New "Analytical Reconstruction Helpers", "Ramp Filter Options", and
205
+ "Analytical FBP / FDK architecture" sections describe the new
206
+ ``diffct.analytical`` module, enumerate every ``ramp_filter_1d``
207
+ option, and explain the analytical scale factors used in each
208
+ ``*_weighted_backproject`` wrapper.
209
+
210
+ ### Fixed
211
+
212
+ #### FBP / FDK amplitude bugs in the ``circular_trajectory/*.py`` examples
213
+
214
+ Prior to this release the circular-trajectory FBP / FDK examples
215
+ (``examples/circular_trajectory/fbp_parallel.py``,
216
+ ``examples/circular_trajectory/fbp_fan.py``,
217
+ ``examples/circular_trajectory/fdk_cone.py``) produced amplitude-wrong
218
+ reconstructions because they:
219
+
220
+ - used ``*BackprojectorFunction.apply`` (the pure Siddon adjoint) as if
221
+ it were an FBP gather, so they missed the ``(sid/U)^2`` / ``(|S|/U_n)^2``
222
+ distance weight that the classical FBP / FDK formula requires;
223
+ - hand-rolled a ramp filter missing the ``1/sample_spacing`` scale;
224
+ - multiplied by a standalone ``pi/num_views`` normalisation that only
225
+ absorbs the angular step, not the ``1/(2*pi)`` Fourier-convention
226
+ constant.
227
+
228
+ All three examples now go through the new
229
+ ``parallel_weighted_backproject`` / ``fan_weighted_backproject`` /
230
+ ``cone_weighted_backproject`` wrappers and end-to-end produce raw MSE
231
+ matching the main branch's 1.2.11 release to within rounding:
232
+
233
+ | Example | Raw MSE | Range |
234
+ |-----------------------------------------|-----------|-------------------|
235
+ | ``circular_trajectory/fbp_parallel.py`` | ~0.00366 | ``[-0.02, 1.00]`` |
236
+ | ``circular_trajectory/fbp_fan.py`` | ~0.00220 | ``[-0.10, 1.01]`` |
237
+ | ``circular_trajectory/fdk_cone.py`` | ~0.00333 | ``[-0.07, 1.00]`` |
238
+
239
+ ### Notes on the sync from ``main``
240
+
241
+ The ``main`` branch's 1.2.10 / 1.2.11 / 1.3.0 releases introduced several
242
+ changes which this update brings to the dev branch, adapted where
243
+ necessary for the arbitrary-trajectory kernel API:
244
+
245
+ - ``1.2.10`` FDK / FBP voxel-driven gather, ``ramp_filter_1d`` options,
246
+ analytical constants, and the ``(sid/U)^2`` correctness fix: **ported**.
247
+ Because dev's kernels take per-view ``(src_pos, det_center, u_vec,
248
+ v_vec)`` arrays instead of closed-form ``sin(beta)/cos(beta)`` math,
249
+ the new gather kernels use ``U_n = (P - S) . n`` (where
250
+ ``n = u_vec x v_vec``) as the generalisation of the classical ``U``.
251
+ For a canonical circular orbit this reduces to the textbook
252
+ ``sid + x*sin(beta) - y*cos(beta)``.
253
+ - ``1.2.10`` cone / fan autograd ``distance_weight=1.0`` bug fix:
254
+ **not applicable**. The dev cone / fan backward kernels were
255
+ already correct (they never had that parameter), so the autograd
256
+ adjoint identity holds on dev and is now protected by
257
+ ``tests/test_adjoint_inner_product.py``.
258
+ - ``1.2.11`` gradcheck / ramp window tests / CHANGELOG: **ported**.
259
+ - ``1.2.11`` benchmark suite: **not ported yet** (dev's benchmarks
260
+ directory stays empty).
261
+ - ``1.3.0`` SF-TR / SF-TT projector backends: **not ported**. SF relies
262
+ on closed-form ``sin(beta)/cos(beta)`` math for the trapezoidal
263
+ transaxial / rectangular axial footprints. Generalising SF to
264
+ arbitrary per-view ``(S, C, u_vec, v_vec)`` trajectories is a
265
+ non-trivial research effort (the "separable" part is not separable
266
+ when the detector is not plane-aligned to the voxel axes) and will
267
+ be tackled in a dedicated follow-up.
268
+
269
+ [Unreleased]: https://github.com/sypsyp97/diffct/tree/dev
270
+
271
+ ## [1.3.4] - 2026-04-23
272
+
273
+ ### Changed
274
+ - **Siddon projectors switched to cell-constant integration.** The 2D
275
+ parallel, 2D fan, and 3D cone Siddon forward/backward kernels in
276
+ `diffct/differentiable.py` now integrate each ray by accumulating
277
+ `d_image[iy, ix] * seg_len` on the traversed pixel/voxel instead of
278
+ sampling the bi-/tri-linear interpolant at the segment midpoint.
279
+ This simplifies the inner loop, removes the interpolation weight
280
+ bookkeeping, and keeps the forward / adjoint pair matched by
281
+ construction (still verified by
282
+ `tests/test_adjoint_inner_product.py`). Kernel docstrings and the
283
+ `"siddon"` backend blurbs in `examples/iterative_reco_*.py` have
284
+ been updated to say *cell-constant Siddon*.
285
+ - **Iterative examples clamp the reconstruction in-place** instead of
286
+ wrapping the forward pass in `nn.ReLU`. `examples/iterative_reco_parallel.py`,
287
+ `iterative_reco_fan.py`, and `iterative_reco_cone.py` now call
288
+ `self.model.reco.clamp_(min=0.0)` under `torch.no_grad()` after each
289
+ optimizer step, so gradients flow through the unclamped volume while
290
+ the stored state stays non-negative.
291
+ - **`examples/iterative_reco_fan.py` default backend** is now
292
+ `"siddon"` (was `"sf"`), matching the parallel and cone iterative
293
+ examples.
294
+
295
+ ### Fixed
296
+ - **FDK cone accuracy test tolerance.** `tests/test_fdk_cone_accuracy.py`
297
+ raises the maximum central z-profile error bound from `0.6` to `0.65`
298
+ to reflect the slightly sharper one-sample transition error at
299
+ ellipsoid boundaries under cell-constant forward projection; global
300
+ FDK RMSE is unchanged.
301
+
8
302
  ## [1.3.3] - 2026-04-23
9
303
 
10
304
  ### Fixed