Grid690 Python Refine-Grid Parity — Change Log

Summary of work aligning Python RefineGridMosaic with legacy C++ ir-refine-grid for RC2_4Square_Assembled section 0690 (Grid690 / IDocBuildTest).

See also: refine-grid-cpp-parity-checklist.md for the C++→Python audit mapping.

Package: nornir-imageregistration

Core algorithm (local_distortion_correction.py)

  • Replaced overlap-subcell incremental delta model with C++ mesh-vertex update architecture:

    • Per-pass full-tile prewarp (_prewarp_tile_for_grid_refine)

    • Per-mesh-vertex phase correlation against each overlapping neighbor (_measure_grid_vertex_displacements)

    • Legacy regularization pipeline (_regularize_displacements: median r=1, gap-fill, Gaussian σ=1.0)

    • 1/(1+mass) shift blending and direct TargetPoints updates

    • Dual convergence stop (threshold or no improvement vs prior pass)

  • min_overlap default for mosaic refine path set to 0.25 (per-cell valid-area gate, matching C++)

  • Removed estimate_cutoff gating from mosaic refine hot path (retained for STOS refine)

  • Skip final lattice resample when transforms are already on the output grid

  • Refinement cells use direct equal-size FFT (no random-noise padding) for deterministic parity

  • Added vertex measurement diagnostics per pass

Supporting modules

  • transforms/gridtransform.py: bulk UpdateTargetPointsByIndex support

  • transforms/factory.py, mosaic_tileset.py, __init__.py: export/API wiring

  • assemble_tiles.py: assemble buffer size guard (NORNIR_MAX_ASSEMBLE_BUFFER_BYTES) for exploded target bounds

  • phasecorrelation.py: guard flat correlation normalization (divide-by-zero)

Tests and fixtures

  • tests/grid_seam_metrics.py — seam MAE, golden target-point delta, chained-pass helpers

    • REFINE_MAX_PASSES = 10, GOLDEN_TARGET_DELTA_MAX = 2.2 px

    • REFINE_DISPLACEMENT_THRESHOLD = 0.5 (matches golden mosaic Thr0.5 filename)

    • Convergence uses max |shift component| per pass (legacy C++ uses mean)

    • Chained passes stop when displacement ≤ threshold (matches C++ breaking after a converged pass)

    • Golden comparison removes global translation gauge before per-tile deltas

  • tests/grid690_diagnostics.py — headless PNG/JSON diagnostics for functional tests

  • tests/test_refine_grid_690_functional.py — RC2 Grid690 end-to-end functional suite

  • tests/test_refine_grid_legacy_parity.py — optional exe parity (skipped without NORNIR_LEGACY_IR_REFINE_GRID)

  • tests/test_local_distortion.py — TestMosaicGridRefinementApi unit coverage

Validation (RC2 fixture)

Metric

Result

Functional tests

3/3 pass

Mean target delta vs golden (gauge-removed)

~1.79 px (gate 2.0)

Mean seam MAE (1 pass, thr 0.2)

~0.096 (golden ~0.091)

Worst vertical seam pair

015-397 (~0.125 MAE) vs horizontal pairs ~0.065

Package: umbrella docs/

  • refine-grid-cpp-parity-checklist.md — Phase 0 C++ audit deliverable

Other Nornir packages

No Grid690-specific code changes were made in nornir-buildmanager, nornir-shared, nornir-pools, nornir-pyre, nornir-web, nornir-docker, nornir-volumecontroller, or nornir-volumemodel. Submodule working trees may show line-ending noise unrelated to this work.

Excluded: dm4 (per request).

Follow-up fixes

  • Prewarp interpolation (Bugbot): _prewarp_tile_for_grid_refine now passes interpolation_order=0 to _TransformImageUsingCoords, matching C++ warp<> (itk::NearestNeighborInterpolateImageFunction).

Known follow-ups (Bugbot / review)

Severity

Location

Finding

~~High~~

~~assemble.py prewarp interpolation~~

Fixed: grid-refine prewarp uses nearest-neighbor (order=0)

Medium

tests/grid_seam_metrics.py

Chained single-iteration passes reset last_average each call; cross-pass “no improvement” stop only applies inside one RefineGridMosaic invocation

High

assemble.py TransformStos

Image warp call commented out — verify STOS path if touched in same branch