diffct 1.3.4__tar.gz → 2.0.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 (99) hide show
  1. {diffct-1.3.4 → diffct-2.0.1}/.gitignore +195 -187
  2. {diffct-1.3.4 → diffct-2.0.1}/CHANGELOG.md +291 -0
  3. diffct-2.0.1/CITATION.cff +51 -0
  4. {diffct-1.3.4 → diffct-2.0.1}/LICENSE +201 -201
  5. diffct-2.0.1/NOTICE +18 -0
  6. diffct-2.0.1/PKG-INFO +238 -0
  7. diffct-2.0.1/README.md +207 -0
  8. diffct-2.0.1/diffct/__init__.py +83 -0
  9. diffct-2.0.1/diffct/analytical.py +573 -0
  10. diffct-2.0.1/diffct/constants.py +72 -0
  11. diffct-2.0.1/diffct/differentiable.py +74 -0
  12. diffct-2.0.1/diffct/geometry.py +1004 -0
  13. diffct-2.0.1/diffct/kernels/__init__.py +43 -0
  14. diffct-2.0.1/diffct/kernels/cone_beam.py +617 -0
  15. diffct-2.0.1/diffct/kernels/fan_beam.py +451 -0
  16. diffct-2.0.1/diffct/kernels/geometry_vjp.py +562 -0
  17. diffct-2.0.1/diffct/kernels/parallel_beam.py +400 -0
  18. diffct-2.0.1/diffct/operators.py +555 -0
  19. diffct-2.0.1/diffct/projectors.py +911 -0
  20. diffct-2.0.1/diffct/utils.py +423 -0
  21. diffct-2.0.1/docs/video/README.md +23 -0
  22. diffct-2.0.1/examples/README.md +141 -0
  23. {diffct-1.3.4 → diffct-2.0.1}/examples/data/NOTICE +47 -47
  24. diffct-2.0.1/formal/README.md +82 -0
  25. diffct-2.0.1/pyproject.toml +49 -0
  26. {diffct-1.3.4 → diffct-2.0.1}/pytest.ini +14 -14
  27. diffct-2.0.1/tests/_phantoms.py +81 -0
  28. {diffct-1.3.4 → diffct-2.0.1}/tests/benchmarks/README.md +56 -60
  29. {diffct-1.3.4 → diffct-2.0.1}/tests/benchmarks/_common.py +57 -62
  30. {diffct-1.3.4 → diffct-2.0.1}/tests/benchmarks/test_bench_cone.py +137 -145
  31. {diffct-1.3.4 → diffct-2.0.1}/tests/benchmarks/test_bench_fan.py +121 -127
  32. {diffct-1.3.4 → diffct-2.0.1}/tests/benchmarks/test_bench_parallel.py +113 -115
  33. diffct-2.0.1/tests/distributed_projector_check.py +709 -0
  34. diffct-2.0.1/tests/test_adjoint_inner_product.py +170 -0
  35. diffct-2.0.1/tests/test_analytical_regressions.py +182 -0
  36. {diffct-1.3.4 → diffct-2.0.1}/tests/test_cone_projector_autograd.py +76 -91
  37. diffct-2.0.1/tests/test_cuda_smoke.py +121 -0
  38. diffct-2.0.1/tests/test_fbp_fan_accuracy.py +92 -0
  39. diffct-2.0.1/tests/test_fbp_fan_offsets.py +117 -0
  40. diffct-2.0.1/tests/test_fbp_parallel_accuracy.py +72 -0
  41. diffct-2.0.1/tests/test_fdk_cone_accuracy.py +114 -0
  42. diffct-2.0.1/tests/test_fdk_cone_offsets.py +118 -0
  43. diffct-2.0.1/tests/test_geometry_autograd.py +51 -0
  44. diffct-2.0.1/tests/test_geometry_gradients.py +402 -0
  45. diffct-2.0.1/tests/test_gradcheck.py +125 -0
  46. diffct-2.0.1/tests/test_multi_gpu_exit.py +83 -0
  47. diffct-2.0.1/tests/test_projector_api.py +796 -0
  48. diffct-2.0.1/tests/test_projector_degenerate_geometry.py +257 -0
  49. diffct-2.0.1/tests/test_projector_geometry_devices.py +69 -0
  50. diffct-2.0.1/tests/test_ramp_filter_windows.py +136 -0
  51. diffct-2.0.1/tests/test_release_2_0_1.py +326 -0
  52. diffct-2.0.1/tests/test_weights.py +157 -0
  53. diffct-1.3.4/.git +0 -1
  54. diffct-1.3.4/.github/workflows/docs.yml +0 -37
  55. diffct-1.3.4/.github/workflows/release.yml +0 -20
  56. diffct-1.3.4/PKG-INFO +0 -385
  57. diffct-1.3.4/README.md +0 -368
  58. diffct-1.3.4/README.zh.md +0 -335
  59. diffct-1.3.4/diffct/__init__.py +0 -18
  60. diffct-1.3.4/diffct/differentiable.py +0 -5220
  61. diffct-1.3.4/docs/Makefile +0 -20
  62. diffct-1.3.4/docs/source/api.rst +0 -291
  63. diffct-1.3.4/docs/source/conf.py +0 -35
  64. diffct-1.3.4/docs/source/examples.rst +0 -30
  65. diffct-1.3.4/docs/source/fbp_fan_example.rst +0 -158
  66. diffct-1.3.4/docs/source/fbp_parallel_example.rst +0 -117
  67. diffct-1.3.4/docs/source/fdk_cone_example.rst +0 -184
  68. diffct-1.3.4/docs/source/getting_started.rst +0 -84
  69. diffct-1.3.4/docs/source/index.rst +0 -67
  70. diffct-1.3.4/docs/source/iterative_reco_cone_example.rst +0 -131
  71. diffct-1.3.4/docs/source/iterative_reco_fan_example.rst +0 -90
  72. diffct-1.3.4/docs/source/iterative_reco_parallel_example.rst +0 -96
  73. diffct-1.3.4/examples/data/preprocess_walnut.py +0 -303
  74. diffct-1.3.4/examples/data/walnut_cone.npz +0 -0
  75. diffct-1.3.4/examples/data/walnut_reco.png +0 -0
  76. diffct-1.3.4/examples/fbp_fan.py +0 -356
  77. diffct-1.3.4/examples/fbp_parallel.py +0 -227
  78. diffct-1.3.4/examples/fdk_cone.py +0 -495
  79. diffct-1.3.4/examples/iterative_reco_cone.py +0 -217
  80. diffct-1.3.4/examples/iterative_reco_fan.py +0 -187
  81. diffct-1.3.4/examples/iterative_reco_parallel.py +0 -134
  82. diffct-1.3.4/examples/realdata_fbp_fan.py +0 -300
  83. diffct-1.3.4/examples/realdata_fbp_parallel.py +0 -269
  84. diffct-1.3.4/examples/realdata_fdk_cone.py +0 -345
  85. diffct-1.3.4/examples/realdata_walnut_fdk.py +0 -363
  86. diffct-1.3.4/pyproject.toml +0 -39
  87. diffct-1.3.4/requirements.txt +0 -5
  88. diffct-1.3.4/tests/test_adjoint_inner_product.py +0 -286
  89. diffct-1.3.4/tests/test_cuda_smoke.py +0 -84
  90. diffct-1.3.4/tests/test_fbp_fan_accuracy.py +0 -147
  91. diffct-1.3.4/tests/test_fbp_fan_offsets.py +0 -162
  92. diffct-1.3.4/tests/test_fbp_parallel_accuracy.py +0 -164
  93. diffct-1.3.4/tests/test_fdk_cone_accuracy.py +0 -199
  94. diffct-1.3.4/tests/test_fdk_cone_offsets.py +0 -210
  95. diffct-1.3.4/tests/test_gradcheck.py +0 -209
  96. diffct-1.3.4/tests/test_ramp_filter_windows.py +0 -217
  97. diffct-1.3.4/tests/test_weights.py +0 -103
  98. {diffct-1.3.4/tests/benchmarks → diffct-2.0.1/tests}/__init__.py +0 -0
  99. /diffct-1.3.4/docs/source/_static/.gitkeep → /diffct-2.0.1/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,297 @@ 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.1] - 2026-10-09
9
+
10
+ ### Fixed
11
+
12
+ - The low-level projector and backprojector Functions and the analytical
13
+ weighted backprojections run in the CUDA context of their input. Before,
14
+ inputs on another GPU than the current device could use the wrong context
15
+ and stream.
16
+ - ``custom_trajectory_3d`` and ``custom_trajectory_2d_fan`` raise
17
+ ``ValueError`` for a source at the isocenter or a non-finite source,
18
+ instead of returning NaN geometry. The figure-8 example in the
19
+ ``custom_trajectory_3d`` docstring no longer puts a source at the origin.
20
+ - The class docstring examples of the six projector Functions use the
21
+ per-view trajectory API.
22
+ - ``_trig_tables`` accepts NumPy dtypes for tensor input.
23
+ - Walnut example: ``preprocess_walnut.py`` raises when projection archives or
24
+ views are missing or an air level is not positive, instead of writing NaN
25
+ data. A non-default crop keeps the detector centre offset, and
26
+ ``walnut_reconstruction.py`` applies it to the geometry and the FDK
27
+ weights. The default crop has no offset.
28
+ - Examples: ``psnr`` returns infinity for identical images; distributed
29
+ reductions in CGLS, TV and the noise peak use the operator's process
30
+ group.
31
+
32
+ ### Added
33
+
34
+ - ``Projector.process_group``: the process group of a distributed operator.
35
+
36
+ ## [2.0.0] - 2026-10-09
37
+
38
+ Version 2.0.0 replaces the circular-orbit API of 1.x with arbitrary
39
+ per-view trajectories. It breaks the 1.x interface; see
40
+ [docs/MIGRATION.md](docs/MIGRATION.md).
41
+
42
+ ### Breaking
43
+
44
+ - ``diffct.Projector`` is the main entry point. Geometry is a tuple of
45
+ per-view tensors (source, detector centre, detector axes) instead of
46
+ scalar ``angles``, ``sid`` and ``sdd``.
47
+ - The low-level ``*ProjectorFunction`` / ``*BackprojectorFunction`` classes
48
+ and the ``*_weighted_backproject`` helpers take per-view geometry tensors.
49
+ - The separable-footprint backends (``sf``, ``sf_tr``, ``sf_tt``) of 1.3.x
50
+ are removed. All projectors use the matched cell-constant Siddon model.
51
+
52
+ ### Added
53
+
54
+ - Multi-GPU execution in one process (``devices=[...]``) and one process per
55
+ GPU with torchrun and NCCL, on one or several nodes.
56
+ - Install extras ``diffct[cu12]`` and ``diffct[cu13]`` pull in the matching
57
+ numba-cuda CUDA bindings, so one ``pip install`` gives a working import.
58
+ - Measured walnut example (``examples/walnut_reconstruction.py``), intro video
59
+ and README figures.
60
+ - Geometry gradients. Trajectory tensors may require gradients; ``Projector``
61
+ and the low-level Function classes return gradients for source, detector
62
+ centre and detector axes (parallel: ray direction, detector origin and
63
+ axis). CUDA kernels compute the exact derivative of the cell-constant
64
+ Siddon model from the face crossings of each ray. Distributed mode sums
65
+ the gradients over ranks.
66
+ - Second derivatives for volumes and sinograms: backward passes are autograd
67
+ Functions themselves. Second derivatives with respect to the geometry
68
+ raise ``RuntimeError``.
69
+
70
+ ### Changed
71
+
72
+ - Detector arrays are centred: cell ``k`` lies at
73
+ ``(k - (N - 1) / 2) * pitch`` from ``det_center`` (or ``det_origin``), as in
74
+ ``main``. Earlier dev versions used ``(k - N / 2) * pitch``. The projector,
75
+ the FBP/FDK gathers and ``detector_coordinates_1d`` all changed.
76
+ - Siddon projections are line integrals in physical length. Earlier
77
+ versions returned voxel-length integrals, which differ by the factor
78
+ ``voxel_spacing``. Backprojection scales the same way, so the pair stays
79
+ matched. Results with ``voxel_spacing == 1`` do not change.
80
+ - FBP and FDK gather kernels sample at voxel centres,
81
+ ``(index + 0.5 - N / 2) * voxel_spacing``, as the cell-constant Siddon
82
+ projector does. Earlier reconstructions were half a voxel off.
83
+ - Siddon projector and backprojector kernels no longer skip ray pieces of
84
+ length ``<= 1e-6``. They skip only pieces of length exactly 0 or less.
85
+ Forward projection is now a continuous function of the geometry: each piece
86
+ changes a projection value by at most cell value x ``1e-6``.
87
+
88
+ ### Fixed
89
+
90
+ - Fan and cone rays integrate only from the source to the detector pixel.
91
+ A source or detector inside the volume box gave the full-line integral.
92
+ - Fan and cone ray setup measures the ray parameter from the endpoint
93
+ nearer the volume centre, and the traversal restarts at the volume entry
94
+ point. A distant source gave relative errors up to 5e-3 (source 1e6
95
+ voxels away) and could stop the traversal. Parallel-beam setup runs in
96
+ float64 for a distant detector origin. Projection speed does not change.
97
+ - Ray setup treats a direction component as axis-parallel only below
98
+ 1e-20, so a distant source with a small lateral component is no longer
99
+ rejected.
100
+ - Siddon kernels use a finite sentinel instead of infinity, because
101
+ ``fastmath`` lets LLVM assume that infinity never occurs.
102
+ - ``Projector`` rejects fan and cone views whose source coincides with the
103
+ detector centre or whose detector is edge-on to the source, and views where
104
+ both lie farther than 1e6 voxels from the volume centre.
105
+ - The traversal treats a direction component as axis-parallel only below
106
+ 1e-20. With the earlier 1e-6 limit, a ray that grazed a grid line was
107
+ assigned to the neighbouring row.
108
+ - ``tests/distributed_projector_check.py`` no longer issues a MAX
109
+ all-reduce after a local failure while other ranks wait in a SUM
110
+ all-reduce.
111
+
112
+ ## [1.3.0.dev0] - 2026-04-14 (dev branch, not released on PyPI)
113
+
114
+ First sync of the dev (arbitrary-trajectory) branch against the main
115
+ branch's 1.2.10 / 1.2.11 analytical reconstruction overhaul. Brings
116
+ dev up to functional parity with main 1.2.11 except for the 1.3.0
117
+ separable-footprint (SF) projector backends, which rely on closed-form
118
+ circular-orbit geometry and are not yet generalised to arbitrary
119
+ trajectories.
120
+
121
+ ### Added
122
+
123
+ #### Analytical reconstruction helpers (``diffct.analytical``)
124
+
125
+ A new module exposes the following helpers, all trajectory-agnostic so
126
+ they work with both the circular trajectories and the arbitrary
127
+ ``(src_pos, det_center, det_u_vec[, det_v_vec])`` trajectory arrays
128
+ that dev-branch kernels already accept:
129
+
130
+ - ``detector_coordinates_1d`` — detector cell centre coordinates.
131
+ - ``angular_integration_weights`` — trapezoidal per-view weights for
132
+ the analytical FBP/FDK sum. Optional ``redundant_full_scan`` flag
133
+ absorbs the ``1/2`` redundancy factor for full scans.
134
+ - ``fan_cosine_weights`` / ``cone_cosine_weights`` — per-detector-cell
135
+ ``cos(gamma)`` pre-weight for fan / cone FBP pipelines.
136
+ - ``parker_weights`` — Parker short-scan redundancy weights for
137
+ circular fan geometries.
138
+ - ``ramp_filter_1d`` — generic 1D ramp filter with ``sample_spacing``,
139
+ ``pad_factor``, ``window`` (``"ram-lak"``, ``"hann"``, ``"hamming"``,
140
+ ``"cosine"``, ``"shepp-logan"``), and ``use_rfft`` options, rescaled
141
+ by ``1 / sample_spacing`` so the output is in physical units.
142
+ - ``parallel_weighted_backproject`` / ``fan_weighted_backproject`` /
143
+ ``cone_weighted_backproject`` — voxel-driven FBP / FDK backprojection
144
+ wrappers that dispatch to new dedicated gather kernels and apply the
145
+ analytical Fourier-convention constant (``1/(2*pi)`` for parallel,
146
+ ``sdd_mean/(2*pi*sid_mean)`` for fan and cone). These are the
147
+ **recommended path for analytical reconstruction** and replace the
148
+ previous pattern of passing a filtered sinogram through
149
+ ``*BackprojectorFunction.apply``.
150
+
151
+ #### Voxel-driven FBP / FDK gather kernels
152
+
153
+ Three new CUDA kernels, each under the dedicated ``fastmath=False``
154
+ ``_FDK_ACCURACY_DECORATOR`` added to ``diffct.constants``:
155
+
156
+ - ``_parallel_2d_fbp_backproject_kernel`` — no distance weighting.
157
+ - ``_fan_2d_fbp_backproject_kernel`` — ``(|S|/U_n)^2`` weighted,
158
+ where ``U_n`` is the signed distance from the per-view source to
159
+ the voxel along the detector normal.
160
+ - ``_cone_3d_fdk_backproject_kernel`` — same pattern in 3D.
161
+
162
+ Every kernel is voxel-driven: one thread per output pixel/voxel, loops
163
+ over views inside, projects the voxel onto the detector using the
164
+ per-view ``(src_pos, det_center, det_u_vec[, det_v_vec])`` arrays,
165
+ bilinearly samples the filtered sinogram, applies the per-view weight
166
+ and accumulates. They are completely separate from the pure Siddon
167
+ adjoint kernels that back the autograd path; autograd is untouched.
168
+
169
+ #### Tests (``tests/``)
170
+
171
+ The dev branch had no test directory at all prior to this change.
172
+ ``pytest.ini`` and ``tests/__init__.py`` are new, plus 58 tests across
173
+ 11 files that mirror the main-branch test layout:
174
+
175
+ - ``tests/test_adjoint_inner_product.py`` — ``<A x, y> = <x, A^T y>``
176
+ identity for parallel / fan / cone autograd pairs, plus an extra
177
+ ``test_cone_autograd_backward_matches_backprojector_forward`` that
178
+ protects the autograd ``ConeProjectorFunction.backward`` from
179
+ drifting away from the standalone ``ConeBackprojectorFunction``.
180
+ - ``tests/test_gradcheck.py`` — ``torch.autograd.gradcheck`` for every
181
+ projector Function with float32-calibrated tolerances.
182
+ - ``tests/test_weights.py`` — unit tests for ``detector_coordinates_1d``,
183
+ ``angular_integration_weights``, ``fan_cosine_weights``,
184
+ ``cone_cosine_weights`` and ``parker_weights``.
185
+ - ``tests/test_cuda_smoke.py`` — end-to-end smoke tests for every
186
+ Projector / Backprojector Function pair + the analytical
187
+ ``*_weighted_backproject`` wrappers.
188
+ - ``tests/test_cone_projector_autograd.py`` — gradient finiteness and
189
+ sparsity guard for the cone projector Function (circular + spiral
190
+ trajectories).
191
+ - ``tests/test_fbp_parallel_accuracy.py`` /
192
+ ``tests/test_fbp_fan_accuracy.py`` /
193
+ ``tests/test_fdk_cone_accuracy.py`` — quantitative RMSE and
194
+ amplitude bounds for a full Shepp-Logan FBP / FDK pipeline per
195
+ geometry. These would have tripped on the old dev reconstruction
196
+ path, which silently produced wrong-amplitude volumes.
197
+ - ``tests/test_fbp_fan_offsets.py`` /
198
+ ``tests/test_fdk_cone_offsets.py`` — adapted from main's offset
199
+ tests: dev's arbitrary-trajectory kernels have no scalar
200
+ ``detector_offset`` / ``center_offset_*`` parameters, so offsets
201
+ are applied by shifting the trajectory arrays directly. Verifies
202
+ that the FBP / FDK gather kernels handle non-centred trajectories.
203
+ - ``tests/test_ramp_filter_windows.py`` — 29 parametrised tests covering
204
+ every ``_ramp_window`` option (DC gain, Nyquist value, non-negativity)
205
+ and the full ``ramp_filter_1d`` end-to-end (shape, DC annihilation,
206
+ rfft vs complex-fft parity, ``sample_spacing`` scaling, high-frequency
207
+ pass-through).
208
+
209
+ #### Benchmark suite (``tests/benchmarks/``)
210
+
211
+ Opt-in ``pytest-benchmark`` suite covering every CUDA kernel in the
212
+ library (forward projector, pure-adjoint backprojector, and the full
213
+ analytical FBP / FDK pipeline) across three sizes for each of the
214
+ three geometries. 27 benchmarks total, excluded from the default
215
+ ``pytest tests/`` run via ``--ignore=tests/benchmarks`` in
216
+ ``pytest.ini``. Run explicitly with
217
+ ``pytest tests/benchmarks/ --benchmark-only``.
218
+
219
+ #### Parker short-scan demos in examples
220
+
221
+ ``examples/circular_trajectory/fbp_fan.py`` and
222
+ ``examples/circular_trajectory/fdk_cone.py`` now expose an
223
+ ``apply_parker`` switch. When enabled, the example switches the
224
+ trajectory to a minimal ``pi + 2*gamma_max`` short scan and applies
225
+ ``parker_weights`` to the sinogram before the ramp filter; when
226
+ disabled, the pipeline runs a full ``2*pi`` scan with the ``1/2``
227
+ redundancy factor absorbed by ``angular_integration_weights``. Both
228
+ branches produce correctly amplitude-calibrated reconstructions.
229
+
230
+ #### Documentation (``docs/source/api.rst``)
231
+
232
+ New "Analytical Reconstruction Helpers", "Ramp Filter Options", and
233
+ "Analytical FBP / FDK architecture" sections describe the new
234
+ ``diffct.analytical`` module, enumerate every ``ramp_filter_1d``
235
+ option, and explain the analytical scale factors used in each
236
+ ``*_weighted_backproject`` wrapper.
237
+
238
+ ### Fixed
239
+
240
+ #### FBP / FDK amplitude bugs in the ``circular_trajectory/*.py`` examples
241
+
242
+ Prior to this release the circular-trajectory FBP / FDK examples
243
+ (``examples/circular_trajectory/fbp_parallel.py``,
244
+ ``examples/circular_trajectory/fbp_fan.py``,
245
+ ``examples/circular_trajectory/fdk_cone.py``) produced amplitude-wrong
246
+ reconstructions because they:
247
+
248
+ - used ``*BackprojectorFunction.apply`` (the pure Siddon adjoint) as if
249
+ it were an FBP gather, so they missed the ``(sid/U)^2`` / ``(|S|/U_n)^2``
250
+ distance weight that the classical FBP / FDK formula requires;
251
+ - hand-rolled a ramp filter missing the ``1/sample_spacing`` scale;
252
+ - multiplied by a standalone ``pi/num_views`` normalisation that only
253
+ absorbs the angular step, not the ``1/(2*pi)`` Fourier-convention
254
+ constant.
255
+
256
+ All three examples now go through the new
257
+ ``parallel_weighted_backproject`` / ``fan_weighted_backproject`` /
258
+ ``cone_weighted_backproject`` wrappers and end-to-end produce raw MSE
259
+ matching the main branch's 1.2.11 release to within rounding:
260
+
261
+ | Example | Raw MSE | Range |
262
+ |-----------------------------------------|-----------|-------------------|
263
+ | ``circular_trajectory/fbp_parallel.py`` | ~0.00366 | ``[-0.02, 1.00]`` |
264
+ | ``circular_trajectory/fbp_fan.py`` | ~0.00220 | ``[-0.10, 1.01]`` |
265
+ | ``circular_trajectory/fdk_cone.py`` | ~0.00333 | ``[-0.07, 1.00]`` |
266
+
267
+ ### Notes on the sync from ``main``
268
+
269
+ The ``main`` branch's 1.2.10 / 1.2.11 / 1.3.0 releases introduced several
270
+ changes which this update brings to the dev branch, adapted where
271
+ necessary for the arbitrary-trajectory kernel API:
272
+
273
+ - ``1.2.10`` FDK / FBP voxel-driven gather, ``ramp_filter_1d`` options,
274
+ analytical constants, and the ``(sid/U)^2`` correctness fix: **ported**.
275
+ Because dev's kernels take per-view ``(src_pos, det_center, u_vec,
276
+ v_vec)`` arrays instead of closed-form ``sin(beta)/cos(beta)`` math,
277
+ the new gather kernels use ``U_n = (P - S) . n`` (where
278
+ ``n = u_vec x v_vec``) as the generalisation of the classical ``U``.
279
+ For a canonical circular orbit this reduces to the textbook
280
+ ``sid + x*sin(beta) - y*cos(beta)``.
281
+ - ``1.2.10`` cone / fan autograd ``distance_weight=1.0`` bug fix:
282
+ **not applicable**. The dev cone / fan backward kernels were
283
+ already correct (they never had that parameter), so the autograd
284
+ adjoint identity holds on dev and is now protected by
285
+ ``tests/test_adjoint_inner_product.py``.
286
+ - ``1.2.11`` gradcheck / ramp window tests / CHANGELOG: **ported**.
287
+ - ``1.2.11`` benchmark suite: **not ported yet** (dev's benchmarks
288
+ directory stays empty).
289
+ - ``1.3.0`` SF-TR / SF-TT projector backends: **not ported**. SF relies
290
+ on closed-form ``sin(beta)/cos(beta)`` math for the trapezoidal
291
+ transaxial / rectangular axial footprints. Generalising SF to
292
+ arbitrary per-view ``(S, C, u_vec, v_vec)`` trajectories is a
293
+ non-trivial research effort (the "separable" part is not separable
294
+ when the detector is not plane-aligned to the voxel axes) and will
295
+ be tackled in a dedicated follow-up.
296
+
297
+ [Unreleased]: https://github.com/sypsyp97/diffct/tree/dev
298
+
8
299
  ## [1.3.4] - 2026-04-23
9
300
 
10
301
  ### Changed