diffct 2.4.3__tar.gz → 2.5.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.
- {diffct-2.4.3 → diffct-2.5.0}/.gitignore +5 -0
- {diffct-2.4.3 → diffct-2.5.0}/CHANGELOG.md +49 -2
- {diffct-2.4.3 → diffct-2.5.0}/CITATION.cff +5 -4
- {diffct-2.4.3 → diffct-2.5.0}/NOTICE +2 -0
- {diffct-2.4.3 → diffct-2.5.0}/PKG-INFO +13 -9
- {diffct-2.4.3 → diffct-2.5.0}/README.md +11 -7
- {diffct-2.4.3 → diffct-2.5.0}/diffct/__init__.py +1 -1
- {diffct-2.4.3 → diffct-2.5.0}/diffct/_solver_state.py +34 -7
- {diffct-2.4.3 → diffct-2.5.0}/diffct/analytical.py +12 -9
- {diffct-2.4.3 → diffct-2.5.0}/diffct/analytical_execution.py +9 -3
- {diffct-2.4.3 → diffct-2.5.0}/diffct/backends.py +544 -121
- {diffct-2.4.3 → diffct-2.5.0}/diffct/chunking.py +3 -2
- {diffct-2.4.3 → diffct-2.5.0}/diffct/constants.py +12 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/geometry.py +57 -5
- {diffct-2.4.3 → diffct-2.5.0}/diffct/kernels/analytical_transpose.py +40 -16
- {diffct-2.4.3 → diffct-2.5.0}/diffct/kernels/cone_beam.py +117 -63
- {diffct-2.4.3 → diffct-2.5.0}/diffct/kernels/fan_beam.py +49 -33
- {diffct-2.4.3 → diffct-2.5.0}/diffct/kernels/geometry_vjp.py +153 -51
- {diffct-2.4.3 → diffct-2.5.0}/diffct/kernels/parallel_beam.py +45 -15
- diffct-2.5.0/diffct/kernels/spacing.py +79 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/kernels/surface_geometry.py +15 -4
- {diffct-2.4.3 → diffct-2.5.0}/diffct/operators.py +102 -28
- {diffct-2.4.3 → diffct-2.5.0}/diffct/projectors.py +13 -6
- {diffct-2.4.3 → diffct-2.5.0}/diffct/reconstruction.py +22 -7
- {diffct-2.4.3 → diffct-2.5.0}/diffct/streaming.py +7 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/surfaces.py +13 -4
- {diffct-2.4.3 → diffct-2.5.0}/diffct/utils.py +20 -0
- diffct-2.5.0/docs/assets/gallery/NOTICE +29 -0
- diffct-2.5.0/docs/search/tests/.gitignore +2 -0
- diffct-2.5.0/docs/search/tests/fixtures/make_tiny_model.py +89 -0
- diffct-2.5.0/docs/search/tests/fixtures/tiny-model/config.json +15 -0
- diffct-2.5.0/docs/search/tests/fixtures/tiny-model/onnx/model_quantized.onnx +0 -0
- diffct-2.5.0/docs/search/tests/fixtures/tiny-model/onnx/model_quantized.onnx_data +0 -0
- diffct-2.5.0/docs/search/tests/fixtures/tiny-model/tokenizer.json +140 -0
- diffct-2.5.0/docs/search/tests/fixtures/tiny-model/tokenizer_config.json +6 -0
- diffct-2.5.0/docs/search/tests/full-model.spec.mjs +59 -0
- diffct-2.5.0/docs/search/tests/harness.mjs +473 -0
- diffct-2.5.0/docs/search/tests/item1-ordering.spec.mjs +106 -0
- diffct-2.5.0/docs/search/tests/item2-single-load.spec.mjs +71 -0
- diffct-2.5.0/docs/search/tests/item3-cancel.spec.mjs +93 -0
- diffct-2.5.0/docs/search/tests/item4-leaks.spec.mjs +123 -0
- diffct-2.5.0/docs/search/tests/item5-errors.spec.mjs +115 -0
- diffct-2.5.0/docs/search/tests/item6-a11y.spec.mjs +186 -0
- diffct-2.5.0/docs/search/tests/item7-security.spec.mjs +101 -0
- diffct-2.5.0/docs/search/tests/playwright.config.mjs +17 -0
- {diffct-2.4.3 → diffct-2.5.0}/formal/README.md +41 -12
- diffct-2.5.0/formal/tla/README.md +131 -0
- {diffct-2.4.3 → diffct-2.5.0}/pyproject.toml +2 -2
- diffct-2.5.0/tests/_native_per_axis_spacing.py +261 -0
- diffct-2.5.0/tests/fixtures/native_scalar_spacing_main.npz +0 -0
- diffct-2.5.0/tests/test_geometry_input_validation.py +179 -0
- diffct-2.5.0/tests/test_mps_backend.py +431 -0
- diffct-2.5.0/tests/test_native_per_axis_admission.py +99 -0
- diffct-2.5.0/tests/test_native_per_axis_extremes.py +139 -0
- diffct-2.5.0/tests/test_native_per_axis_helical.py +93 -0
- diffct-2.5.0/tests/test_native_per_axis_spacing.py +505 -0
- diffct-2.5.0/tests/test_native_per_axis_spacing_gpu.py +237 -0
- diffct-2.5.0/tests/test_portable_bounded_autograd.py +945 -0
- diffct-2.5.0/tests/test_portable_cuda_budget.py +996 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_reference_backend.py +12 -4
- {diffct-2.4.3 → diffct-2.5.0}/LICENSE +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/_solver_tv.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/differentiable.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/distribution.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/kernels/__init__.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/pipeline.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/solvers.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/storage.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/diffct/surface_gradients.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/docs/video/README.md +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/examples/README.md +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/examples/_block_cgls.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/examples/_common.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/examples/data/NOTICE +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/examples/data/preprocess_walnut.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/examples/walnut_reconstruction.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/pytest.ini +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/__init__.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/_multiprocessing.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/_phantoms.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/benchmarks/README.md +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/benchmarks/__init__.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/benchmarks/_common.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/benchmarks/test_bench_cone.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/benchmarks/test_bench_fan.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/benchmarks/test_bench_parallel.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/distributed_projector_check.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_adjoint_inner_product.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_allocator_capacity.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_analytical_autograd.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_analytical_distribution.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_analytical_regressions.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_analytical_weight_distribution.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_angular_low_precision.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_axis_aligned_chords.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_batch_execution_edges.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_batched_surfaces.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_block_cgls.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_block_storage.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_call_trajectory.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_cgls_state_admission.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_checkpoint_metadata_validation.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_chunked_projector.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_completed_transfer_reuse.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_cone_backprojection_transpose.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_cone_projector_autograd.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_cpu_install.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_cuda_exit_deallocations.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_cuda_sink_lifetimes.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_cuda_smoke.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_detector_surface_dtypes.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_detector_surface_mapping.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_detector_surface_units.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_detector_surfaces.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_distributed_training_transforms.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_example_psnr.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_execution_buffers.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_execution_features.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_execution_schedules.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_fbp_fan_accuracy.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_fbp_fan_offsets.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_fbp_parallel_accuracy.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_fdk_cone_accuracy.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_fdk_cone_offsets.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_geometry_autograd.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_geometry_conversions.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_geometry_gradients.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_gpu_performance_contracts.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_gradcheck.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_lazy_native_observers.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_live_trajectory_validation.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_multi_gpu_exit.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_multiprocessing.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_native_endpoint_precision.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_native_surface_transforms.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_native_volume_layout.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_native_volume_layout_distributed.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_output_pipeline_overlap.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_parallel3d.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_parallel3d_surface_oracle.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_partition_lifecycle.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_plan_memory_bounds.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_portable_distributed_geometry.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_portable_endpoint_precision.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_portable_execution_bounds.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_portable_numeric_regressions.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_portable_sampler_snapshot.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_portable_surface_admission.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_pr42_review_regressions.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_projector_api.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_projector_degenerate_geometry.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_projector_geometry_devices.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_ramp_filter_windows.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_reconstruction_api.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_reconstruction_work_reuse.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_release_2_0_1.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_review_230.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_review_regressions.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_solver_admission_regressions.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_solver_api.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_solver_checkpoint_edges.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_solver_collective_failures.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_solver_device_scalars.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_spatial_partition.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_collective_failures.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_composition_kernel.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_compute_shards.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_geometry_cudasim.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_gpu_execution.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_gradient_replay.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_host_sampling_budget.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_native_regressions.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_parameter_updates.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_pipeline_contracts.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_saved_snapshot_budget.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_snapshot_admission.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_surface_trajectory_transform.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_training_transforms.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_trajectory_admission_cost.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_trajectory_surface_integration.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_transfer_pipeline.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_transfer_retirement_lifetimes.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_tv_zero_dependency.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_two_far_endpoints.py +0 -0
- {diffct-2.4.3 → diffct-2.5.0}/tests/test_weights.py +0 -0
|
@@ -5,7 +5,54 @@ 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
|
-
## [
|
|
8
|
+
## [2.5.0] - 2026-10-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Apple Silicon GPU (MPS) support for the portable PyTorch backend, including
|
|
13
|
+
the solvers, detector surfaces and streamed stores. MPS has no FP64, so ray
|
|
14
|
+
clipping and entry setup run in FP32 there; `precision="float64"` raises a
|
|
15
|
+
`ValueError` for MPS data, outputs or trajectories. See `docs/EXECUTION.md`
|
|
16
|
+
for the FP32 error bound. Thanks to @cslht11
|
|
17
|
+
([#57](https://github.com/sypsyp97/diffct/pull/57)).
|
|
18
|
+
- TLA+ protocol models with TLC/Apalache checks and execution trace validation
|
|
19
|
+
for memory budgets, native admission and distributed collectives, run in a
|
|
20
|
+
separate CI workflow. They live in `formal/` and do not change library
|
|
21
|
+
behavior ([#55](https://github.com/sypsyp97/diffct/pull/55)).
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Trajectory constructors reject degenerate inputs instead of building NaN or empty geometry: zero or non-finite `ray_dir_fn` directions in `custom_trajectory_2d_parallel`, `random_trajectory_3d` sources at the isocenter, and view counts that are not positive integers (`TypeError` for floats and booleans, `ValueError` for counts below 1). Explicit angle tensors are still accepted. Thanks to @cslht11 ([#56](https://github.com/sypsyp97/diffct/pull/56)).
|
|
26
|
+
|
|
27
|
+
### Documentation
|
|
28
|
+
|
|
29
|
+
- README, documentation landing page, `llms.txt` summary, `CITATION.cff` and package keywords name the native CUDA kernels and the portable backend on CPU, ROCm and Apple Silicon. The landing quickstart has an Apple Silicon (MPS) tab ([#60](https://github.com/sypsyp97/diffct/pull/60)).
|
|
30
|
+
- Every documentation page uses the same three-column layout with the site navigation on the left ([#58](https://github.com/sypsyp97/diffct/pull/58)).
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Continuous integration: pull requests that change only `docs/` skip the package, test and formal jobs; the documentation build still runs. Pushes to `main` and release tags run every job ([#59](https://github.com/sypsyp97/diffct/pull/59)).
|
|
35
|
+
|
|
36
|
+
## [2.4.4] - 2026-10-25
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- 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.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- 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.
|
|
45
|
+
- Second derivatives with respect to the geometry now raise an error on the single-process portable path, as documented. Before, they returned a value.
|
|
46
|
+
- Forward-mode derivatives (`torch.func.jvp`, `jacfwd`) with respect to `Projector` trajectory tensors now raise an error. Before, they returned zero tangents.
|
|
47
|
+
- Distributed operators reject trajectory dtype differences between ranks before gradient reduction, including per-call trajectory overrides.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- 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.
|
|
52
|
+
- 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.
|
|
53
|
+
- Portable outputs accumulate in place, so a call no longer holds a second full-size output. Execution statistics report the loop order that ran.
|
|
54
|
+
- `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.
|
|
55
|
+
- `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.
|
|
9
56
|
|
|
10
57
|
## [2.4.3] - 2026-10-22
|
|
11
58
|
|
|
@@ -254,7 +301,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
254
301
|
|
|
255
302
|
Version 2.0.0 replaces the circular-orbit API of 1.x with arbitrary
|
|
256
303
|
per-view trajectories. It breaks the 1.x interface; see
|
|
257
|
-
[docs/MIGRATION.md](docs/MIGRATION.md).
|
|
304
|
+
[docs/MIGRATION.md](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md).
|
|
258
305
|
|
|
259
306
|
### Breaking
|
|
260
307
|
|
|
@@ -2,8 +2,9 @@ cff-version: 1.2.0
|
|
|
2
2
|
message: "If you use this software, please cite both the software (below) and the technical report listed under preferred-citation."
|
|
3
3
|
title: "diffct: Differentiable Computed Tomography Reconstruction with CUDA"
|
|
4
4
|
abstract: >-
|
|
5
|
-
Differentiable
|
|
6
|
-
autograd
|
|
5
|
+
Differentiable projectors for computed tomography (CT) with PyTorch
|
|
6
|
+
autograd: native NVIDIA CUDA kernels and a portable PyTorch backend for
|
|
7
|
+
CPU, ROCm and Apple Silicon (MPS). Each view has its own source and detector geometry, so circular,
|
|
7
8
|
helical, saddle and calibrated scans use the same matched Siddon operator.
|
|
8
9
|
Projections run on one GPU, several GPUs in one process, or across nodes,
|
|
9
10
|
and trajectory tensors receive geometry gradients for calibration.
|
|
@@ -13,8 +14,8 @@ authors:
|
|
|
13
14
|
given-names: Yipeng
|
|
14
15
|
email: yipeng.sun@fau.de
|
|
15
16
|
alias: sypsyp97
|
|
16
|
-
version: 2.
|
|
17
|
-
date-released: "2026-10-
|
|
17
|
+
version: 2.5.0
|
|
18
|
+
date-released: "2026-10-26"
|
|
18
19
|
license: Apache-2.0
|
|
19
20
|
repository-code: "https://github.com/sypsyp97/diffct"
|
|
20
21
|
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.
|
|
3
|
+
Version: 2.5.0
|
|
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/
|
|
@@ -10,7 +10,7 @@ Author-email: Yipeng Sun <yipeng.sun@fau.de>
|
|
|
10
10
|
License-Expression: Apache-2.0
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
License-File: NOTICE
|
|
13
|
-
Keywords: CT reconstruction,CUDA,PyTorch,ROCm,computed tomography,differentiable programming
|
|
13
|
+
Keywords: Apple Silicon,CT reconstruction,CUDA,MPS,PyTorch,ROCm,computed tomography,differentiable programming
|
|
14
14
|
Classifier: Development Status :: 5 - Production/Stable
|
|
15
15
|
Classifier: Environment :: GPU :: NVIDIA CUDA
|
|
16
16
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -34,7 +34,8 @@ Description-Content-Type: text/markdown
|
|
|
34
34
|
<p align="center">English · <a href="https://github.com/sypsyp97/diffct/blob/main/README.zh.md">简体中文</a></p>
|
|
35
35
|
|
|
36
36
|
<p align="center">
|
|
37
|
-
Differentiable
|
|
37
|
+
Differentiable CT projectors: arbitrary trajectories, multi-GPU, multi-node, geometry gradients.<br>
|
|
38
|
+
Native NVIDIA CUDA kernels, plus a portable PyTorch backend for CPU, ROCm and Apple Silicon (MPS).
|
|
38
39
|
</p>
|
|
39
40
|
|
|
40
41
|
<p align="center">
|
|
@@ -66,7 +67,7 @@ Description-Content-Type: text/markdown
|
|
|
66
67
|
</p>
|
|
67
68
|
|
|
68
69
|
> **Version 2.0** replaces the circular-orbit API of 1.x with per-view trajectories. Code written for 1.x needs changes; see the [migration notes](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md).
|
|
69
|
-
>
|
|
70
|
+
> Apple Silicon runs through the built-in MPS support (`backend="torch"`); a community [MLX port by Linda-Sophie Schneider](https://github.com/Linda-SophieSchneider/DiffCT-MLX) also exists.
|
|
70
71
|
|
|
71
72
|
This README describes the source checkout. Features listed under [Unreleased](CHANGELOG.md#unreleased) require installation from source.
|
|
72
73
|
|
|
@@ -79,7 +80,7 @@ To our knowledge, diffct is the only open-source GPU CT library that combines ar
|
|
|
79
80
|
- **Matched operators.** `project()` and `backproject()` form an exact adjoint pair for the cell-constant Siddon model. Both support PyTorch autograd, with volume/sinogram gradients and Hessian-vector products.
|
|
80
81
|
- **Large volumes.** Automatic spatial, view and pixel blocks with bounded transfer buffers control working memory. `project_into`/`backproject_into` accept caller-owned tensor or disk stores; tensor autograd keeps complete data on its input device. See [chunked execution](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#chunked-execution).
|
|
81
82
|
- **Multi-GPU and multi-node.** Use `devices=[0, 1, 2, 3]` in one process, or torchrun and NCCL across ranks. Views, space and hybrid partitions control replication of volumes and projections. Speedup depends on workload and communication costs.
|
|
82
|
-
- **Precision and training.** Explicit native/PyTorch backends, FP32/FP64 modes, per-axis voxel spacing, leading batch/channel dimensions and data `torch.func` transforms. The portable path supports CPU
|
|
83
|
+
- **Precision and training.** Explicit native/PyTorch backends, FP32/FP64 modes, per-axis voxel spacing, leading batch/channel dimensions and data `torch.func` transforms. The portable path supports CPU, PyTorch CUDA/ROCm devices and Apple Silicon GPUs (MPS, `backend="torch"`); see [execution contracts](docs/EXECUTION.md).
|
|
83
84
|
- **Solver API.** `cgls`, `sirt` and `tv_reconstruction` report residual history, tolerances and callbacks, with scan/data fingerprints and complete checkpoint state. `cgls_into` keeps state in bounded mapped blocks.
|
|
84
85
|
- **Differentiable FBP and FDK.** `fbp`, `fdk` and approximate `helical_fdk` compose the public filters, weights and analytical backprojectors. Their backward kernels transpose the actual interpolation stencil; custom filters and weights can be learned. See the [analytical guide](docs/source/analytical_learning.rst).
|
|
85
86
|
|
|
@@ -90,11 +91,12 @@ Capabilities, limits and isocenter rules: [docs/REFERENCE.md](https://github.com
|
|
|
90
91
|
For the native path, install NVIDIA CUDA PyTorch first, then diffct with the matching CUDA major extra:
|
|
91
92
|
|
|
92
93
|
```bash
|
|
94
|
+
python -c "import torch; print(torch.version.cuda)" # 12.x or 13.x
|
|
93
95
|
pip install "diffct[cu12]" # PyTorch built for CUDA 12
|
|
94
96
|
pip install "diffct[cu13]" # PyTorch built for CUDA 13
|
|
95
97
|
```
|
|
96
98
|
|
|
97
|
-
The extra installs the CUDA compiler libraries Numba needs. For CPU
|
|
99
|
+
The extra installs the CUDA compiler libraries Numba needs. For CPU, PyTorch ROCm or Apple Silicon (MPS) execution, install the appropriate PyTorch build and plain `pip install diffct`, then select `backend="torch"`. Importing diffct and using this backend require no CUDA runtime; Numba CUDA initializes when native execution is used. Plain installation also works for native execution when a compatible CUDA Toolkit is already present.
|
|
98
100
|
|
|
99
101
|
From source, with the examples:
|
|
100
102
|
|
|
@@ -142,6 +144,8 @@ loss.backward() # x.grad = A^T (A x - y)
|
|
|
142
144
|
|
|
143
145
|
**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
146
|
|
|
147
|
+
**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.
|
|
148
|
+
|
|
145
149
|
**3D parallel beams.** Use `circular_trajectory_3d_parallel(...)` and `beam="parallel3d"`. The PyTorch backend provides matched projection and backprojection on CPU and CUDA.
|
|
146
150
|
|
|
147
151
|
**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 +181,8 @@ volume slabs use `--partition space` under torchrun. Run
|
|
|
177
181
|
|
|
178
182
|
<p align="center">
|
|
179
183
|
<picture>
|
|
180
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png
|
|
181
|
-
<img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png
|
|
184
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png">
|
|
185
|
+
<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
186
|
</picture>
|
|
183
187
|
</p>
|
|
184
188
|
|
|
@@ -188,7 +192,7 @@ volume slabs use `--partition space` under torchrun. Run
|
|
|
188
192
|
| Adjoint (backprojection) | 1060.77 ms | 279.37 ms (3.8×) | 224.04 ms (4.7×) |
|
|
189
193
|
| CGLS iteration | 1753.73 ms | 468.09 ms (3.7×) | 337.39 ms (5.2×) |
|
|
190
194
|
|
|
191
|
-
|
|
195
|
+
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
196
|
|
|
193
197
|
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
198
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
<p align="center">English · <a href="https://github.com/sypsyp97/diffct/blob/main/README.zh.md">简体中文</a></p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
Differentiable
|
|
6
|
+
Differentiable CT projectors: arbitrary trajectories, multi-GPU, multi-node, geometry gradients.<br>
|
|
7
|
+
Native NVIDIA CUDA kernels, plus a portable PyTorch backend for CPU, ROCm and Apple Silicon (MPS).
|
|
7
8
|
</p>
|
|
8
9
|
|
|
9
10
|
<p align="center">
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
</p>
|
|
36
37
|
|
|
37
38
|
> **Version 2.0** replaces the circular-orbit API of 1.x with per-view trajectories. Code written for 1.x needs changes; see the [migration notes](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md).
|
|
38
|
-
>
|
|
39
|
+
> Apple Silicon runs through the built-in MPS support (`backend="torch"`); a community [MLX port by Linda-Sophie Schneider](https://github.com/Linda-SophieSchneider/DiffCT-MLX) also exists.
|
|
39
40
|
|
|
40
41
|
This README describes the source checkout. Features listed under [Unreleased](CHANGELOG.md#unreleased) require installation from source.
|
|
41
42
|
|
|
@@ -48,7 +49,7 @@ To our knowledge, diffct is the only open-source GPU CT library that combines ar
|
|
|
48
49
|
- **Matched operators.** `project()` and `backproject()` form an exact adjoint pair for the cell-constant Siddon model. Both support PyTorch autograd, with volume/sinogram gradients and Hessian-vector products.
|
|
49
50
|
- **Large volumes.** Automatic spatial, view and pixel blocks with bounded transfer buffers control working memory. `project_into`/`backproject_into` accept caller-owned tensor or disk stores; tensor autograd keeps complete data on its input device. See [chunked execution](https://github.com/sypsyp97/diffct/blob/main/docs/REFERENCE.md#chunked-execution).
|
|
50
51
|
- **Multi-GPU and multi-node.** Use `devices=[0, 1, 2, 3]` in one process, or torchrun and NCCL across ranks. Views, space and hybrid partitions control replication of volumes and projections. Speedup depends on workload and communication costs.
|
|
51
|
-
- **Precision and training.** Explicit native/PyTorch backends, FP32/FP64 modes, per-axis voxel spacing, leading batch/channel dimensions and data `torch.func` transforms. The portable path supports CPU
|
|
52
|
+
- **Precision and training.** Explicit native/PyTorch backends, FP32/FP64 modes, per-axis voxel spacing, leading batch/channel dimensions and data `torch.func` transforms. The portable path supports CPU, PyTorch CUDA/ROCm devices and Apple Silicon GPUs (MPS, `backend="torch"`); see [execution contracts](docs/EXECUTION.md).
|
|
52
53
|
- **Solver API.** `cgls`, `sirt` and `tv_reconstruction` report residual history, tolerances and callbacks, with scan/data fingerprints and complete checkpoint state. `cgls_into` keeps state in bounded mapped blocks.
|
|
53
54
|
- **Differentiable FBP and FDK.** `fbp`, `fdk` and approximate `helical_fdk` compose the public filters, weights and analytical backprojectors. Their backward kernels transpose the actual interpolation stencil; custom filters and weights can be learned. See the [analytical guide](docs/source/analytical_learning.rst).
|
|
54
55
|
|
|
@@ -59,11 +60,12 @@ Capabilities, limits and isocenter rules: [docs/REFERENCE.md](https://github.com
|
|
|
59
60
|
For the native path, install NVIDIA CUDA PyTorch first, then diffct with the matching CUDA major extra:
|
|
60
61
|
|
|
61
62
|
```bash
|
|
63
|
+
python -c "import torch; print(torch.version.cuda)" # 12.x or 13.x
|
|
62
64
|
pip install "diffct[cu12]" # PyTorch built for CUDA 12
|
|
63
65
|
pip install "diffct[cu13]" # PyTorch built for CUDA 13
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
The extra installs the CUDA compiler libraries Numba needs. For CPU
|
|
68
|
+
The extra installs the CUDA compiler libraries Numba needs. For CPU, PyTorch ROCm or Apple Silicon (MPS) execution, install the appropriate PyTorch build and plain `pip install diffct`, then select `backend="torch"`. Importing diffct and using this backend require no CUDA runtime; Numba CUDA initializes when native execution is used. Plain installation also works for native execution when a compatible CUDA Toolkit is already present.
|
|
67
69
|
|
|
68
70
|
From source, with the examples:
|
|
69
71
|
|
|
@@ -111,6 +113,8 @@ loss.backward() # x.grad = A^T (A x - y)
|
|
|
111
113
|
|
|
112
114
|
**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
115
|
|
|
116
|
+
**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.
|
|
117
|
+
|
|
114
118
|
**3D parallel beams.** Use `circular_trajectory_3d_parallel(...)` and `beam="parallel3d"`. The PyTorch backend provides matched projection and backprojection on CPU and CUDA.
|
|
115
119
|
|
|
116
120
|
**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 +150,8 @@ volume slabs use `--partition space` under torchrun. Run
|
|
|
146
150
|
|
|
147
151
|
<p align="center">
|
|
148
152
|
<picture>
|
|
149
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png
|
|
150
|
-
<img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png
|
|
153
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_dark.png">
|
|
154
|
+
<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
155
|
</picture>
|
|
152
156
|
</p>
|
|
153
157
|
|
|
@@ -157,7 +161,7 @@ volume slabs use `--partition space` under torchrun. Run
|
|
|
157
161
|
| Adjoint (backprojection) | 1060.77 ms | 279.37 ms (3.8×) | 224.04 ms (4.7×) |
|
|
158
162
|
| CGLS iteration | 1753.73 ms | 468.09 ms (3.7×) | 337.39 ms (5.2×) |
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
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
165
|
|
|
162
166
|
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
167
|
|
|
@@ -12,6 +12,7 @@ import torch.distributed as dist
|
|
|
12
12
|
from .pipeline import _fragments
|
|
13
13
|
from .storage import NpyStore, TensorStore
|
|
14
14
|
from .distribution import control_status, preflight
|
|
15
|
+
from .utils import _work_dtype
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def count(value, name, positive=False):
|
|
@@ -65,20 +66,46 @@ def scalar_sum(operator, value, kind, *, return_tensor=False):
|
|
|
65
66
|
dist.all_reduce(accumulator, group=group)
|
|
66
67
|
return accumulator if return_tensor else accumulator.item()
|
|
67
68
|
if return_tensor:
|
|
68
|
-
return torch.as_tensor(value, dtype=
|
|
69
|
+
return torch.as_tensor(value, dtype=_work_dtype(result_device), device=result_device)
|
|
69
70
|
return float(value)
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
def squared_norm(operator, value, kind, elements=1048576, *, return_tensor=False):
|
|
73
74
|
store = TensorStore(value) if isinstance(value, torch.Tensor) else value
|
|
74
|
-
|
|
75
|
+
# Scale every block by its own peak before squaring and track the
|
|
76
|
+
# running maximum on the device: unscaled FP32 squares overflow for
|
|
77
|
+
# large measurements and underflow for tiny geometries.
|
|
78
|
+
device = getattr(store, "device", torch.device("cpu"))
|
|
79
|
+
if _work_dtype(device) == torch.float64:
|
|
80
|
+
total = torch.zeros((), dtype=torch.float64, device=device)
|
|
81
|
+
if owns_values(operator, kind):
|
|
82
|
+
for selected in _fragments(tuple(store.shape), elements):
|
|
83
|
+
# Clone even an FP64 view: norms must never alter caller measurements.
|
|
84
|
+
block = store.read(selected).to(dtype=torch.float64).clone()
|
|
85
|
+
# Preserve the block reductions and their sequential FP64 order.
|
|
86
|
+
total.add_(block.square_().sum().to(device=total.device))
|
|
87
|
+
return scalar_sum(operator, total, kind, return_tensor=return_tensor)
|
|
88
|
+
# FP32 devices (MPS): squares of large measurements overflow and tiny
|
|
89
|
+
# geometries underflow, so every block is rescaled by its peak and the
|
|
90
|
+
# running sum follows the largest peak seen. All block math stays on the
|
|
91
|
+
# device; the two closing scalar reads fit the per-iteration reduction
|
|
92
|
+
# budget, and their product leaves FP32 before it can overflow.
|
|
93
|
+
peak = torch.zeros((), device=device, dtype=torch.float32)
|
|
94
|
+
total = torch.zeros((), device=device, dtype=torch.float32)
|
|
75
95
|
if owns_values(operator, kind):
|
|
76
96
|
for selected in _fragments(tuple(store.shape), elements):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
block = store.read(selected)
|
|
98
|
+
if not block.numel():
|
|
99
|
+
continue
|
|
100
|
+
block_peak = block.detach().abs().amax()
|
|
101
|
+
safe = torch.where(block_peak > 0, block_peak, torch.ones_like(block_peak))
|
|
102
|
+
combined = torch.maximum(peak, block_peak)
|
|
103
|
+
guard = torch.where(combined > 0, combined, torch.ones_like(combined))
|
|
104
|
+
total = (total * (peak / guard).square()
|
|
105
|
+
+ (block.detach() / safe).square().sum() * (block_peak / guard).square())
|
|
106
|
+
peak = combined
|
|
107
|
+
return scalar_sum(operator, peak.item() ** 2 * total.item(), kind,
|
|
108
|
+
return_tensor=return_tensor)
|
|
82
109
|
|
|
83
110
|
|
|
84
111
|
def copy_blocks(source, target, elements):
|
|
@@ -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,
|
|
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,
|
|
526
|
-
*(_DTYPE(value
|
|
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,
|
|
588
|
-
*(_DTYPE(value
|
|
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
|
|
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
|
-
|
|
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),
|
|
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):
|
|
@@ -156,6 +160,8 @@ class AnalyticalExecution:
|
|
|
156
160
|
raise TypeError("sinogram store must have dtype float32 or float64")
|
|
157
161
|
self.source = source
|
|
158
162
|
self.output_device = torch.device(getattr(source, "device", "cpu"))
|
|
163
|
+
if self.dtype == torch.float64 and self.output_device.type == "mps":
|
|
164
|
+
raise ValueError("precision must be 'fast' or 'float32' on MPS; MPS provides no float64")
|
|
159
165
|
self.detector_shape = tuple(source.shape[1:])
|
|
160
166
|
for component in trajectory:
|
|
161
167
|
_fixed(component, "trajectory")
|
|
@@ -336,14 +342,14 @@ class AnalyticalExecution:
|
|
|
336
342
|
device=data.device, dtype=self.dtype)
|
|
337
343
|
if self.beam == "parallel":
|
|
338
344
|
geometry = (geometry[0], geometry[1] - centre, geometry[2])
|
|
339
|
-
return parallel_weighted_backproject(data, *geometry, self.pitch[0], *shape, self.
|
|
345
|
+
return parallel_weighted_backproject(data, *geometry, self.pitch[0], *shape, self.voxel_spacing)
|
|
340
346
|
geometry = (geometry[0] - centre, geometry[1] - centre, *geometry[2:])
|
|
341
347
|
isocenter = self.isocenter.to(data.device) - centre
|
|
342
348
|
if self.beam == "fan":
|
|
343
349
|
return fan_weighted_backproject(data, *geometry, self.pitch[0], *shape,
|
|
344
|
-
self.
|
|
350
|
+
self.voxel_spacing, isocenter=isocenter)
|
|
345
351
|
return cone_weighted_backproject(data, *geometry, *shape, *self.pitch,
|
|
346
|
-
self.
|
|
352
|
+
self.voxel_spacing, isocenter=isocenter)
|
|
347
353
|
|
|
348
354
|
def _tile(self, spatial):
|
|
349
355
|
shape = tuple(part.stop - part.start for part in spatial)
|