Skip to content

Fix: deg/rad unit mismatch in removeProjections corrupts chunked helical FBP - #214

Open
zezisme wants to merge 1 commit into
llnl:mainfrom
zezisme:patch-1
Open

Fix: deg/rad unit mismatch in removeProjections corrupts chunked helical FBP#214
zezisme wants to merge 1 commit into
llnl:mainfrom
zezisme:patch-1

Conversation

@zezisme

@zezisme zezisme commented Aug 11, 2026

Copy link
Copy Markdown

Bug Summary

In helical cone-beam FBP, when the projection + volume data exceed GPU memory, LEAP triggers backproject_FBP_multiGPU_splitViews, which creates per-chunk parameter copies via removeProjections(). Two bugs in this function caused incorrect reconstruction values:

Bug 1 (Major): phis_full stored in degrees, consumed as radians

removeProjections() calls get_angles(phis_full) to save the full-scan angle array. get_angles() converts from internal radians to degrees:
phis_full[i] = (phis[i] + 0.5*PI) * 180.0 / PI

Later, T_phi() preferentially reads phis_full over phis:
if (phis_full != NULL) phis_local = phis_full; // ← uses degrees!

Since T_phi() returns angular step without deg→rad conversion, and FBPscalar ∝ T_phi(), the normalization applied during Hilbert filtering is scaled by 180/π ≈ 57.3×. The derivative kernel (parallelRay_derivative) uses phis[] (always radians) for its local T_phi, so the 1/T_phi and ×T_phi do NOT cancel — the error propagates directly to reconstructed CT values.

Bug 2 (Minor): phi_start/phi_end restored to full-scan range

After set_angles(), phi_start/phi_end correctly reflect the chunk's angular range, but are immediately restored to full-scan values:
phi_start = phi_start_save; // full-scan
phi_end = phi_end_save;

These are copied to GPU constant memory (d_phi_start, d_phi_end) and used in the helical weighted backprojector kernel to bound the redundancy sumWeights computation. With full-scan bounds, sumWeights includes turns not present in the chunk's data, causing incorrect per-view normalization.

Repro Conditions

  • Helical cone-beam scan (pitch ≠ 0)
  • Projection data + volume data exceed available_GPU_memory (triggers backproject_FBP_multiGPU_splitViews)
  • Example: 6000 views × 505 rows × 2063 cols = 23.3 GiB, vol 1527³ × 445 = 3.9 GiB, required ≈ 50.7 GiB > typical 48 GB GPU → triggered

Small datasets that fit entirely on GPU are NOT affected (no chunking, phis_full remains NULL, T_phi() uses phis[] in radians).

Verification

FBPscalar can be printed before FBP (via get_FBPscalar()) to verify:
FBPscalar = 1/(2π) × T_φ × pixW × (sod/sdd)² × pixH / (voxW² × voxH)

With the fix, FBPscalar ≈ 0.03–0.04 for typical micro-CT geometries (corresponding to T_φ ≈ 0.004 rad). Before the fix, chunked FBP would report the same FBPscalar value (computed from the pre-chunk params), but internally use a 57× larger value for each chunk.

Files Changed

  • src/parameters.cpp: removeProjections() — store phis_full in radians, manually set phis and phi_start/phi_end without degree-expecting set_angles().

…cal FBP

### Bug Summary

In helical cone-beam FBP, when the projection + volume data exceed GPU
memory, LEAP triggers `backproject_FBP_multiGPU_splitViews`, which creates
per-chunk parameter copies via `removeProjections()`. Two bugs in this
function caused incorrect reconstruction values:

**Bug 1 (Major): phis_full stored in degrees, consumed as radians**

`removeProjections()` calls `get_angles(phis_full)` to save the full-scan
angle array. `get_angles()` converts from internal radians to degrees:
  phis_full[i] = (phis[i] + 0.5*PI) * 180.0 / PI

Later, `T_phi()` preferentially reads `phis_full` over `phis`:
  if (phis_full != NULL) phis_local = phis_full;  // ← uses degrees!

Since `T_phi()` returns angular step without deg→rad conversion, and
`FBPscalar ∝ T_phi()`, the normalization applied during Hilbert filtering
is scaled by 180/π ≈ 57.3×. The derivative kernel (`parallelRay_derivative`)
uses `phis[]` (always radians) for its local T_phi, so the 1/T_phi and ×T_phi
do NOT cancel — the error propagates directly to reconstructed CT values.

**Bug 2 (Minor): phi_start/phi_end restored to full-scan range**

After `set_angles()`, `phi_start`/`phi_end` correctly reflect the chunk's
angular range, but are immediately restored to full-scan values:
  phi_start = phi_start_save;  // full-scan
  phi_end = phi_end_save;

These are copied to GPU constant memory (`d_phi_start`, `d_phi_end`) and
used in the helical weighted backprojector kernel to bound the redundancy
`sumWeights` computation. With full-scan bounds, `sumWeights` includes
turns not present in the chunk's data, causing incorrect per-view
normalization.

### Repro Conditions

- Helical cone-beam scan (pitch ≠ 0)
- Projection data + volume data exceed `available_GPU_memory` (triggers
  `backproject_FBP_multiGPU_splitViews`)
- Example: 6000 views × 505 rows × 2063 cols = 23.3 GiB, vol 1527³ × 445
  = 3.9 GiB, required ≈ 50.7 GiB > typical 48 GB GPU → triggered

Small datasets that fit entirely on GPU are NOT affected (no chunking,
`phis_full` remains NULL, `T_phi()` uses `phis[]` in radians).

### Verification

FBPscalar can be printed before FBP (via `get_FBPscalar()`) to verify:
  FBPscalar = 1/(2π) × T_φ × pixW × (sod/sdd)² × pixH / (voxW² × voxH)

With the fix, `FBPscalar ≈ 0.03–0.04` for typical micro-CT geometries
(corresponding to T_φ ≈ 0.004 rad). Before the fix, chunked FBP would
report the same FBPscalar value (computed from the pre-chunk params),
but internally use a 57× larger value for each chunk.

### Files Changed

- `src/parameters.cpp`: `removeProjections()` — store `phis_full` in
  radians, manually set `phis` and `phi_start`/`phi_end` without
  degree-expecting `set_angles()`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant