Skip to content

feat(py): add Trim, a selection combine that keeps the most central patch - #70

Merged
vboussot merged 5 commits into
mainfrom
feat/combine-selection-contract
Jul 29, 2026
Merged

feat(py): add Trim, a selection combine that keeps the most central patch#70
vboussot merged 5 commits into
mainfrom
feat/combine-selection-contract

Conversation

@vboussot

@vboussot vboussot commented Jul 28, 2026

Copy link
Copy Markdown
Member

Stacked on #69 — review that one first.

The problem

Without a combine, an overlapped voxel keeps whichever patch wrote last. That is a choice, not a
consequence, and an arbitrary one: the winning patch may hold that voxel on its own border, with no
context behind it. That is what a seam is.

Trim

Each patch keeps its central patch - overlap band. Those bands tile every axis exactly, so the
weights already sum to one and there is nothing to normalise; the first and last patch of an axis open
to the volume edge, so the tiling has no gap there either. Every kept voxel then carries at least half
the overlap of context on each side.

Volume 8, patch 4, overlap 2 (starts 0, 2, 4), each patch carrying its own index:

last-write-wins   0 0 1 1 2 2 2 2
Trim              0 0 0 1 1 2 2 2      kept bands [0,3) [3,5) [5,8)

The values are 0 or 1, so the patch is selected, not averaged — a label map or an argmax survives
reassembly, where any weighting invents values between its classes. Verified on int64, uint8 and
float32.

It is now the default

patch_combine left undeclared resolves to Trim instead of last-write-wins. This changes the
output of any configuration that declared no combine
; an explicit Mean/Cosinus/Gaussian is
untouched. Separate commit, so it can be dropped on its own.

Assembly takes the shorter path for it: a selection declares itself through PathCombine.selects, and
_blend writes the box a patch owns instead of scaling by a 0/1 mask and accumulating. On a
128×384×384 volume, patch [64,192,192], 3 channels, CUDA:

last-write-wins (plain assign) 2.4 ms
Trim 3.3 ms
Cosinus (a reduction) 9.8 ms

The kept box is read once from the host-side windows and cached per grid position. Deriving it from
the device share instead syncs the host on every patch — that first version measured 25.4 ms, more
than the weighting it replaced.

What it needed

The window is now asked for per grid position: a selection opens its border patches, a weighting
returns the same window everywhere. Per-axis overlaps are kept alongside their max for the same
reason. Both additive; every existing combine takes the default.

Tested

The partition is the property everything rests on — a gap leaves voxels unwritten and nothing
reports it. Seven geometries (1-D, no axis divisible by the stride, a different overlap per axis
including none, a patch larger than the volume, no overlap, a single patch, an odd overlap, a
truncated last patch) assert every voxel is written exactly once. An off-by-one in the kept band fails
five of them; disabling the last patch's tail opening fails six.

The truncated geometry is chosen deliberately: with shape 9, patch 4, overlap 2 the volume clamp
already cuts where the tail would have been opened, so that case passes even with the opening removed.
Shape 9/11, patch 6, overlap 4 exercises it.

Also fixes a crash found on the way: an overlap at least as wide as the patch left no central band, so
the window came out all zeros and the box derivation raised IndexError on an empty nonzero(). The
patch is kept whole there instead.

Plus three properties of the reassembly the suite did not pin: an anisotropic overlap reassembles
exactly, a weighted blend does not depend on the order patches arrive in, and the last-write-wins
semantics itself.

Not done

No quality measurement of the new default on a real model — what is established is that the tiling is
exact and that discrete values survive, not that the result is better.

Summary by CodeRabbit

  • New Features

    • Added a new patch combination strategy (Trim) that keeps a central interior region when reconstructing discrete/label-like outputs.
    • Prediction output now defaults to Trim automatically when no patch-combine strategy is specified.
  • Bug Fixes

    • Improved patch reassembly to support selection-based blending (writes only the “kept” region) and correct per-axis anisotropic overlap weighting.
    • Ensures deterministic reconstruction independent of patch arrival order.
    • Treats an explicitly empty patch_size as “no patch tiling” (skips patch combining).
  • Tests

    • Expanded unit tests to cover exact reconstruction, selection/tiling behavior, and empty-trim edge cases.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1db4a307-3938-4afe-9f42-1350d29fd5a1

📥 Commits

Reviewing files that changed from the base of the PR and between 3ff278e and e928a75.

📒 Files selected for processing (3)
  • konfai/data/patching.py
  • konfai/predictor.py
  • tests/unit/test_patching.py

📝 Walkthrough

Walkthrough

Patch reassembly now supports position-aware per-axis weighting, boundary-aware discrete Trim selection, default Trim predictor configuration, and tests covering overlap geometry, ordering, overwrite behavior, and exact label reconstruction.

Changes

Patch reassembly behavior

Layer / File(s) Summary
Window geometry and Trim strategy
konfai/data/patching.py
PathCombine stores per-axis overlaps and exposes selection behavior; Trim creates binary central windows that extend to volume boundaries.
Selection and share-based accumulation
konfai/data/patching.py
Accumulator writes Trim kept regions or blends weighted patches using position-aware per-axis shares.
Default Trim configuration
konfai/predictor.py
OutputDataset.prepare defaults to Trim, while an empty patch-size list clears patch combining.
Reassembly contract tests
tests/unit/test_patching.py
Tests cover anisotropic overlaps, insertion-order independence, last-write behavior, discrete Trim reconstruction, kept-box partitioning, and empty-band fallback.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OutputDataset
  participant Trim
  participant Accumulator
  participant ResultTensor
  OutputDataset->>Trim: configure default patch combiner
  Accumulator->>Trim: request position-aware window
  Trim-->>Accumulator: return weighted or kept-region window
  Accumulator->>ResultTensor: write kept region or weighted patch
Loading

Possibly related PRs

Poem

A rabbit trims each patch with care,
Shares the overlap everywhere.
Labels stay crisp, no values stray,
Kept boxes tile the whole array.
Hop-hop, reassembly’s bright today!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it doesn't follow the required template and omits sections like Related issues, Type of change, testing, checklist, and migration. Reformat the PR description to match the template and add the missing sections, especially Related issues, Type of change, How has this been tested?, and Breaking changes & migration.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Clear, conventional title that names the main change: adding Trim as a selection combine.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/combine-selection-contract

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/unit/test_patching.py (1)

484-497: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Exercise actual arrival order for last-write-wins.

The test currently inserts patches in spatial/index order, so it does not distinguish arrival-order semantics from a spatially ordered implementation. Add a reversed or non-spatial insertion order and assert that the final overlap reflects the last-added patch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_patching.py` around lines 484 - 497, Update
test_unweighted_overlap_is_last_write_wins to add patches in a reversed or
otherwise non-spatial order instead of enumerating slices sequentially, then
adjust the expected output so each overlap reflects the last-added patch’s
value. Keep the test focused on validating arrival-order last-write-wins
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/unit/test_patching.py`:
- Around line 484-497: Update test_unweighted_overlap_is_last_write_wins to add
patches in a reversed or otherwise non-spatial order instead of enumerating
slices sequentially, then adjust the expected output so each overlap reflects
the last-added patch’s value. Keep the test focused on validating arrival-order
last-write-wins behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f22eb935-a19d-4196-8359-145308b06881

📥 Commits

Reviewing files that changed from the base of the PR and between 04c3269 and 08a0588.

📒 Files selected for processing (1)
  • tests/unit/test_patching.py

@vboussot
vboussot force-pushed the feat/combine-selection-contract branch from 08a0588 to 6132401 Compare July 29, 2026 00:12
@vboussot vboussot changed the title test(py): cover the reassembly contract the accumulator relies on feat(py): add Trim, a selection combine that keeps the most central patch Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
konfai/data/patching.py (1)

525-550: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: ternary-as-statement in the per-axis share multiply is unconventional.

torch.mul(data, share, out=self._weighted) if dim == 0 else self._weighted.mul_(share)

Using a conditional expression purely for its side effect works but reads awkwardly. An explicit if/else would be clearer without changing behavior.

♻️ Optional readability tweak
         for dim, s in enumerate(patch_slice):
             view = [1] * data.ndim
             view[self._n + dim] = -1
             share = self._share(dim, s.start, data).view(view)
-            torch.mul(data, share, out=self._weighted) if dim == 0 else self._weighted.mul_(share)
+            if dim == 0:
+                torch.mul(data, share, out=self._weighted)
+            else:
+                self._weighted.mul_(share)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@konfai/data/patching.py` around lines 525 - 550, Replace the side-effect-only
conditional expression in _weighted_patch with an explicit if/else: initialize
self._weighted using torch.mul(data, share, out=self._weighted) for dim == 0,
and apply self._weighted.mul_(share) for subsequent dimensions, preserving the
existing behavior.
tests/unit/test_patching.py (1)

564-579: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider also covering a non-exact-tiling (truncated last patch) geometry for Trim.

This test and its sibling only use shapes/patch sizes/overlaps that tile exactly (every patch reaches full patch_size). Trim's binary 0/1 selection is the most fragile combine to a coverage gap (a weighted combine like Cosinus/Gaussian always has a strictly-positive total, so it can't silently produce a 0/0 division; Trim could, if the abutting-band invariant were ever violated by a future change). A regression test with a shape that doesn't divide evenly (e.g. shape=[9], patch=[4], overlap=2) would pin the truncated-last-patch case that this suite currently doesn't exercise for Trim.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_patching.py` around lines 564 - 579, Extend
test_trim_reassembles_exactly_and_keeps_values_discrete to cover a non-exact
tiling case, such as shape [9], patch [4], and overlap 2, where the final patch
is truncated. Verify Trim through Accumulator.assemble still reconstructs the
original labels exactly and preserves discrete values, while retaining the
existing exact-tiling coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@konfai/data/patching.py`:
- Around line 525-550: Replace the side-effect-only conditional expression in
_weighted_patch with an explicit if/else: initialize self._weighted using
torch.mul(data, share, out=self._weighted) for dim == 0, and apply
self._weighted.mul_(share) for subsequent dimensions, preserving the existing
behavior.

In `@tests/unit/test_patching.py`:
- Around line 564-579: Extend
test_trim_reassembles_exactly_and_keeps_values_discrete to cover a non-exact
tiling case, such as shape [9], patch [4], and overlap 2, where the final patch
is truncated. Verify Trim through Accumulator.assemble still reconstructs the
original labels exactly and preserves discrete values, while retaining the
existing exact-tiling coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec08bae7-23e4-4fe0-8b75-ea53cd58ea5c

📥 Commits

Reviewing files that changed from the base of the PR and between 08a0588 and 6132401.

📒 Files selected for processing (2)
  • konfai/data/patching.py
  • tests/unit/test_patching.py

vboussot added 5 commits July 29, 2026 17:21
…atch

Without a combine an overlapped voxel keeps whichever patch wrote last. That is a
choice, not a consequence, and an arbitrary one: the winning patch may hold that
voxel on its own border, with no context behind it, which is what shows up as a
seam.

Trim keeps each patch's central `patch - overlap` band instead, so the kept bands
tile every axis exactly and the weights already sum to one -- there is nothing to
normalise. The first and last patch of an axis open to the volume edge, so the
tiling has no gap there either. Every kept voxel then carries at least half the
overlap of context on each side.

The values are 0 or 1, so the patch is selected rather than averaged: a label map
or an argmax survives reassembly, where any weighting invents values between its
classes.

The window is now asked for per grid position, because a selection has to open
its border patches while a weighting returns the same window everywhere. Per-axis
overlaps are kept alongside their max for the same reason.

Also covers three properties of the reassembly the suite did not pin: that an
anisotropic overlap reassembles exactly, that a weighted blend does not depend on
the order patches arrive in, and the last-write-wins semantics itself -- so any
future change to it is visible in the diff.
A patch grid overlaps whether or not a combine is declared, and an undeclared one
left the overlap to whichever patch wrote last -- an arbitrary winner, possibly
holding that voxel on its own border with no context behind it, which is what a
seam is. The default is now Trim: each patch keeps its central band, the bands
tile the volume exactly, and nothing is averaged, so a label map still survives
reassembly.

Assembly takes the shorter path for it. A selection declares itself through
PathCombine.selects, and _blend then writes the box a patch owns instead of
scaling by a 0/1 mask and accumulating -- the same values for less work. Measured
on a 128x384x384 volume, patch [64,192,192], 3 channels, CUDA:

  last-write-wins (plain assign)   2.4 ms
  Trim                             3.3 ms
  Cosinus (a reduction)            9.8 ms

The kept box is read once from the host-side windows and cached per grid
position: deriving it from the device share instead syncs the host on every
patch, which cost 25.4 ms -- more than the weighting it replaces.

This changes the output of any configuration that declared no combine. An
explicit Mean/Cosinus/Gaussian is untouched.
An overlap at least as wide as the patch leaves nothing between the two trimmed
sides, so the window came out all zeros and the box derivation raised IndexError
on an empty nonzero(). The patch is kept whole instead: that axis stops being a
partition and falls back to last-write-wins on it, which is what it was before.

Reachable from any direct caller of set_patch_config -- the slicing plan refuses
overlap >= patch_size, but nothing guards the blend window built beside it.

Proves the property the selection path rests on, over seven geometries (1-D,
no axis divisible by the stride, a different overlap per axis including none, a
patch larger than the volume, no overlap, a single patch, an odd overlap): the
kept boxes cover every voxel exactly once. An off-by-one in the kept band fails
five of the seven, so the test discriminates. Free axes now run Trim too.
…y out

Review follow-up.

The reassembly and partition tests only used geometries that tile exactly, and a
0/1 selection is what a coverage gap hurts most: a weighting always has a
strictly positive total, so a gap only darkens a voxel, where a selection leaves
it unwritten and nothing reports it.

Both now also run a truncated last patch. The geometry matters more than it
looks: with shape 9, patch 4, overlap 2 the volume clamp already cuts where the
last patch's tail would have been opened, so that case never exercises the tail
and passes with the opening removed. Shape 9/11, patch 6, overlap 4 does exercise
it -- disabling the opening fails both tests, so they discriminate.

Also replaces the side-effect-only conditional expression in the per-axis share
multiply with an explicit if/else. Same behaviour.
Defaulting to Trim made this reachable: the caller drops the axes of extent 1, so
a case with nothing tiled arrives as an EMPTY patch size, not a missing one, and
`[] is not None` let it through. A blend window built on no axis then left max()
nothing to take and the prediction died on `max() iterable argument is empty`.

Before the default, patch_combine was None on those cases and set_patch_config
was never reached -- the guard had simply never been asked the question.

An empty patch size is a single patch covering the volume, so no combine applies:
it now takes the same branch as a missing one. Caught by
test_auto_patch_restart_is_voxel_identical_to_whole_volume, which the unit suite
does not cover.
@vboussot
vboussot force-pushed the feat/combine-selection-contract branch from 3ff278e to e928a75 Compare July 29, 2026 15:21
@vboussot
vboussot merged commit 6ae25d6 into main Jul 29, 2026
27 of 34 checks passed
@vboussot
vboussot deleted the feat/combine-selection-contract branch July 29, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant