Skip to content

feat: Add ability to use new display set split rules#6137

Open
wayfarer3130 wants to merge 5 commits into
masterfrom
feat/customization-use-metadata-display-set
Open

feat: Add ability to use new display set split rules#6137
wayfarer3130 wants to merge 5 commits into
masterfrom
feat/customization-use-metadata-display-set

Conversation

@wayfarer3130

@wayfarer3130 wayfarer3130 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Context

This PR starts using the @cornerstonejs/metadata display set storage, as well as adding the ability to use the new display set split rules via enabling them through a customization. An example of a split rule that splits the first image of CT's as a "SCOUT" is created to demo how to use the new customization design.

Changes & Results

Testing

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:
  • [] Node version:
  • [] Browser:

Summary by CodeRabbit

  • New Features
    • Added opt-in metadata-driven display set splitting, creating split-based display sets from metadata rules with incremental instance merging and fallback to existing handling for unmatched instances.
    • Included default split rules for common modalities (e.g., mammography, ultrasound multiframe, MR variants, CT SCOUT) and support for declarative rule customization.
    • Enhanced URL customizations with parameter-style opt-ins.
    • Added safe, read-time $function support for declarative customization logic (including aggregation helpers) with graceful failure on invalid expressions.
  • Documentation
    • Added a guide covering enablement, split-rule configuration, and $function usage.

@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 for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

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

@netlify

netlify Bot commented Jul 12, 2026

Copy link
Copy Markdown

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit 2a37b6e
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a57a1e348b2710008db87c9
😎 Deploy Preview https://deploy-preview-6137--ohif-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 981c0bd0-98ba-4084-80cd-088947949a26

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds opt-in metadata-driven display-set splitting with safe $function expressions, ordered OHIF rules, incremental reconciliation, metadata-backed storage, legacy fallback handling, customization presets, tests, and documentation.

Changes

Metadata display-set splitting

Layer / File(s) Summary
Safe expression runtime
platform/core/src/services/CustomizationService/expression/*, platform/core/src/services/CustomizationService/CustomizationService.ts, platform/core/src/services/CustomizationService/CustomizationService.function.test.ts
Adds tokenization, parsing, CSP-safe compilation, aggregate/helper evaluation, $function resolution, memoization, error handling, and tests.
Declarative and default split rules
platform/core/src/services/DisplaySetService/normalizeSplitRules.ts, extensions/default/src/displaySetSplitting/ohifDefaultSplitRules.ts, extensions/default/src/displaySetSplitting/ohifDefaultSplitRules.test.ts
Normalizes declarative rules and adds ordered image, multiframe, diffusion, volume, and specialized-instance handling.
ImageSet factories and handler integration
extensions/default/src/displaySetSplitting/*, extensions/default/src/getSopClassHandlerModule.js, platform/core/src/types/DisplaySet.ts
Centralizes ImageSet construction and creates split display sets with rule attributes, dynamic-volume metadata, and incremental merging.
DisplaySetService orchestration and storage
platform/core/src/services/DisplaySetService/*, platform/core/src/types/DisplaySet.ts
Integrates grouping, reconciliation, stale-set removal, legacy fallback, and typed metadata-backed storage.
Customization entrypoints and presets
extensions/default/package.json, extensions/default/src/customizations/*, extensions/default/src/getCustomizationModule.tsx, platform/app/public/customizations/*
Registers the default customization, adds launch parameters, and provides metadata-splitting and CT SCOUT presets.
Display-set splitting documentation
platform/docs/docs/platform/services/customization-service/displaySetSplitting.md
Documents enablement, rule structure, expressions, precedence, examples, and overrides.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DisplaySetService
  participant CustomizationService
  participant SplitRulesEngine
  participant DisplaySetFactory
  participant displaySetStore
  DisplaySetService->>CustomizationService: read useMetadataDisplaySet
  DisplaySetService->>SplitRulesEngine: group instances by splitRules
  SplitRulesEngine-->>DisplaySetService: matched groups and unmatched instances
  DisplaySetService->>DisplaySetFactory: createDisplaySetFromGroup
  DisplaySetFactory->>displaySetStore: store split display set
  DisplaySetService->>DisplaySetService: route unmatched instances to SOP handlers
Loading

Possibly related PRs

  • OHIF/Viewers#5992: Adds the URL-driven customization loading behavior used by the new split/* presets.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning Core sections are present, but Changes & Results and Testing are empty and the checklist boxes remain unchecked. Fill in Changes & Results and Testing with concrete details, and mark the checklist items that apply.
Docstring Coverage ⚠️ Warning Docstring coverage is 69.57% 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 The title clearly matches the main change: enabling new display set split rules and metadata-backed display set behavior.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/customization-use-metadata-display-set

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.

@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: 2

🧹 Nitpick comments (2)
extensions/default/src/getSopClassHandlerModule.js (1)

15-15: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove the unused second argument from makeDisplaySet calls

makeDisplaySet only forwards instances and appContext to makeImageSetDisplaySet, so instanceIndex / displaySets.length are dead arguments here. Remove them from the three call sites to avoid confusion.

🤖 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 `@extensions/default/src/getSopClassHandlerModule.js` at line 15, Update the
three call sites of makeDisplaySet to pass only the required instances argument,
removing the unused instanceIndex and displaySets.length arguments while
preserving the existing makeDisplaySet implementation.
platform/core/src/types/DisplaySet.ts (1)

93-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a type annotation for the displaySetService parameter.

The displaySetService parameter on updateInstances has no type annotation, making it implicitly any. Importing DisplaySetService directly would create a circular dependency (the services layer imports from types/), so a minimal interface would preserve type safety without the architectural concern.

♻️ Suggested interface to avoid circular dependency
 export type DisplaySet = {
   displaySetInstanceUID: string;
   instances: InstanceMetadata[];
   isReconstructable?: boolean;
   StudyInstanceUID: string;
   SeriesInstanceUID?: string;
   SeriesNumber?: number;
   SeriesDescription?: string;
   numImages?: number;
   unsupported?: boolean;
   Modality?: string;
   imageIds?: string[];
   images?: unknown[];
   label?: string;
   /** Flag indicating if this is an overlay display set (e.g., SEG, RTSTRUCT) */
   isOverlayDisplaySet?: boolean;
   /** Flag indicating this is a derived dataset */
   isDerived?: boolean;
   /** flag indicating if it supports window level */
   supportsWindowLevel?: boolean;

   // Details about how to display:
   /**
    *  A URL that can be used to display the thumbnail.  Typically a data url
    * This can be set to null to avoid trying to display a thumbnail, eg for
    * display sets without a thumbnail.
    */
   thumbnailSrc?: string;
   /** A fetch method to get the thumbnail */
   getThumbnailSrc?(imageId?: string): Promise<string>;

   /** An opaque type of this viewport, used internally to specify which viewport to use */
   viewportType;

   /**
    * A fetch URL to display the content.  This is used for content such as
    * pdf display.
    */
   renderedUrl?: string;

   /**
    * The instance UID of the display set that this display set references.
    * This is used to determine if the display set is a referenced display set.
    * It usually is for SEG, RTSTRUCT, etc.
    */
   referencedDisplaySetInstanceUID?: string;

   /**
    * The FrameOfReferenceUID shared by every frame within this display set.
    * It will be undefined if the frames do not all share the same Frame of Reference.
    */
   FrameOfReferenceUID?: string;

   SeriesDate?: string;
   SeriesTime?: string;
   instance?: InstanceMetadata;

   /**
    * The predecessor image id refers to the SOP instance that is currently loaded
    * into this display set for SEG/SR/RTSTRUCT type values.  The name is chosen
    * for consistency when this value is used as the origin instance
    * for saving a new instance intended to replace this instance where the
    * new instance has a "predecessor sequence".
    */
   predecessorImageId?: string;

   /**
    * isLoaded is used for display sets containing a load operation that
    * is required before the display set can be shown.  This is separate from
    * isHydrated, which means it is loaded into view.
    */
   isLoaded?: boolean;
   isHydrated?: boolean;
   isRehydratable?: boolean;

   /**
    * The name of the comparison function (for sort) to use when comparing display
    * sets that are coming from same series instanceUID.
    */
   compareSameSeries?: string;

+  /**
+   * Minimal interface for the DisplaySetService methods that
+   * `updateInstances` needs, avoiding a circular import from
+   * `types/` into the services layer.
+   */
   /**
    * The deterministic, rule-namespaced group key assigned by the
    * `@cornerstonejs/metadata` split-rules engine when this display set was
    * created via the `useMetadataDisplaySet` customization.  Used to reconcile
    * re-splits of the same series with already-created display sets.
    */
   splitKey?: string;

   /** The id of the split rule that created this display set, when applicable. */
   splitRuleId?: string;

   /**
    * Incremental-merge hook for split-rule display sets.  Intentionally named
    * differently from `addInstances` (the SOP-class-handler merge hook) so the
    * legacy handler loop never feeds unmatched instances into split-rule
    * display sets.  Returns the updated display set, or undefined when the
    * display set cannot merge the instances.
    */
-  updateInstances?(instances: InstanceMetadata[], displaySetService): DisplaySet | undefined;
+  updateInstances?(
+    instances: InstanceMetadata[],
+    displaySetService: DisplaySetServiceLike
+  ): DisplaySet | undefined;
 };
+
+/**
+ * Minimal interface for the DisplaySetService methods that `updateInstances`
+ * callers need, avoiding a circular import from `types/` into the services layer.
+ */
+export interface DisplaySetServiceLike {
+  setDisplaySetMetadataInvalidated(displaySetInstanceUID: string): void;
+  getDisplaySetsForSeries(seriesInstanceUID: string): DisplaySet[];
+}
🤖 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 `@platform/core/src/types/DisplaySet.ts` around lines 93 - 112, Update the
DisplaySet.updateInstances signature to replace the implicit-any
displaySetService parameter with a minimal local interface describing the
service members this hook uses. Define or reuse that interface within the types
layer rather than importing DisplaySetService, preserving type safety without
introducing a circular dependency.
🤖 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 `@extensions/default/src/displaySetSplitting/makeImageSetDisplaySet.ts`:
- Around line 36-42: Update the volumeLoaderUtility lookup in
makeImageSetDisplaySet to check whether getModuleEntry returns undefined before
accessing exports. If the utility is unavailable, throw a clear descriptive
error; otherwise preserve the existing getDynamicVolumeInfo extraction and
invocation.

In
`@platform/core/src/services/CustomizationService/expression/expression.test.ts`:
- Around line 137-149: Rename the test around compileExpression to describe
graceful null property access in templates rather than runtime errors, warnings,
or an undefined result. Keep the existing `${a.b.c}` assertion and setup
unchanged.

---

Nitpick comments:
In `@extensions/default/src/getSopClassHandlerModule.js`:
- Line 15: Update the three call sites of makeDisplaySet to pass only the
required instances argument, removing the unused instanceIndex and
displaySets.length arguments while preserving the existing makeDisplaySet
implementation.

In `@platform/core/src/types/DisplaySet.ts`:
- Around line 93-112: Update the DisplaySet.updateInstances signature to replace
the implicit-any displaySetService parameter with a minimal local interface
describing the service members this hook uses. Define or reuse that interface
within the types layer rather than importing DisplaySetService, preserving type
safety without introducing a circular dependency.
🪄 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: f0e9f3f4-e8d2-47ba-94e0-8102288831c9

📥 Commits

Reviewing files that changed from the base of the PR and between 110b293 and 14cfb7b.

📒 Files selected for processing (25)
  • extensions/default/package.json
  • extensions/default/src/customizations/metadataDisplaySetCustomization.ts
  • extensions/default/src/displaySetSplitting/makeDisplaySetFromInstanceGroup.ts
  • extensions/default/src/displaySetSplitting/makeImageSetDisplaySet.ts
  • extensions/default/src/displaySetSplitting/ohifDefaultSplitRules.test.ts
  • extensions/default/src/displaySetSplitting/ohifDefaultSplitRules.ts
  • extensions/default/src/getCustomizationModule.tsx
  • extensions/default/src/getSopClassHandlerModule.js
  • platform/app/public/customizations/index.html
  • platform/app/public/customizations/split/enableNewSplit.jsonc
  • platform/app/public/customizations/split/scoutSeries.jsonc
  • platform/core/src/services/CustomizationService/CustomizationService.function.test.ts
  • platform/core/src/services/CustomizationService/CustomizationService.ts
  • platform/core/src/services/CustomizationService/expression/compiler.ts
  • platform/core/src/services/CustomizationService/expression/expression.test.ts
  • platform/core/src/services/CustomizationService/expression/index.ts
  • platform/core/src/services/CustomizationService/expression/parser.ts
  • platform/core/src/services/CustomizationService/expression/tokenizer.ts
  • platform/core/src/services/DisplaySetService/DisplaySetService.test.ts
  • platform/core/src/services/DisplaySetService/DisplaySetService.ts
  • platform/core/src/services/DisplaySetService/displaySetStore.test.ts
  • platform/core/src/services/DisplaySetService/displaySetStore.ts
  • platform/core/src/services/DisplaySetService/normalizeSplitRules.ts
  • platform/core/src/types/DisplaySet.ts
  • platform/docs/docs/platform/services/customization-service/displaySetSplitting.md

Comment thread platform/core/src/services/CustomizationService/expression/expression.test.ts Outdated
@cypress

cypress Bot commented Jul 15, 2026

Copy link
Copy Markdown

Viewers    Run #6549

Run Properties:  status check passed Passed #6549  •  git commit 2a37b6e241: Lock
Project Viewers
Branch Review feat/customization-use-metadata-display-set
Run status status check passed Passed #6549
Run duration 01m 54s
Commit git commit 2a37b6e241: Lock
Committer Bill Wallace
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 28
View all changes introduced in this branch ↗︎

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