Stitching artifact correction: fix inverted background mask + add evaluation/sample tooling (WIP)#86
Conversation
…rection
- scripts/sample/extract_sample.py: crop 3 adjacent strips (full Y, Z/X cropped)
from real strips_raw OME-Zarr into scratch for fast iteration (FR-014).
- scripts/eval/{evaluate_stitch,compare}.py: seam-discontinuity + stripe-prominence
+ in-strip metrics with SC-002/003/004 gates.
- .gitignore: never commit extracted samples / eval outputs (restricted data).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…otes - convert_spool_or_zarr.py: background_threshold mask was data*(data<threshold), which kept background and zeroed foreground (silent data loss). Now data*(data>=threshold), gated by background_threshold (default unchanged). - scripts/eval/evaluate_stitch.py: stripe prominence now measured along Y (the per-(z,y)-line direction the correction targets), not X. - docs/stitching_artifact_correction_notes.md: baseline metrics on a representative tissue sample + investigation findings (seam = whole-strip brightness offset; naive in-blend gain equalization regressed and was reverted; mip->stripes naming mismatch blocks the stripe path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eduction
- R5: mip.py writes {name}_proc-mip.tiff (what stripes.py reads); drop the
slice0->slice rewrite that corrupted names like sample-slice036.
- stripes.py: harden correction-map generation. Empty (no-foreground) (z,y) lines
no longer become the 9999999 sentinel (-> black holes after white_matter/corr);
filled with the tile's finite-line median and LOGGED (sample chunk-0001: 49% of
lines). Add Y-smoothing (smooth_y, Gaussian) so the noisy per-line map does not
inject new stripes. Robust volume read (np.asarray(reader[:])).
- convert_spool_or_zarr.py: guard stripe-correction apply against non-finite values
(log + set to 1.0), never silently zero/NaN data.
- scripts/eval/evaluate_stitch.py: seam metric now measures the strip-body brightness
step across seams (the blend turns the junction into a gradient); stripe metric
measures prominence along Y.
Result on the real-data sample: seam-body step 126.6% -> 13.6% (~89% reduction, SC-002).
Stripe prominence ~neutral (needs the FFT-notch destripe / re-targeted metric). See
docs/stitching_artifact_correction_notes.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update: mip→stripes→stitch chain now works; ~89% seam reductionPushed a follow-up commit implementing the illumination-normalization path and hardening:
Result on the real-data sample (
Still open: stripe suppression (SC-003) is ~neutral — the per-line flat-field removes the low-frequency falloff that drives the seam but not the mid-frequency stripe prominence; needs a dedicated FFT-notch destripe (or a re-targeted metric). Also: adjacent strips' shared-overlap foreground medians disagree ~2.5× (153 vs 379), i.e. strong within-strip Y illumination falloff — so a single per-strip gain can't fix it (confirmed). Question still stands: is this variation purely illumination (normalize away) or partly real signal? That gates how aggressive to be. The existing |
…ndings - convert_spool_or_zarr.py + stitch.py: optional correction_clip (default 0.0=off) bounds the per-line stripe-correction gain to [clip, 100-clip] percentiles so low-signal/background lines are not amplified into horizontal bands. - docs: spectral + visual analysis shows there is NO narrow periodic stripe in the sample (broadband energy ~40px, low-freq dominated), so an FFT-notch is inappropriate and was deliberately not implemented. The residual banding is introduced by the multiplicative per-line correction on background; the right fix is a foreground-gated/ additive correction (model change, needs maintainer intent). Seam fix (~89%) stands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing) foreground_gated=True applies corrected = bg + (data - bg) * correction (bg = per-tile 5th percentile or background_level param), so the per-line gain normalizes signal above background while leaving background flat. This removes the horizontal banding the blanket multiplicative correction introduced in low-signal regions, while preserving output scale (no ~5x amplification) and reducing stripe prominence (22.1->20.8). Visually the cleanest result: strips equalized, tissue preserved, no background bands. Default False (no behavior change). Wired through stitch.convert. See docs/stitching_artifact_correction_notes.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Work toward correcting LSM stitching artifacts (intensity seams + stripe/shadow artifacts) on
james/spool_zarr, evaluated on realsub-MF283data. This PR contributes a bug fix, evaluation + sample tooling, and a characterized baseline. The full artifact correction is in progress (see "Remaining work") — opening now to share the fix and findings and to get input on a domain question.What's included
convert_spool_or_zarr.py). The background suppression wasdata * (data < threshold), which keeps background and zeroes foreground signal. Changed todata * (data >= threshold), gated bybackground_threshold(defaultNone→ behavior unchanged). Found by inspection — please confirm the intent. The existingtest_lsm_stitch.pygolden test still passes.scripts/eval/):evaluate_stitch.pycomputes a seam-discontinuity metric, a stripe-prominence metric (along Y, the per-(z,y)-line direction the stripe correction targets), and in-strip preservation;compare.pychecks before/after reduction gates.scripts/sample/extract_sample.py): crops a few adjacent strips (full Y, cropped Z/X) from the real strips into a small OME-Zarr sample for fast iteration. Extracted data is gitignored (restricted) — only the script is committed.docs/stitching_artifact_correction_notes.md): baseline metrics and findings.Baseline findings (3 strips, slice036, tissue region X≈12000–16000)
Open question for maintainers
Is the progressive inter-strip brightness ramp an illumination artifact to normalize, or real signal? This determines whether equalization belongs in the stripe/white-matter-normalization path (my current hypothesis) vs. elsewhere. A naive in-blend gain-match of overlap means overcorrects (compounding) and was not included.
Remaining work (not in this PR)
mip.pywrites{name}.tiffbutstripes.pyreads{name}_proc-mip.tiff— themip → stripeschain is broken; fix before producing a stripe before/after.(z,y)lines currently hit a9999999sentinel →wm/corr ≈ 0→ black lines); add logged fallback.🤖 Generated with Claude Code