diffct 2.4.2__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.
- {diffct-2.4.2 → diffct-2.4.4}/.gitignore +5 -0
- {diffct-2.4.2 → diffct-2.4.4}/CHANGELOG.md +38 -1
- {diffct-2.4.2 → diffct-2.4.4}/CITATION.cff +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/NOTICE +2 -0
- {diffct-2.4.2 → diffct-2.4.4}/PKG-INFO +12 -9
- {diffct-2.4.2 → diffct-2.4.4}/README.md +11 -8
- {diffct-2.4.2 → diffct-2.4.4}/diffct/__init__.py +1 -1
- {diffct-2.4.2 → diffct-2.4.4}/diffct/_solver_state.py +12 -9
- {diffct-2.4.2 → diffct-2.4.4}/diffct/_solver_tv.py +3 -2
- {diffct-2.4.2 → diffct-2.4.4}/diffct/analytical.py +12 -12
- {diffct-2.4.2 → diffct-2.4.4}/diffct/analytical_execution.py +24 -3
- {diffct-2.4.2 → diffct-2.4.4}/diffct/backends.py +513 -113
- {diffct-2.4.2 → diffct-2.4.4}/diffct/chunking.py +4 -2
- {diffct-2.4.2 → diffct-2.4.4}/diffct/constants.py +12 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/kernels/analytical_transpose.py +40 -16
- {diffct-2.4.2 → diffct-2.4.4}/diffct/kernels/cone_beam.py +117 -63
- {diffct-2.4.2 → diffct-2.4.4}/diffct/kernels/fan_beam.py +49 -33
- {diffct-2.4.2 → diffct-2.4.4}/diffct/kernels/geometry_vjp.py +153 -51
- {diffct-2.4.2 → diffct-2.4.4}/diffct/kernels/parallel_beam.py +45 -15
- diffct-2.4.4/diffct/kernels/spacing.py +79 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/kernels/surface_geometry.py +22 -9
- {diffct-2.4.2 → diffct-2.4.4}/diffct/operators.py +300 -106
- {diffct-2.4.2 → diffct-2.4.4}/diffct/projectors.py +13 -6
- {diffct-2.4.2 → diffct-2.4.4}/diffct/reconstruction.py +7 -4
- {diffct-2.4.2 → diffct-2.4.4}/diffct/solvers.py +22 -11
- {diffct-2.4.2 → diffct-2.4.4}/diffct/surfaces.py +20 -14
- {diffct-2.4.2 → diffct-2.4.4}/diffct/utils.py +10 -0
- diffct-2.4.4/docs/assets/gallery/NOTICE +29 -0
- diffct-2.4.4/docs/search/tests/.gitignore +2 -0
- diffct-2.4.4/docs/search/tests/fixtures/make_tiny_model.py +89 -0
- diffct-2.4.4/docs/search/tests/fixtures/tiny-model/config.json +15 -0
- diffct-2.4.4/docs/search/tests/fixtures/tiny-model/onnx/model_quantized.onnx +0 -0
- diffct-2.4.4/docs/search/tests/fixtures/tiny-model/onnx/model_quantized.onnx_data +0 -0
- diffct-2.4.4/docs/search/tests/fixtures/tiny-model/tokenizer.json +140 -0
- diffct-2.4.4/docs/search/tests/fixtures/tiny-model/tokenizer_config.json +6 -0
- diffct-2.4.4/docs/search/tests/full-model.spec.mjs +59 -0
- diffct-2.4.4/docs/search/tests/harness.mjs +473 -0
- diffct-2.4.4/docs/search/tests/item1-ordering.spec.mjs +106 -0
- diffct-2.4.4/docs/search/tests/item2-single-load.spec.mjs +71 -0
- diffct-2.4.4/docs/search/tests/item3-cancel.spec.mjs +93 -0
- diffct-2.4.4/docs/search/tests/item4-leaks.spec.mjs +123 -0
- diffct-2.4.4/docs/search/tests/item5-errors.spec.mjs +115 -0
- diffct-2.4.4/docs/search/tests/item6-a11y.spec.mjs +186 -0
- diffct-2.4.4/docs/search/tests/item7-security.spec.mjs +101 -0
- diffct-2.4.4/docs/search/tests/playwright.config.mjs +17 -0
- {diffct-2.4.2 → diffct-2.4.4}/examples/README.md +3 -0
- {diffct-2.4.2 → diffct-2.4.4}/formal/README.md +25 -12
- {diffct-2.4.2 → diffct-2.4.4}/pyproject.toml +1 -1
- diffct-2.4.4/tests/_multiprocessing.py +26 -0
- diffct-2.4.4/tests/_native_per_axis_spacing.py +261 -0
- diffct-2.4.4/tests/fixtures/native_scalar_spacing_main.npz +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_allocator_capacity.py +7 -7
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_batched_surfaces.py +55 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_block_storage.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_cgls_state_admission.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_chunked_projector.py +5 -5
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_completed_transfer_reuse.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_detector_surfaces.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_distributed_training_transforms.py +2 -7
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_execution_buffers.py +67 -4
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_execution_features.py +2 -2
- diffct-2.4.4/tests/test_gpu_performance_contracts.py +229 -0
- diffct-2.4.4/tests/test_multiprocessing.py +78 -0
- diffct-2.4.4/tests/test_native_per_axis_admission.py +99 -0
- diffct-2.4.4/tests/test_native_per_axis_extremes.py +139 -0
- diffct-2.4.4/tests/test_native_per_axis_helical.py +93 -0
- diffct-2.4.4/tests/test_native_per_axis_spacing.py +505 -0
- diffct-2.4.4/tests/test_native_per_axis_spacing_gpu.py +237 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_native_volume_layout_distributed.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_parallel3d.py +2 -2
- diffct-2.4.4/tests/test_portable_bounded_autograd.py +945 -0
- diffct-2.4.4/tests/test_portable_cuda_budget.py +996 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_portable_distributed_geometry.py +2 -2
- diffct-2.4.4/tests/test_pr42_review_regressions.py +301 -0
- diffct-2.4.4/tests/test_reconstruction_work_reuse.py +231 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_reference_backend.py +12 -4
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_solver_admission_regressions.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_solver_collective_failures.py +7 -5
- diffct-2.4.4/tests/test_solver_device_scalars.py +255 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_spatial_partition.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_collective_failures.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_compute_shards.py +3 -3
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_geometry_cudasim.py +4 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_pipeline_contracts.py +10 -6
- diffct-2.4.4/tests/test_surface_saved_snapshot_budget.py +274 -0
- diffct-2.4.4/tests/test_surface_snapshot_admission.py +209 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_trajectory_surface_integration.py +8 -4
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_transfer_pipeline.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_transfer_retirement_lifetimes.py +2 -2
- {diffct-2.4.2 → diffct-2.4.4}/LICENSE +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/differentiable.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/distribution.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/geometry.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/kernels/__init__.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/pipeline.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/storage.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/streaming.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/diffct/surface_gradients.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/docs/video/README.md +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/examples/_block_cgls.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/examples/_common.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/examples/data/NOTICE +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/examples/data/preprocess_walnut.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/examples/walnut_reconstruction.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/pytest.ini +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/__init__.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/_phantoms.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/benchmarks/README.md +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/benchmarks/__init__.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/benchmarks/_common.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/benchmarks/test_bench_cone.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/benchmarks/test_bench_fan.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/benchmarks/test_bench_parallel.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/distributed_projector_check.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_adjoint_inner_product.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_analytical_autograd.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_analytical_distribution.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_analytical_regressions.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_analytical_weight_distribution.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_angular_low_precision.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_axis_aligned_chords.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_batch_execution_edges.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_block_cgls.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_call_trajectory.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_checkpoint_metadata_validation.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_cone_backprojection_transpose.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_cone_projector_autograd.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_cpu_install.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_cuda_exit_deallocations.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_cuda_sink_lifetimes.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_cuda_smoke.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_detector_surface_dtypes.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_detector_surface_mapping.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_detector_surface_units.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_example_psnr.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_execution_schedules.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_fbp_fan_accuracy.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_fbp_fan_offsets.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_fbp_parallel_accuracy.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_fdk_cone_accuracy.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_fdk_cone_offsets.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_geometry_autograd.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_geometry_conversions.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_geometry_gradients.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_gradcheck.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_lazy_native_observers.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_live_trajectory_validation.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_multi_gpu_exit.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_native_endpoint_precision.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_native_surface_transforms.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_native_volume_layout.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_output_pipeline_overlap.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_parallel3d_surface_oracle.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_partition_lifecycle.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_plan_memory_bounds.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_portable_endpoint_precision.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_portable_execution_bounds.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_portable_numeric_regressions.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_portable_sampler_snapshot.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_portable_surface_admission.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_projector_api.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_projector_degenerate_geometry.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_projector_geometry_devices.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_ramp_filter_windows.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_reconstruction_api.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_release_2_0_1.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_review_230.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_review_regressions.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_solver_api.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_solver_checkpoint_edges.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_composition_kernel.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_gpu_execution.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_gradient_replay.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_host_sampling_budget.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_native_regressions.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_parameter_updates.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_surface_trajectory_transform.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_training_transforms.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_trajectory_admission_cost.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_tv_zero_dependency.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_two_far_endpoints.py +0 -0
- {diffct-2.4.2 → diffct-2.4.4}/tests/test_weights.py +0 -0
|
@@ -7,6 +7,43 @@ 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
|
+
|
|
31
|
+
## [2.4.3] - 2026-10-22
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Reduce GPU transfers and host synchronization in native geometry admission: validity flags are reduced on the device and flat detector corners are admitted in one batch. Invalid-geometry errors and their order are unchanged (#42).
|
|
36
|
+
- Compose streamed sampled and per-view detector surfaces for a complete admitted view/pixel batch in one launch, with a bounded fallback workspace. Eligible full legacy-surface calls keep their geometry snapshots on the input GPU (#42).
|
|
37
|
+
- Read the two CGLS scalar norms together, keep tensor-store updates and TV reductions on their device, and reuse SIRT/TV residuals when no callback can change the volume or operator (#42).
|
|
38
|
+
- Reuse filtered view batches across volume tiles in single-rank analytical reconstruction (#42).
|
|
39
|
+
- Add `examples/benchmark_gpu_overheads.py`, a synthetic benchmark for launch and transfer overheads (#42).
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Geometry-gradient replay uses the surface workspace that admission selected. Surface admission charges the larger of the composition and replay totals, the largest allocation of either phase, and a reservation bound that counts both phases (#42).
|
|
44
|
+
- Spawned multi-process test workers exit after a successful teardown, which removes intermittent `terminate called without an active exception` aborts in CI (#43).
|
|
45
|
+
- Add a formal check that the union reservation bounds every modelled composition/replay allocation order (#44).
|
|
46
|
+
|
|
10
47
|
## [2.4.2] - 2026-10-21
|
|
11
48
|
|
|
12
49
|
### Fixed
|
|
@@ -238,7 +275,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
238
275
|
|
|
239
276
|
Version 2.0.0 replaces the circular-orbit API of 1.x with arbitrary
|
|
240
277
|
per-view trajectories. It breaks the 1.x interface; see
|
|
241
|
-
[docs/MIGRATION.md](docs/MIGRATION.md).
|
|
278
|
+
[docs/MIGRATION.md](https://github.com/sypsyp97/diffct/blob/main/docs/MIGRATION.md).
|
|
242
279
|
|
|
243
280
|
### Breaking
|
|
244
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.
|
|
17
|
-
date-released: "2026-10-
|
|
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
|
+
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/
|
|
@@ -68,7 +68,7 @@ Description-Content-Type: text/markdown
|
|
|
68
68
|
> **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
69
|
> For Apple Silicon, use the [MLX port by Linda-Sophie Schneider](https://github.com/Linda-SophieSchneider/DiffCT-MLX).
|
|
70
70
|
|
|
71
|
-
This README describes the source checkout
|
|
71
|
+
This README describes the source checkout. Features listed under [Unreleased](CHANGELOG.md#unreleased) require installation from source.
|
|
72
72
|
|
|
73
73
|
## Why diffct
|
|
74
74
|
|
|
@@ -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,18 +180,18 @@ 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
|
|
181
|
-
<img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png
|
|
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
|
|
|
185
188
|
| 512³ volume | 1 GPU | 4 GPUs, 1 node | 8 GPUs, 2 nodes |
|
|
186
189
|
|---|---:|---:|---:|
|
|
187
|
-
| Forward projection |
|
|
188
|
-
| Adjoint (backprojection) |
|
|
189
|
-
| CGLS iteration |
|
|
190
|
+
| Forward projection | 566.79 ms | 150.76 ms (3.8×) | 81.58 ms (6.9×) |
|
|
191
|
+
| Adjoint (backprojection) | 1060.77 ms | 279.37 ms (3.8×) | 224.04 ms (4.7×) |
|
|
192
|
+
| CGLS iteration | 1753.73 ms | 468.09 ms (3.7×) | 337.39 ms (5.2×) |
|
|
190
193
|
|
|
191
|
-
|
|
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
|
|
|
@@ -228,7 +231,7 @@ Walnut data: Meaney 2022, Zenodo 6986012, CC BY 4.0; see [examples/data/NOTICE](
|
|
|
228
231
|
|
|
229
232
|
## Examples
|
|
230
233
|
|
|
231
|
-
`quickstart.py`, `curved_detector.py`, `chunked_reconstruction.py`, `disk_reconstruction.py`, `benchmark_execution.py`, `benchmark_trajectory_admission.py`, `analytical_reconstruction.py`, `learnable_reconstruction.py`, `helical_fdk.py`, `iterative_reconstruction.py`, `walnut_reconstruction.py`, `geometry_calibration.py`, `benchmark_projector.py`, `benchmark_scaling.py`, `plot_trajectory.py`, and the Slurm template `slurm/multi_node.sbatch`. Launch modes and distributed-loss rules are in [examples/README.md](https://github.com/sypsyp97/diffct/blob/main/examples/README.md).
|
|
234
|
+
`quickstart.py`, `curved_detector.py`, `chunked_reconstruction.py`, `disk_reconstruction.py`, `benchmark_execution.py`, `benchmark_trajectory_admission.py`, `benchmark_surface_geometry.py`, `benchmark_gpu_overheads.py`, `analytical_reconstruction.py`, `learnable_reconstruction.py`, `helical_fdk.py`, `iterative_reconstruction.py`, `walnut_reconstruction.py`, `geometry_calibration.py`, `benchmark_projector.py`, `benchmark_scaling.py`, `plot_trajectory.py`, and the Slurm template `slurm/multi_node.sbatch`. Launch modes and distributed-loss rules are in [examples/README.md](https://github.com/sypsyp97/diffct/blob/main/examples/README.md).
|
|
232
235
|
|
|
233
236
|
## Documentation
|
|
234
237
|
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
> **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
38
|
> For Apple Silicon, use the [MLX port by Linda-Sophie Schneider](https://github.com/Linda-SophieSchneider/DiffCT-MLX).
|
|
39
39
|
|
|
40
|
-
This README describes the source checkout
|
|
40
|
+
This README describes the source checkout. Features listed under [Unreleased](CHANGELOG.md#unreleased) require installation from source.
|
|
41
41
|
|
|
42
42
|
## Why diffct
|
|
43
43
|
|
|
@@ -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,18 +149,18 @@ 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
|
|
150
|
-
<img src="https://raw.githubusercontent.com/sypsyp97/diffct/main/docs/assets/scaling_light.png
|
|
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
|
|
|
154
157
|
| 512³ volume | 1 GPU | 4 GPUs, 1 node | 8 GPUs, 2 nodes |
|
|
155
158
|
|---|---:|---:|---:|
|
|
156
|
-
| Forward projection |
|
|
157
|
-
| Adjoint (backprojection) |
|
|
158
|
-
| CGLS iteration |
|
|
159
|
+
| Forward projection | 566.79 ms | 150.76 ms (3.8×) | 81.58 ms (6.9×) |
|
|
160
|
+
| Adjoint (backprojection) | 1060.77 ms | 279.37 ms (3.8×) | 224.04 ms (4.7×) |
|
|
161
|
+
| CGLS iteration | 1753.73 ms | 468.09 ms (3.7×) | 337.39 ms (5.2×) |
|
|
159
162
|
|
|
160
|
-
|
|
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
|
|
|
@@ -197,7 +200,7 @@ Walnut data: Meaney 2022, Zenodo 6986012, CC BY 4.0; see [examples/data/NOTICE](
|
|
|
197
200
|
|
|
198
201
|
## Examples
|
|
199
202
|
|
|
200
|
-
`quickstart.py`, `curved_detector.py`, `chunked_reconstruction.py`, `disk_reconstruction.py`, `benchmark_execution.py`, `benchmark_trajectory_admission.py`, `analytical_reconstruction.py`, `learnable_reconstruction.py`, `helical_fdk.py`, `iterative_reconstruction.py`, `walnut_reconstruction.py`, `geometry_calibration.py`, `benchmark_projector.py`, `benchmark_scaling.py`, `plot_trajectory.py`, and the Slurm template `slurm/multi_node.sbatch`. Launch modes and distributed-loss rules are in [examples/README.md](https://github.com/sypsyp97/diffct/blob/main/examples/README.md).
|
|
203
|
+
`quickstart.py`, `curved_detector.py`, `chunked_reconstruction.py`, `disk_reconstruction.py`, `benchmark_execution.py`, `benchmark_trajectory_admission.py`, `benchmark_surface_geometry.py`, `benchmark_gpu_overheads.py`, `analytical_reconstruction.py`, `learnable_reconstruction.py`, `helical_fdk.py`, `iterative_reconstruction.py`, `walnut_reconstruction.py`, `geometry_calibration.py`, `benchmark_projector.py`, `benchmark_scaling.py`, `plot_trajectory.py`, and the Slurm template `slurm/multi_node.sbatch`. Launch modes and distributed-loss rules are in [examples/README.md](https://github.com/sypsyp97/diffct/blob/main/examples/README.md).
|
|
201
204
|
|
|
202
205
|
## Documentation
|
|
203
206
|
|
|
@@ -53,30 +53,32 @@ def owns_values(operator, kind):
|
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def scalar_sum(operator, value, kind):
|
|
57
|
-
"""Count physical rays/voxels once,
|
|
56
|
+
def scalar_sum(operator, value, kind, *, return_tensor=False):
|
|
57
|
+
"""Count physical rays/voxels once, optionally retaining the reduction tensor."""
|
|
58
58
|
world = getattr(operator, "world_size", 1)
|
|
59
|
+
result_device = value.device if isinstance(value, torch.Tensor) else torch.device("cpu")
|
|
59
60
|
value = value if owns_values(operator, kind) else 0.
|
|
60
61
|
if world > 1:
|
|
61
62
|
group = operator.process_group
|
|
62
63
|
device = torch.device("cuda", torch.cuda.current_device()) if dist.get_backend(group) == "nccl" else torch.device("cpu")
|
|
63
64
|
accumulator = torch.as_tensor(value, dtype=torch.float64, device=device).clone()
|
|
64
65
|
dist.all_reduce(accumulator, group=group)
|
|
65
|
-
return accumulator.item()
|
|
66
|
+
return accumulator if return_tensor else accumulator.item()
|
|
67
|
+
if return_tensor:
|
|
68
|
+
return torch.as_tensor(value, dtype=torch.float64, device=result_device)
|
|
66
69
|
return float(value)
|
|
67
70
|
|
|
68
71
|
|
|
69
|
-
def squared_norm(operator, value, kind, elements=1048576):
|
|
72
|
+
def squared_norm(operator, value, kind, elements=1048576, *, return_tensor=False):
|
|
70
73
|
store = TensorStore(value) if isinstance(value, torch.Tensor) else value
|
|
71
|
-
total =
|
|
74
|
+
total = torch.zeros((), dtype=torch.float64, device=getattr(store, "device", "cpu"))
|
|
72
75
|
if owns_values(operator, kind):
|
|
73
|
-
total = torch.zeros((), dtype=torch.float64, device=getattr(store, "device", "cpu"))
|
|
74
76
|
for selected in _fragments(tuple(store.shape), elements):
|
|
75
77
|
# Clone even an FP64 view: norms must never alter caller measurements.
|
|
76
78
|
block = store.read(selected).to(dtype=torch.float64).clone()
|
|
77
79
|
# Preserve the block reductions and their sequential FP64 order.
|
|
78
80
|
total.add_(block.square_().sum().to(device=total.device))
|
|
79
|
-
return scalar_sum(operator, total, kind)
|
|
81
|
+
return scalar_sum(operator, total, kind, return_tensor=return_tensor)
|
|
80
82
|
|
|
81
83
|
|
|
82
84
|
def copy_blocks(source, target, elements):
|
|
@@ -91,9 +93,10 @@ def fill_blocks(target, elements):
|
|
|
91
93
|
|
|
92
94
|
|
|
93
95
|
def update_blocks(target, source, factor, elements, scale=1.):
|
|
96
|
+
device = getattr(target, "device", "cpu")
|
|
94
97
|
for selected in _fragments(tuple(target.shape), elements):
|
|
95
|
-
block = target.read(selected).to(device=
|
|
96
|
-
block.mul_(scale).add_(source.read(selected).to(device=
|
|
98
|
+
block = target.read(selected).to(device=device, dtype=target.dtype).clone()
|
|
99
|
+
block.mul_(scale).add_(source.read(selected).to(device=device, dtype=target.dtype), alpha=factor)
|
|
97
100
|
target.write(selected, block)
|
|
98
101
|
|
|
99
102
|
|
|
@@ -104,7 +104,8 @@ def total_variation(volume, *, operator=None, voxel_spacing=1., eps=1e-6, reduct
|
|
|
104
104
|
total = local
|
|
105
105
|
elements = volume.numel()
|
|
106
106
|
if operator is not None:
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
if getattr(operator, "world_size", 1) > 1:
|
|
108
|
+
global_value = scalar_sum(operator, local.detach(), "volume", return_tensor=True)
|
|
109
|
+
total = local + (global_value.to(local) - local.detach())
|
|
109
110
|
elements = math.prod(operator.global_volume_shape)
|
|
110
111
|
return total if reduction == "sum" else total / max(elements, 1)
|
|
@@ -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,9 +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(
|
|
527
|
-
|
|
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()),
|
|
528
528
|
)
|
|
529
529
|
|
|
530
530
|
return reco
|
|
@@ -585,10 +585,9 @@ def _cone_weighted_backproject_raw(sinogram, src_pos, det_center, det_u_vec, det
|
|
|
585
585
|
d_sino, n_views, n_u, n_v, d_reco, Nx, Ny, Nz,
|
|
586
586
|
_DTYPE(du), _DTYPE(dv),
|
|
587
587
|
d_src_pos, d_det_center, d_det_u_vec, d_det_v_vec,
|
|
588
|
-
cx, cy, cz,
|
|
589
|
-
_DTYPE(
|
|
590
|
-
|
|
591
|
-
_DTYPE(isocenter[2].item() / voxel_spacing),
|
|
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()),
|
|
592
591
|
)
|
|
593
592
|
|
|
594
593
|
# Convert back to (D, H, W) layout for the user.
|
|
@@ -640,7 +639,8 @@ def _analytical_transpose_raw(image, beam, geometry, parameters, isocenter, sino
|
|
|
640
639
|
scale = (sdd_n / (2.0 * math.pi * sid_n)).view(
|
|
641
640
|
(-1,) + (1,) * (len(sinogram_shape) - 1)
|
|
642
641
|
)
|
|
643
|
-
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())
|
|
644
644
|
|
|
645
645
|
if beam == "cone":
|
|
646
646
|
D, H, W, du, dv, spacing = parameters
|
|
@@ -650,7 +650,7 @@ def _analytical_transpose_raw(image, beam, geometry, parameters, isocenter, sino
|
|
|
650
650
|
_resolve_kernel('_cone_3d_fdk_transpose_kernel')[grid, tpb, stream](
|
|
651
651
|
d_sino, *sinogram_shape, d_image, W, H, D, _DTYPE(du), _DTYPE(dv),
|
|
652
652
|
*d_geometry, _DTYPE(W * 0.5), _DTYPE(H * 0.5), _DTYPE(D * 0.5),
|
|
653
|
-
|
|
653
|
+
_native_spacing(spacing), *iso,
|
|
654
654
|
)
|
|
655
655
|
else:
|
|
656
656
|
pitch, H, W, spacing = parameters
|
|
@@ -660,7 +660,7 @@ def _analytical_transpose_raw(image, beam, geometry, parameters, isocenter, sino
|
|
|
660
660
|
else '_fan_2d_fbp_transpose_kernel')
|
|
661
661
|
kernel[grid, tpb, stream](
|
|
662
662
|
d_sino, *sinogram_shape, d_image, W, H, _DTYPE(pitch), *d_geometry,
|
|
663
|
-
_DTYPE(W * 0.5), _DTYPE(H * 0.5),
|
|
663
|
+
_DTYPE(W * 0.5), _DTYPE(H * 0.5), _native_spacing(spacing), *iso,
|
|
664
664
|
)
|
|
665
665
|
return sinogram * scale
|
|
666
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
|
|
@@ -53,6 +54,8 @@ def _sum(value, group, count):
|
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
def _sum_blocks(value, group, count, block_elements):
|
|
57
|
+
if count == 1:
|
|
58
|
+
return value
|
|
56
59
|
flat = value.reshape(-1)
|
|
57
60
|
blocks = [_sum(flat.narrow(0, start, min(block_elements, flat.numel() - start)), group, count)
|
|
58
61
|
for start in range(0, flat.numel(), block_elements)]
|
|
@@ -141,6 +144,9 @@ class AnalyticalExecution:
|
|
|
141
144
|
self.rank = 3 if beam == "cone" else 2
|
|
142
145
|
self.global_volume_shape = _shape(volume_shape, self.rank, "volume_shape")
|
|
143
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)
|
|
144
150
|
self.pitch = _detector_spacing(detector_spacing, beam)
|
|
145
151
|
self.devices = _normalize_devices(devices)
|
|
146
152
|
if not isinstance(source, torch.Tensor):
|
|
@@ -334,14 +340,14 @@ class AnalyticalExecution:
|
|
|
334
340
|
device=data.device, dtype=self.dtype)
|
|
335
341
|
if self.beam == "parallel":
|
|
336
342
|
geometry = (geometry[0], geometry[1] - centre, geometry[2])
|
|
337
|
-
return parallel_weighted_backproject(data, *geometry, self.pitch[0], *shape, self.
|
|
343
|
+
return parallel_weighted_backproject(data, *geometry, self.pitch[0], *shape, self.voxel_spacing)
|
|
338
344
|
geometry = (geometry[0] - centre, geometry[1] - centre, *geometry[2:])
|
|
339
345
|
isocenter = self.isocenter.to(data.device) - centre
|
|
340
346
|
if self.beam == "fan":
|
|
341
347
|
return fan_weighted_backproject(data, *geometry, self.pitch[0], *shape,
|
|
342
|
-
self.
|
|
348
|
+
self.voxel_spacing, isocenter=isocenter)
|
|
343
349
|
return cone_weighted_backproject(data, *geometry, *shape, *self.pitch,
|
|
344
|
-
self.
|
|
350
|
+
self.voxel_spacing, isocenter=isocenter)
|
|
345
351
|
|
|
346
352
|
def _tile(self, spatial):
|
|
347
353
|
shape = tuple(part.stop - part.start for part in spatial)
|
|
@@ -385,6 +391,21 @@ class AnalyticalExecution:
|
|
|
385
391
|
def tensor_result(self):
|
|
386
392
|
self._prepare_gradients()
|
|
387
393
|
result = torch.zeros(self.volume_shape, device=self.output_device, dtype=self.dtype)
|
|
394
|
+
inputs = (self.source, self.projection_weights, self.angular_weights, self.filter_response)
|
|
395
|
+
active_gradients = torch.is_grad_enabled() and any(value is not None and value.requires_grad
|
|
396
|
+
for value in inputs)
|
|
397
|
+
if self.partition.world_size == 1 and not active_gradients:
|
|
398
|
+
# Each tile still accumulates view batches in their original order.
|
|
399
|
+
# The numerical result itself holds the partial sums between batches.
|
|
400
|
+
for views, device in self._view_batches():
|
|
401
|
+
filtered, geometry = self._filtered(views, device)
|
|
402
|
+
for spatial, _ in _pixel_tiles(self.volume_shape, self.chunk_shape):
|
|
403
|
+
partial = (self._native_gather(filtered, geometry, spatial) if self.backend == "numba"
|
|
404
|
+
else self._portable_gather(filtered, geometry, views, spatial))
|
|
405
|
+
result[spatial].add_(partial.to(self.output_device))
|
|
406
|
+
del partial
|
|
407
|
+
del filtered, geometry
|
|
408
|
+
return result
|
|
388
409
|
# Every owned tile's backward collective precedes the bundled input
|
|
389
410
|
# transpose, even when this rank owns no data views. Empty space groups
|
|
390
411
|
# have no tile collectives but still contribute zero input cotangents.
|