Skip to content

perf(segmentation): defer projection-heavy renders and merge only modified slices during labelmap edits#2797

Merged
wayfarer3130 merged 1 commit into
mainfrom
cornerstoneSegModifiedRenderPerf
Jul 10, 2026
Merged

perf(segmentation): defer projection-heavy renders and merge only modified slices during labelmap edits#2797
wayfarer3130 merged 1 commit into
mainfrom
cornerstoneSegModifiedRenderPerf

Conversation

@sedghi

@sedghi sedghi commented Jul 10, 2026

Copy link
Copy Markdown
Member

Context

Editing a segmentation that is displayed in a projection-heavy viewport was effectively unusable. Every SEGMENTATION_DATA_MODIFIED event (brush strokes fire many per second) re-rendered every viewport showing the segmentation, and for viewports that re-march a slab per fragment (legacy volume viewports in LABELMAP_EDGE_PROJECTION_BLEND, or generic planar viewports with a slab presentation) each of those renders is expensive enough to freeze the UI for seconds. On top of that, the legacy independent-components path (addVolumesAsIndependentComponents, used by the labelmap-over-MIP example) re-merged the whole segmentation volume on the CPU with a per-voxel getAtIndex loop on every event, and had several lifecycle bugs around removing and re-adding the representation.

These changes are independent of the new generic-viewport labelmap-over-MIP capability (which depends on an unreleased vtk.js change) and can merge on their own; the generic feature is split out into #2784.

Changes & Results

Deferred rendering for projection-heavy viewports

  • New triggerSegmentationRenderForModified: on SEGMENTATION_DATA_MODIFIED, cheap viewports render live while projection-heavy viewports (legacy edge-projection blend, or generic viewports with slab presentations) are re-rendered on a 240 ms trailing debounce, once the edit stream goes quiet. Projection viewports do not need to update in real time while drawing.

Legacy independent-components path (addVolumesAsIndependentComponents)

  • The data-modified merge now touches only modifiedSlicesToUse with direct per-slice typed-array copies instead of a full-volume getAtIndex loop, keeping the CPU cost proportional to the edit instead of the volume.
  • The combined actor is re-added under the render plan's representationUID; the plan's reconcile step otherwise tore down the combined actor (which is also the viewport's base volume actor) on the next render.
  • Removing the representation now restores the shared vtkImageData scalars (the injected CPU array misconfigured the shared streaming mapper) and the label-outline property state (the plain MIP otherwise kept rendering through the label-outline shader).
  • The debounced data-modified listener is removed with removeEventListenerDebounced; the plain removeEventListener leaked the debounce wrapper, which kept merging into the detached image data after every delete/re-add cycle.

Measured on the labelmapMIP example (PET volume, Apple Silicon): a brush stroke costs ~1.3 ms of main thread per pointer move, and the projection viewport refreshes ~240 ms after the stroke ends.

Testing

  • pnpm run example labelmapMIP (stock vtk.js): click "Add Labelmap Representation", draw on the left viewport with the brush - drawing stays responsive and the MIP viewport updates shortly after the stroke ends. Delete and re-add the representation: after deleting, the MIP viewport should render the plain MIP without posterized/white artifacts, and repeated delete/re-add cycles should not slow editing down.

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • "OS: macOS (Darwin 25.5.0)"
  • "Node version: 22.22.2"
  • "Browser: Chrome for Testing (latest)"

Summary by CodeRabbit

  • Performance

    • Improved responsiveness during rapid segmentation edits, including brush-stroke updates.
    • Reduced unnecessary rendering for projection-heavy views by deferring expensive updates until editing activity slows.
  • Bug Fixes

    • Segmentation display updates now refresh only the affected image slices when possible.
    • Improved restoration of volume and label-outline display settings when segmentation overlays are removed.
    • Prevented stale update handling after segmentation representations are closed.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7421eb80-cfb0-4dd8-906a-aaac4d494d00

📥 Commits

Reviewing files that changed from the base of the PR and between 7d7dfb8 and 9f7f5a9.

📒 Files selected for processing (3)
  • packages/tools/src/eventListeners/segmentation/segmentationDataModifiedEventListener.ts
  • packages/tools/src/stateManagement/segmentation/SegmentationRenderingEngine.ts
  • packages/tools/src/tools/displayTools/Labelmap/addVolumesAsIndependentComponents.ts

📝 Walkthrough

Walkthrough

Segmentation modification rendering now defers projection-heavy viewport updates, while combined labelmap representations process modified slices incrementally and restore mapper and actor state during removal.

Changes

Segmentation rendering and labelmap lifecycle

Layer / File(s) Summary
Deferred segmentation rendering
packages/tools/src/stateManagement/segmentation/SegmentationRenderingEngine.ts, packages/tools/src/eventListeners/segmentation/segmentationDataModifiedEventListener.ts
Adds projection-heavy viewport detection, immediate rendering for other viewports, and per-viewport delayed render coalescing for segmentation modifications.
Incremental combined labelmap updates
packages/tools/src/tools/displayTools/Labelmap/addVolumesAsIndependentComponents.ts
Uses reconciled representation UIDs and updates only modified z-slices, preferring slice scalar data and falling back to voxel-manager reads.
Labelmap teardown restoration
packages/tools/src/tools/displayTools/Labelmap/addVolumesAsIndependentComponents.ts
Removes the debounced modification listener and restores original scalar, blend, interpolation, component, and label-outline state.

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

Sequence Diagram(s)

sequenceDiagram
  participant SegmentationEventListener
  participant SegmentationRenderingEngine
  participant ProjectionHeavyViewport
  participant CombinedLabelmap
  SegmentationEventListener->>SegmentationRenderingEngine: triggerSegmentationRenderForModified(segmentationId)
  SegmentationRenderingEngine->>ProjectionHeavyViewport: schedule coalesced delayed render
  SegmentationEventListener->>CombinedLabelmap: modifiedSlicesToUse
  CombinedLabelmap->>CombinedLabelmap: update touched slices
  CombinedLabelmap-->>ProjectionHeavyViewport: render updated representation
Loading

Suggested reviewers: wayfarer3130, jbocce

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific, accurate, and uses the expected semantic-release perf format.
Description check ✅ Passed The description includes the required Context, Changes & Results, Testing, and completed checklist sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cornerstoneSegModifiedRenderPerf

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.

❤️ Share

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

@sedghi

sedghi commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

known flaky test at ohif
1 failed
[chromium] › tests/Scoord3dProbe.spec.ts:39:5 › should hydrate SCOORD3D probe measurements correctly
5 skipped
168 passed (2.5m)

@wayfarer3130 wayfarer3130 merged commit 4f52f41 into main Jul 10, 2026
16 of 17 checks passed
@wayfarer3130 wayfarer3130 deleted the cornerstoneSegModifiedRenderPerf branch July 10, 2026 20:32
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.

2 participants