Skip to content

feat: Add display set split store logic to metadata#2738

Merged
sedghi merged 28 commits into
mainfrom
fix/dwi-wrong-wl-split
Jun 30, 2026
Merged

feat: Add display set split store logic to metadata#2738
sedghi merged 28 commits into
mainfrom
fix/dwi-wrong-wl-split

Conversation

@wayfarer3130

@wayfarer3130 wayfarer3130 commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Context

This extracts OHIF-style display set splitting into a new, framework-agnostic
@cornerstonejs/metadata/displayset module and wires the legacy viewports and
demo helpers to drive content from display sets.

The motivating bug is diffusion MR (DWI): a series that mixes 4D b-value
frames with trailing frames that have no b-value. The undefined-b-value frames
are not part of the 4D data set, so rendering them together applies the wrong
window/level. The new mixedDimensionalityBValue split rule separates them into
their own display set.

This is purely additive — it does not change any existing API. The new
setDisplaySets/getDisplaySets/clearDisplaySets on the legacy Viewport
hierarchy do not collide with the GenericViewport/ViewportController
members (separate hierarchy), and existing setStack/setVolumes/setVideo/
setWSI/setEcg callers are unaffected.

Changes & Results

New metadata module — packages/metadata/src/displayset/

  • Split pipeline: splitSeriesInstanceGroupsFromImageIdscreateDisplaySetFromGroup,
    with defaultDisplaySetSplitRules, buildSeriesInfo, and groupInstancesBySplitRules.
  • Data model: IDisplaySet (plain data-shaped object, OHIF parity) with
    BaseDisplaySet/ImageStackDisplaySet, plus underlying-vs-frame imageId semantics.
  • Instance classifiers: isImageSopClass, isVideoInstance, isEcgInstance, isWsiInstance.
  • A DISPLAY_SET metadata module + provider (registerDisplaySetProviders /
    registerDisplaySetMetadata) so a display set can be resolved from any image id.
  • Module-augmentation extension point for app-/extension-specific attributes.

Viewports — packages/core/src/RenderingEngine/

  • Base Viewport gains setDisplaySets/getDisplaySets/clearDisplaySets and a
    shared mountDisplaySets() template that owns the clear-then-record ordering.
  • Stack, Volume, Video, WSI, and ECG viewports override setDisplaySets, each
    delegating to mountDisplaySets() (resolve → native setter → record).

Demos — helpers and examples are rewired to drive viewports from display
sets, including the new packages/core/examples/displaySets walkthrough.

Review round (applied in this PR) — addressed code-review feedback:

  • isVideoInstance now reuses the shared videoUIDs list (fixes MPEG2 misclassification).
  • buildSeriesInfo and every rule's makeSeriesInfo are guarded against empty instance lists.
  • numImageFrames is coerced with Number(...) (naturalized DICOM yields strings).
  • The typed DISPLAY_SET return type is the full IDisplaySet (no shape drift).
  • customAttributes cannot clobber the reserved data fields (imageIds,
    underlyingImageIds, instances, displaySetInstanceUID).
  • Extracted the copy-pasted ordering contract into mountDisplaySets().
  • Re-exported isWsiInstance; added JSDoc to the classifiers and viewport-type helpers.
  • Demo toBaseImageId normalizes ?frame=N as well as &frame=N.
  • Expanded the metadata docs with the end-to-end workflow.

Testing

Unit:

npx jest packages/metadata/src/displayset/displayset.test.ts

Covers the DWI mixed-b-value split, video/ECG/WSI/volume routing, customAttributes
spreading, empty-instance-list safety, MPEG2 video classification, string-NumberOfFrames
coercion, and reserved-key protection.

Manual / visual: run the displaySets example (one viewport per display set
across stack, volume, video, ECG, and whole-slide). Loading the DWI series shows
the undefined-b-value frames split into their own display set rather than
distorting the 4D volume's window/level.

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)"
  • "Node version: 22.x"
  • "Browser:

Summary by CodeRabbit

  • New Features

    • Added display-set support across core viewports, including mounting, querying, and switching content by display set.
    • Introduced display-set splitting and helpers for stack, volume, video, ECG, and WSI content.
    • Updated examples to use display sets for loading, playback, and viewport setup.
  • Documentation

    • Added new docs explaining display sets, split rules, and viewport usage.
  • Tests

    • Added coverage for display-set grouping, metadata handling, and viewport behaviors.

@claude claude 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.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jun 8, 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
📝 Walkthrough

Walkthrough

Adds a framework-agnostic display-set subsystem to @cornerstonejs/metadata: IDisplaySet interface, BaseDisplaySet/ImageStackDisplaySet classes, a split/group pipeline with default rules, and metadata provider wiring. All five core viewport types (Stack, BaseVolume, Video, ECG, WSI) gain setDisplaySets APIs. The GenericViewport layer renames DataIdDisplaySetId and replaces genericViewportDataSetMetadataProvider with a new genericViewportDisplaySetMetadataProvider. Demo helpers and all video/generic-viewport examples are updated to use the new flow.

Changes

Display set metadata, viewport APIs, and example migration

Layer / File(s) Summary
IDisplaySet interface, base classes, and instance classifiers
packages/metadata/src/displayset/IDisplaySet.ts, packages/metadata/src/displayset/types.ts, packages/metadata/src/displayset/BaseDisplaySet.ts, packages/metadata/src/displayset/ImageStackDisplaySet.ts, packages/metadata/src/displayset/viewportTypes.ts, packages/metadata/src/displayset/is*.ts
Defines IDisplaySet, split-domain types (NaturalizedInstance, SplitRule, InstanceGroup, etc.), BaseDisplaySet and ImageStackDisplaySet with factory methods, viewport-hint utilities, and SOP-class-based instance classifiers for image, video, ECG, and WSI.
Split/group pipeline, default rules, display-set factory, and tests
packages/metadata/src/displayset/resolveInstances.ts, packages/metadata/src/displayset/buildSeriesInfo.ts, packages/metadata/src/displayset/groupInstancesBySplitRules.ts, packages/metadata/src/displayset/splitImageIdsBySplitRules.ts, packages/metadata/src/displayset/defaultDisplaySetSplitRules.ts, packages/metadata/src/displayset/createDisplaySetFromGroup.ts, packages/metadata/src/displayset/displayset.test.ts, packages/metadata/jest.config.js
Implements ordered instance resolution, series-info aggregation, deterministic bucket grouping, default split rules (video/ECG/WSI/single-image/multi-frame/DWI/volume3d), and createDisplaySetFromGroup with reserved-key guarding for customAttributes; full Jest test suite covers all behaviors.
Metadata module wiring and public exports
packages/metadata/src/enums/MetadataModules.ts, packages/metadata/src/types/MetadataModuleTypes.ts, packages/metadata/src/displayset/registerDisplaySetMetadata.ts, packages/metadata/src/displayset/displaySetProvider.ts, packages/metadata/src/registerDefaultProviders.ts, packages/metadata/src/displayset/index.ts, packages/metadata/src/index.ts, packages/metadata/src/utilities/index.ts
Adds DISPLAY_SET enum member, extends MetadataModuleType with displaySetModule: IDisplaySet, implements registerDisplaySetMetadata and registerDisplaySetProviders, wires into registerDefaultProviders, and exposes all display-set APIs through barrel exports.
GenericViewport DataId→DisplaySetId rename and provider swap
packages/core/src/RenderingEngine/GenericViewport/ViewportArchitectureTypes.ts, packages/core/src/RenderingEngine/GenericViewport/genericViewportDisplaySetAccess.ts, packages/core/src/utilities/genericViewportDisplaySetMetadataProvider.ts, packages/core/src/utilities/index.ts, packages/core/src/RenderingEngine/GenericViewport/GenericViewport.ts, packages/core/src/RenderingEngine/GenericViewport/**/*.ts
Replaces DataId with DisplaySetId throughout ViewportArchitectureTypes and all GenericViewport APIs; renames all DataSet accessors and type-guards to DisplaySet equivalents in genericViewportDisplaySetAccess; replaces the removed genericViewportDataSetMetadataProvider with a new genericViewportDisplaySetMetadataProvider across all Planar, Volume3D, WSI, Video, and ECG sub-components.
Viewport setDisplaySets across all viewport types
packages/core/src/RenderingEngine/Viewport.ts, packages/core/src/RenderingEngine/StackViewport.ts, packages/core/src/RenderingEngine/BaseVolumeViewport.ts, packages/core/src/RenderingEngine/VideoViewport.ts, packages/core/src/RenderingEngine/ECGViewport.ts, packages/core/src/RenderingEngine/WSIViewport.ts
Adds protected _displaySets bookkeeping, mountDisplaySets, getDisplaySets, and clearDisplaySets to the Viewport base class; each concrete viewport type adds setDisplaySets that resolves the displaySetId to underlying source data and delegates recording to mountDisplaySets.
Demo helper library and 4D example migrations
utils/demo/helpers/splitDisplaySetsFromImageIds.ts, utils/demo/helpers/index.js, packages/core/examples/dynamicVolume/index.ts, packages/tools/examples/dynamicCINETool/index.ts, packages/tools/examples/generateImageFromTimeData/index.ts, packages/tools/examples/localAdvanced/index.ts
Adds splitDisplaySetsFromImageIds, createDisplaySets, getVolumeFrameImageIds, get4DVolumeImageIds, and related frame-selection helpers; re-exports from helpers index; updates 4D examples to use shared helpers instead of inline metadata filtering.
New display-sets showcase example
packages/core/examples/displaySets/index.ts
New example that loads series, splits into display sets, builds one viewport-row per display set with a hint/type dropdown, registers per-hint display-set metadata, mounts via setDisplaySets, and renders per-row details including getDisplaySets() output.
Video and generic-viewport example migrations, provider-swap tests
packages/core/examples/video/index.ts, packages/tools/examples/video*/index.ts, packages/core/examples/generic*/index.ts, packages/tools/examples/generic*/index.ts, packages/tools/src/tools/displayTools/Labelmap/..., packages/core/test/planar*.jest.js, tests/vitest-browser/genericStackApi.browser.test.ts
Updates all video examples to use createDisplaySets/getViewportTypeForDisplaySet/setDisplaySets; updates generic-viewport examples and labelmap tools to use genericViewportDisplaySetMetadataProvider; updates Jest and browser tests to match new provider.
Display-set documentation
packages/docs/docs/concepts/cornerstone-metadata/display-sets.md, packages/docs/docs/concepts/cornerstone-metadata/index.md, packages/docs/sidebars.js, packages/docs/docs/concepts/cornerstone-core/generic-viewport/..., packages/docs/docs/migration-guides/5x/2-generic-viewport.md
Adds display-sets.md covering the full split-rule system, viewport mounting, metadata caching, and module augmentation; updates generic-viewport API/migration docs and the 5x migration guide to use genericViewportDisplaySetMetadataProvider.

CI / Example Runner

Layer / File(s) Summary
Windows-aware rspack spawn and error handling
utils/ExampleRunner/example-runner-cli.js
Adds isWin flag to enable shell mode on Windows, handles spawnSync errors, and exits with the child process status on non-zero exit.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant splitImageIdsBySplitRules
  participant createDisplaySetFromGroup
  participant registerDisplaySetMetadata
  participant Viewport
  participant genericViewportDisplaySetMetadataProvider
  App->>splitImageIdsBySplitRules: imageIds + SplitContext
  splitImageIdsBySplitRules->>splitImageIdsBySplitRules: resolveInstances → groupInstancesBySplitRules
  splitImageIdsBySplitRules-->>App: InstanceGroup[]
  App->>createDisplaySetFromGroup: group + options
  createDisplaySetFromGroup-->>App: IDisplaySet
  App->>registerDisplaySetMetadata: imageIds + displaySet
  registerDisplaySetMetadata->>genericViewportDisplaySetMetadataProvider: addTyped per imageId
  App->>genericViewportDisplaySetMetadataProvider: add(displaySetId, {imageIds, kind})
  App->>Viewport: setDisplaySets({displaySetId})
  Viewport->>genericViewportDisplaySetMetadataProvider: getGenericViewportXxxDisplaySet(displaySetId)
  genericViewportDisplaySetMetadataProvider-->>Viewport: imageIds / webClient / sourceDataId
  Viewport->>Viewport: clearDisplaySets → raw setter → mountDisplaySets
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • sedghi

Poem

🐇 A rabbit sorts slices with elegant care,
Display sets now split through the metadata air!
From ECG waves to whole-slide glass,
Each series gets sorted to viewport class.
No more hard-coded DataId string—
DisplaySetId hops on a brand new spring! 🌸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed All required sections are present and the checklist is mostly complete, though the browser field is still unfilled.
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.
Title check ✅ Passed The title is concise and accurately reflects the main change: adding display set split and storage logic to metadata.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dwi-wrong-wl-split

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.

@wayfarer3130 wayfarer3130 changed the base branch from main to beta June 8, 2026 17:41
@wayfarer3130 wayfarer3130 force-pushed the fix/dwi-wrong-wl-split branch from 0c48d0c to 5cfd79a Compare June 8, 2026 19:20
@wayfarer3130 wayfarer3130 changed the base branch from beta to main June 8, 2026 19:21
@wayfarer3130 wayfarer3130 changed the title [WIP] fix: Add display set split/store logic to metadata fix: Add display set split/store logic to metadata Jun 8, 2026
Comment thread packages/core/examples/displaySets/index.ts Fixed

@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.

Actionable comments posted: 12

🧹 Nitpick comments (5)
packages/core/examples/displaySets/index.ts (1)

225-231: ⚡ Quick win

Validate viewport type before casting.

Lines 227 and 230 cast viewport to specific types without runtime validation. If enableElement silently failed or returned an unexpected viewport type, the casts would succeed but method calls could throw at runtime.

♻️ Suggested defensive check
     if (row.hint === 'volume3d') {
       // 3D needs a preset to be visible; setDisplaySets already loaded the volume.
-      (viewport as Types.IVolumeViewport).setProperties({ preset: 'CT-Bone' });
+      if ('setProperties' in viewport) {
+        (viewport as Types.IVolumeViewport).setProperties({ preset: 'CT-Bone' });
+      }
     }
     if (row.hint === 'video') {
-      (viewport as Types.IVideoViewport).play();
+      if ('play' in viewport) {
+        (viewport as Types.IVideoViewport).play();
+      }
     }
🤖 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/core/examples/displaySets/index.ts` around lines 225 - 231, The code
casts viewport to Types.IVolumeViewport and Types.IVideoViewport and calls
setProperties/play without runtime checks, which can throw if enableElement
returned a different viewport; update the branches handling row.hint ===
'volume3d' and 'video' to first validate the viewport type (e.g., check for
presence of expected methods/properties such as setProperties for volume and
play for video or use a type guard) before casting and calling setProperties or
play on the viewport instance to safely bail or log an error if the viewport is
not the expected type.
packages/metadata/src/displayset/ImageStackDisplaySet.ts (1)

13-37: 💤 Low value

Consider refactoring to eliminate code duplication.

The loops in collectUnderlyingImageIds (lines 15-19) and collectImageIds (lines 28-32) perform identical operations: iterating instances and collecting instance.imageId values. collectImageIds could delegate to collectUnderlyingImageIds and only handle the fallback logic.

♻️ Proposed refactor
 function collectImageIds(
   instances: NaturalizedInstance[],
   underlyingImageIds: string[]
 ): string[] {
-  const imageIds: string[] = [];
-  for (const instance of instances) {
-    if (instance.imageId) {
-      imageIds.push(instance.imageId);
-    }
-  }
+  const imageIds = collectUnderlyingImageIds(instances);
   if (imageIds.length === 0) {
     return [...underlyingImageIds];
   }
   return imageIds;
 }
🤖 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/metadata/src/displayset/ImageStackDisplaySet.ts` around lines 13 -
37, Duplicate loops collectUnderlyingImageIds and collectImageIds both iterate
instances to gather instance.imageId; refactor by making collectImageIds call
collectUnderlyingImageIds(instances) to get imageIds, then if that result is
empty return [...underlyingImageIds] else return the collected ids;
update/remove the duplicated loop in collectImageIds and keep
collectUnderlyingImageIds as the single source of truth (refer to functions
collectUnderlyingImageIds and collectImageIds).
packages/metadata/src/displayset/displaySetProvider.ts (1)

5-11: ⚡ Quick win

Add type annotations to provider function parameters.

The displaySetAddProvider function has untyped parameters (next, _data, options). Adding type annotations would improve type safety and IDE autocomplete support.

🔧 Suggested typing improvement
-function displaySetAddProvider(next, query: string, _data, options) {
+function displaySetAddProvider(
+  next: (...args: unknown[]) => unknown,
+  query: string,
+  _data: unknown,
+  options?: { displaySet?: unknown }
+) {
   const displaySet = options?.displaySet;
   if (displaySet) {
     return displaySet;
   }
   return next(query, _data, options);
 }
🤖 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/metadata/src/displayset/displaySetProvider.ts` around lines 5 - 11,
The provider function displaySetAddProvider should have explicit TypeScript
types added for its parameters: annotate next as a function type (e.g. (query:
string, data?: unknown, options?: DisplayOptions) => DisplaySetType) so callers
and returns are typed, type _data as unknown or a more specific payload type
used by callers, and type options as an interface with an optional displaySet?:
DisplaySetType (or use the existing DisplaySet/DisplayOptions types in the
codebase). Update the function signature of displaySetAddProvider to use these
types and import or declare DisplaySetType/DisplayOptions as needed so tooling
and autocomplete pick up the shapes of next, _data, and options.
packages/metadata/package.json (1)

1-1: Pipeline failures are unrelated CI configuration issues.

The reported pipeline failures concern a pnpm version conflict between packageManager (pnpm@11.5.2) and GitHub Action configuration (11.4.0). This is a CI/workflow configuration issue outside the scope of the code changes in this file. The packageManager field is not modified in this diff and likely resides in the repository root package.json or workflow files.

🤖 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/metadata/package.json` at line 1, CI failures are caused by a pnpm
version mismatch between the repository/packageManager field and the GitHub
Actions workflow; do not change packages/metadata/package.json in this PR—either
update the workflow action that installs pnpm to use pnpm@11.5.2 (matching the
packageManager field) or update the root package.json packageManager field to
the version used by the workflow so they align; look for the packageManager
field in the root package.json and the pnpm install step in your GitHub Actions
workflow to make the change.
packages/metadata/src/displayset/buildSeriesInfo.ts (1)

16-19: ⚡ Quick win

Validate NumberOfFrames before numeric conversion.

If NumberOfFrames contains a malformed string, Number() returns NaN, silently corrupting the numberOfFrames total. Consider using parseInt() with validation or explicitly checking for valid numeric values.

🛡️ Proposed validation
     if (instance.NumberOfFrames) {
-      numberOfFrames += Number(instance.NumberOfFrames);
+      const frames = parseInt(String(instance.NumberOfFrames), 10);
+      if (!isNaN(frames)) {
+        numberOfFrames += frames;
+      }
     } else if (instance.Rows) {
🤖 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/metadata/src/displayset/buildSeriesInfo.ts` around lines 16 - 19,
The code adds instance.NumberOfFrames to numberOfFrames without validating it,
so malformed strings yield NaN; change the branch in buildSeriesInfo (the logic
that reads instance.NumberOfFrames) to validate and parse the value first (e.g.,
use parseInt or Number and then test with Number.isFinite / Number.isInteger or
isNaN) and only add the parsed numeric value to numberOfFrames when valid,
otherwise treat it as zero or log/skip the invalid value; reference the
variables instance.NumberOfFrames and numberOfFrames and the function/build
block in buildSeriesInfo.ts when making this change.
🤖 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 `@package.json`:
- Line 183: package.json declares "packageManager": "pnpm@11.5.2" but CI
workflows pin pnpm to 11.4.0 causing ERR_PNPM_BAD_PM_VERSION; update the pnpm
version pin in the referenced GitHub Actions workflows
(.github/workflows/validate-codemod-registry.yml, test.yml, playwright.yml,
format-check.yml, docusaurus-build.yml, build-docs.yml) and the PNPM_VERSION env
in ohif-downstream.yml to 11.5.2 (or remove explicit pnpm version pins so the
runner respects packageManager), or alternatively change the packageManager
value back to "pnpm@11.4.0" to keep CI consistent. Ensure you update all
occurrences of pnpm@11.4.0 and PNPM_VERSION to 11.5.2 (or remove pins) so pnpm
install --frozen-lockfile no longer errors.

In `@packages/core/examples/displaySets/index.ts`:
- Around line 180-193: The loop that builds the details rows uses line.innerHTML
= `<strong>${label}:</strong> ${value}` which injects untrusted DICOM metadata
into HTML; change this to create elements and set textContent to avoid HTML
parsing: for each (label, value) create a container div, create a <strong>
element and set its textContent to `${label}:`, append it to the container, then
append a text node or set the container.textContent (or append a text node for
value) using the string `value` (not innerHTML); also replace
statusLine.innerText with statusLine.textContent for consistency. Update the
code paths that reference row.detailsElement, the loop over lines, and the
statusLine creation to use textContent-safe methods.

In `@packages/core/src/RenderingEngine/BaseVolumeViewport.ts`:
- Around line 1511-1513: The code casts dataSet.volumeId to string without
checking type before calling resolveViewportVolumeId, which can hide non-string
values; update the logic in BaseVolumeViewport (around the
resolveViewportVolumeId call) to validate dataSet.volumeId is a string (e.g.,
typeof dataSet.volumeId === 'string' && dataSet.volumeId.length > 0) before
using it, otherwise fall back to entry.displaySetId; ensure the computed
volumeId passed into resolveViewportVolumeId is guaranteed to be a string or
null/undefined so downstream code can rely on its type.
- Around line 1515-1520: The volume is being created with
createAndCacheVolume(...) but volume.load() is not awaited, causing potential
race conditions when the viewport mounts; change the flow to await the load
(e.g., await volume.load()) before proceeding to any mounting or calling
setVolumes, and wrap the await in a try/catch to handle and log load errors so
failures don’t leave the cache in an inconsistent state (refer to
cache.getVolume, createAndCacheVolume, and volume.load).

In `@packages/core/src/RenderingEngine/StackViewport.ts`:
- Around line 1998-2004: The code uses dataSet.initialImageIdIndex directly and
can pass invalid values (NaN, negative, out-of-range, non-integer) into
setStack; before calling setStack, validate and clamp initialImageIdIndex: read
dataSet.initialImageIdIndex, coerce to a finite integer (e.g., via
Number.isFinite and Math.floor), default to 0 on invalid input, then clamp it to
the range 0 .. Math.max(0, dataSet.imageIds.length - 1); pass that sanitized
value to setStack instead of the raw value so setStack always receives a valid
index.

In `@packages/metadata/src/displayset/displayset.test.ts`:
- Around line 56-57: The test assertions are out of sync with the `volume3d`
rule in defaultDisplaySetSplitRules.ts which sets viewportTypes: ['volume',
'volume3d', 'stack'] (so the first element is 'volume'); update the test in
displayset.test.ts to assert expect(displaySet.viewportTypes[0]).toBe('volume')
and adjust expect(displaySet.preferredViewportType) to match the actual
preferredViewportType produced by the rule (or, if the intent was to prefer
'volume3d', change the rule in defaultDisplaySetSplitRules.ts where the
'volume3d' rule is defined so its viewportTypes order and preferredViewportType
reflect that intent).

In `@packages/metadata/src/displayset/ImageStackDisplaySet.ts`:
- Around line 85-102: ImageStackDisplaySet.fromImageIds currently drops
unresolved imageIds silently; modify it to surface missing-ID handling by either
routing through the existing resolveInstances utility or by adding
options.skipMissing?: boolean and options.onMissing?: (imageId: string) => void
to the fromImageIds signature, then use those options to call onMissing for each
undefined instance and throw or skip based on skipMissing before calling
ImageStackDisplaySet.fromInstances; reference the method name
ImageStackDisplaySet.fromImageIds, the helper resolveInstances, and the
downstream ImageStackDisplaySet.fromInstances so reviewers can locate the
change.

In `@packages/metadata/src/displayset/resolveInstances.ts`:
- Line 4: Update the doc comment for the skipMissing parameter in
resolveInstances (or the top-level comment in resolveInstances.ts) to accurately
state that when skipMissing is true missing ids are skipped silently, and when
skipMissing is false the function throws an error (with optional warning
behavior removed), rather than "omitted with optional warn"; reference the
skipMissing parameter name and the resolveInstances function so readers can find
the behavior.

In `@packages/tools/examples/videoNavigation/index.ts`:
- Around line 259-261: Ensure you check that displaySet.instances exists and has
at least one element before using displaySet.instances[0].imageId; update the
code around the call to viewport.setDisplaySets to validate (e.g., if
(!displaySet || !Array.isArray(displaySet.instances) ||
displaySet.instances.length === 0) then handle the empty case or skip calling
viewport.setDisplaySets) so you never access instances[0] on an empty array and
include a clear fallback or error handling path.

In `@packages/tools/examples/videoSegmentation/index.ts`:
- Around line 193-204: The code assumes displaySet.instances has elements when
deriving videoId; update the block after createDisplaySets to validate that
displaySet.instances is an array with length > 0 (and that instances[0].imageId
exists) before accessing instances[0].imageId; if the check fails, throw a clear
Error (e.g., "No instances found in display set") or handle the empty case, and
only assign videoId once the guard passes—apply this change around the
displaySets/displaySet/videoId usage to prevent runtime crashes.

In `@packages/tools/examples/videoSplineROITools/index.ts`:
- Around line 286-290: The code assumes displaySet.instances has at least one
element before accessing instances[0]; update the logic around the displaySet
extraction to validate that displaySet.instances is an array with length > 0 (or
at least one element) before reading displaySet.instances[0].imageId and
assigning videoId; if empty, throw a clear Error (e.g., "No instances found in
display set") or handle the empty-case appropriately so videoId is not accessed
from an empty array. Ensure references to displaySet.instances and the videoId
assignment are updated accordingly.

In `@packages/tools/examples/videoTools/index.ts`:
- Around line 179-183: The code assumes displaySet.instances has elements before
accessing instances[0]; update the block that sets videoId to first verify
displaySet.instances is an array with length > 0 (e.g., check
Array.isArray(displaySet.instances) && displaySet.instances.length > 0) and
throw a clear Error if empty, then safely read displaySet.instances[0].imageId
to set videoId; reference the existing variables displaySets, displaySet, and
videoId when making this change.

---

Nitpick comments:
In `@packages/core/examples/displaySets/index.ts`:
- Around line 225-231: The code casts viewport to Types.IVolumeViewport and
Types.IVideoViewport and calls setProperties/play without runtime checks, which
can throw if enableElement returned a different viewport; update the branches
handling row.hint === 'volume3d' and 'video' to first validate the viewport type
(e.g., check for presence of expected methods/properties such as setProperties
for volume and play for video or use a type guard) before casting and calling
setProperties or play on the viewport instance to safely bail or log an error if
the viewport is not the expected type.

In `@packages/metadata/package.json`:
- Line 1: CI failures are caused by a pnpm version mismatch between the
repository/packageManager field and the GitHub Actions workflow; do not change
packages/metadata/package.json in this PR—either update the workflow action that
installs pnpm to use pnpm@11.5.2 (matching the packageManager field) or update
the root package.json packageManager field to the version used by the workflow
so they align; look for the packageManager field in the root package.json and
the pnpm install step in your GitHub Actions workflow to make the change.

In `@packages/metadata/src/displayset/buildSeriesInfo.ts`:
- Around line 16-19: The code adds instance.NumberOfFrames to numberOfFrames
without validating it, so malformed strings yield NaN; change the branch in
buildSeriesInfo (the logic that reads instance.NumberOfFrames) to validate and
parse the value first (e.g., use parseInt or Number and then test with
Number.isFinite / Number.isInteger or isNaN) and only add the parsed numeric
value to numberOfFrames when valid, otherwise treat it as zero or log/skip the
invalid value; reference the variables instance.NumberOfFrames and
numberOfFrames and the function/build block in buildSeriesInfo.ts when making
this change.

In `@packages/metadata/src/displayset/displaySetProvider.ts`:
- Around line 5-11: The provider function displaySetAddProvider should have
explicit TypeScript types added for its parameters: annotate next as a function
type (e.g. (query: string, data?: unknown, options?: DisplayOptions) =>
DisplaySetType) so callers and returns are typed, type _data as unknown or a
more specific payload type used by callers, and type options as an interface
with an optional displaySet?: DisplaySetType (or use the existing
DisplaySet/DisplayOptions types in the codebase). Update the function signature
of displaySetAddProvider to use these types and import or declare
DisplaySetType/DisplayOptions as needed so tooling and autocomplete pick up the
shapes of next, _data, and options.

In `@packages/metadata/src/displayset/ImageStackDisplaySet.ts`:
- Around line 13-37: Duplicate loops collectUnderlyingImageIds and
collectImageIds both iterate instances to gather instance.imageId; refactor by
making collectImageIds call collectUnderlyingImageIds(instances) to get
imageIds, then if that result is empty return [...underlyingImageIds] else
return the collected ids; update/remove the duplicated loop in collectImageIds
and keep collectUnderlyingImageIds as the single source of truth (refer to
functions collectUnderlyingImageIds and collectImageIds).
🪄 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: d98b3480-8b3c-4da7-a312-0acc2575fc1f

📥 Commits

Reviewing files that changed from the base of the PR and between d6f3fba and 318f3ae.

📒 Files selected for processing (52)
  • package.json
  • packages/core/examples/displaySets/index.ts
  • packages/core/examples/dynamicVolume/index.ts
  • packages/core/examples/video/index.ts
  • packages/core/src/RenderingEngine/BaseVolumeViewport.ts
  • packages/core/src/RenderingEngine/ECGViewport.ts
  • packages/core/src/RenderingEngine/StackViewport.ts
  • packages/core/src/RenderingEngine/VideoViewport.ts
  • packages/core/src/RenderingEngine/Viewport.ts
  • packages/core/src/RenderingEngine/WSIViewport.ts
  • packages/docs/docs/concepts/cornerstone-metadata/index.md
  • packages/metadata/jest.config.js
  • packages/metadata/package.json
  • packages/metadata/src/displayset/BaseDisplaySet.ts
  • packages/metadata/src/displayset/IDisplaySet.ts
  • packages/metadata/src/displayset/ImageStackDisplaySet.ts
  • packages/metadata/src/displayset/buildSeriesInfo.ts
  • packages/metadata/src/displayset/createDisplaySetFromGroup.ts
  • packages/metadata/src/displayset/defaultDisplaySetSplitRules.ts
  • packages/metadata/src/displayset/displaySetProvider.ts
  • packages/metadata/src/displayset/displayset.test.ts
  • packages/metadata/src/displayset/groupInstancesBySplitRules.ts
  • packages/metadata/src/displayset/index.ts
  • packages/metadata/src/displayset/isEcgInstance.ts
  • packages/metadata/src/displayset/isImageSopClass.ts
  • packages/metadata/src/displayset/isVideoInstance.ts
  • packages/metadata/src/displayset/isWsiInstance.ts
  • packages/metadata/src/displayset/registerDisplaySetMetadata.ts
  • packages/metadata/src/displayset/resolveInstances.ts
  • packages/metadata/src/displayset/splitSeriesInstanceGroupsFromImageIds.ts
  • packages/metadata/src/displayset/types.ts
  • packages/metadata/src/displayset/viewportTypes.ts
  • packages/metadata/src/enums/MetadataModules.ts
  • packages/metadata/src/index.ts
  • packages/metadata/src/registerDefaultProviders.ts
  • packages/metadata/src/types/MetadataModuleTypes.ts
  • packages/metadata/src/types/index.ts
  • packages/metadata/src/utilities/index.ts
  • packages/tools/examples/dynamicCINETool/index.ts
  • packages/tools/examples/generateImageFromTimeData/index.ts
  • packages/tools/examples/localAdvanced/index.ts
  • packages/tools/examples/videoColor/index.ts
  • packages/tools/examples/videoContourSegmentation/index.ts
  • packages/tools/examples/videoGroup/index.ts
  • packages/tools/examples/videoNavigation/index.ts
  • packages/tools/examples/videoRange/index.ts
  • packages/tools/examples/videoSegmentation/index.ts
  • packages/tools/examples/videoSplineROITools/index.ts
  • packages/tools/examples/videoTools/index.ts
  • utils/ExampleRunner/example-runner-cli.js
  • utils/demo/helpers/index.js
  • utils/demo/helpers/splitDisplaySetsFromImageIds.ts

Comment thread package.json
Comment thread packages/core/examples/displaySets/index.ts
Comment thread packages/core/src/RenderingEngine/BaseVolumeViewport.ts Outdated
Comment thread packages/core/src/RenderingEngine/BaseVolumeViewport.ts Outdated
Comment thread packages/core/src/RenderingEngine/StackViewport.ts Outdated
Comment thread packages/metadata/src/displayset/resolveInstances.ts
Comment thread packages/tools/examples/videoNavigation/index.ts
Comment thread packages/tools/examples/videoSegmentation/index.ts
Comment thread packages/tools/examples/videoSplineROITools/index.ts Outdated
Comment thread packages/tools/examples/videoTools/index.ts Outdated
wayfarer3130 and others added 6 commits June 10, 2026 11:29
…ack and volume viewports (#2717)

* fix: closed freehand stat value mismatch

* fix: review comments

* chore: renamed function

* docs: fix MDX rendering issue in snapIndexBounds JSDoc

* Fix view issue on stack so that the issue is testable

---------

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
sedghi added 3 commits June 29, 2026 13:09
- isVideoInstance: reuse shared videoUIDs (no MPEG2 misclassification)
- buildSeriesInfo + default split rules: guard empty instance lists
- numImageFrames: coerce NumberOfFrames with Number()
- MetadataModuleType.displaySetModule: type as full IDisplaySet
- createDisplaySetFromGroup: denylist reserved data keys
- Viewport: extract shared mountDisplaySets template for the 5 overrides
- re-export isWsiInstance; add JSDoc to classifiers/viewport-type helpers
- demo toBaseImageId: normalize ?frame=N as well as &frame=N
- tests for the above
Add the split -> create -> consume pipeline, driving a viewport via
setDisplaySets, caching display sets in the metadata layer, the split-rule
model (with the DWI mixedDimensionalityBValue worked example), and the
reusable instance classifiers.
@sedghi sedghi changed the title fix: Add display set split/store logic to metadata feat: Add display set split/store logic to metadata Jun 29, 2026
sedghi added 3 commits June 29, 2026 13:52
- GenericViewport: add getDisplaySets() to ViewportController + base impl
  (derived from live bindings), giving parity with the legacy viewport.
- createDisplaySetFromGroup: fold splitNumber into the default
  displaySetInstanceUID so a split series (DWI) yields unique ids; the
  viewport displaySetId == the display set instance UID.
- demo splitter passes splitNumber; displaySets example keys the registry
  on displaySetInstanceUID to exercise the convention.
- legacy mountDisplaySets records only the mounted source (honest
  getDisplaySets); documents overlays as a non-breaking future addition.
- rename genericViewportDataSet* -> genericViewportDisplaySet* across the
  registry/access layer, types, and call sites (beta, no alias).
Collapse the displaySetId / displaySetInstanceUID duality onto a single
name, displaySetId:
- metadata: rename IDisplaySet.displaySetInstanceUID -> displaySetId (and
  CreateDisplaySetFromGroup option, reserved keys, tests, docs).
- generic viewport: rename the DataId type -> DisplaySetId; align the
  registry/access param names (dataId -> displaySetId).
- keep dataId in the public, persisted ViewReference / ViewportDataReference
  (a generic data-ref pointer) and in loadECGWaveform (an imageId), per the
  identifier-surface-only scope.
- group buckets are now namespaced by rule and JSON-encoded, so different
  rules can't collide on a shared key and separator/undefined aliasing is gone
  (issues 1, 6).
- groups are returned in deterministic (split-key sorted) order, making
  split identity / displaySetId suffixes stable across input ordering (3).
- groupInstancesBySplitRules/splitImageIdsBySplitRules accept onUnmatched(d)
  so instances matching no rule are observable, not silently dropped (5).
- updateSeriesInfo typed () => void (return was always ignored) (4).
- default rules: singleImageModality split key is now series-scoped (7);
  comments document the instances[0] homogeneity assumption (2), the
  multiFrame SliceLocation guard (8); removed the redundant viewportTypes-only
  customAttributes now that rule-level viewportTypes is applied (9).
- tests for rule namespacing, deterministic order, and onUnmatched.
sedghi added 6 commits June 29, 2026 14:47
The metadata page had grown to be mostly display-set content. Move it to a
dedicated concepts/cornerstone-metadata/display-sets page (pipeline, driving a
viewport, caching, split-rule model + DWI example, IDisplaySet model), leave a
short pointer on the metadata index, and add it to the sidebar. Also folds in
the split-engine guarantees (rule-namespaced keys, deterministic order,
onUnmatched, updateSeriesInfo mutation).
Complete the in-progress rename of the SplitRule fields so the engine and
the rules line up again:

- ruleSelector -> matches (per-instance predicate)
- splitKey (rule field) -> groupBy (bucket recipe)
- keep updateSeriesInfo; revert the stray augmentSeriesInfo so the
  series-level hook actually fires

The rename had been applied to types.ts and groupInstancesBySplitRules.ts
but not to defaultDisplaySetSplitRules.ts/tests, so every rule fell back to
a catch-all grouped by SeriesInstanceUID and the series flags were never set
- silently disabling the DWI mixed-b-value split. Updates the rules, tests,
and docs to the new names.
…ies hook

Series-level facts used to be contributed by a rule's `updateSeriesInfo`
hook, which mutated a shared `seriesInfo` object that `matches`/`groupBy`
then read. Replace that with a pure, rule-scoped `series` hook:

- `series?: ({ instances }) => SeriesFacts` runs once per rule per split and
  *returns* that rule's derived facts (no shared mutation).
- `matches`/`groupBy` receive a `RuleContext` ({ series }) carrying only that
  rule's own facts.
- `buildSeriesInfo` is now a standalone series-statistics helper, decoupled
  from rules; `SeriesInfo` no longer carries rule flags.

Most rules need only matches + groupBy; series is the opt-in for whole-series
facts (multi-frame, volumetric, mixed-b-value DWI). Adds tests covering the
DWI mixed/non-mixed split and updates the display-sets docs.
…tadataProvider

Three core jest suites still imported/mocked the pre-rename
`genericViewportDataSetMetadataProvider` module and its `VIEWPORT_V2_DATA_SET`
constant, so they failed to load after the DataSet->DisplaySet rename. Update
the module path and constant to the current `genericViewportDisplaySetMetadataProvider`
/ `VIEWPORT_V2_DISPLAY_SET` names.
Library:
- defaultDisplaySetSplitRules: mixed-b-value DWI rule now prefers volume
  (MPR) over stack, matching normal MR series; it runs before volume3d so
  stack-first was regressing the defined-b-value subgroup.
- isVideoInstance: check all advertised transfer syntaxes, not just the
  first array entry, so a video syntax in a later slot is not missed.
- BaseDisplaySet: clone viewportTypes and instances so a caller mutating
  them after construction cannot drift the display set / preferredViewportType.
- ImageStackDisplaySet.fromImageIds: preserve caller frame-level imageIds
  instead of collapsing them to underlying-instance ids.
- BaseVolumeViewport: attach a rejection handler to the fire-and-forget
  streaming volume.load().

Examples/docs:
- video examples: select the video display set by preferredViewportType
  instead of assuming displaySets[0], which is fragile once a series splits.
- displaySets example: build metadata rows with DOM nodes/textContent
  instead of innerHTML (avoids injecting DICOM-derived markup).
- docs: import metaData in the getNaturalizedInstance snippet; align the
  mixed-b-value rule example with the volume-first viewport order.
The CodeRabbit nitpick assumed volume.load() returns a promise, but it
returns void, so .catch was a type error that broke the ESM build
(TS2339). Restore the original fire-and-forget volume.load() call.

@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.

Actionable comments posted: 6

🧹 Nitpick comments (1)
packages/core/src/RenderingEngine/GenericViewport/index.ts (1)

43-64: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider keeping a deprecated DataId export for one release.

Dropping the public alias here turns a naming cleanup into a source-breaking change for downstream TypeScript consumers that import DataId. If this PR is not intentionally shipping behind a major-version migration, re-exporting type DataId = DisplaySetId as a shim would make the rename much safer.

🤖 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/core/src/RenderingEngine/GenericViewport/index.ts` around lines 43 -
64, Keep the public DataId type available as a temporary compatibility shim in
GenericViewport/index.ts; the current export list removes a downstream-facing
alias and can break TypeScript consumers. Re-export DataId alongside
DisplaySetId (for example by aliasing DataId to DisplaySetId) and mark it
deprecated for one release so existing imports continue to compile while the
rename is rolled out.
🤖 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/core/src/RenderingEngine/GenericViewport/GenericViewport.ts`:
- Around line 140-153: The source promotion logic in GenericViewport is updating
roles without updating ordering, so the newly added source can stay behind
earlier bindings. Adjust the addDisplaySet / source-selection path so when a
later display set becomes role 'source' it is moved to the front of the bindings
order before getDisplaySets() and getCurrentBinding() read from the Map. Keep
the existing role flips for other entries, but ensure the promoted source is
inserted/reinserted as the first binding so implicit updates target the correct
display set.

In `@packages/core/src/RenderingEngine/WSIViewport.ts`:
- Around line 643-655: `WSIViewport.setDisplaySets` is indirectly re-entering
`WSIViewportLegacyAdapter` through `this.setWSI(...)`, causing an infinite
recursion path between `setWSI`, `setDataIds`, and `setDisplaySets`. Update the
WSI load path in `setDisplaySets` to call the non-virtual internal
implementation directly (or otherwise bypass the legacy adapter override) so the
legacy adapter cannot dispatch back into `setDisplaySets`. Keep the existing
validation in `setDisplaySets`, but ensure the final load uses the base
WSI-loading method rather than the polymorphic `setWSI` entrypoint.

In `@packages/core/src/utilities/genericViewportDisplaySetMetadataProvider.ts`:
- Around line 5-27: The cache in genericViewportDisplaySetMetadataProvider uses
a plain object state, which can mishandle special keys like __proto__ and
constructor. Replace state with a Map<string, unknown> (or an
Object.create(null) store) and update add, get, remove, and clear to use the Map
API while keeping the VIEWPORT_V2_DISPLAY_SET check in get.

In `@packages/metadata/src/displayset/BaseDisplaySet.ts`:
- Around line 33-34: The BaseDisplaySet constructor is deduplicating both image
id arrays, which changes the caller-supplied cardinality and ordering that
IDisplaySet relies on. Update BaseDisplaySet to preserve options.imageIds and
options.underlyingImageIds exactly as provided instead of wrapping them in Set,
so repeated ids and frame-to-instance mapping remain intact.

In `@packages/metadata/src/displayset/defaultDisplaySetSplitRules.ts`:
- Around line 64-72: The series-wide split rules are matching non-image SOP
instances because the matches callbacks only check the cached series fact from
series. Update the relevant matches logic in defaultDisplaySetSplitRules so
that, in addition to checking series.isMultiFrame, it also verifies the current
instance is an image before claiming it, while leaving the series-level gate in
place. Apply this same adjustment to the other affected rule blocks in the same
file so non-image instances continue to fall through to onUnmatchedInstance.

In `@packages/metadata/src/displayset/groupInstancesBySplitRules.ts`:
- Around line 76-80: The bucket key in groupInstancesBySplitRules is too broad
because buildSplitKey is using SplitRule.id alone when it exists, which can
collide across different rules and merge instances into the wrong InstanceGroup.
Update the discriminator logic in groupInstancesBySplitRules so it always
includes the rule index together with splitRule.id (or otherwise namespaces by
both id and ruleIndex), and ensure the matchedRule recorded for each bucket
still comes from the correct split rule.

---

Nitpick comments:
In `@packages/core/src/RenderingEngine/GenericViewport/index.ts`:
- Around line 43-64: Keep the public DataId type available as a temporary
compatibility shim in GenericViewport/index.ts; the current export list removes
a downstream-facing alias and can break TypeScript consumers. Re-export DataId
alongside DisplaySetId (for example by aliasing DataId to DisplaySetId) and mark
it deprecated for one release so existing imports continue to compile while the
rename is rolled out.
🪄 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: 9bb21406-0f85-46e1-9d13-cee2b4f8980e

📥 Commits

Reviewing files that changed from the base of the PR and between 24f5ed4 and 8128046.

📒 Files selected for processing (86)
  • packages/core/examples/displaySets/index.ts
  • packages/core/examples/genericEcg/index.ts
  • packages/core/examples/genericMultiVolumeAPI/index.ts
  • packages/core/examples/genericStackAPI/index.ts
  • packages/core/examples/genericStackPosition/index.ts
  • packages/core/examples/genericVideo/index.ts
  • packages/core/examples/genericViewportScale/index.ts
  • packages/core/examples/genericWsi/index.ts
  • packages/core/examples/viewportProjection/index.ts
  • packages/core/examples/wsi/index.ts
  • packages/core/src/RenderingEngine/BaseVolumeViewport.ts
  • packages/core/src/RenderingEngine/ECGViewport.ts
  • packages/core/src/RenderingEngine/GenericViewport/ECG/DefaultECGDataProvider.ts
  • packages/core/src/RenderingEngine/GenericViewport/GenericViewport.ts
  • packages/core/src/RenderingEngine/GenericViewport/Planar/DefaultPlanarDataProvider.ts
  • packages/core/src/RenderingEngine/GenericViewport/Planar/PlanarLegacyCompatibilityController.ts
  • packages/core/src/RenderingEngine/GenericViewport/Planar/PlanarViewport.ts
  • packages/core/src/RenderingEngine/GenericViewport/Planar/PlanarViewportLegacyAdapter.ts
  • packages/core/src/RenderingEngine/GenericViewport/Video/DefaultVideoDataProvider.ts
  • packages/core/src/RenderingEngine/GenericViewport/Video/VideoViewport.ts
  • packages/core/src/RenderingEngine/GenericViewport/ViewportArchitectureTypes.ts
  • packages/core/src/RenderingEngine/GenericViewport/Volume3D/DefaultVolume3DDataProvider.ts
  • packages/core/src/RenderingEngine/GenericViewport/Volume3D/VolumeViewport3DLegacyAdapter.ts
  • packages/core/src/RenderingEngine/GenericViewport/Volume3D/viewport3D.ts
  • packages/core/src/RenderingEngine/GenericViewport/WSI/DefaultWSIDataProvider.ts
  • packages/core/src/RenderingEngine/GenericViewport/WSI/WSIViewportLegacyAdapter.ts
  • packages/core/src/RenderingEngine/GenericViewport/genericViewportDisplaySetAccess.ts
  • packages/core/src/RenderingEngine/GenericViewport/index.ts
  • packages/core/src/RenderingEngine/StackViewport.ts
  • packages/core/src/RenderingEngine/VideoViewport.ts
  • packages/core/src/RenderingEngine/Viewport.ts
  • packages/core/src/RenderingEngine/WSIViewport.ts
  • packages/core/src/utilities/genericViewportDataSetMetadataProvider.ts
  • packages/core/src/utilities/genericViewportDisplaySetMetadataProvider.ts
  • packages/core/src/utilities/index.ts
  • packages/core/test/PlanarLegacyCompatibilityController.jest.js
  • packages/core/test/planarOrchestration.jest.js
  • packages/core/test/planarViewportState.jest.js
  • packages/docs/docs/concepts/cornerstone-core/generic-viewport/api.md
  • packages/docs/docs/concepts/cornerstone-core/generic-viewport/data-bindings-and-loading.md
  • packages/docs/docs/concepts/cornerstone-core/generic-viewport/migration.md
  • packages/docs/docs/concepts/cornerstone-metadata/display-sets.md
  • packages/docs/docs/concepts/cornerstone-metadata/index.md
  • packages/docs/docs/migration-guides/5x/2-generic-viewport.md
  • packages/docs/sidebars.js
  • packages/metadata/src/displayset/BaseDisplaySet.ts
  • packages/metadata/src/displayset/IDisplaySet.ts
  • packages/metadata/src/displayset/ImageStackDisplaySet.ts
  • packages/metadata/src/displayset/buildSeriesInfo.ts
  • packages/metadata/src/displayset/createDisplaySetFromGroup.ts
  • packages/metadata/src/displayset/defaultDisplaySetSplitRules.ts
  • packages/metadata/src/displayset/displayset.test.ts
  • packages/metadata/src/displayset/groupInstancesBySplitRules.ts
  • packages/metadata/src/displayset/index.ts
  • packages/metadata/src/displayset/isEcgInstance.ts
  • packages/metadata/src/displayset/isImageInstance.ts
  • packages/metadata/src/displayset/isVideoInstance.ts
  • packages/metadata/src/displayset/isWsiInstance.ts
  • packages/metadata/src/displayset/splitImageIdsBySplitRules.ts
  • packages/metadata/src/displayset/types.ts
  • packages/metadata/src/displayset/viewportTypes.ts
  • packages/metadata/src/index.ts
  • packages/metadata/src/types/MetadataModuleTypes.ts
  • packages/tools/examples/genericLabelmapOverlapPlayground/index.ts
  • packages/tools/examples/genericLabelmapRendering/index.ts
  • packages/tools/examples/genericLabelmapSegmentationTools/index.ts
  • packages/tools/examples/genericLabelmapSliceRendering/index.ts
  • packages/tools/examples/genericLabelmapSliceRenderingTools/index.ts
  • packages/tools/examples/genericStackLabelmapSegmentation/index.ts
  • packages/tools/examples/genericStackManipulationTools/index.ts
  • packages/tools/examples/genericVolumeAnnotationTools/index.ts
  • packages/tools/examples/videoColor/index.ts
  • packages/tools/examples/videoContourSegmentation/index.ts
  • packages/tools/examples/videoGroup/index.ts
  • packages/tools/examples/videoNavigation/index.ts
  • packages/tools/examples/videoRange/index.ts
  • packages/tools/examples/videoSegmentation/index.ts
  • packages/tools/examples/videoSplineROITools/index.ts
  • packages/tools/examples/videoTools/index.ts
  • packages/tools/examples/viewportProjectionSynchronizer/index.ts
  • packages/tools/examples/wsiAnnotationTools/index.ts
  • packages/tools/src/tools/displayTools/Labelmap/labelmapRenderPlan.spec.ts
  • packages/tools/src/tools/displayTools/Labelmap/labelmapRenderPlan/planarGenericVolumeLabelmap.ts
  • packages/tools/src/tools/displayTools/Labelmap/removeLabelmapRepresentationData.ts
  • tests/vitest-browser/genericStackApi.browser.test.ts
  • utils/demo/helpers/splitDisplaySetsFromImageIds.ts
💤 Files with no reviewable changes (1)
  • packages/core/src/utilities/genericViewportDataSetMetadataProvider.ts
✅ Files skipped from review due to trivial changes (5)
  • packages/docs/docs/concepts/cornerstone-core/generic-viewport/data-bindings-and-loading.md
  • packages/metadata/src/displayset/isImageInstance.ts
  • packages/core/src/RenderingEngine/GenericViewport/Video/DefaultVideoDataProvider.ts
  • packages/docs/docs/concepts/cornerstone-metadata/index.md
  • packages/docs/docs/concepts/cornerstone-core/generic-viewport/migration.md
🚧 Files skipped from review as they are similar to previous changes (18)
  • packages/metadata/src/displayset/isEcgInstance.ts
  • packages/metadata/src/displayset/isWsiInstance.ts
  • packages/metadata/src/displayset/viewportTypes.ts
  • packages/metadata/src/displayset/isVideoInstance.ts
  • packages/core/src/RenderingEngine/StackViewport.ts
  • packages/metadata/src/displayset/IDisplaySet.ts
  • packages/tools/examples/videoColor/index.ts
  • packages/tools/examples/videoRange/index.ts
  • packages/tools/examples/videoContourSegmentation/index.ts
  • packages/core/src/RenderingEngine/BaseVolumeViewport.ts
  • packages/tools/examples/videoGroup/index.ts
  • packages/core/src/RenderingEngine/ECGViewport.ts
  • packages/tools/examples/videoNavigation/index.ts
  • packages/tools/examples/videoSegmentation/index.ts
  • packages/tools/examples/videoTools/index.ts
  • packages/tools/examples/videoSplineROITools/index.ts
  • packages/core/examples/displaySets/index.ts
  • utils/demo/helpers/splitDisplaySetsFromImageIds.ts

Comment on lines +140 to +153
/**
* Returns the display sets currently mounted on the viewport, in mount order
* (source binding first, then overlays). Derived from the live bindings, so
* it always reflects what is actually rendered - including overlays and any
* `removeData` calls. The per-entry `options` carry the binding `role`.
*/
getDisplaySets(): Array<{ displaySetId: DisplaySetId; options?: unknown }> {
return Array.from(this.bindings.entries()).map(
([displaySetId, binding]) => ({
displaySetId,
options: { role: binding.role },
})
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Promoting a later display set to source does not update the active/source ordering.

When addDisplaySet(..., { role: 'source' }) runs after other bindings exist, this only flips their roles to overlay; it does not move the new source to the front. Because getDisplaySets() and getCurrentBinding() both depend on Map iteration order, the old first binding can remain “current”, which breaks the source-first contract and can send implicit presentation/view-reference updates to the wrong display set.

Also applies to: 537-545

🤖 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/core/src/RenderingEngine/GenericViewport/GenericViewport.ts` around
lines 140 - 153, The source promotion logic in GenericViewport is updating roles
without updating ordering, so the newly added source can stay behind earlier
bindings. Adjust the addDisplaySet / source-selection path so when a later
display set becomes role 'source' it is moved to the front of the bindings order
before getDisplaySets() and getCurrentBinding() read from the Map. Keep the
existing role flips for other entries, but ensure the promoted source is
inserted/reinserted as the first binding so implicit updates target the correct
display set.

Comment thread packages/core/src/RenderingEngine/WSIViewport.ts
Comment thread packages/core/src/utilities/genericViewportDisplaySetMetadataProvider.ts Outdated
Comment thread packages/metadata/src/displayset/BaseDisplaySet.ts Outdated
Comment thread packages/metadata/src/displayset/defaultDisplaySetSplitRules.ts Outdated
Comment thread packages/metadata/src/displayset/groupInstancesBySplitRules.ts Outdated
- WSIViewport.setDisplaySets: call WSIViewport.prototype.setWSI non-virtually.
  The legacy adapter overrides setWSI -> setDataIds -> setDisplaySets, so the
  polymorphic call recursed infinitely on WSIViewportLegacyAdapter (critical).
- genericViewportDisplaySetMetadataProvider: back the cache with a Map so
  reserved keys (__proto__/constructor) are stored as ordinary keys.
- defaultDisplaySetSplitRules: the series-gated rules (multiFrame, mixedBValue,
  volume3d) now also require the instance to be an image, so non-image SOPs in a
  qualifying series fall through to onUnmatchedInstance instead of being pulled
  into an image display set.
- groupInstancesBySplitRules: always fold ruleIndex into the bucket key so two
  rules reusing the same id can't collide into one InstanceGroup.
- BaseDisplaySet: stop deduping imageIds/underlyingImageIds; preserve caller
  cardinality/order (underlyingImageIds is one entry per instance).
@sedghi sedghi changed the title feat: Add display set split/store logic to metadata feat: Add display set split store logic to metadata Jun 30, 2026
…osted runner

The legacy Playwright job intermittently recorded a near-zero drag (a
~0.5mm length instead of ~138mm) because a single batched mousemove - even
with Playwright's built-in { steps } - can be coalesced/dropped on the
self-hosted runner, and moving in the same burst as the mousedown can be
dropped before the tool attaches its active-drag handler.

For stepped gestures (length / window-level), settle briefly after mousedown
and deliver the move as discrete, individually-flushed mousemoves paced with a
short wait. The end point is unchanged so screenshot baselines still match.
Path-integrating tools (planar-rotate) keep the single-move path.

Confirmed locally: stackAnnotation + stackAnnotationTiled length tests pass.
…aySets

Addresses review findings on the setDisplaySets migration:

- VideoViewport.setDisplaySets now threads an optional initial frame
  (frameNumber, or viewReference.sliceIndex matching setDataIds) through
  to setVideo. Restores the frame-25 start in the four migrated video
  examples (videoColor, videoGroup, videoSplineROITools, videoNavigation)
  that had regressed to frame 1.
- WSIViewport.setDisplaySets injects addWSIMiniNavigationOverlayCss
  (honoring miniNavigationOverlay !== false), mirroring setDataIds, so
  the overview/position-control overlay is styled on the display-set path.
@sedghi sedghi merged commit a08cd83 into main Jun 30, 2026
14 checks passed
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.

4 participants