fix(segmentation): undoable overlap labelmap strokes, faster eager stroke resolution, brush hover guard#2785
Conversation
A stroke that forces a segment move to a private labelmap layer changed three things outside its history memo: the bulk voxel move, the layer registration, and the segment rebinding. Cross-layer overwrite erases were also unrecorded. Undo then restored only the stroke's own voxels, leaving the moved voxels stranded on a layer that should not exist at that point in history. Labelmap memos now carry ordered restore steps (priorSteps/postSteps): moveSegmentToPrivateLabelmap emits a step that reverses/replays the whole move, cross-layer erases are recorded per layer through crossLayerEraseCallback, and a mid-stroke voxel-manager swap rides the committed earlier memo along. Undo walks steps in reverse-chronological order, redo chronologically, so each stroke restores as one unit.
Every eager (non-lazy) brush or eraser drag event re-ran isReferenceViewable over ALL labelmap imageIds via updateLabelmapSegmentationImageReferences, each call doing full metadata and slice-basis resolution. On a two-layer, 120-image labelmap this cost about 51ms per drag event (about 19fps while erasing). The resolver now remembers completed scans per segmentation, keyed by the viewport's current image plus a fingerprint of the labelmap imageId set, and replays the stored mapping instead of rescanning. Layer additions or removals change the fingerprint and force a fresh scan; removeSegmentation and reset clear the memo. Same scenario now costs about 2ms per event.
…eated preMouseDownCallback dereferenced this._hoverData.viewport without guarding the case where createHoverData returns undefined (no active segment index yet, or the viewport camera is not resolvable), crashing with 'Cannot read properties of undefined (reading viewport)'. Every other createHoverData call site already guards this; do the same here and undo the draw activation before bailing.
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds coordinated undo/redo handling for labelmap moves and cross-layer erases, extends memo replay ordering, caches labelmap reference scans, and adds an early BrushTool guard. ChangesUndo/Redo Step Recording for Segment Moves and Cross-Layer Erase
Reference Resolver Memoization and Brush Tool Guard
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant BrushStrategy
participant LabelmapEditTransaction
participant LabelmapMemo
participant PrivateLabelmap
participant SegmentationEvent
BrushStrategy->>LabelmapEditTransaction: begin labelmap edit transaction
LabelmapEditTransaction->>PrivateLabelmap: move segment and capture move step
BrushStrategy->>LabelmapMemo: record move and erase steps
LabelmapMemo->>PrivateLabelmap: restore voxel and binding state
LabelmapMemo->>SegmentationEvent: trigger segmentation data modified
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In
`@packages/tools/src/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.ts`:
- Around line 160-199: The memoized path in labelmapImageReferenceResolver’s
current-image lookup is reusing stackLabelmapImageIdReferenceMap entries that
are shared across viewports, so a scanKey can pass while still returning a value
computed for another viewport. Update the caching in the same resolver flow that
uses generateScanKey, hasCompletedScan, and markCompletedScan so the resolved
labelmap image id is stored per scanKey (or otherwise scoped by viewport.id)
instead of only by segmentationId/currentImageId, and read back that same keyed
value on cache hits.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4436fc02-1c91-4d1f-ac16-d6d7396187d5
📒 Files selected for processing (9)
packages/tools/src/stateManagement/segmentation/labelmapModel/labelmapEditTransaction.tspackages/tools/src/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.tspackages/tools/src/stateManagement/segmentation/labelmapModel/privateLabelmap.spec.tspackages/tools/src/stateManagement/segmentation/labelmapModel/privateLabelmap.tspackages/tools/src/tools/segmentation/BrushTool.tspackages/tools/src/tools/segmentation/strategies/BrushStrategy.tspackages/tools/src/tools/segmentation/strategies/utils/crossLayerErase.tspackages/tools/src/utilities/segmentation/createLabelmapMemo.spec.tspackages/tools/src/utilities/segmentation/createLabelmapMemo.ts
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/tools/src/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.spec.ts (1)
218-251: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTest doesn't assert
viewport2.isReferenceViewablecall count.The test validates
viewport1.isReferenceViewableinvocation count but notviewport2's, leaving the vp-2 scan path (and its interaction with the vp-1 cache entry) partially unverified.♻️ Suggested addition
expect( resolver.updateLabelmapSegmentationImageReferences('vp-1', 'seg-1') ).toBe('lm-a-0'); expect(viewport1.isReferenceViewable).toHaveBeenCalledTimes(2); + expect(viewport2.isReferenceViewable).toHaveBeenCalledTimes(1); });🤖 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 `@packages/tools/src/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.spec.ts` around lines 218 - 251, The cache replay test for updateLabelmapSegmentationImageReferences only verifies viewport1’s scan path; add an assertion for viewport2.isReferenceViewable call count so the vp-2 lookup and its interaction with the vp-1 cache entry are fully covered. Use the existing viewport1 and viewport2 mock setup in labelmapImageReferenceResolver.spec.ts and assert viewport2’s invocation count alongside the current expectations.
🤖 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
`@packages/tools/src/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.spec.ts`:
- Around line 218-251: The cache replay test for
updateLabelmapSegmentationImageReferences only verifies viewport1’s scan path;
add an assertion for viewport2.isReferenceViewable call count so the vp-2 lookup
and its interaction with the vp-1 cache entry are fully covered. Use the
existing viewport1 and viewport2 mock setup in
labelmapImageReferenceResolver.spec.ts and assert viewport2’s invocation count
alongside the current expectations.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 29387e0e-5219-4cd4-94e3-51ef8520581d
📒 Files selected for processing (2)
packages/tools/src/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.spec.tspackages/tools/src/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/tools/src/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.ts
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
What
Three fixes for overlapping-labelmap editing (
segmentation.overwriteMode), found while wiring overlap authoring into a viewer.1. Undo/redo loses layer bookkeeping for overlap strokes
A stroke that forces a segment move to a private labelmap layer changes three things outside its history memo: the bulk voxel move, the layer registration, and the segment rebinding. Cross-layer overwrite erases were also unrecorded. Undo restored only the stroke's own voxels — the moved voxels stayed stranded on a layer that should not exist at that point in history, and further undos became no-ops.
Labelmap memos now carry ordered restore steps (
priorSteps/postSteps, typeLabelmapRestoreStep):moveSegmentToPrivateLabelmapemits amoveStep(viamoveStepCallback) that reverses/replays the whole move — voxels back to the source layer, binding restored, private layer unregistered on undo; fully replayed on redo.crossLayerEraseCallbackonEraseLabelmapEditTransactionOptions.memoAsStep).2. Eager strokes (eraser) were ~20x slower than they should be
Every eager (non-lazy) drag event re-ran
isReferenceViewableover ALL labelmap imageIds viaupdateLabelmapSegmentationImageReferences, each call doing full metadata + slice-basis resolution. On a two-layer, 120-image labelmap: ~51ms per drag event. The resolver now memoizes completed scans per segmentation, keyed by the viewport's current image plus a fingerprint of the labelmap imageId set (layer changes invalidate naturally). Same scenario: ~2ms per event.3. BrushTool crash on mousedown
preMouseDownCallbackdereferencedthis._hoverData.viewportwithout guardingcreateHoverDatareturning undefined (every other call site guards it). Now bails out cleanly, undoing the draw activation.Testing
createLabelmapMemo.spec.ts(6 tests): plain stroke round-trips, no-op commit rejection, steps-only commit, undo/redo step ordering,memoAsStep.privateLabelmap.spec.ts(6 tests): segment move emits a reversible step (voxels, binding, layer registration), default-mover integration viabeginLabelmapEditTransaction, cross-layer erase recording, and a full two-stroke timeline (move + private write + cross-layer erase) that round-trips undo x2 / redo x2 to exact states.tsc --noEmitclean.Summary by CodeRabbit