Skip to content

Fix per-angle geometry indexing in iterative algorithms (MATLAB) and blocksize-1 angle blocks (Python) - #801

Merged
AnderBiguri merged 2 commits into
CERN:masterfrom
Pooryamn:master
Oct 30, 2026
Merged

AnderBiguri merged 2 commits into
CERN:masterfrom
Pooryamn:master

Conversation

@Pooryamn

Copy link
Copy Markdown
Contributor

Summary

When using non-circular trajectories (e.g., per-angle DSO, DSD, offDetector, rotDetector, such as on Loop-X), several iterative algorithms apply the geometry or weights (W) of a different angle than the projection currently being updated. Because these values are constant across all angles in circular scans, the bug was previously invisible. However, with per-angle geometry, this mismatch causes the reconstruction to diverge or turn into noise.

In addition, this PR fixes a bug in the Python blocksize-1 algorithms where three angles were being projected per update instead of one.


Python Changes

File: tigre/algorithms/iterative_recon_alg.py (specifically set_angle_index)

  • The Bug: With blocksize=1, order_subsets returns each block as a 1D (3,) row [alpha, theta, psi] with a scalar index. Since commit a7c1f8a ("fix TypeError: only 0-dimensional arrays can be converted to Python scalars #741") removed the blocksize-1 reshaping in art_data_minimizing, this row is passed directly to Ax/Atb. check_geo reads this 1D array as a list of three separate rotation angles. As a result, every SART-type update back-projects W·(p_alpha − A_0°x) twice at angle 0°. set_v also builds V from the same three back-projections.
  • The Fix: Ensure every angle block is strictly 2D (k,3) and every index block is 1D (k,). For blocksize > 1, this does nothing as the shapes are already correct.
  • Impact:
    • Affects sart, sart_tv, asd_pocs, awasd_pocs, piccs, pcsd, and aw_pcsd.
    • The OS algorithms (sirt, fista, cgls, lsqr, lsmr, and mlem) yield bit-identical results.
    • The crash fixed in TypeError: only 0-dimensional arrays can be converted to Python scalars #741 does not regress: the 1-element DSO stays (1,) since commit 26d8e2e. (Checked with NumPy's "ndim>0 to scalar" deprecation turned into an error).

MATLAB Changes

1. Corrected Angle Indexing in Loops

In non-OS loops, jj is the position in the shuffled order, while index_angles(jj) is the actual angle. In OS loops, jj is the block number, and orig_index{jj} holds the block's angles. All of these algorithms default to OrderStrategy='random', making correct indexing critical.

  • PICCS.m, SART_TV.m, B_ASD_POCS_beta.m: Updated geo.DSD=DSD(jj) / geo.DSO=DSO(jj) to use DSD(index_angles(:,jj)) / DSO(index_angles(:,jj)). This now correctly matches the indexing behavior for offOrigin, offDetector, and rotDetector.
  • SART_TV.m: The update now uses W(:,:,index_angles(:,jj)) instead of W(:,:,jj). Also, resL2 previously compared proj(:,:,index_angles) (shuffled) with Ax(res,geo,angles) (input order), resulting in a wrong residual that triggered the "error increased" early exit. It now correctly uses proj in input order.
  • OS_AwASD_POCS.m: Changed DSD(jj) / DSO(jj) to DSD(:,orig_index{jj}) / DSO(:,orig_index{jj}). This also fixes AwASD_POCS (which wraps this file).
  • OS_PCSD.m, OS_AwPCSD.m: offOrigin, offDetector, rotDetector, DSD, DSO, and W were incorrectly accessed by block number (jj); they now properly use orig_index{jj}. This also fixes the PCSD and AwPCSD wrappers.

2. Geometry Restoration Before Full-Data Ax Calls

Several fields were previously left unrestored before error/stop checks, est_proj, resL2, and Bregman steps, causing these global calls to mistakenly use a single block's geometry.

  • OS_SART.m, OSEM.m, SART_TV.m, B_ASD_POCS_beta.m: Added missing geo.DSO = DSO.
  • OS_PCSD.m, OS_AwPCSD.m: Added geo.DSD, geo.DSO, and geo.rotDetector to both restore blocks.
  • Note on OS_SART/OSEM: Requesting resL2 used to throw an error (geo.DSD Should be 1x1 or 1xsize(angles,2)). Because SART (a wrapper) always requests it, it was computing an incorrect residual and could stop early. This is now resolved.

3. Fixed Existing Crashes/Typos

  • OS_PCSD.m, OS_AwPCSD.m: f0 was being used in dp_vec=(f-f0) before being initialized. Set f0=f at the start of each iteration (matching ASD_POCS). Also updated resL2(ii) to resL2(iter).
  • OS_AwPCSD.m: Corrected niter to maxiter.
  • B_ASD_POCS_beta.m: The default epsilon call to im3Dnorm was missing the 'L2' argument.

@AnderBiguri
AnderBiguri merged commit 69bdd9f into CERN:master Oct 30, 2026
2 checks passed
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.

TypeError: only 0-dimensional arrays can be converted to Python scalars

2 participants