Skip to content

feat(lif): apply per-tile shading correction using LIF's ShadingReference #47

Description

@ferrinm

Motivation

On Leica widefield mosaics where the acquisition's declared overlap didn't translate to overlapped stage motion (i.e. stage-stitch produces essentially butt joints), the dominant visible seam artifact is photometric, not geometric — vignetting and shading falloff at each tile's edge produces feature "duplication" and brightness discontinuities that no better stitcher can fix.

The astrocytes acquisition (surfaced in the follow-up to #41) is exactly this shape:

  • Stage positions describe butt joints (~2 px overlap between adjacent 2048-px tiles).
  • OverlapPercentageX=0.10 is UI-configured but not actualized.
  • Adjacent tile pixel content therefore doesn't overlap — no external stitcher (ASHLAR, m2stitch, BigStitcher) can register on content that isn't there.
  • The visible seams are edge-of-field vignetting + shading falloff, not misalignment.

The LIF file already references the shading correction that the acquisition software applies at capture time, per-camera-lane:

<InScanProcessing Application="LAS AF">
  <CameraLane0>
    <ShadingProcessingStep>
      <ShadingReferenceID_UsedSetting_0>1431</ShadingReferenceID_UsedSetting_0>
      <ShadingReference>CA21M726010K8 (b/w), 2048x2048, O10x11506537, MagChanger , FLUO</ShadingReference>
    </ShadingProcessingStep>
  </CameraLane0>
  <CameraLane1>...</CameraLane1>
  ...
</InScanProcessing>

If zarrmony (or bioio-lif) can apply this correction at conversion time, seam appearance improves dramatically on this class of acquisition without changing anything geometric.

What to build

Investigate whether bioio-lif already applies shading correction on read (I suspect not — the raw tile pixels look uncorrected). If it doesn't:

  1. Extractor: parse the <InScanProcessing>/<CameraLane*>/<ShadingProcessingStep> block per-scene from the LIF XML. Surface per-channel-lane shading reference identifiers.
  2. Reference lookup: figure out how the ShadingReferenceID_UsedSetting_0 value resolves to actual shading reference image data. It might be:
    • Stored elsewhere in the same LIF (an internal reference image the extractor should also read)
    • Stored in a sidecar .lif shading library the user has separately
    • Referenced by ID only, with pixels living in a Leica database (in which case: not recoverable, at least document the limitation)
  3. Correction pass: if reference data is available, apply per-tile flat-field correction (tile_corrected = (tile - dark) / (flat - dark) * mean(flat - dark) or the equivalent Leica convention) BEFORE stitching.
  4. Cascade integration: correction is orthogonal to stitcher choice — should apply under stage-stitch, grid-stitch, per-tile, and bioio-lif alike. New lif_shading_correction: bool = True (default on when reference data is available) with clear docstring naming when it silently no-ops.
  5. Audit surface: record whether shading correction ran (mosaic.shading_correction: {applied: true, reference_id: 1431, source: "lif-internal"} or {applied: false, reason: "no shading reference in scene XML"}).

Acceptance criteria

  • ADR documenting the shading model applied (Leica convention: multiplicative? subtractive dark + multiplicative flat? per-lane vs per-channel?) and where reference data is sourced from.
  • On the astrocytes LIF: visible seam brightness discontinuities reduced or eliminated on a representative scene, comparison screenshots in the PR.
  • lif_shading_correction=False cleanly disables the pass (backward-compat escape hatch).
  • Missing / unresolvable reference data → no-op with a MosaicShadingWarning (new warning class) naming what's missing; conversion still succeeds.
  • Audit block records the outcome per scene.
  • Tests: synthetic fixture with a known shading pattern → correction restores flat field within tolerance; missing reference → no-op path emits warning.
  • CHANGELOG entry under a new minor version (v0.8.0).

Notes / open questions

  • May be worth lifting the extractor into bioio-lif upstream if the reference data is reachable from LIF alone — benefits all bioio-lif consumers, not just zarrmony.
  • Interacts with the "true overlap requires an external stitcher" answer in ways worth documenting: shading correction addresses PHOTOMETRIC seams; external stitchers address GEOMETRIC seams. On acquisitions with real content overlap, both matter and can compose.
  • Not urgent for the Aperture pipeline unless quantitative analysis at tile boundaries becomes a requirement — the audit currently correctly surfaces the mismatch, downstream can defer or filter.

Discovered while

Diagnosing tile-seam appearance on the 2026-05-14_ODC51_astrocytes.lif conversion (follow-up to #41).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions