Skip to content

Add RingArtifactTransform: GPU CT ring-artifact augmentation (#201)#1020

Open
pscamillo wants to merge 2 commits into
ScrollPrize:mainfrom
pscamillo:add-ring-artifact-augmentation
Open

Add RingArtifactTransform: GPU CT ring-artifact augmentation (#201)#1020
pscamillo wants to merge 2 commits into
ScrollPrize:mainfrom
pscamillo:add-ring-artifact-augmentation

Conversation

@pscamillo

Copy link
Copy Markdown
Contributor

RingArtifactTransform: GPU CT ring-artifact augmentation

Adds a scroll/CT-specific augmentation that simulates ring artifacts — the
concentric rings produced by miscalibrated or defective detector elements in
reconstructed CT. They appear in the axial plane, centered on the center of
rotation, extruded along the scan axis and consistent across stacked slices.
This is a real, common scroll-CT acquisition artifact, and follows the
"additional augmentations welcome" note in #201 (after the merged Squeeze /
Decohesion / Warp transforms).

Grounding in the Vesuvius pipeline. Per the project FAQ, the scrolls are
reconstructed with filtered backprojection from synchrotron parallel-beam scans.
Ring artifacts are the classic FBP signature of detector-element inconsistency,
so this models an artifact actually present in the reconstructed volumes (this is
augmentation for robustness on the reconstructed images, not reconstruction).
Amplitudes are kept in per-channel std units because the released reconstructions
are unitless / relative intensities (raw floats ~[-0.1, 0.1]); a fixed absolute
amplitude would not transfer across scans. The transform is additive and
non-destructive, consistent with the project keeping reconstruction values
unaltered.

Design

  • ImageOnlyTransform (batchgeneratorsv2 style), fully on-device, in-place.
  • Builds a 1-D radial profile (sum of Gaussian-shaped rings) and gathers it onto
    the volume by per-voxel radius with linear interpolation; the field is H×W and
    broadcasts along the rotation axis (no per-voxel materialization).
  • Amplitudes in per-channel std units by default → consistent under z-scored
    nnU-Net inputs.
  • Handles (C,X,Y) and (C,X,Y,Z); singleton spatial axes are never used for
    the axial plane (pseudo-2D safe); no-op when < 2 non-singleton spatial axes.
  • Configurable: ring count, amplitude, width, radius range, center-of-rotation
    jitter, sign probability, rotation axis, per-channel prob, channel sync.

Speed (RTX 5070, Blackwell sm_120, single sample)

patch (C,X,Y,Z) Ring ms/sample Ring Mvox/s
(1,192,192,1) 0.346 107
(1,192,192,64) 0.322 7319
(1,128,128,128) 0.374 5603
(1,256,256,96) 0.423 14874

Sub-millisecond per sample at every size, up to ~14.9 Gvox/s. Cost is
essentially flat as the stack (rotation) axis grows (64→128 in Z stays
~0.32–0.37 ms) because a single 2-D radial field is broadcast along that axis
rather than generating per-voxel values. Runs natively on GPU. (Benchmark
script included; GaussianNoiseTransform in the vendored 0.2.1 build is CPU-only,
so it is reported as a CPU reference rather than a GPU comparison.)

Ablation (controlled, synthetic — same format as the merged transforms)

Two identical SmallSeg3D nets, same seed/data/steps (400, patch 64³, RTX 5070).
A = baseline (flips only); B = baseline + RingArtifactTransform. Both are
evaluated on a sweep of test-time ring severities. Crucially the test rings are
generated by an independent mechanism (hard-edged top-hat annuli, absolute
amplitude) — not by the trained transform — so this is not testing on the
training augmentation.

test ring amp A (flips) B (+ring) Δ Dice
0.00 (clean) 1.0000 1.0000 +0.0000
0.40 0.9931 1.0000 +0.0069
0.80 0.9542 0.9995 +0.0453
1.20 0.9062 0.9986 +0.0924

The augmentation buys monotonically increasing robustness as ring severity rises
(the baseline degrades to 0.906 Dice while B holds at ~0.999), with zero
clean-data cost
. A sweep rather than a single operating point avoids a
cherry-picked result.

Figure

ring_triptych

Illustrative effect of RingArtifactTransform, rendered at 256² for visibility
(training patches are 64³). Left: clean synthetic papyrus. Centre: the additive
field (after − before) — concentric rings as produced by the transform. Right:
corrupted result. Parameters here are stronger than the proposed training
defaults (intensity=(0.05,0.3)) to make the rings legible.

Open questions for bruniss / giorgioangel

  • Default rotation axis: pin to Z (ring_axes=[2]) to match the scan geometry,
    or leave axis-agnostic (None) for robustness? Defaulting to Z for now.
  • Preferred wrapper probability in the training list (defaulting to ~0.15–0.25).

@pscamillo pscamillo requested a review from giorgioangel as a code owner June 6, 2026 16:11
@vercel

vercel Bot commented Jun 6, 2026

Copy link
Copy Markdown

@pscamillo is attempting to deploy a commit to the scroll Team on Vercel.

A member of the Team first needs to authorize it.

…te to a central blob/corner arc; document scan-axis usage. Validated on real Scroll-5 CT.
@pscamillo

Copy link
Copy Markdown
Contributor Author

Validated on real Scroll-5 CT + small robustness fix

I ran this transform against a real raw CT crop from Scroll 5 (PHerc172, pulled straight from the open zarr store) to check fidelity beyond the synthetic self-test — before/after plus the per-voxel difference across depth.

With the rotation axis set to the acquisition/scan axis, the artifact reproduces cleanly: concentric rings centered in the reconstruction plane, additive over the real papyrus texture (fibers preserved underneath), consistent across slices stacked along the scan axis — matching the physics in the docstring.

Two things came out of it:

  1. Floored radius_range to (0.1, 0.9) (latest commit). With the old (0.0, 1.0), a draw with r_frac ≈ 0 collapsed the "ring" into a central blob and r_frac ≈ 1 gave a corner-only arc; the floor keeps every ring an actual visible ring. The geometry itself was already correct.
  2. Scan-axis usage (now noted in the docstring): with ring_axes=None the rotation axis is sampled across all spatial axes, so on anisotropic patches the rings can land in a plane containing the through-axis — non-physical for scroll CT and distorted by the short axis. For the scroll trainer I'd set ring_axes to the acquisition scan axis.

GPU cost (RTX 5070, Blackwell sm_120): 0.379 ms/call for a (1, 192, 192, 64) patch. (Fuller table in the description above.)
ring_floored_1

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