Conservative, read-only surface-label diagnostics for volumetric papyrus CT
ScrollAnchor takes an approximate papyrus surface (Volume Cartographer
tifxyz) and a CT volume/ROI, and produces diagnostics for two high-value failure
modes:
- Normal-direction drift - the surface sits off the physical sheet
- Sheet-switch jumps - a patch has jumped onto a neighboring sheet (the most harmful error: it looks fine because it sits on a real sheet)
It emits per-vertex confidence, confidence-ranked review regions, and machine-readable reports. It is diagnostics-first: it flags, it does not move labels. Conservative correction proposals are available but off by default
ScrollAnchor is an early open-source research tool developed for the Vesuvius
Challenge 2026 open problems around surface-label quality, mesh-tracing errors, and
neighboring-sheet switches (see scrollprize.org/2026_open_problems, problems on
Surface Prediction & Topology, Mesh Tracing Failures, and Label Quality &
Imprecision). It is functional, reproducible research software with a clear
validation roadmap, useful today for expert-assisted surface review, controlled
benchmark construction, and identifying potentially high-risk surface regions
The core workflow is the 3D surface-label and CT diagnostic workflow. The 2D render workflow is exploratory review prioritization and is not volumetric validation. The PHerc artifacts are a real-data case study and evidence chain rather than a separate product, and the render candidates in them are not confirmed reconstruction failures
| Available data | Command | Purpose |
|---|---|---|
| Surface labels or tifxyz plus a CT volume or ROI | scroll-anchor analyze |
Run the core 3D drift and neighboring-sheet diagnostics |
| A 2D render image without CT evidence | scroll-anchor analyze-render |
Export exploratory review candidates |
| Existing render-analysis outputs | scroll-anchor render-report |
Build a manual review report without rerunning detection |
| Render candidates plus tifxyz channels | scroll-anchor map-render-candidates |
Map selected candidate ranks to surface coordinates |
Mapping selects candidates by exported rank, which is not the component ID
Verified against ScrollPrize/villa:
lasagnacorrects/grows surfaces via GPU-oriented optimization (needs preprocessed evidence + winding volumes). ScrollAnchor is a read-only pre-filter that says where correction is safe versus riskysegmentation/vc_proofreaderis a human napari review UI with no automatic error localization. ScrollAnchor prioritizes which patches a human should opensegmentation/evaluationcomputes global metrics against full ground truth. ScrollAnchor is localized and needs no ground truth at inference
The gap it fills: a standalone, CPU-friendly, ground-truth-free tool that turns
(tifxyz surface + CT ROI) into localized drift/switch diagnostics
Validation is layered, and each layer supports a different claim:
- the synthetic benchmark demonstrates precise detection under controlled synthetic corruptions of a gently curved multi-sheet volume
- the real-cube experiment uses real CT and real sheet geometry with injected corruptions and supports the conservative safety concept
- naturally occurring real annotation failures have not yet been evaluated against full ground truth
The exploratory 2D PHerc workflow provides review candidates and supporting evidence, not volumetric detector validation
Two parallel-sheet CT volume + a clean surface corrupted with drift, a
sheet-switch, an ambiguous low-contrast+drift zone, and a hole. Mean over 5 seeds
(80x80 grid), default config:
| Metric | Value |
|---|---|
| Sheet-switch detection precision / recall | 1.00 / 1.00 |
| Drift displacement recovery MAE | ~0.56 voxels |
| Harmful acceptance - trust labels as-is | 0.051 |
| Harmful acceptance - naive snap-to-brightest | ~0.60 |
| Harmful acceptance - ScrollAnchor | 0.00 |
| Clean-region stability (not needlessly flagged) | 1.00 |
| Review fraction | ~0.12 |
Harmful acceptance = fraction of vertices a method confidently accepts (keeps or moves) that end up on the wrong sheet. This is the primary metric and encodes the core safety principle: when evidence is ambiguous, flag for review - never emit a confident label on the wrong sheet
Reproduce:
pip install -e .
scroll-anchor benchmark --output results/bench --seed 0
cat results/bench/metrics.jsonscripts/run_real_cube_benchmark.py runs the diagnostics on a real Scroll 1
instance-label cube (02256_02512_04816): a medial surface is extracted from one
labelled sheet, then controlled drift and a real neighbouring-sheet switch
are injected. This is not validation on naturally occurring annotation errors
pip install -e ".[benchmark]"
python scripts/run_real_cube_benchmark.py --output results/real_cube_02256_02512_04816Findings on this cube (source sheet 328, neighbour 329, 96³ ROI):
- Conservative safety behaviour transferred to the tested cube. ScrollAnchor's harmful acceptance (confidently accepting a vertex that sits on the wrong sheet) is 0.00 versus ~0.15 for naive snap-to-brightest; switch review-recall is 1.00 - the injected neighboring-sheet switch is always surfaced for review, and no wrong-sheet vertex is confidently accepted
- Precision is currently limited on the tested strongly curved real geometry. Thresholds calibrated on flat synthetic sheets over-fire on real papyrus curvature: switch precision ~0.19, drift localization remains weak (F1 ~0.01), and ~27% of the clean surface is flagged for review. On strongly curved real geometry the tool currently behaves as a very conservative "flag-for-review" filter rather than a precise localizer
On the tested cube this experiment supports the conservative safety concept on this controlled real-geometry benchmark and illustrates a viable expert-in-the-loop workflow - the injected switch is always surfaced and nothing wrong-sheet is confidently accepted. It also usefully identifies the next research bottleneck: real curvature increases false positives, so curvature-aware residual modelling and improved calibration are the next development priorities. This single controlled-corruption cube does not, on its own, establish general real-scroll precision
pip install -e . # CPU-only: numpy, scipy, tifffile, pyyaml
pip install -e ".[remote]" # + zarr/fsspec to stream real CT ROIs over HTTP/S3To run the full test suite, install the dev and benchmark extras - the NRRD-related tests require the benchmark dependencies (pynrrd) and are skipped otherwise:
pip install -e ".[dev,benchmark]"
python -m pytest -qThe repository includes a CPU-only Dockerfile for reproducible runs. Build the image from the repository root:
docker build -t scroll-anchor:local .The entrypoint is the scroll-anchor CLI, so running the image with no arguments
prints the help screen:
docker run --rm scroll-anchor:localOutput directories require a writable mount. The synthetic benchmark writes into the mounted path:
mkdir -p results/docker-bench
docker run --rm \
-v "$PWD/results/docker-bench:/output" \
scroll-anchor:local \
benchmark --output /output --seed 0Commands that use local files follow the same mount pattern. Mount input data read-only where possible, and mount a separate writable directory for output:
docker run --rm \
-v "/path/to/input:/data:ro" \
-v "/path/to/output:/output" \
scroll-anchor:local \
<command> <arguments>Manually validated in Docker: the image build, the CLI help screen, the runtime extras, all subcommand help screens, and one synthetic benchmark run
Not validated in Docker: real CT analysis, remote Zarr access, real render analysis, PDF generation, candidate mapping on real artifacts, and the full real-cube benchmark
scroll-anchor analyze \
--surface path/to/segment_tifxyz/ \
--volume path/to/volume.zarr \
--config configs/default.yaml \
--output results/run/--volumeaccepts a local.npy(in-memory, for tests) or a zarr path/URL indexed[z, y, x]. For zarr, only the surface's bounding box (+margin) is read into memory, so this is ROI-scoped and memory-safe- Add
--enable-correctionto also emit conservative, gated correction proposals
Outputs:
results/run/
├── diagnostics.json # summary stats + resolved config
├── review_regions.json # prioritised, clustered regions to inspect
├── arrays/*.npy # per-vertex fields (confidence, drift, switch, ...)
└── surface/ # tifxyz copy + sa_confidence/sa_drift/sa_switch/sa_review channels
For each surface vertex: estimate the world-space normal, sample the CT intensity
profile along ±radius voxels (trilinear, CPU), then:
- Drift = signed offset to the distance-weighted nearest sheet peak
- Sheet-switch = a robust (median-consensus) ~one-spacing positional jump, confirmed by strong on-sheet evidence, grown by hysteresis over the patch
- Confidence = product of contrast, peak margin, geometric continuity, and evidence - so any single weakness drives confidence toward 0
- Review = switch, or low confidence, or large drift
See docs/method.md for details and docs/coordinate_conventions.md for the
coordinate/normal conventions (verified against villa/lasagna/tifxyz_format.md
and the vesuvius tifxyz API)
The analyze command above is the primary 3D pipeline: it needs a tifxyz surface
and a CT volume/ROI, and reports drift and sheet-switch diagnostics with
through-thickness CT evidence and surface-normal geometry
analyze-render is a separate, exploratory workflow for the case where all you
have is a single downsampled 2D surface render (a JPG), with no surface geometry
and no CT volume. It runs a CPU-only, classical image-processing detector that flags
candidate visual discontinuities - places where the render texture appears to
shift laterally, which may correspond to a sheet skip or a local render shift
pip install -e ".[render]" # + Pillow for JPG decode and overlay drawing
scroll-anchor analyze-render \
--render path/to/surface_render_ds8.jpg \
--output results/render-run/ \
--working-downsample 2- Memory policy: the JPG is never decoded at full resolution. Pillow
draft()downscales at the decoder, then--working-downsamplereduces it further; a--max-pixelsbudget aborts before an unsafe allocation - Coordinates: every region carries JPG row/col plus a mapped full-render row/col
(JPG coordinates multiplied by the documented
--full-render-factor, default 8). The mapped coordinates are not verified VC3D coordinates
Outputs:
results/render-run/
├── overlay.png # ranked, ID-labelled boxes over a readable copy of the render
├── regions.json # candidate regions with scores, directions, coordinates
├── diagnostics.npz # compact processed-resolution seam/anomaly/texture arrays
├── metadata.json # shapes, scales, params, runtime, peak RSS, limitations
├── summary.json # count funnel and exported score range (ranked subset)
├── top_candidates.png # contact sheet of the top-ranked candidate crops
└── report.pdf # multi-page review packet for manual inspection
render-report rebuilds report.pdf (and top_candidates.png when the source
render is available) from artifacts that already exist. It is report-only: it reads
metadata.json, summary.json, regions.json and overlay.png, does not run
the detector, does not read diagnostics.npz, and overwrites only the report
files. Numerical results are unchanged
scroll-anchor render-report \
--results results/render-run/ \
--render path/to/surface_render_ds8.jpg # optional, for larger crop pages- With
--render, crop pages are decoded fresh from the source JPG at the recorded working resolution andtop_candidates.pngis regenerated - Without
--render, the existingtop_candidates.pngis reused and the PDF notes that the source render was unavailable
This is a real-data case study built from one PHerc render. It links exploratory 2D candidates to boundary diagnostics, orientation testing, tifxyz mapping, and limited local CT inspection. It does not classify any candidate as a confirmed reconstruction failure, and it does not constitute full volumetric validation
A published example run (PHercParis4 segment w110-112) is recorded in
results/pherc-render/
The PHerc artifacts are a real-data case study on a single render, not a separate product. They form one chain:
- render analysis exports exploratory 2D candidates
- combined diagnostics describe tifxyz-invalid and render-background relationships without changing detector scores
- the alignment audit compares four simple orientation hypotheses, with identity strongest among those tested but not proven
- the ranking-policy audit tests alternative review-order policies without changing the production ranking
- candidate mapping converts selected exported ranks to tifxyz surface coordinates under an explicit identity-orientation assumption
- local CT evidence inspects ranks 7 and 11 under a registration-supported transformed-coordinate interpretation that remains supported, not globally verified
No step in this chain classifies any candidate as a confirmed sheet skip, a reconstruction error, or a false positive
A lightweight combined run on the same PHerc render is now published, carrying both
diagnostic systems at once: normalized tifxyz valid-surface diagnostics and
render-derived edge-connected near-black background diagnostics. Both are reviewer
fields only, so raw scores and the exported candidate order are unchanged
Only the machine-readable core is committed (regions.json, metadata.json,
summary.json, plus the contact sheet); diagnostics.npz, the full overlay, and the
source JPG are not. Those candidates are the shared input behind the ranking-policy
and candidate-to-tifxyz artifacts below:
results/pherc-render-combined-diagnostics/
The two diagnostic masks above disagreed, so a separate offline audit compared
identity against horizontal, vertical, and combined flip hypotheses to check whether
that gap was an orientation error. Identity had the strongest measured agreement on
every metric (mean IoU 0.4348, against 0.3435, 0.1995 and 0.2008)
The masks are not equivalent: tifxyz-invalid surface was a subset of a broader
render-background mask covering more than twice the area (0.1338 against 0.3096
at threshold 32), with recall 1.0. That supports different validity semantics
rather than a misalignment. This makes identity the best tested hypothesis but does
not remove the explicit assumption, so map-render-candidates still requires
--assume-identity-orientation:
results/pherc-alignment-audit/
Render-background diagnostics exposed several top-ranked candidates dominated by
black render background rather than papyrus texture. An offline audit compared
raw, half-penalty, full-penalty, and hard-split review orders on an already
exported run, without rerunning detection. The half penalty
(raw_score * (1 - 0.5 * overlap)) was the least aggressive sufficient tested
policy: it cleared the known background cases out of the top 20 while keeping the
interior controls prominent
It was not adopted, because the evaluation used a single render and
substantially reordered the full candidate list (Spearman 0.599 against raw
order; 129 of 200 candidates moved by more than 20 positions). Runtime scores and
ranking are unchanged; render-background overlap stays a diagnostic field for
reviewers to read alongside the score. Details, artifacts, and limitations:
results/pherc-ranking-policy-audit/
map-render-candidates is the reproducible bridge from an exported 2D candidate to
normalized tifxyz surface coordinates. It selects candidates by exported rank or
component ID (never confusing the two), maps the source-render centroid onto the
tifxyz grid by aligned half-open containment, and reads x.tif, y.tif, z.tif at
that cell
scroll-anchor map-render-candidates \
--regions results/render-run/regions.json \
--metadata results/render-run/metadata.json \
--tifxyz-dir path/to/tifxyz_normalized \
--rank 7 --rank 11 \
--assume-identity-orientation \
--output results/mapping-run/- strict shape validation: scale factors are derived from the input shapes and must be exact positive integers per axis; incompatible rasters fail
- dual agreement: the cell is derived independently from the source and processed centroids and the two must match
- identity render-to-tifxyz orientation must be explicitly acknowledged with
--assume-identity-orientation; it is never assumed silently and the command cannot verify it - it does not assume CT array order and performs no CT validation. The values are raw tifxyz coordinates, not CT NumPy indices
- on the published PHerc run, ranks 7 and 11 mapped to valid tifxyz cells with both
coordinate paths in agreement:
results/pherc-candidate-tifxyz-mapping/
What this workflow cannot report, by construction: confirmed sheet switches, true 3D drift, signed error along a surface normal, voxel displacement, or corrected surface coordinates. A flat render has no through-thickness CT evidence and no normal geometry, so its output is a set of 2D candidates for manual community review, not a validated correction. If nothing passes the conservative texture, border, multi-scale, and coherence gates, it returns an empty region list rather than forcing a positive
Small level-0 CT neighbourhoods were extracted at the positions that exported ranks 7 and 11 map to, so the mapped candidates can be looked at against real volumetric data. The tifxyz coordinates were carried into the CT volume frame by the inverse registration; that transformed interpretation is supported - not verified - by the registration landmark residuals and by a native-versus-transformed chunk-presence check. Each crop is 64x64x64 uint8, centred on the mapped voxel
Both mapped points fall within populated regions of the masked reconstruction, where laminar structure is visible, and the two ranks sit in measurably different local intensity contexts (rank 7 in a lower-density neighbourhood than its crop, rank 11 in a higher-density one). This is evidence for inspection only: it does not classify either candidate and it does not validate the detector. The coordinate-frame interpretation and the identity render-to-tifxyz orientation assumption both remain open
These are the current boundaries of what has been demonstrated, and the research priorities that follow from them:
- Precise detection is established only for the synthetic benchmark. Those results come from controlled synthetic corruptions of a gently curved multi-sheet volume, not from real annotation failures
- The real-cube experiment uses real CT and real sheet geometry with injected corruptions. It validates the conservative safety concept; it is not validation on naturally occurring annotation errors, which have not yet been evaluated
- Real curvature currently increases false positives. Thresholds tuned on flat synthetic sheets over-fire on strongly curved real papyrus (~27% of the clean surface flagged on the tested cube); curvature-aware residuals are a priority
- Drift localization requires improvement on real geometry (F1 ~0.01 on the tested cube), where genuinely ambiguous zones are surfaced through review rather than corrected
- Switch detection should become less dependent on a predefined smoothing window.
switch_smooth_windowmust exceed the switched-patch diameter; too small a window silently lowers switch recall - Confidence calibration requires validation on additional cubes. Normal estimation degrades at surface discontinuities; switch detection uses a 3D positional residual (not the normal projection) to stay robust to this
- Direct VC3D coordinate alignment remains to be verified. The real-cube surface is exported in a cube-index coordinate frame (ROI-local indices offset by the cube origin) with NRRD metadata validation / axis resolution enforced; full VC3D coordinate compatibility is not claimed until visual alignment is checked
Conclusion: ScrollAnchor is ready for technical community review as an experimental diagnostic and validation framework. The current real-cube benchmark supports its conservative safety principle and demonstrates a viable expert-in-the-loop workflow, while also identifying precision on strongly curved surfaces as the main development priority. The current release is most useful for assisted review, controlled benchmark construction, failure analysis, and collaborative method development. Additional validation with known real annotation failures is needed before recommending broader or unattended use
Research and engineering directions (priorities, not delivery commitments):
- Curvature-detrended local residuals
- Multi-scale neighboring-sheet-switch detection
- Improved confidence calibration on real surfaces
- Validation on known naturally occurring failure regions
- Direct VC3D coordinate-alignment verification
- Integration feedback from annotation and proof-reading workflows
ScrollAnchor is currently seeking technical review from participants familiar with Vesuvius Challenge surface extraction, annotation, proof-reading, and volumetric papyrus CT data
The most useful feedback would include:
- whether the targeted drift and neighboring-sheet-switch failure modes match real annotation or tracing problems;
- known small regions containing naturally occurring failures;
- review of the real-cube benchmark construction and coordinate assumptions;
- inspection of generated confidence, drift, switch, and review fields;
- advice on integration with existing VC3D, proof-reading, or segmentation workflows;
- comparison with existing tools that may already address part of the problem
The current repository should be treated as a working research contribution open to validation and refinement, not as a community-endorsed solution
MIT. Interoperates with the Volume Cartographer tifxyz format; see
docs/coordinate_conventions.md for attribution of format details
Olga Ivanova - ivolga.vak@gmail.com