diffct 2.4.3__tar.gz → 2.4.4__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 (182) hide show
  1. {diffct-2.4.3 → diffct-2.4.4}/.gitignore +5 -0
  2. {diffct-2.4.3 → diffct-2.4.4}/CHANGELOG.md +22 -1
  3. {diffct-2.4.3 → diffct-2.4.4}/CITATION.cff +2 -2
  4. {diffct-2.4.3 → diffct-2.4.4}/NOTICE +2 -0
  5. {diffct-2.4.3 → diffct-2.4.4}/PKG-INFO +7 -4
  6. {diffct-2.4.3 → diffct-2.4.4}/README.md +6 -3
  7. {diffct-2.4.3 → diffct-2.4.4}/diffct/__init__.py +1 -1
  8. {diffct-2.4.3 → diffct-2.4.4}/diffct/analytical.py +12 -9
  9. {diffct-2.4.3 → diffct-2.4.4}/diffct/analytical_execution.py +7 -3
  10. {diffct-2.4.3 → diffct-2.4.4}/diffct/backends.py +505 -110
  11. {diffct-2.4.3 → diffct-2.4.4}/diffct/chunking.py +3 -2
  12. {diffct-2.4.3 → diffct-2.4.4}/diffct/constants.py +12 -0
  13. {diffct-2.4.3 → diffct-2.4.4}/diffct/kernels/analytical_transpose.py +40 -16
  14. {diffct-2.4.3 → diffct-2.4.4}/diffct/kernels/cone_beam.py +117 -63
  15. {diffct-2.4.3 → diffct-2.4.4}/diffct/kernels/fan_beam.py +49 -33
  16. {diffct-2.4.3 → diffct-2.4.4}/diffct/kernels/geometry_vjp.py +153 -51
  17. {diffct-2.4.3 → diffct-2.4.4}/diffct/kernels/parallel_beam.py +45 -15
  18. diffct-2.4.4/diffct/kernels/spacing.py +79 -0
  19. {diffct-2.4.3 → diffct-2.4.4}/diffct/kernels/surface_geometry.py +15 -4
  20. {diffct-2.4.3 → diffct-2.4.4}/diffct/operators.py +67 -11
  21. {diffct-2.4.3 → diffct-2.4.4}/diffct/projectors.py +13 -6
  22. {diffct-2.4.3 → diffct-2.4.4}/diffct/reconstruction.py +7 -4
  23. {diffct-2.4.3 → diffct-2.4.4}/diffct/surfaces.py +7 -2
  24. diffct-2.4.4/docs/assets/gallery/NOTICE +29 -0
  25. diffct-2.4.4/docs/search/tests/.gitignore +2 -0
  26. diffct-2.4.4/docs/search/tests/fixtures/make_tiny_model.py +89 -0
  27. diffct-2.4.4/docs/search/tests/fixtures/tiny-model/config.json +15 -0
  28. diffct-2.4.4/docs/search/tests/fixtures/tiny-model/onnx/model_quantized.onnx +0 -0
  29. diffct-2.4.4/docs/search/tests/fixtures/tiny-model/onnx/model_quantized.onnx_data +0 -0
  30. diffct-2.4.4/docs/search/tests/fixtures/tiny-model/tokenizer.json +140 -0
  31. diffct-2.4.4/docs/search/tests/fixtures/tiny-model/tokenizer_config.json +6 -0
  32. diffct-2.4.4/docs/search/tests/full-model.spec.mjs +59 -0
  33. diffct-2.4.4/docs/search/tests/harness.mjs +473 -0
  34. diffct-2.4.4/docs/search/tests/item1-ordering.spec.mjs +106 -0
  35. diffct-2.4.4/docs/search/tests/item2-single-load.spec.mjs +71 -0
  36. diffct-2.4.4/docs/search/tests/item3-cancel.spec.mjs +93 -0
  37. diffct-2.4.4/docs/search/tests/item4-leaks.spec.mjs +123 -0
  38. diffct-2.4.4/docs/search/tests/item5-errors.spec.mjs +115 -0
  39. diffct-2.4.4/docs/search/tests/item6-a11y.spec.mjs +186 -0
  40. diffct-2.4.4/docs/search/tests/item7-security.spec.mjs +101 -0
  41. diffct-2.4.4/docs/search/tests/playwright.config.mjs +17 -0
  42. {diffct-2.4.3 → diffct-2.4.4}/formal/README.md +24 -12
  43. {diffct-2.4.3 → diffct-2.4.4}/pyproject.toml +1 -1
  44. diffct-2.4.4/tests/_native_per_axis_spacing.py +261 -0
  45. diffct-2.4.4/tests/fixtures/native_scalar_spacing_main.npz +0 -0
  46. diffct-2.4.4/tests/test_native_per_axis_admission.py +99 -0
  47. diffct-2.4.4/tests/test_native_per_axis_extremes.py +139 -0
  48. diffct-2.4.4/tests/test_native_per_axis_helical.py +93 -0
  49. diffct-2.4.4/tests/test_native_per_axis_spacing.py +505 -0
  50. diffct-2.4.4/tests/test_native_per_axis_spacing_gpu.py +237 -0
  51. diffct-2.4.4/tests/test_portable_bounded_autograd.py +945 -0
  52. diffct-2.4.4/tests/test_portable_cuda_budget.py +996 -0
  53. {diffct-2.4.3 → diffct-2.4.4}/tests/test_reference_backend.py +12 -4
  54. {diffct-2.4.3 → diffct-2.4.4}/LICENSE +0 -0
  55. {diffct-2.4.3 → diffct-2.4.4}/diffct/_solver_state.py +0 -0
  56. {diffct-2.4.3 → diffct-2.4.4}/diffct/_solver_tv.py +0 -0
  57. {diffct-2.4.3 → diffct-2.4.4}/diffct/differentiable.py +0 -0
  58. {diffct-2.4.3 → diffct-2.4.4}/diffct/distribution.py +0 -0
  59. {diffct-2.4.3 → diffct-2.4.4}/diffct/geometry.py +0 -0
  60. {diffct-2.4.3 → diffct-2.4.4}/diffct/kernels/__init__.py +0 -0
  61. {diffct-2.4.3 → diffct-2.4.4}/diffct/pipeline.py +0 -0
  62. {diffct-2.4.3 → diffct-2.4.4}/diffct/solvers.py +0 -0
  63. {diffct-2.4.3 → diffct-2.4.4}/diffct/storage.py +0 -0
  64. {diffct-2.4.3 → diffct-2.4.4}/diffct/streaming.py +0 -0
  65. {diffct-2.4.3 → diffct-2.4.4}/diffct/surface_gradients.py +0 -0
  66. {diffct-2.4.3 → diffct-2.4.4}/diffct/utils.py +0 -0
  67. {diffct-2.4.3 → diffct-2.4.4}/docs/video/README.md +0 -0
  68. {diffct-2.4.3 → diffct-2.4.4}/examples/README.md +0 -0
  69. {diffct-2.4.3 → diffct-2.4.4}/examples/_block_cgls.py +0 -0
  70. {diffct-2.4.3 → diffct-2.4.4}/examples/_common.py +0 -0
  71. {diffct-2.4.3 → diffct-2.4.4}/examples/data/NOTICE +0 -0
  72. {diffct-2.4.3 → diffct-2.4.4}/examples/data/preprocess_walnut.py +0 -0
  73. {diffct-2.4.3 → diffct-2.4.4}/examples/walnut_reconstruction.py +0 -0
  74. {diffct-2.4.3 → diffct-2.4.4}/pytest.ini +0 -0
  75. {diffct-2.4.3 → diffct-2.4.4}/tests/__init__.py +0 -0
  76. {diffct-2.4.3 → diffct-2.4.4}/tests/_multiprocessing.py +0 -0
  77. {diffct-2.4.3 → diffct-2.4.4}/tests/_phantoms.py +0 -0
  78. {diffct-2.4.3 → diffct-2.4.4}/tests/benchmarks/README.md +0 -0
  79. {diffct-2.4.3 → diffct-2.4.4}/tests/benchmarks/__init__.py +0 -0
  80. {diffct-2.4.3 → diffct-2.4.4}/tests/benchmarks/_common.py +0 -0
  81. {diffct-2.4.3 → diffct-2.4.4}/tests/benchmarks/test_bench_cone.py +0 -0
  82. {diffct-2.4.3 → diffct-2.4.4}/tests/benchmarks/test_bench_fan.py +0 -0
  83. {diffct-2.4.3 → diffct-2.4.4}/tests/benchmarks/test_bench_parallel.py +0 -0
  84. {diffct-2.4.3 → diffct-2.4.4}/tests/distributed_projector_check.py +0 -0
  85. {diffct-2.4.3 → diffct-2.4.4}/tests/test_adjoint_inner_product.py +0 -0
  86. {diffct-2.4.3 → diffct-2.4.4}/tests/test_allocator_capacity.py +0 -0
  87. {diffct-2.4.3 → diffct-2.4.4}/tests/test_analytical_autograd.py +0 -0
  88. {diffct-2.4.3 → diffct-2.4.4}/tests/test_analytical_distribution.py +0 -0
  89. {diffct-2.4.3 → diffct-2.4.4}/tests/test_analytical_regressions.py +0 -0
  90. {diffct-2.4.3 → diffct-2.4.4}/tests/test_analytical_weight_distribution.py +0 -0
  91. {diffct-2.4.3 → diffct-2.4.4}/tests/test_angular_low_precision.py +0 -0
  92. {diffct-2.4.3 → diffct-2.4.4}/tests/test_axis_aligned_chords.py +0 -0
  93. {diffct-2.4.3 → diffct-2.4.4}/tests/test_batch_execution_edges.py +0 -0
  94. {diffct-2.4.3 → diffct-2.4.4}/tests/test_batched_surfaces.py +0 -0
  95. {diffct-2.4.3 → diffct-2.4.4}/tests/test_block_cgls.py +0 -0
  96. {diffct-2.4.3 → diffct-2.4.4}/tests/test_block_storage.py +0 -0
  97. {diffct-2.4.3 → diffct-2.4.4}/tests/test_call_trajectory.py +0 -0
  98. {diffct-2.4.3 → diffct-2.4.4}/tests/test_cgls_state_admission.py +0 -0
  99. {diffct-2.4.3 → diffct-2.4.4}/tests/test_checkpoint_metadata_validation.py +0 -0
  100. {diffct-2.4.3 → diffct-2.4.4}/tests/test_chunked_projector.py +0 -0
  101. {diffct-2.4.3 → diffct-2.4.4}/tests/test_completed_transfer_reuse.py +0 -0
  102. {diffct-2.4.3 → diffct-2.4.4}/tests/test_cone_backprojection_transpose.py +0 -0
  103. {diffct-2.4.3 → diffct-2.4.4}/tests/test_cone_projector_autograd.py +0 -0
  104. {diffct-2.4.3 → diffct-2.4.4}/tests/test_cpu_install.py +0 -0
  105. {diffct-2.4.3 → diffct-2.4.4}/tests/test_cuda_exit_deallocations.py +0 -0
  106. {diffct-2.4.3 → diffct-2.4.4}/tests/test_cuda_sink_lifetimes.py +0 -0
  107. {diffct-2.4.3 → diffct-2.4.4}/tests/test_cuda_smoke.py +0 -0
  108. {diffct-2.4.3 → diffct-2.4.4}/tests/test_detector_surface_dtypes.py +0 -0
  109. {diffct-2.4.3 → diffct-2.4.4}/tests/test_detector_surface_mapping.py +0 -0
  110. {diffct-2.4.3 → diffct-2.4.4}/tests/test_detector_surface_units.py +0 -0
  111. {diffct-2.4.3 → diffct-2.4.4}/tests/test_detector_surfaces.py +0 -0
  112. {diffct-2.4.3 → diffct-2.4.4}/tests/test_distributed_training_transforms.py +0 -0
  113. {diffct-2.4.3 → diffct-2.4.4}/tests/test_example_psnr.py +0 -0
  114. {diffct-2.4.3 → diffct-2.4.4}/tests/test_execution_buffers.py +0 -0
  115. {diffct-2.4.3 → diffct-2.4.4}/tests/test_execution_features.py +0 -0
  116. {diffct-2.4.3 → diffct-2.4.4}/tests/test_execution_schedules.py +0 -0
  117. {diffct-2.4.3 → diffct-2.4.4}/tests/test_fbp_fan_accuracy.py +0 -0
  118. {diffct-2.4.3 → diffct-2.4.4}/tests/test_fbp_fan_offsets.py +0 -0
  119. {diffct-2.4.3 → diffct-2.4.4}/tests/test_fbp_parallel_accuracy.py +0 -0
  120. {diffct-2.4.3 → diffct-2.4.4}/tests/test_fdk_cone_accuracy.py +0 -0
  121. {diffct-2.4.3 → diffct-2.4.4}/tests/test_fdk_cone_offsets.py +0 -0
  122. {diffct-2.4.3 → diffct-2.4.4}/tests/test_geometry_autograd.py +0 -0
  123. {diffct-2.4.3 → diffct-2.4.4}/tests/test_geometry_conversions.py +0 -0
  124. {diffct-2.4.3 → diffct-2.4.4}/tests/test_geometry_gradients.py +0 -0
  125. {diffct-2.4.3 → diffct-2.4.4}/tests/test_gpu_performance_contracts.py +0 -0
  126. {diffct-2.4.3 → diffct-2.4.4}/tests/test_gradcheck.py +0 -0
  127. {diffct-2.4.3 → diffct-2.4.4}/tests/test_lazy_native_observers.py +0 -0
  128. {diffct-2.4.3 → diffct-2.4.4}/tests/test_live_trajectory_validation.py +0 -0
  129. {diffct-2.4.3 → diffct-2.4.4}/tests/test_multi_gpu_exit.py +0 -0
  130. {diffct-2.4.3 → diffct-2.4.4}/tests/test_multiprocessing.py +0 -0
  131. {diffct-2.4.3 → diffct-2.4.4}/tests/test_native_endpoint_precision.py +0 -0
  132. {diffct-2.4.3 → diffct-2.4.4}/tests/test_native_surface_transforms.py +0 -0
  133. {diffct-2.4.3 → diffct-2.4.4}/tests/test_native_volume_layout.py +0 -0
  134. {diffct-2.4.3 → diffct-2.4.4}/tests/test_native_volume_layout_distributed.py +0 -0
  135. {diffct-2.4.3 → diffct-2.4.4}/tests/test_output_pipeline_overlap.py +0 -0
  136. {diffct-2.4.3 → diffct-2.4.4}/tests/test_parallel3d.py +0 -0
  137. {diffct-2.4.3 → diffct-2.4.4}/tests/test_parallel3d_surface_oracle.py +0 -0
  138. {diffct-2.4.3 → diffct-2.4.4}/tests/test_partition_lifecycle.py +0 -0
  139. {diffct-2.4.3 → diffct-2.4.4}/tests/test_plan_memory_bounds.py +0 -0
  140. {diffct-2.4.3 → diffct-2.4.4}/tests/test_portable_distributed_geometry.py +0 -0
  141. {diffct-2.4.3 → diffct-2.4.4}/tests/test_portable_endpoint_precision.py +0 -0
  142. {diffct-2.4.3 → diffct-2.4.4}/tests/test_portable_execution_bounds.py +0 -0
  143. {diffct-2.4.3 → diffct-2.4.4}/tests/test_portable_numeric_regressions.py +0 -0
  144. {diffct-2.4.3 → diffct-2.4.4}/tests/test_portable_sampler_snapshot.py +0 -0
  145. {diffct-2.4.3 → diffct-2.4.4}/tests/test_portable_surface_admission.py +0 -0
  146. {diffct-2.4.3 → diffct-2.4.4}/tests/test_pr42_review_regressions.py +0 -0
  147. {diffct-2.4.3 → diffct-2.4.4}/tests/test_projector_api.py +0 -0
  148. {diffct-2.4.3 → diffct-2.4.4}/tests/test_projector_degenerate_geometry.py +0 -0
  149. {diffct-2.4.3 → diffct-2.4.4}/tests/test_projector_geometry_devices.py +0 -0
  150. {diffct-2.4.3 → diffct-2.4.4}/tests/test_ramp_filter_windows.py +0 -0
  151. {diffct-2.4.3 → diffct-2.4.4}/tests/test_reconstruction_api.py +0 -0
  152. {diffct-2.4.3 → diffct-2.4.4}/tests/test_reconstruction_work_reuse.py +0 -0
  153. {diffct-2.4.3 → diffct-2.4.4}/tests/test_release_2_0_1.py +0 -0
  154. {diffct-2.4.3 → diffct-2.4.4}/tests/test_review_230.py +0 -0
  155. {diffct-2.4.3 → diffct-2.4.4}/tests/test_review_regressions.py +0 -0
  156. {diffct-2.4.3 → diffct-2.4.4}/tests/test_solver_admission_regressions.py +0 -0
  157. {diffct-2.4.3 → diffct-2.4.4}/tests/test_solver_api.py +0 -0
  158. {diffct-2.4.3 → diffct-2.4.4}/tests/test_solver_checkpoint_edges.py +0 -0
  159. {diffct-2.4.3 → diffct-2.4.4}/tests/test_solver_collective_failures.py +0 -0
  160. {diffct-2.4.3 → diffct-2.4.4}/tests/test_solver_device_scalars.py +0 -0
  161. {diffct-2.4.3 → diffct-2.4.4}/tests/test_spatial_partition.py +0 -0
  162. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_collective_failures.py +0 -0
  163. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_composition_kernel.py +0 -0
  164. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_compute_shards.py +0 -0
  165. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_geometry_cudasim.py +0 -0
  166. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_gpu_execution.py +0 -0
  167. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_gradient_replay.py +0 -0
  168. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_host_sampling_budget.py +0 -0
  169. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_native_regressions.py +0 -0
  170. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_parameter_updates.py +0 -0
  171. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_pipeline_contracts.py +0 -0
  172. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_saved_snapshot_budget.py +0 -0
  173. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_snapshot_admission.py +0 -0
  174. {diffct-2.4.3 → diffct-2.4.4}/tests/test_surface_trajectory_transform.py +0 -0
  175. {diffct-2.4.3 → diffct-2.4.4}/tests/test_training_transforms.py +0 -0
  176. {diffct-2.4.3 → diffct-2.4.4}/tests/test_trajectory_admission_cost.py +0 -0
  177. {diffct-2.4.3 → diffct-2.4.4}/tests/test_trajectory_surface_integration.py +0 -0
  178. {diffct-2.4.3 → diffct-2.4.4}/tests/test_transfer_pipeline.py +0 -0
  179. {diffct-2.4.3 → diffct-2.4.4}/tests/test_transfer_retirement_lifetimes.py +0 -0
  180. {diffct-2.4.3 → diffct-2.4.4}/tests/test_tv_zero_dependency.py +0 -0
  181. {diffct-2.4.3 → diffct-2.4.4}/tests/test_two_far_endpoints.py +0 -0
  182. {diffct-2.4.3 → diffct-2.4.4}/tests/test_weights.py +0 -0
@@ -193,3 +193,8 @@ projectBrief.md
193
193
  docs/video/media/
194
194
  docs/video/scenes.txt
195
195
  docs/video/palette.png
196
+
197
+ # Sphinx autosummary stubs
198
+ docs/source/generated/
199
+ docs/search/node_modules/
200
+ docs/search/tests/test-results/
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.4.4] - 2026-10-25
11
+
12
+ ### Added
13
+
14
+ - Native per-axis voxel spacing for projection, matched backprojection, geometry gradients and analytical FBP/FDK. Tensor axis order and physical length units match the PyTorch backend. Native pitches must be in `[1e-6, 1e6]`, with a maximum/minimum ratio of at most `1e4`. Outside these bounds, `numba` raises and `auto` selects PyTorch. On NVIDIA CUDA, the fallback warning names the reason. Scalar native arithmetic and admission are unchanged.
15
+
16
+ ### Fixed
17
+
18
+ - Portable (`backend="torch"`) autograd no longer records every ray batch and volume tile. Data gradients apply the matched adjoint, and geometry gradients recompute one ray batch and tile at a time. Saved autograd memory no longer grows with the number of batches, also inside `torch.vmap` and `torch.func.jvp` with differentiable inputs or tangents. Geometry backward splits large leading batches like the forward call and accepts empty leading batches. First-order values are unchanged.
19
+ - Second derivatives with respect to the geometry now raise an error on the single-process portable path, as documented. Before, they returned a value.
20
+ - Forward-mode derivatives (`torch.func.jvp`, `jacfwd`) with respect to `Projector` trajectory tensors now raise an error. Before, they returned zero tangents.
21
+ - Distributed operators reject trajectory dtype differences between ranks before gradient reduction, including per-call trajectory overrides.
22
+
23
+ ### Changed
24
+
25
+ - The portable PyTorch backend sizes its automatic volume tiles, view batches and pixel batches from CUDA memory when the compute devices are CUDA or ROCm devices. One call uses at most a quarter of the memory that the PyTorch allocator can still use when the call starts. This quarter covers work arrays and reduction buffers; geometry backward recomputes each pair with half of that limit. Geometry backward may also keep one cotangent-sized buffer outside that quarter, for a contiguous copy or a distributed sum. With replicated distributed outputs, geometry backward may hold up to three cotangent-sized buffers outside that quarter during the cotangent reduction. CPU inputs, outputs and host stores with CUDA compute devices stage at most the smaller of a quarter of the free host memory and the CUDA limit, but never less than 8 MiB. One work array stays below 2^31 elements. CPU calls keep the 8 MiB ceiling and their previous plans. Distributed ranks agree on the smallest limits. Results change only in floating-point summation order.
26
+ - Portable projection sets up the rays of a view/pixel batch once and reuses them for every volume tile. Projections, backprojections and their gradients are bitwise unchanged for the same chunks. Distributed backward through a backprojection keeps the previous tile order and holds one summed cotangent tile at a time.
27
+ - Portable outputs accumulate in place, so a call no longer holds a second full-size output. Execution statistics report the loop order that ran.
28
+ - `Projector(backend="auto")` emits one `UserWarning` when it selects the portable backend on an NVIDIA CUDA machine. The warning names the reasons. `backend="torch"` silences it.
29
+ - `backend="auto"` selects native NVIDIA execution for per-axis spacing in fast mode with views/space ownership. Strict precision modes and hybrid ownership continue to use PyTorch.
30
+
10
31
  ## [2.4.3] - 2026-10-22
11
32
 
12
33
  ### Changed
@@ -254,7 +275,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
254
275
 
255
276
  Version 2.0.0 replaces the circular-orbit API of 1.x with arbitrary
256
277
  per-view trajectories. It breaks the 1.x interface; see
257
- [docs/MIGRATION.md](docs/MIGRATION.md).
278
+ [docs/MIGRATION.md](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md).
258
279
 
259
280
  ### Breaking
260
281
 
@@ -13,8 +13,8 @@ authors:
13
13
  given-names: Yipeng
14
14
  email: yipeng.sun@fau.de
15
15
  alias: sypsyp97
16
- version: 2.4.3
17
- date-released: "2026-10-22"
16
+ version: 2.4.4
17
+ date-released: "2026-10-25"
18
18
  license: Apache-2.0
19
19
  repository-code: "https://github.com/sypsyp97/diffct"
20
20
  url: "https://sypsyp97.github.io/diffct/"
@@ -16,3 +16,5 @@ https://creativecommons.org/licenses/by/4.0/). The walnut images in
16
16
  docs/assets/ (walnut_measured.png, walnut_helical.png, diffct_intro.gif,
17
17
  diffct_intro.mp4) are reconstructions from these data. Credit the dataset as
18
18
  above when you reuse them. examples/data/NOTICE describes the preprocessing.
19
+
20
+ The documentation figures use third-party CT data; see docs/assets/gallery/NOTICE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: diffct
3
- Version: 2.4.3
3
+ Version: 2.4.4
4
4
  Summary: Differentiable CT projection and reconstruction: precision modes, streaming, distributed execution and geometry gradients
5
5
  Project-URL: Homepage, https://github.com/sypsyp97/diffct
6
6
  Project-URL: Documentation, https://sypsyp97.github.io/diffct/
@@ -90,6 +90,7 @@ Capabilities, limits and isocenter rules: [docs/REFERENCE.md](https://github.com
90
90
  For the native path, install NVIDIA CUDA PyTorch first, then diffct with the matching CUDA major extra:
91
91
 
92
92
  ```bash
93
+ python -c "import torch; print(torch.version.cuda)" # 12.x or 13.x
93
94
  pip install "diffct[cu12]" # PyTorch built for CUDA 12
94
95
  pip install "diffct[cu13]" # PyTorch built for CUDA 13
95
96
  ```
@@ -142,6 +143,8 @@ loss.backward() # x.grad = A^T (A x - y)
142
143
 
143
144
  **Geometry gradients.** Reuse one projector with `A.project(volume, trajectory=current)` or `A.backproject(sinogram, trajectory=current)`. Circular helpers accept learnable angle tensors, and `transform_trajectory` applies differentiable rigid transforms. Omitting the override keeps the constructor trajectory. See [REFERENCE](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#geometry-gradients).
144
145
 
146
+ **Per-axis voxel spacing.** Set `voxel_spacing=(dy, dx)` or `(dz, dy, dx)` in tensor axis order. Both backends support these pitches for projection, projection geometry gradients and FBP/FDK with fixed geometry.
147
+
145
148
  **3D parallel beams.** Use `circular_trajectory_3d_parallel(...)` and `beam="parallel3d"`. The PyTorch backend provides matched projection and backprojection on CPU and CUDA.
146
149
 
147
150
  **Native volume layout.** For unbatched, flat-detector cone operations, set `native_volume_layout=True` to avoid volume layout copies. This applies to the native full-CUDA path on one compute device. Shapes stay `(D, H, W)`; the returned strides follow the kernel layout. The default is `False`. See [chunked execution](docs/REFERENCE.md#chunked-execution) for limits.
@@ -177,8 +180,8 @@ volume slabs use `--partition space` under torchrun. Run
177
180
 
178
181
  <p align="center">
179
182
  <picture>
180
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png?v=2.4.3">
181
- <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png?v=2.4.3" width="100%" alt="Time per forward, adjoint and CGLS iteration on 1, 4 and 8 GPUs, 512^3 volume">
183
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png">
184
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png" width="100%" alt="Time per forward, adjoint and CGLS iteration on 1, 4 and 8 GPUs, 512^3 volume">
182
185
  </picture>
183
186
  </p>
184
187
 
@@ -188,7 +191,7 @@ volume slabs use `--partition space` under torchrun. Run
188
191
  | Adjoint (backprojection) | 1060.77 ms | 279.37 ms (3.8×) | 224.04 ms (4.7×) |
189
192
  | CGLS iteration | 1753.73 ms | 468.09 ms (3.7×) | 337.39 ms (5.2×) |
190
193
 
191
- diffct 2.4.3, FP32, A100 64 GB GPUs; circular cone beam, 360 views, 1024×768 detector, pitch 1.25. Default memory settings, without an imposed allocator cap.
194
+ FP32, A100 64 GB GPUs; circular cone beam, 360 views, 1024×768 detector, pitch 1.25. Default memory settings, without an imposed allocator cap.
192
195
 
193
196
  Projection times are medians of seven samples after three warmups. CGLS uses three ten-iteration solves after a two-iteration warmup; the median includes initialization and is divided by ten. Timings use synchronized wall time and the slowest rank. Raw samples and configuration: [scaling.json](https://github.com/sypsyp97/diffct/blob/main/docs/assets/scaling.json).
194
197
 
@@ -59,6 +59,7 @@ Capabilities, limits and isocenter rules: [docs/REFERENCE.md](https://github.com
59
59
  For the native path, install NVIDIA CUDA PyTorch first, then diffct with the matching CUDA major extra:
60
60
 
61
61
  ```bash
62
+ python -c "import torch; print(torch.version.cuda)" # 12.x or 13.x
62
63
  pip install "diffct[cu12]" # PyTorch built for CUDA 12
63
64
  pip install "diffct[cu13]" # PyTorch built for CUDA 13
64
65
  ```
@@ -111,6 +112,8 @@ loss.backward() # x.grad = A^T (A x - y)
111
112
 
112
113
  **Geometry gradients.** Reuse one projector with `A.project(volume, trajectory=current)` or `A.backproject(sinogram, trajectory=current)`. Circular helpers accept learnable angle tensors, and `transform_trajectory` applies differentiable rigid transforms. Omitting the override keeps the constructor trajectory. See [REFERENCE](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#geometry-gradients).
113
114
 
115
+ **Per-axis voxel spacing.** Set `voxel_spacing=(dy, dx)` or `(dz, dy, dx)` in tensor axis order. Both backends support these pitches for projection, projection geometry gradients and FBP/FDK with fixed geometry.
116
+
114
117
  **3D parallel beams.** Use `circular_trajectory_3d_parallel(...)` and `beam="parallel3d"`. The PyTorch backend provides matched projection and backprojection on CPU and CUDA.
115
118
 
116
119
  **Native volume layout.** For unbatched, flat-detector cone operations, set `native_volume_layout=True` to avoid volume layout copies. This applies to the native full-CUDA path on one compute device. Shapes stay `(D, H, W)`; the returned strides follow the kernel layout. The default is `False`. See [chunked execution](docs/REFERENCE.md#chunked-execution) for limits.
@@ -146,8 +149,8 @@ volume slabs use `--partition space` under torchrun. Run
146
149
 
147
150
  <p align="center">
148
151
  <picture>
149
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png?v=2.4.3">
150
- <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png?v=2.4.3" width="100%" alt="Time per forward, adjoint and CGLS iteration on 1, 4 and 8 GPUs, 512^3 volume">
152
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png">
153
+ <img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png" width="100%" alt="Time per forward, adjoint and CGLS iteration on 1, 4 and 8 GPUs, 512^3 volume">
151
154
  </picture>
152
155
  </p>
153
156
 
@@ -157,7 +160,7 @@ volume slabs use `--partition space` under torchrun. Run
157
160
  | Adjoint (backprojection) | 1060.77 ms | 279.37 ms (3.8×) | 224.04 ms (4.7×) |
158
161
  | CGLS iteration | 1753.73 ms | 468.09 ms (3.7×) | 337.39 ms (5.2×) |
159
162
 
160
- diffct 2.4.3, FP32, A100 64 GB GPUs; circular cone beam, 360 views, 1024×768 detector, pitch 1.25. Default memory settings, without an imposed allocator cap.
163
+ FP32, A100 64 GB GPUs; circular cone beam, 360 views, 1024×768 detector, pitch 1.25. Default memory settings, without an imposed allocator cap.
161
164
 
162
165
  Projection times are medians of seven samples after three warmups. CGLS uses three ten-iteration solves after a two-iteration warmup; the median includes initialization and is divided by ten. Timings use synchronized wall time and the slowest rank. Raw samples and configuration: [scaling.json](https://github.com/sypsyp97/diffct/blob/main/docs/assets/scaling.json).
163
166
 
@@ -64,7 +64,7 @@ try:
64
64
  except ImportError:
65
65
  pass
66
66
 
67
- __version__ = '2.4.3'
67
+ __version__ = '2.4.4'
68
68
 
69
69
  __all__ = [
70
70
  'Projector',
@@ -27,7 +27,7 @@ import math
27
27
 
28
28
  import torch
29
29
 
30
- from .constants import _DTYPE
30
+ from .constants import _DTYPE, _native_spacing
31
31
  from .utils import (
32
32
  DeviceManager,
33
33
  TorchCUDABridge,
@@ -431,7 +431,7 @@ def _parallel_weighted_backproject_raw(sinogram, ray_dir, det_origin, det_u_vec,
431
431
  _resolve_kernel('_parallel_2d_fbp_backproject_kernel')[grid, tpb, numba_stream](
432
432
  d_sino, n_views, n_det, d_reco, Nx, Ny,
433
433
  _DTYPE(detector_spacing), d_ray_dir, d_det_origin, d_det_u_vec,
434
- cx, cy, _DTYPE(voxel_spacing),
434
+ cx, cy, _native_spacing(voxel_spacing),
435
435
  )
436
436
 
437
437
  return reco * (1.0 / (2.0 * math.pi))
@@ -522,8 +522,9 @@ def _fan_weighted_backproject_raw(sinogram, src_pos, det_center, det_u_vec,
522
522
  _resolve_kernel('_fan_2d_fbp_backproject_kernel')[grid, tpb, numba_stream](
523
523
  d_sino, n_views, n_det, d_reco, Nx, Ny,
524
524
  _DTYPE(detector_spacing), d_src_pos, d_det_center, d_det_u_vec,
525
- cx, cy, _DTYPE(voxel_spacing),
526
- *(_DTYPE(value / voxel_spacing) for value in isocenter.tolist()),
525
+ cx, cy, _native_spacing(voxel_spacing),
526
+ *(_DTYPE(value if isinstance(voxel_spacing, (tuple, list)) else value / voxel_spacing)
527
+ for value in isocenter.tolist()),
527
528
  )
528
529
 
529
530
  return reco
@@ -584,8 +585,9 @@ def _cone_weighted_backproject_raw(sinogram, src_pos, det_center, det_u_vec, det
584
585
  d_sino, n_views, n_u, n_v, d_reco, Nx, Ny, Nz,
585
586
  _DTYPE(du), _DTYPE(dv),
586
587
  d_src_pos, d_det_center, d_det_u_vec, d_det_v_vec,
587
- cx, cy, cz, _DTYPE(voxel_spacing),
588
- *(_DTYPE(value / voxel_spacing) for value in isocenter.tolist()),
588
+ cx, cy, cz, _native_spacing(voxel_spacing),
589
+ *(_DTYPE(value if isinstance(voxel_spacing, (tuple, list)) else value / voxel_spacing)
590
+ for value in isocenter.tolist()),
589
591
  )
590
592
 
591
593
  # Convert back to (D, H, W) layout for the user.
@@ -637,7 +639,8 @@ def _analytical_transpose_raw(image, beam, geometry, parameters, isocenter, sino
637
639
  scale = (sdd_n / (2.0 * math.pi * sid_n)).view(
638
640
  (-1,) + (1,) * (len(sinogram_shape) - 1)
639
641
  )
640
- iso = tuple(_DTYPE(value / parameters[-1]) for value in isocenter.tolist())
642
+ iso = tuple(_DTYPE(value if isinstance(parameters[-1], (tuple, list)) else value / parameters[-1])
643
+ for value in isocenter.tolist())
641
644
 
642
645
  if beam == "cone":
643
646
  D, H, W, du, dv, spacing = parameters
@@ -647,7 +650,7 @@ def _analytical_transpose_raw(image, beam, geometry, parameters, isocenter, sino
647
650
  _resolve_kernel('_cone_3d_fdk_transpose_kernel')[grid, tpb, stream](
648
651
  d_sino, *sinogram_shape, d_image, W, H, D, _DTYPE(du), _DTYPE(dv),
649
652
  *d_geometry, _DTYPE(W * 0.5), _DTYPE(H * 0.5), _DTYPE(D * 0.5),
650
- _DTYPE(spacing), *iso,
653
+ _native_spacing(spacing), *iso,
651
654
  )
652
655
  else:
653
656
  pitch, H, W, spacing = parameters
@@ -657,7 +660,7 @@ def _analytical_transpose_raw(image, beam, geometry, parameters, isocenter, sino
657
660
  else '_fan_2d_fbp_transpose_kernel')
658
661
  kernel[grid, tpb, stream](
659
662
  d_sino, *sinogram_shape, d_image, W, H, _DTYPE(pitch), *d_geometry,
660
- _DTYPE(W * 0.5), _DTYPE(H * 0.5), _DTYPE(spacing), *iso,
663
+ _DTYPE(W * 0.5), _DTYPE(H * 0.5), _native_spacing(spacing), *iso,
661
664
  )
662
665
  return sinogram * scale
663
666
 
@@ -16,6 +16,7 @@ from .analytical import (
16
16
  fan_weighted_backproject, parallel_weighted_backproject,
17
17
  )
18
18
  from .backends import _zero_dependency, select_backend, voxel_spacing_tuple
19
+ from .constants import _native_spacing
19
20
  from .chunking import _memory_budget, _pixel_tiles
20
21
  from .distribution import Partition, preflight
21
22
  from .operators import _detector_spacing, _normalize_devices, _positive_int, _shape, _validate_trajectory
@@ -143,6 +144,9 @@ class AnalyticalExecution:
143
144
  self.rank = 3 if beam == "cone" else 2
144
145
  self.global_volume_shape = _shape(volume_shape, self.rank, "volume_shape")
145
146
  self.spacing = voxel_spacing_tuple(voxel_spacing, self.rank)
147
+ self.voxel_spacing = self.spacing if isinstance(voxel_spacing, (tuple, list)) else self.spacing[0]
148
+ if self.backend == "numba" and isinstance(self.voxel_spacing, tuple):
149
+ _native_spacing(self.voxel_spacing)
146
150
  self.pitch = _detector_spacing(detector_spacing, beam)
147
151
  self.devices = _normalize_devices(devices)
148
152
  if not isinstance(source, torch.Tensor):
@@ -336,14 +340,14 @@ class AnalyticalExecution:
336
340
  device=data.device, dtype=self.dtype)
337
341
  if self.beam == "parallel":
338
342
  geometry = (geometry[0], geometry[1] - centre, geometry[2])
339
- return parallel_weighted_backproject(data, *geometry, self.pitch[0], *shape, self.spacing[0])
343
+ return parallel_weighted_backproject(data, *geometry, self.pitch[0], *shape, self.voxel_spacing)
340
344
  geometry = (geometry[0] - centre, geometry[1] - centre, *geometry[2:])
341
345
  isocenter = self.isocenter.to(data.device) - centre
342
346
  if self.beam == "fan":
343
347
  return fan_weighted_backproject(data, *geometry, self.pitch[0], *shape,
344
- self.spacing[0], isocenter=isocenter)
348
+ self.voxel_spacing, isocenter=isocenter)
345
349
  return cone_weighted_backproject(data, *geometry, *shape, *self.pitch,
346
- self.spacing[0], isocenter=isocenter)
350
+ self.voxel_spacing, isocenter=isocenter)
347
351
 
348
352
  def _tile(self, spatial):
349
353
  shape = tuple(part.stop - part.start for part in spatial)