feat(stitch): add patch-placement / mosaic stitching module#10
Merged
Conversation
Garrett Bischof (gwbischof)
added a commit
that referenced
this pull request
Jun 5, 2026
…eable Fact-check of PR #10 surfaced a misleading test: the Fourier and nearest paths only place identically for vertically-symmetric patches. In general they differ — the Fourier and livestitch paths flip patches up-down while nearest does not, and all three use different center-rounding (so a footprint can shift ~1px). This is pre-existing holoptycho behavior, lifted verbatim; harmless there (distinct roles) but a real gotcha for a library. - Split the misleading parity test into an honest counts-footprint test plus an explicit flip-difference test (non-symmetric patch). - Document the non-interchangeability in README + AGENTS.md.
Move the generic ViT patch-placement algorithms out of holoptycho/mosaic_stitch.py into a new ptychoml.stitch module so they can be shared instead of duplicated. Three placement strategies: - place_patches_fourier_shift / stitch_batch_into: sub-pixel Fourier-shift - stitch_batch_livestitch_into: nearest-integer + touched bounding box - stitch_batch_nearest: plain nearest-integer, edge-clamped Pure numpy + scipy.fft; depends only on fourier_shift from preprocess. Exported via __init__ __all__, covered by tests/test_stitch.py (12 cases: placement, accumulation, counts footprint, livestitch bbox, edge clamping, up-down flip, fourier-vs-nearest parity, per-batch associativity), and documented in README + AGENTS.md. First step of breaking NSLS2/holoptycho#37 into focused PRs: holoptycho will re-export these under their original names (zero behavior change). Co-authored-by: Himanshu Goel <4122621+himanshugoel2797@users.noreply.github.com>
All three strategies are streaming-safe (per-patch placement, no batch-global reduction, normalization deferred to caller). Flag the two gotchas: the nearest/livestitch paths are bit-exact across batchings while the Fourier path is associative only up to FFT round-off, and the Fourier path may reallocate the canvas on edge straddle so callers must use the returned arrays. Co-authored-by: Himanshu Goel <4122621+himanshugoel2797@users.noreply.github.com>
Document the actual call pattern (allocate canvas/counts once, loop per batch for streaming or one call for offline), the (B, ph, pw) shape contract (single patch -> (1, ph, pw)), and the must-use-return-value rule. Adds a usage block to the README and module docstring. Co-authored-by: Himanshu Goel <4122621+himanshugoel2797@users.noreply.github.com>
…eable Fact-check of PR #10 surfaced a misleading test: the Fourier and nearest paths only place identically for vertically-symmetric patches. In general they differ — the Fourier and livestitch paths flip patches up-down while nearest does not, and all three use different center-rounding (so a footprint can shift ~1px). This is pre-existing holoptycho behavior, lifted verbatim; harmless there (distinct roles) but a real gotcha for a library. - Split the misleading parity test into an honest counts-footprint test plus an explicit flip-difference test (non-symmetric patch). - Document the non-interchangeability in README + AGENTS.md. Co-authored-by: Himanshu Goel <4122621+himanshugoel2797@users.noreply.github.com>
2088916 to
13684f4
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new ptychoml.stitch module to centralize ViT patch-placement / mosaic stitching utilities (previously duplicated in holoptycho), along with library exports, documentation, and a dedicated test suite.
Changes:
- Added
ptychoml/stitch.pyimplementing three stitching strategies (Fourier-shift, LiveStitch-style nearest + bbox, and plain nearest). - Added
tests/test_stitch.pycovering placement, accumulation/counts, edge clamping, bbox behavior, and method convention differences (flip/rounding). - Updated
README.md,AGENTS.md, andptychoml/__init__.pyto document and export the new stitching APIs.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
ptychoml/stitch.py |
New patch placement / mosaic stitching implementation and API docs. |
tests/test_stitch.py |
New unit tests for stitching behavior and conventions. |
README.md |
New public documentation section for ptychoml.stitch with usage guidance. |
ptychoml/__init__.py |
Re-exports new stitch functions at package top-level. |
AGENTS.md |
Codebase notes documenting the new module’s conventions and gotchas. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+62
to
+65
| * The nearest paths mutate ``canvas``/``counts`` in place, but the | ||
| Fourier path **reallocates** the canvas when a patch straddles the edge | ||
| (it pads then crops back). Always use the **returned** arrays rather | ||
| than relying on in-place mutation. |
Comment on lines
+117
to
+120
| ph, pw = patch_shape | ||
| sys_float = positions[:, 0] - (ph - 1.0) / 2.0 | ||
| sxs_float = positions[:, 1] - (pw - 1.0) / 2.0 | ||
|
|
Comment on lines
+161
to
+164
| ph, pw = patches.shape[-2:] | ||
| sys, sxs, ph_eff, pw_eff, pad_lengths, fractional = _placement_indices( | ||
| image.shape, positions, (ph, pw), pad, | ||
| ) |
Comment on lines
+337
to
+340
| ph, pw = patches.shape[-2:] | ||
| ch, cw = canvas.shape | ||
| for i in range(len(patches)): | ||
| ry = int(round(positions_px[i, 0])) |
Comment on lines
+160
to
+166
| Patch-placement helpers that accumulate a batch of reconstructed ViT | ||
| patches into a running `(canvas, counts)` mosaic. Both arrays accumulate | ||
| in place; the displayed/written mosaic is `canvas / np.maximum(counts, 1)` | ||
| (no normalization happens inside these functions — the caller picks the | ||
| min-overlap threshold). `positions_px` is `(N, 2)` in canvas pixel | ||
| coordinates `(y, x)` pointing at patch centers. | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
ptychoml/stitch.py— the generic ViT patch-placement / mosaicstitching algorithms, lifted from
holoptycho/mosaic_stitch.pyso theycan be shared from the library instead of duplicated in the streaming app.
Three placement strategies (all pure numpy +
scipy.fft):place_patches_fourier_shift/stitch_batch_intostitch_batch_livestitch_into(y0,y1,x0,x1)bbox for incremental displaystitch_batch_nearestcanvas/countsaccumulate in place; callers normalize ascanvas / np.maximum(counts, 1). Depends only onfourier_shiftfrompreprocess.py.The executable code is identical to holoptycho's (verified by diffing
each function with docstrings/comments stripped — all six match); only
docstrings/comments were expanded. So once holoptycho re-exports these
(follow-up PR), it's a zero-behavior-change refactor.
Why
First step of breaking NSLS2/holoptycho#37
into focused, reviewable PRs. Stitching is genuinely generic ptychography
code that didn't yet live in ptychoml.
The three strategies are not pixel-interchangeable: the Fourier and
livestitch paths flip each patch up-down before placement while
stitch_batch_nearestdoes not, and the three use differentcenter-rounding (a footprint can shift ~1px between them). Their
countsagree but placed values don't. Harmless in holoptycho (each method has a
distinct role — livestitch for live accumulation, nearest for JIT warm-up)
but callers must pick one strategy per mosaic. Documented in README +
AGENTS.md.
Tests
tests/test_stitch.py— 13 pure-numpy cases: single-patch placement,overlap accumulation + counts, counts footprint, livestitch bbox,
edge-clamping (no wrap), up-down flip convention, the flip difference
between methods, Fourier/nearest counts-footprint agreement, and per-batch
vs one-shot associativity.
Docs
ptychoml.stitch) section with a batch +streaming usage example, the non-interchangeability caveat, and updated
pipeline-map bullet.
ptychoml/stitch.pycodebase note — in-place accumulation,streaming safety, and the not-interchangeable gotcha.