Skip to content

feat: Add support for labelmap seg images in any supported tsuid (also for compressed bitmap)#5806

Open
wayfarer3130 wants to merge 28 commits into
masterfrom
feat/load-seg-images
Open

feat: Add support for labelmap seg images in any supported tsuid (also for compressed bitmap)#5806
wayfarer3130 wants to merge 28 commits into
masterfrom
feat/load-seg-images

Conversation

@wayfarer3130

@wayfarer3130 wayfarer3130 commented Feb 10, 2026

Copy link
Copy Markdown
Contributor

Context

This PR adds integration with the cs3d adapter changes that provide an image loader instead of providing a full binary segmentation object. That results in significantly faster loading and saving of segmentations.

cs3d_ref: fix/use-imageLoader-for-seg

Changes & Results

Testing

[] Save → restore: Create/edit a segmentation, save it, reload, and confirm it restores correctly.
[] Download: Download a saved segmentation and confirm the output is an RLE-compressed labelmap.
[] Backward compat: Loading existing/older segmentations still works.

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

    • Configurable segmentation store modes (labelmap or bitmap) with optional transfer syntax encoding.
    • Added a default segmentation customization module hook.
  • Improvements

    • Enhanced SEG multiframe loading, imageId expansion, and metadata readiness.
    • SEG overlays/representations now wait for segmentation readiness to avoid missing rendering.
    • SEG viewport now shows the referenced grayscale display set alongside the segmentation.
    • Segmentation label tool configuration now comes from customization settings.
  • Updates

    • Improved DICOM Part 10 writing and local WADO-RS “naturalized” dataset handling.
    • Metadata image-id normalization now supports ?frame= lookups.
    • Updated core DICOM processing library to dcmjs 0.52.0.

Greptile Summary

This PR refactors DICOM SEG loading to use a per-frame image-loader approach (via createFromDicomSegImageId) instead of downloading the full binary buffer, enabling support for any transfer syntax including RLE-compressed labelmaps. It also fixes the ordering of addOrUpdateSegmentation vs SEGMENTATION_LOADING_COMPLETE broadcast, adds a wait-and-timeout mechanism in the viewport service so the representation is applied only after the segmentation is in Cornerstone state, and upgrades dcmjs to 0.52.0.

  • SEG loading rewrite: _loadSegments now resolves frame-level imageIds through the datasource and passes them to the new adapter API, removing the dependency on fetching the full DICOM buffer upfront.
  • Race-condition fix: addOrUpdateSegmentation is now called before _broadcastEvent(SEGMENTATION_LOADING_COMPLETE), ensuring Cornerstone state is populated before any subscriber attempts to attach a representation.
  • New utilities: dicomWriter.ts centralises DICOM Part-10 writing with fragmentMultiframe: false (one fragment per frame), runtime property non-enumeration helpers, and a getDatasetTransferSyntaxUID lookup; covered by new unit tests.

Confidence Score: 4/5

The critical race condition that previously silently prevented representations from being attached on initial SEG load is now fixed by reordering addOrUpdateSegmentation before the broadcast; an async ordering subtlety in the viewport service subscriber (settle() resolving the outer promise before applyRepresentation() completes) flagged in a prior round remains.

The core ordering bug is fixed and the indefinitely-hanging promise path is addressed with a timeout. The remaining async sequencing issue means _addOverlayRepresentations can return while the first SEG's representation is still being attached, which could affect downstream viewport setup code that assumes overlays are fully applied.

extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts — async subscriber callback and settle() ordering; extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts — duplicate constant and dead headers argument.

Important Files Changed

Filename Overview
extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts Major rewrite of SEG loading: replaces buffer fetch with per-frame imageId resolution; adds helper functions for metadata registration and frame expansion. Duplicate LABELMAP_SEG_SOP_CLASS_UID constant defined locally when it is already exported from segmentationConfig.ts.
extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts Critical ordering fix: addOrUpdateSegmentation moved before _broadcastEvent(SEGMENTATION_LOADING_COMPLETE). Guards added for missing representation and missing labelmap data.
extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts Adds an async wait-and-timeout wrapper for SEG overlays so applyRepresentation() is deferred until SEGMENTATION_LOADING_COMPLETE fires. settle() still resolves the outer promise before applyRepresentation() completes (noted in previous thread).
extensions/default/src/utils/dicomWriter.ts New utility centralising DICOM Part-10 writing with fragmentMultiframe:false, non-enumerable property helpers, and transfer syntax detection. Well-tested.
extensions/default/src/utils/registerNaturalizedDatasetForLocalWadouri.js New utility for registering generated/saved DICOM datasets locally as wadouri blobs, with proper frame-qualified imageId mapping and metadata registration.
extensions/cornerstone-dicom-seg/src/utils/segmentationConfig.ts New module exporting SOP Class UID constants, parser-type resolution, and save-options helpers. Logic is clear and correctly separated from extension default customizations.
platform/core/src/classes/MetadataProvider.ts Introduces baseImageURIForMetadata to normalise imageIds for metadata lookups, adding ?frame= support alongside existing &frame= stripping. Clean refactor.
extensions/default/src/utils/appendFrameQueryToImageId.js New utility for appending frame query parameters; uses a naive string split that can drop query parameters appearing after frame= in the URL.

Comments Outside Diff (1)

  1. extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts, line 216-221 (link)

    P1 Event listener is never removed

    The SEGMENTATION_LOAD_PROGRESS listener is registered every time _loadSegments runs but never cleaned up. Loading N SEG display sets accumulates N listeners, so each subsequent progress tick fires N broadcasts of SEGMENT_LOADING_COMPLETE, causing incorrect progress UI and a growing memory leak.

    Then add eventTarget.removeEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, onProgress); in a finally block (or right after the await).

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts
    Line: 216-221
    
    Comment:
    **Event listener is never removed**
    
    The `SEGMENTATION_LOAD_PROGRESS` listener is registered every time `_loadSegments` runs but never cleaned up. Loading N SEG display sets accumulates N listeners, so each subsequent progress tick fires N broadcasts of `SEGMENT_LOADING_COMPLETE`, causing incorrect progress UI and a growing memory leak.
    
    
    
    Then add `eventTarget.removeEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, onProgress);` in a `finally` block (or right after the `await`).
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts, line 591-615 (link)

    P1 SEGMENTATION_LOADING_COMPLETE fired before segmentation enters Cornerstone state

    _broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, ...) is called at line 591, but addOrUpdateSegmentation(seg) — which calls cstSegmentation.addSegmentations — isn't called until line 615. The new subscriber in CornerstoneViewportService fires applyRepresentation() synchronously inside the event callback, which immediately calls addSegmentationRepresentation. That function's new guard (getSegmentation(segmentationId)cstSegmentation.state.getSegmentation(...)) finds nothing and returns early with a warning. Once the broadcast returns, step 615 finally adds the segmentation to Cornerstone state — but the representation callback has already run and bailed out silently. No other mechanism re-triggers addSegmentationRepresentation after addOrUpdateSegmentation, so the SEG overlay never appears in the viewport on fresh load.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
    Line: 591-615
    
    Comment:
    **`SEGMENTATION_LOADING_COMPLETE` fired before segmentation enters Cornerstone state**
    
    `_broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, ...)` is called at line 591, but `addOrUpdateSegmentation(seg)` — which calls `cstSegmentation.addSegmentations` — isn't called until line 615. The new subscriber in `CornerstoneViewportService` fires `applyRepresentation()` synchronously inside the event callback, which immediately calls `addSegmentationRepresentation`. That function's new guard (`getSegmentation(segmentationId)``cstSegmentation.state.getSegmentation(...)`) finds nothing and returns early with a warning. Once the broadcast returns, step 615 finally adds the segmentation to Cornerstone state — but the representation callback has already run and bailed out silently. No other mechanism re-triggers `addSegmentationRepresentation` after `addOrUpdateSegmentation`, so the SEG overlay never appears in the viewport on fresh load.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts:17
`LABELMAP_SEG_SOP_CLASS_UID` is already exported from `segmentationConfig.ts` (which this file already imports from). Redefining it as a local constant creates a second source of truth that could silently drift if one is updated without the other.

```suggestion
// Re-use the exported constant from segmentationConfig to avoid duplication.
```

### Issue 2 of 3
extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts:365-370
`_loadSegments` no longer destructures or uses `headers` — it was removed from the parameter list when the implementation switched from a buffer-fetch to the image-loader approach. Passing `headers` here is dead code; it will be silently swallowed by the object destructuring.

```suggestion
        await _loadSegments({
          extensionManager,
          servicesManager,
          segDisplaySet,
        });
```

### Issue 3 of 3
extensions/default/src/utils/appendFrameQueryToImageId.js:4-9
The chained `.split('&frame=')[0].split('?frame=')[0]` approach truncates everything after `frame=` in the query string. A URL like `wadouri:blob:...?type=WADO&frame=1&quality=high` becomes `wadouri:blob:...?type=WADO`, silently dropping `quality=high`. The more robust implementation in `segLocalImageIds.ts` (`stripFrameFromImageId`) filters only the `frame` key while preserving all other parameters — consider reusing it here.

```suggestion
export function appendFrameQueryToImageId(baseImageId, frame) {
  const queryIndex = baseImageId.indexOf('?');
  let withoutFrame;
  if (queryIndex === -1) {
    withoutFrame = baseImageId;
  } else {
    const basePath = baseImageId.slice(0, queryIndex);
    const rebuiltQuery = baseImageId
      .slice(queryIndex + 1)
      .split('&')
      .filter(param => param.split('=')[0] !== 'frame')
      .join('&');
    withoutFrame = rebuiltQuery ? `${basePath}?${rebuiltQuery}` : basePath;
  }
  const separator = withoutFrame.includes('?') ? '&' : '?';
  return `${withoutFrame}${separator}frame=${frame}`;
}
```

Reviews (22): Last reviewed commit: "Merge branch 'master' into feat/load-seg..." | Re-trigger Greptile

@netlify

netlify Bot commented Feb 10, 2026

Copy link
Copy Markdown

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit f81c8b4
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a3befd56f7b140008a5aecc
😎 Deploy Preview https://deploy-preview-5806--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.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts
@cypress

cypress Bot commented Feb 10, 2026

Copy link
Copy Markdown

Viewers    Run #6424

Run Properties:  status check passed Passed #6424  •  git commit f81c8b4333: Debug fixes
Project Viewers
Branch Review feat/load-seg-images
Run status status check passed Passed #6424
Run duration 01m 50s
Commit git commit f81c8b4333: Debug fixes
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 ↗︎

Comment thread extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts Outdated
Comment thread package.json Outdated
@wayfarer3130 wayfarer3130 changed the title [WIP]: Add support for loading seg images from imageLoader [WIP]: Add support for labelmap seg images in any supported tsuid (also for compressed bitmap) Apr 16, 2026
@wayfarer3130 wayfarer3130 changed the title [WIP]: Add support for labelmap seg images in any supported tsuid (also for compressed bitmap) feat: Add support for labelmap seg images in any supported tsuid (also for compressed bitmap) Apr 16, 2026
@jbocce jbocce self-requested a review May 19, 2026 01:47
@jbocce

jbocce commented May 19, 2026

Copy link
Copy Markdown
Collaborator

@wayfarer3130 please add details for testing this. Thank you.

@jbocce

jbocce commented May 19, 2026

Copy link
Copy Markdown
Collaborator

@claude review

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, reopen this pull request to trigger a review.

Comment thread extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts Outdated
Comment thread platform/app/public/config/default.js

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, reopen this pull request to trigger a review.

Comment thread extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts Outdated

@fedorov fedorov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI-Assisted Code Review

How this review was generated: This review was produced using Claude Code (model: claude-sonnet-4-6, Anthropic, May 2026). The review process had two passes:

  1. Diff review — the full PR diff (2,621 lines, 39 files) was analyzed for correctness, style, and known anti-patterns.
  2. Codebase analysis — the branch was checked out locally at /Users/af61/github/Viewers and ~160 targeted file reads and greps were performed across the full monorepo to assess cross-cutting impact on unchanged code.

All findings were verified against the actual source. Sections marked "Cleared" below were flagged in the diff pass but found safe after full-codebase inspection.


Overview

The PR replaces the ArrayBuffer-based SEG loading path with a per-frame imageId approach that delegates pixel data loading to the Cornerstone image loader. Key changes:

  • New imageId-based API for adaptersSEG.Cornerstone3D.Segmentation.createFromDICOMSegBuffer
  • Support for the labelmap SEG SOP class (1.2.840.10008.5.1.4.1.1.66.7) in addition to bitmap
  • Configurable store encoding (labelmap vs bitmap, transfer syntax) via customizationService
  • OHIF runtime properties (imageId, wadoRoot, frameNumber, etc.) made non-enumerable to prevent dcmjs from writing them into stored DICOM
  • Centralized DICOM serialization in a new dicomWriter.ts utility
  • dcmjs bumped from 0.49.4 → 0.51.1 across the monorepo

The architectural direction is sound and several pre-existing bugs are fixed. However there is one critical sequencing issue that would likely prevent the primary feature from working.


Critical Issues

1. SEGMENTATION_LOADING_COMPLETE fires before segmentation is in state — null-guard silently drops overlay

This is the most significant issue, found via full codebase analysis. In SegmentationService.ts, SEGMENTATION_LOADING_COMPLETE is broadcast (line ~587) before addOrUpdateSegmentation is called (line ~611), which is what actually puts the segmentation into state.

The new subscriber in CornerstoneViewportService.ts fires applyRepresentation() immediately on the event. That calls addSegmentationRepresentationgetSegmentation(segmentationId). Because the segmentation isn't in state yet at that point, the null-guard introduced by this same PR at line 302 triggers — logs a warning and silently returns without adding the overlay representation. The SEG labelmap is parsed and loaded, but never applied to the viewport.

Fix: Move the SEGMENTATION_LOADING_COMPLETE broadcast to after addOrUpdateSegmentation completes, or restructure the subscriber to wait until the segmentation is in state before calling addSegmentationRepresentation.

2. Promise in addOverlayRepresentationForDisplaySet never resolves on SEG load failure

If _loadSegments throws (no imageId, adapter error, missing referenced display set), SEGMENTATION_LOADING_COMPLETE is never fired. The Promise returned by addOverlayRepresentationForDisplaySet never resolves or rejects, so _addOverlayRepresentations stalls indefinitely. In practice this blocks the VIEWPORT_DATA_CHANGED broadcast but not viewport rendering (since viewport.setStack completes before overlay setup is awaited). Still, any downstream update dependent on that broadcast is silently dropped with no way to recover short of a page reload. No error event subscription, timeout, or rejection path is present.


Issues Flagged in Diff Pass, Cleared by Codebase Analysis

frameIndexframeNumber rename: Not a breaking change. MetadataProvider.getUIDsFromImageID always overrides the stored field with the URL-parsed frame value; the stored field name was always ignored.

baseImageURIForMetadata coverage: Correct and consistent. The new function strips both ?frame= and &frame=, fixing an inconsistency in the old code that only stripped &frame=. Keys remain consistent within a session.

getImageIdsForInstance behavior change: Correct fix. The old code always used &frame= even for URLs with no existing query params; appendFrameQueryToImageId correctly chooses ? vs &. No callers depended on the old behavior.

displaySets={[referencedDisplaySet, segDisplaySet]}: Correct fix. OHIFCornerstoneViewport uses only viewportData.data[0].imageIds for the stack; SEG is picked up by _processExtraDisplaySetsForViewport as an overlay. No double rendering.

Non-enumerable frameNumber: Safe. All access patterns use direct property access (destructuring, dot notation) — no code uses Object.keys, JSON.stringify, or spread on combined frame instances expecting frameNumber.


Medium Issues

3. tmtv RT export not updated

tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js still calls dcmjs.data.datasetToBlob directly without makeExistingPropertiesNonEnumerable. Any OHIF runtime properties that remain enumerable on RT export instances will be serialized as DICOM tags, potentially corrupting RT output. This is pre-existing, but the PR introduces the fix pattern everywhere else and misses this file.

4. Blob URL memory leak in registerNaturalizedDatasetForLocalWadouri

dicomImageLoader.wadouri.fileManager.add(blob) registers a blob URL that is never revoked. Each report store/download/clipboard operation accumulates a blob in memory for the session lifetime. No fileManager.remove() call exists anywhere in the codebase. For long sessions with many reports this could be meaningful.

5. Duplicate getDatasetTransferSyntaxUID function

Defined privately in both dicomWriter.ts and DicomWebDataSource/index.ts with slightly different fallback logic (the latter falls back to EXPLICIT_VR_LITTLE_ENDIAN; the former returns undefined). The DicomWebDataSource version should import from dicomWriter.ts to avoid future divergence.

6. Dual transferSyntaxUID / transferSyntaxUid keys in segmentationConfig.ts

options.transferSyntaxUID = transferSyntaxUID;
options.transferSyntaxUid = transferSyntaxUID;  // camelCase inconsistency

This is clearly a workaround for an API inconsistency in @cornerstonejs/adapters. Without a comment referencing the upstream issue or PR, future readers will remove one key as an obvious duplicate and silently break one code path.

7. LABELMAP_SEG_SOP_CLASS_UID defined twice

Defined at the top of getSopClassHandlerModule.ts and again in segmentationConfig.ts. The local definition in getSopClassHandlerModule.ts is dead code since getSegmentationParserType is imported from segmentationConfig.ts.

8. registerNaturalizedDatasetForLocalWadouri.js formatting

Every line in this new file is separated by an extra blank line — the entire file is double-spaced. Likely an editor/diff artifact. Makes the file hard to read and should be cleaned up before merge.


Minor / Nits

  • Verbose console.info in production paths_logSegImageIds dumps full imageId arrays on every SEG load; registerNaturalizedDatasetForLocalWadouri logs blob byte size and all frame imageIds. Useful for debugging, noisy in production.
  • PR checklist not filled out — all checkboxes use []; tested OS/Node/Browser fields are blank.
  • @cornerstonejs/adapters 4.22.8 still depends on dcmjs 0.49.4 — per bun.lock, adapters may bundle their own copy alongside OHIF's 0.51.1. Worth verifying the adapters' behavior is compatible with the API surface used in dicomWriter.ts.

What's Done Well

  • Event listener cleanuptry/finally around SEGMENTATION_LOAD_PROGRESS correctly fixes a pre-existing leak where N SEG loads accumulated N persistent listeners.
  • Non-enumerable runtime properties — clean, surgical fix preventing dcmjs from writing OHIF bookkeeping fields into stored DICOM output.
  • dicomWriter.ts centralization — single place for dcmjs serialization with consistent options and transfer syntax preservation; a clear improvement over scattered ad-hoc datasetToBlob calls.
  • _getSegDataSource fallback logic — correctly prefers dicomlocal for local-scheme URLs, falls back to the active data source.
  • appendFrameQueryToImageId — fixes a real bug where &frame= was always used regardless of whether the base URL already had query params.
  • Null-guards in addOrUpdateSegmentation and determineViewportAndSegmentationType — reasonable defensive programming; the warn log in each case makes skips visible rather than truly silent.
  • SEG viewport stack fix — passing [referencedDisplaySet, segDisplaySet] instead of just [segDisplaySet] is a meaningful and correct fix; the old code left the viewport with only derived labelmap imageIds as the primary stack, which are not displayable as grayscale images.

Summary

The critical issue is the event ordering in SegmentationService.ts: SEGMENTATION_LOADING_COMPLETE is broadcast before the segmentation is in state, so the new CornerstoneViewportService subscriber calls addSegmentationRepresentation too early, hits the null-guard introduced by this same PR, and silently exits — meaning the labelmap overlay is never applied. This would be caught only by end-to-end testing; the null-guard makes it invisible as a code-level error.

@coderabbitai

coderabbitai Bot commented Jun 12, 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

This PR refactors SEG multiframe loading to use imageId-based parsing via createFromDicomSegImageId, introduces a shared DICOM Part 10 writing layer with transfer-syntax preservation and non-enumerable runtime property guards, adds a customization-driven segmentation mode/label/transfer-syntax system, hardens segmentation service state transitions with async deferred overlay application, and bumps dcmjs to 0.52.0 across all workspace packages.

Changes

SEG Loading, DICOM Writing, and Workspace Infrastructure

Layer / File(s) Summary
Workspace manifest and pnpm config
pnpm-workspace.yaml, platform/app/package.json, platform/core/package.json, extensions/*/package.json
All packages bump dcmjs to 0.52.0; pnpm-workspace.yaml updates the override pin; platform/app drops three postcss devDependencies; extensions/default switches @ohif/* peers to workspace:*.
DICOM Part 10 writing utilities and imageId normalization
extensions/default/src/utils/dicomWriter.ts, extensions/default/src/utils/appendFrameQueryToImageId.js, platform/core/src/utils/imageIdToURI.js, platform/core/src/classes/MetadataProvider.ts, platform/core/src/utils/combineFrameInstance.ts
dicomWriter.ts introduces DICOM_WRITE_OPTIONS, non-enumerable runtime-property helpers, transfer-syntax extraction/application, and public Part 10 writing APIs. baseImageURIForMetadata normalizes imageIds for metadata lookup. MetadataProvider adopts baseImageURIForMetadata and gains ?frame= support. combineFrameInstance marks _parentInstance and frameNumber non-enumerable.
Naturalized dataset local wadouri registration
extensions/default/src/utils/registerNaturalizedDatasetForLocalWadouri.js
registerNaturalizedDatasetForLocalWadouri converts a dataset to a DICOM blob, stores it in the wadouri file manager, normalizes NumberOfFrames, registers UID-to-imageId mappings per frame, and makes existing dataset properties non-enumerable. A batch wrapper registerNaturalizedDatasetsForLocalWadouri applies this to arrays.
Data source and storage command integration
extensions/default/src/DicomLocalDataSource/index.js, extensions/default/src/DicomWebDataSource/index.ts, extensions/default/src/commandsModule.ts
DicomLocalDataSource adopts setNonEnumerableInstanceProperty, datasetToDicomBlob, and appendFrameQueryToImageId for multiframe imageId construction. DicomWebDataSource preserves transfer syntax via getDatasetTransferSyntaxUID and uses writeDicomDictToPart10Buffer. The default commandsModule registers naturalized datasets, uses datasetToDicomBlob for download/copy, and applies wadoRoot non-enumerably.
Segmentation customization contracts and defaults
extensions/cornerstone-dicom-seg/src/utils/segmentationConfig.ts, extensions/cornerstone-dicom-seg/src/customizations/segmentationCustomization.ts, extensions/cornerstone-dicom-seg/src/getCustomizationModule.ts, extensions/cornerstone-dicom-seg/src/index.tsx, extensions/cornerstone-dicom-seg/src/commandsModule.ts, extensions/cornerstone/src/commandsModule.ts, platform/app/public/config/default.js
segmentationConfig.ts adds SOP Class UID constants, SegmentationMode type, and getSegmentationParserType/getSegmentationSaveOptions helpers. segmentationCustomization.ts defines defaults and types. The customization module is registered on the extension. Save options are injected from customizationService. The Cornerstone commandsModule reads segmentLabel config from customizationService. default.js sets labelmap + RLE Lossless as the default store mode.
SEG imageId frame utilities
extensions/cornerstone-dicom-seg/src/utils/segLocalImageIds.ts
Adds four exported helpers: isLocalSchemeImageId, stripFrameFromImageId, appendFrameToImageId, and getFrameIndexFromImageId.
SEG multiframe loading pipeline refactor
extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts, extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx
getSopClassHandlerModule gains helpers for imageId normalization, per-frame WADO-RS expansion, SEG frame count, and metadata registration. _loadSegments resolves segImageIdStr from the data source, prefers cached referencedDisplaySet.imageIds, derives frameImageIds, ensures metadata availability, forwards SEGMENTATION_LOAD_PROGRESS events, and calls createFromDicomSegImageId with parserType from getSegmentationParserType. OHIFCornerstoneSEGViewport passes [referencedDisplaySet, segDisplaySet] and expands useCallback deps.
Segmentation service state guards and async overlay deferred apply
extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts, extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts, extensions/cornerstone/src/utils/segmentationHandlers.ts
addSegmentationRepresentation aborts early when segmentation is absent. createSegmentationForSEGDisplaySet uses a fallback chain for imageIds. addOrUpdateSegmentation skips insert when representation is missing. CornerstoneViewportService defers addSegmentationRepresentation for SEG until SEGMENTATION_LOADING_COMPLETE. segmentationHandlers re-fetches the segmentation before calling addOrUpdateSegmentation.
Report dialog series-select refactor
extensions/default/src/customizations/reportDialogCustomization.tsx
Adds NEW_SERIES_SELECT_VALUE sentinel and SeriesOption type to decouple Radix Select value from internal selectedSeries state. seriesOptions is rewritten with typed entries, handleSeriesChange maps selectValue back to selectedSeries, and both rendering branches adopt the new key/value fields.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over OHIFCornerstoneSEGViewport,segmentationService: SEG Load and Deferred Overlay Application
  end
  participant OHIFCornerstoneSEGViewport
  participant getSopClassHandlerModule
  participant MetadataProvider
  participant adaptersSEG
  participant segmentationService
  participant CornerstoneViewportService

  OHIFCornerstoneSEGViewport->>getSopClassHandlerModule: loadSEG(segDisplaySet)
  getSopClassHandlerModule->>getSopClassHandlerModule: _getSegDataSource → segImageIdStr
  getSopClassHandlerModule->>getSopClassHandlerModule: _resolveFrameImageIds → frameImageIds
  getSopClassHandlerModule->>MetadataProvider: addImageIdToUIDs per frame
  getSopClassHandlerModule->>segmentationService: subscribe SEGMENTATION_LOAD_PROGRESS
  getSopClassHandlerModule->>adaptersSEG: createFromDicomSegImageId(segImageIdForMetadata, frameImageIds, parserType)
  adaptersSEG-->>getSopClassHandlerModule: labelMaps
  getSopClassHandlerModule->>segmentationService: unsubscribe + emit SEGMENTATION_LOADING_COMPLETE

  CornerstoneViewportService->>segmentationService: getSegmentation(segmentationId)
  alt segmentation already present
    CornerstoneViewportService->>segmentationService: addSegmentationRepresentation(immediate)
  else segmentation not yet loaded
    CornerstoneViewportService->>segmentationService: subscribe SEGMENTATION_LOADING_COMPLETE
    segmentationService-->>CornerstoneViewportService: event(segDisplaySetInstanceUID match)
    CornerstoneViewportService->>segmentationService: addSegmentationRepresentation(deferred)
    CornerstoneViewportService->>segmentationService: unsubscribe
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

ohif-integration

Suggested reviewers

  • sedghi
  • dan-rukas

🐇 Hops through frames, one by one,
SEG metadata baked and done!
Non-enumerable keys hide away,
So dcmjs won't go astray.
From labelmap to blob we go—
RLE Lossless, quite the show! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning PR description is incomplete: Context section is minimal, Changes & Results section is empty, Testing section lacks detail, and all PR checklist items are unchecked. Expand the description with specific implementation details, document changes made and their effects, provide concrete testing procedures and results, and complete all checklist items including PR title format, code documentation status, and tested environment details.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and accurately describes the main change: adding support for labelmap SEG images in any supported transfer syntax, including compressed bitmap formats.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/load-seg-images

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
extensions/default/src/customizations/reportDialogCustomization.tsx (1)

25-49: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

minSeriesNumber is destructured but missing from ReportDialogProps (TS compile break).

At Line 44, minSeriesNumber is read from props, but ReportDialogProps (Line 25-38) does not declare it. This creates a type error and breaks type-safe compilation.

Suggested fix
 type ReportDialogProps = {
   dataSources: DataSource[];
   modality?: string;
   predecessorImageId?: string;
+  minSeriesNumber?: number;
   hide: () => void;
   onSave: (data: {
     reportName: string;
     dataSource: string | null;
     series: string | null;
     priorSeriesNumber: number;
   }) => void;
   onCancel: () => void;
   enableDownload?: boolean;
 };
🤖 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/customizations/reportDialogCustomization.tsx` around
lines 25 - 49, ReportDialog destructures minSeriesNumber but ReportDialogProps
doesn't declare it; add an optional numeric property minSeriesNumber?: number to
the ReportDialogProps type (or change to required number if intended), and keep
the existing default in the ReportDialog function signature; update any callers
if needed to satisfy the new prop contract so the TypeScript compile error is
resolved (refer to ReportDialogProps and the ReportDialog function).
extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx (1)

83-90: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle unresolved referenced display set before rendering overlay stack.

displaySetService.getDisplaySetByUID(...) can return undefined even when referencedDisplaySetInstanceUID exists. On that path, Line 104 passes undefined into displaySets, and Line 260 dereferences referencedDisplaySet.displaySetInstanceUID, which can crash the viewport. Add an explicit guard right after Line 83 and short-circuit (or invoke the same missing-reference handler) when referencedDisplaySet is absent.

Suggested patch
   const referencedDisplaySet = displaySetService.getDisplaySetByUID(
     referencedDisplaySetInstanceUID
   );
+  if (!referencedDisplaySet) {
+    const missingReferenceDisplaySetHandler = customizationService.getCustomization(
+      'missingReferenceDisplaySetHandler'
+    );
+    if (typeof missingReferenceDisplaySetHandler === 'function') {
+      missingReferenceDisplaySetHandler();
+    }
+    return null;
+  }

   const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
     referencedDisplaySet,
     segDisplaySet
   );

Also applies to: 104-104, 260-261

🤖 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/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx`
around lines 83 - 90, After calling
displaySetService.getDisplaySetByUID(referencedDisplaySetInstanceUID), add an
explicit guard for referencedDisplaySet === undefined and short-circuit
rendering (or call the component's existing missing-reference handler) instead
of continuing; this prevents passing undefined into displaySets and avoids
dereferencing referencedDisplaySet.displaySetInstanceUID later. Update the code
around the getDisplaySetByUID call and the
_getReferencedDisplaySetMetadata(referencedDisplaySet, segDisplaySet) usage so
that when referencedDisplaySet is missing you return early (or invoke the same
missing-reference handler used elsewhere) and do not call
_getReferencedDisplaySetMetadata or access
referencedDisplaySet.displaySetInstanceUID.
extensions/cornerstone/src/commandsModule.ts (1)

1995-2002: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve background in the disabled tool config too.

When enabledByDefault is false, the registered SegmentLabel tool drops segmentLabelConfig.background, so the same customization renders differently depending on the initial enabled state. Include background in the disabled branch as well.

Suggested fix
       disabledTools.push({
         toolName: toolNames.SegmentLabel,
         configuration: {
           hoverTimeout: segmentLabelConfig?.hoverTimeout ?? 1,
           color: segmentLabelConfig?.labelColor,
+          background: segmentLabelConfig?.background,
         },
       });
🤖 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/cornerstone/src/commandsModule.ts` around lines 1995 - 2002, The
disabled-tools branch drops segmentLabelConfig.background causing inconsistent
rendering; update the object pushed into disabledTools (the entry with toolName:
toolNames.SegmentLabel) to include background: segmentLabelConfig?.background
alongside hoverTimeout and labelColor so the disabled configuration mirrors the
enabled registration (ensure the key is added to the configuration object
referenced by disabledTools, using the same nullable access pattern as
hoverTimeout/labelColor).
extensions/default/src/DicomLocalDataSource/index.js (1)

147-169: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing per-frame metadata registration for multiframe instances.

DicomLocalDataSource registers metadata once per instance (line 164-169), but multiframe instances require per-frame registration like DicomWebDataSource (lines 565-580) and registerNaturalizedDatasetForLocalWadouri (lines 91-97). Without per-frame metadata mappings, imageIds with frame queries (e.g., wadouri:...?frame=2) won't resolve correctly.

Additionally, line 168 incorrectly uses the instance index within the series (index + 1) as the frame number, rather than iterating over the frames within each instance.

🔧 Proposed fix to register per-frame metadata
 aSeries.instances.forEach((instance, index) => {
   const {
     url: imageId,
     StudyInstanceUID,
     SeriesInstanceUID,
     SOPInstanceUID,
   } = instance;
   
   setNonEnumerableInstanceProperty(instance, 'imageId', imageId);
   makeExistingPropertiesNonEnumerable(instance);
   
+  const numberOfFrames = instance.NumberOfFrames || 1;
+  
-  // Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI.
-  metadataProvider.addImageIdToUIDs(imageId, {
-    StudyInstanceUID,
-    SeriesInstanceUID,
-    SOPInstanceUID,
-    frameNumber: isMultiframe ? index + 1 : 1,
-  });
+  // Register metadata for each frame
+  for (let frame = 1; frame <= numberOfFrames; frame++) {
+    const frameImageId = numberOfFrames > 1 
+      ? appendFrameQueryToImageId(imageId, frame)
+      : imageId;
+    
+    metadataProvider.addImageIdToUIDs(frameImageId, {
+      StudyInstanceUID,
+      SeriesInstanceUID,
+      SOPInstanceUID,
+      frameNumber: numberOfFrames > 1 ? frame : 1,
+    });
+  }
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/default/src/DicomLocalDataSource/index.js` around lines 147 - 169,
The current loop in DicomLocalDataSource registers one mapping per instance
using index+1 as frameNumber, which fails for multiframe instances; update the
aSeries.instances.forEach block so that for multiframe instances you register an
imageId mapping for each actual frame (1..NumberOfFrames) by calling
metadataProvider.addImageIdToUIDs with frameNumber equal to the frame index + 1
for each frame, not the instance index, mirroring the per-frame registration
logic used in DicomWebDataSource and registerNaturalizedDatasetForLocalWadouri;
ensure you still call setNonEnumerableInstanceProperty and
makeExistingPropertiesNonEnumerable for the instance, but add the additional
per-frame registrations when aSeries.instances[0].NumberOfFrames > 1.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@extensions/cornerstone-dicom-seg/src/utils/segLocalImageIds.ts`:
- Around line 5-13: stripFrameFromImageId currently chops everything after the
first occurrence of "?frame=" or "&frame=", which discards other query params;
update stripFrameFromImageId and appendFrameToImageId to only remove or replace
the "frame" query parameter while preserving all other query parameters and the
original separator/ordering. Specifically, in stripFrameFromImageId(imageId:
string) parse the query portion (text after the first '?') into key/value pairs,
drop only the "frame" key, rebuild the query string (empty if no params left)
and return basePath + (rebuiltQuery ? '?' + rebuiltQuery : ''); then change
appendFrameToImageId(baseImageId: string, frame: number) to call the updated
stripFrameFromImageId, then append or set the "frame" param into the existing
query string (using '&' when a query exists) so other loader options or
transfer-syntax hints remain intact.

In
`@extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts`:
- Around line 1310-1323: The wait for SEGMENTATION_LOADING_COMPLETE in
_addOverlayRepresentations() can hang forever; change the Promise returned by
the block that subscribes via segmentationService.subscribe to include a bounded
timeout and an explicit failure cleanup: start a timer (e.g., 10s) when
subscribing, and if the timeout fires or a segmentation-load-failure event
occurs, unsubscribe using the captured unsubscribe(), reject or resolve the
Promise (resolve to a no-op fallback) and ensure applyRepresentation() is not
called; keep using segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE and
the segmentationId check, but add handling for a
SEGMENTATION_LOADING_FAILED/timeout path and ensure
setViewportData()/updateViewport() can proceed by resolving instead of leaving
the Promise pending.

In `@extensions/default/src/DicomLocalDataSource/index.js`:
- Around line 231-233: getImageIdsForInstance currently returns undefined when
baseImageId is falsy, which causes callers that push its result directly into
the imageIds array to inject undefined; change getImageIdsForInstance to return
an empty array ([]) instead of undefined when baseImageId is missing so callers
(the code that pushes/concat results into imageIds) will safely add no entries,
and verify any use of getImageIdsForInstance (the call sites that push/concat
its return) expects an array rather than possibly undefined.

---

Outside diff comments:
In
`@extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx`:
- Around line 83-90: After calling
displaySetService.getDisplaySetByUID(referencedDisplaySetInstanceUID), add an
explicit guard for referencedDisplaySet === undefined and short-circuit
rendering (or call the component's existing missing-reference handler) instead
of continuing; this prevents passing undefined into displaySets and avoids
dereferencing referencedDisplaySet.displaySetInstanceUID later. Update the code
around the getDisplaySetByUID call and the
_getReferencedDisplaySetMetadata(referencedDisplaySet, segDisplaySet) usage so
that when referencedDisplaySet is missing you return early (or invoke the same
missing-reference handler used elsewhere) and do not call
_getReferencedDisplaySetMetadata or access
referencedDisplaySet.displaySetInstanceUID.

In `@extensions/cornerstone/src/commandsModule.ts`:
- Around line 1995-2002: The disabled-tools branch drops
segmentLabelConfig.background causing inconsistent rendering; update the object
pushed into disabledTools (the entry with toolName: toolNames.SegmentLabel) to
include background: segmentLabelConfig?.background alongside hoverTimeout and
labelColor so the disabled configuration mirrors the enabled registration
(ensure the key is added to the configuration object referenced by
disabledTools, using the same nullable access pattern as
hoverTimeout/labelColor).

In `@extensions/default/src/customizations/reportDialogCustomization.tsx`:
- Around line 25-49: ReportDialog destructures minSeriesNumber but
ReportDialogProps doesn't declare it; add an optional numeric property
minSeriesNumber?: number to the ReportDialogProps type (or change to required
number if intended), and keep the existing default in the ReportDialog function
signature; update any callers if needed to satisfy the new prop contract so the
TypeScript compile error is resolved (refer to ReportDialogProps and the
ReportDialog function).

In `@extensions/default/src/DicomLocalDataSource/index.js`:
- Around line 147-169: The current loop in DicomLocalDataSource registers one
mapping per instance using index+1 as frameNumber, which fails for multiframe
instances; update the aSeries.instances.forEach block so that for multiframe
instances you register an imageId mapping for each actual frame
(1..NumberOfFrames) by calling metadataProvider.addImageIdToUIDs with
frameNumber equal to the frame index + 1 for each frame, not the instance index,
mirroring the per-frame registration logic used in DicomWebDataSource and
registerNaturalizedDatasetForLocalWadouri; ensure you still call
setNonEnumerableInstanceProperty and makeExistingPropertiesNonEnumerable for the
instance, but add the additional per-frame registrations when
aSeries.instances[0].NumberOfFrames > 1.
🪄 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: 79ed8425-84cc-4f91-ac64-296a1ea79516

📥 Commits

Reviewing files that changed from the base of the PR and between 571c2b4 and 599f2b4.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (37)
  • addOns/package.json
  • extensions/cornerstone-dicom-seg/package.json
  • extensions/cornerstone-dicom-seg/src/commandsModule.ts
  • extensions/cornerstone-dicom-seg/src/customizations/segmentationCustomization.ts
  • extensions/cornerstone-dicom-seg/src/getCustomizationModule.ts
  • extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts
  • extensions/cornerstone-dicom-seg/src/index.tsx
  • extensions/cornerstone-dicom-seg/src/utils/segLocalImageIds.ts
  • extensions/cornerstone-dicom-seg/src/utils/segmentationConfig.ts
  • extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx
  • extensions/cornerstone-dicom-sr/package.json
  • extensions/cornerstone-dynamic-volume/package.json
  • extensions/cornerstone/package.json
  • extensions/cornerstone/src/commandsModule.ts
  • extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
  • extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
  • extensions/cornerstone/src/utils/segmentationHandlers.ts
  • extensions/default/package.json
  • extensions/default/src/DicomLocalDataSource/index.js
  • extensions/default/src/DicomWebDataSource/index.ts
  • extensions/default/src/commandsModule.ts
  • extensions/default/src/customizations/reportDialogCustomization.tsx
  • extensions/default/src/utils/appendFrameQueryToImageId.js
  • extensions/default/src/utils/dicomWriter.ts
  • extensions/default/src/utils/registerNaturalizedDatasetForLocalWadouri.js
  • extensions/dicom-pdf/package.json
  • extensions/dicom-video/package.json
  • extensions/measurement-tracking/package.json
  • extensions/test-extension/package.json
  • extensions/tmtv/package.json
  • package.json
  • platform/app/package.json
  • platform/app/public/config/default.js
  • platform/core/package.json
  • platform/core/src/classes/MetadataProvider.ts
  • platform/core/src/utils/combineFrameInstance.ts
  • platform/core/src/utils/imageIdToURI.js

Comment thread extensions/cornerstone-dicom-seg/src/utils/segLocalImageIds.ts
Comment thread extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts Outdated
Comment thread extensions/default/src/DicomLocalDataSource/index.js
@wayfarer3130 wayfarer3130 force-pushed the feat/load-seg-images branch from 599f2b4 to 1324bc5 Compare June 12, 2026 16:08

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/codeql.yml (1)

14-16: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update the PR branch filter to master.

CodeQL is still scoped to pull_request.branches: ["main"], but this PR targets master and the Playwright workflow is already keyed off master. In the current state, CodeQL won't run for normal PRs into the active default branch.

🤖 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 @.github/workflows/codeql.yml around lines 14 - 16, Update the CodeQL
workflow trigger so pull_request is run against the repository's active branch
by changing the pull_request.branches setting from ["main"] to ["master"]; edit
the on: pull_request block in .github/workflows/codeql.yml (the
pull_request.branches array) to include "master" instead of "main" so CodeQL
runs for PRs targeting master.
🧹 Nitpick comments (4)
extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts (1)

420-439: 💤 Low value

Consider logging when falling back to alternative imageId sources.

The fallback cascade silently proceeds through three different sources for imageIds. If the primary source (referencedDisplaySet.imageIds) is unexpectedly empty due to a configuration issue, this silent fallback could mask the root cause and make debugging harder.

Also, backfilling referencedDisplaySet.imageIds on line 438 mutates the display set object, which could cause unexpected side effects if other code expects that property to remain empty.

🤖 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/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts` around
lines 420 - 439, Detect and log which fallback source provided imageIds instead
of silently proceeding: when falling back from referencedDisplaySet.imageIds to
dataSource.getImageIdsForDisplaySet(referencedDisplaySet) and then to
(referencedDisplaySet as {images?})...map(...), emit a warning (include
referencedDisplaySet.id or other identifying fields and the selected source) so
callers can trace unexpected empty primary data; assign the chosen IDs to
segDisplaySet.referencedImageIds as you already do but do not mutate
referencedDisplaySet.imageIds in place—either remove that backfill or only apply
it to a shallow clone/copy if truly required, and ensure logs reflect that no
mutation was performed.
tests/SegmentationPanel.spec.ts (1)

23-29: ⚡ Quick win

Keep segment-count assertions behind getSegmentCount().

These retrying checks fixed the timing issue, but this project prefers count assertions through the page object API. A retrying expect.poll(async () => panel.getSegmentCount()).toBe(1) / toBe(0) keeps the stabilization and avoids coupling the spec to panel.rows.

Based on learnings: In OHIF/Viewers Playwright tests, prefer asserting segment counts via the relevant page object’s getSegmentCount() method and do not replace those assertions with locator-based toHaveCount.

Also applies to: 40-41

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

In `@tests/SegmentationPanel.spec.ts` around lines 23 - 29, Replace the
locator-based count assertions that use panel.rows and toHaveCount with retrying
assertions that call the page object API: use expect.poll(async () =>
panel.getSegmentCount()).toBe(1) for the expectation that one segment exists
(and similarly toBe(0) where checking for zero), and keep the text assertion
using panel.nthSegment(0).locator unchanged; specifically, change the
toHaveCount on panel.rows to a poll on panel.getSegmentCount(), and apply the
same replacement for the assertions referenced at lines 40-41.

Source: Learnings

extensions/default/src/utils/appendFrameQueryToImageId.js (1)

4-9: ⚡ Quick win

Add JSDoc and input validation.

The function lacks documentation of its contract and doesn't validate the frame parameter. Consider adding JSDoc to clarify expected input format and return value, plus basic validation.

📝 Proposed JSDoc and validation
+/**
+ * Appends or updates the frame query parameter in a wadouri imageId.
+ * Removes any existing frame parameter and appends the new frame number.
+ * 
+ * `@param` {string} baseImageId - The base imageId (e.g., 'wadouri:blob:...')
+ * `@param` {number} frame - The frame number (1-based)
+ * `@returns` {string} The imageId with frame parameter appended
+ * `@example`
+ * appendFrameQueryToImageId('wadouri:file.dcm', 2)
+ * // Returns: 'wadouri:file.dcm?frame=2'
+ */
 export function appendFrameQueryToImageId(baseImageId, frame) {
+  if (typeof baseImageId !== 'string' || !baseImageId) {
+    throw new Error('baseImageId must be a non-empty string');
+  }
+  if (typeof frame !== 'number' || frame < 1 || !Number.isInteger(frame)) {
+    throw new Error('frame must be a positive integer');
+  }
+
   const withoutFrame = baseImageId.split('&frame=')[0].split('?frame=')[0];
   const separator = withoutFrame.includes('?') ? '&' : '?';
 
   return `${withoutFrame}${separator}frame=${frame}`;
 }
🤖 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/utils/appendFrameQueryToImageId.js` around lines 4 -
9, Add JSDoc for appendFrameQueryToImageId that documents parameters
(baseImageId: string, frame: number|string expected to be a non-negative integer
or numeric string) and the returned string (image id with appended or replaced
frame query). Add input validation at the start of appendFrameQueryToImageId:
ensure baseImageId is a non-empty string and frame is a finite non-negative
integer (or numeric string coerced to integer); if invalid, throw a TypeError
with a clear message. Keep the existing logic using withoutFrame and separator
unchanged but coerce/validate frame before constructing the return value.
Dockerfile (1)

31-31: ⚡ Quick win

Consider pinning pnpm to the same version as packageManager.

The Dockerfile installs pnpm@11 (major version only) while package.json pins pnpm@11.5.2. This inconsistency could lead to different behavior between local development and Docker builds if pnpm 11.x introduces breaking changes in minor releases.

♻️ Proposed fix
-RUN npm install -g pnpm@11
+RUN npm install -g pnpm@11.5.2
🤖 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 `@Dockerfile` at line 31, The Dockerfile currently installs pnpm with a
floating major version via the RUN npm install -g pnpm@11 instruction; update
that to install the exact version declared in package.json's "packageManager"
field (e.g., pnpm@11.5.2) so Docker builds match local dev; ensure the RUN line
is pinned to the same version string used in package.json and keep them in sync
going forward.
🤖 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 @.github/workflows/build-docs.yml:
- Around line 82-84: The current validation only checks for any .html file in
./coverage-artifact which allows partial artifacts; update the conditional that
validates the downloaded artifact (the shell if that references
./coverage-artifact and the echo with coverage-report-pr and
steps.find_pr_run.outputs.run_id) to assert the presence of the full expected
asset set (e.g., ./coverage-artifact/*.html plus ./coverage-artifact/base.css,
./coverage-artifact/prettify.js, ./coverage-artifact/sorter.js and any other
required coverage assets) before proceeding to the copy step; implement this by
extending the test to verify each required file exists (or a stricter pattern
match) and fail with the same error message if any are missing.

In `@extensions/default/package.json`:
- Around line 20-27: The package.json in the extension is missing the
devDependency declaration for "cross-env" while its scripts (e.g., the "dev" and
"build" npm scripts) reference it; add "cross-env": "7.0.3" to the
"devDependencies" object in extensions/default/package.json (and likewise in
extensions/measurement-tracking/package.json) so the scripts are self-contained
and consistent with other extensions; ensure you update the correct
package.json's "devDependencies" section and run a quick install to verify
scripts still work.

In `@extensions/default/src/utils/appendFrameQueryToImageId.js`:
- Line 5: The current one-liner in appendFrameQueryToImageId.js that builds
withoutFrame via baseImageId.split(...).split(...) can drop other query params;
replace that logic in the appendFrameQueryToImageId function by parsing
baseImageId's query string and removing only the "frame" parameter (e.g., use
URL/URLSearchParams or a precise regex) so other parameters remain untouched,
then reconstruct the URL preserving pathname and remaining search params before
appending the desired &frame= value; target the withoutFrame computation and
baseImageId handling to implement this change.

In `@modes/basic-dev-mode/package.json`:
- Around line 22-27: The scripts in the mode packages (scripts like "dev",
"build", "start", "dev:cornerstone" in the package.json for
modes/basic-dev-mode, modes/basic-test-mode, modes/basic, and
modes/longitudinal) call the cross-env binary but do not declare it locally; add
"cross-env" to each package's devDependencies (e.g., add "cross-env": "^X.Y.Z"
under devDependencies in each package.json), run install so the package has a
local binary, or alternatively change the scripts to invoke the
workspace-installed binary explicitly (e.g., via pnpm -w or a relative path),
ensuring the mode package scripts reliably find cross-env.

In `@platform/app/package.json`:
- Around line 103-104: Remove the duplicate top-level JSON key "productVersion"
found near the end of platform/app/package.json; keep the original declaration
(the one at the top of the file) and delete the later `"productVersion":
"3.4.0",` entry so there is only one productVersion key and the remaining
`"proxy": "http://localhost:8042"` entry stays intact.

In `@platform/cli/src/commands/utils/uninstallNPMPackage.js`:
- Around line 4-6: The execa('pnpm', ['remove', packageName]) call in
uninstallNPMPackage currently catches and logs errors which swallows failures;
change this so uninstall failures propagate: either remove the .catch handler
entirely or rethrow the caught error (throw err) inside the catch, and ensure
callers like removeMode.js can await and handle the rejection. Update the
uninstallNPMPackage function to let the promise reject on failure (or explicitly
throw) so downstream cleanup halts when pnpm remove fails.

In `@platform/core/package.json`:
- Line 21: The package.json entry "sideEffects": "false" uses a string instead
of a boolean; change the sideEffects value in platform/core/package.json from
the string "false" to the boolean false (i.e., sideEffects: false) so bundlers
recognize the package as side-effect-free and enable proper tree-shaking for
`@ohif/core`.

In `@platform/docs/docs/migration-guide/3p12-to-3p13/build-tooling.md`:
- Around line 25-27: The sentence "There is no Webpack fallback. Webpack and all
of its plugins have been removed from the dependency tree; the only supported
bundler is Rspack…" overstates removal; update it to scope the claim to the
app/extension build path (e.g., "There is no Webpack fallback for the
app/extension build. Webpack and its plugins have been removed from the
app/extension build dependency tree; other packages in the repo may still
declare webpack. The only supported bundler for that build path is Rspack…") so
it no longer claims repo-wide removal.
- Around line 402-403: Remove the stray closing MDX tags `</content>` and
`</invoke>` found at the end of the MDX document; open the file and delete those
two tags (and any other non-MDX orphaned tags) so the document ends with valid
MDX content, then run the MDX build/lint to confirm parsing succeeds.

In `@platform/docs/docs/migration-guide/3p12-to-3p13/node-version.md`:
- Around line 10-12: The sentence in node-version.md overstates changes by
claiming "every workspace package's `engines.node` field have been updated";
instead update the wording to be accurate: mention that the repository root
`.node-version` was bumped to 24 and that only specific workspace packages had
their `engines.node` fields updated (e.g., list or say "relevant runtime
packages" or enumerate the updated workspaces), and avoid asserting that
`platform/docs/package.json` or all workspaces were changed.

In `@platform/docs/docs/platform/extensions/index.md`:
- Around line 187-192: Replace the stale generated filename `pluginImport.js`
with the correct `pluginImports.js` in the paragraph that describes the
non-tracked internal import file; update the sentence to read that the final
registration and import of extensions happen inside a non-tracked file
`pluginImports.js` (internal use only) so it matches the surrounding migration
docs and the actual generated filename.

In `@platform/ui-next/package.json`:
- Around line 9-17: The package.json exposes "./tailwind.config" via the
"exports" map but the "files" array does not include tailwind.config.js, so the
file won't be published; update package.json to include "tailwind.config.js" (or
the path you used for the export) in the "files" array so that the exported
entry "./tailwind.config" is present in the published tarball (refer to the
"exports" entry "./tailwind.config" and the "files" array when making the
change).

In `@platform/ui/package.json`:
- Around line 23-24: The package.json scripts "build" and "build:package" use
cross-env but the package does not declare cross-env; add cross-env to this
package's devDependencies (e.g., in package.json add a devDependency entry for
"cross-env" with an appropriate version), so the script "build" has an explicit
local contract rather than relying on a hoisted/transitive binary; after adding,
run the package manager (pnpm) to update the lockfile and verify pnpm --filter
`@ohif/ui` run build works in isolation.

In `@tests/pages/RightPanelPageObject.ts`:
- Around line 227-231: RightPanelPageObject's rows and getSegmentCount use
page.getByTestId('data-row') which matches rows across the whole page; scope
them to the panel root instead. Replace page.getByTestId('data-row') in the rows
property and in getSegmentCount() with a panel-scoped locator (e.g., use an
existing root locator like this.panel or this.root.getByTestId('data-row'); if
none exists, add a panelRoot =
page.getByTestId('segmentation-panel')/page.getByTestId('<panel-testid>') and
use panelRoot.getByTestId('data-row')). Update both the rows field and the
getSegmentCount method to use that panel-scoped locator.

---

Outside diff comments:
In @.github/workflows/codeql.yml:
- Around line 14-16: Update the CodeQL workflow trigger so pull_request is run
against the repository's active branch by changing the pull_request.branches
setting from ["main"] to ["master"]; edit the on: pull_request block in
.github/workflows/codeql.yml (the pull_request.branches array) to include
"master" instead of "main" so CodeQL runs for PRs targeting master.

---

Nitpick comments:
In `@Dockerfile`:
- Line 31: The Dockerfile currently installs pnpm with a floating major version
via the RUN npm install -g pnpm@11 instruction; update that to install the exact
version declared in package.json's "packageManager" field (e.g., pnpm@11.5.2) so
Docker builds match local dev; ensure the RUN line is pinned to the same version
string used in package.json and keep them in sync going forward.

In `@extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts`:
- Around line 420-439: Detect and log which fallback source provided imageIds
instead of silently proceeding: when falling back from
referencedDisplaySet.imageIds to
dataSource.getImageIdsForDisplaySet(referencedDisplaySet) and then to
(referencedDisplaySet as {images?})...map(...), emit a warning (include
referencedDisplaySet.id or other identifying fields and the selected source) so
callers can trace unexpected empty primary data; assign the chosen IDs to
segDisplaySet.referencedImageIds as you already do but do not mutate
referencedDisplaySet.imageIds in place—either remove that backfill or only apply
it to a shallow clone/copy if truly required, and ensure logs reflect that no
mutation was performed.

In `@extensions/default/src/utils/appendFrameQueryToImageId.js`:
- Around line 4-9: Add JSDoc for appendFrameQueryToImageId that documents
parameters (baseImageId: string, frame: number|string expected to be a
non-negative integer or numeric string) and the returned string (image id with
appended or replaced frame query). Add input validation at the start of
appendFrameQueryToImageId: ensure baseImageId is a non-empty string and frame is
a finite non-negative integer (or numeric string coerced to integer); if
invalid, throw a TypeError with a clear message. Keep the existing logic using
withoutFrame and separator unchanged but coerce/validate frame before
constructing the return value.

In `@tests/SegmentationPanel.spec.ts`:
- Around line 23-29: Replace the locator-based count assertions that use
panel.rows and toHaveCount with retrying assertions that call the page object
API: use expect.poll(async () => panel.getSegmentCount()).toBe(1) for the
expectation that one segment exists (and similarly toBe(0) where checking for
zero), and keep the text assertion using panel.nthSegment(0).locator unchanged;
specifically, change the toHaveCount on panel.rows to a poll on
panel.getSegmentCount(), and apply the same replacement for the assertions
referenced at lines 40-41.
🪄 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: 53948896-9daa-42c0-9adc-4f931570491d

📥 Commits

Reviewing files that changed from the base of the PR and between 599f2b4 and 1324bc5.

⛔ Files ignored due to path filters (42)
  • .webpack/resolveConfig.js is excluded by !**/.webpack/**
  • .webpack/rules/cssToJavaScript.js is excluded by !**/.webpack/**
  • .webpack/rules/transpileJavaScript.js is excluded by !**/.webpack/**
  • .webpack/webpack.base.js is excluded by !**/.webpack/**
  • bun.lock is excluded by !**/*.lock
  • extensions/cornerstone-dicom-pmap/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/cornerstone-dynamic-volume/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/cornerstone/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/default/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/dicom-microscopy/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/dicom-pdf/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/dicom-video/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/measurement-tracking/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/test-extension/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/tmtv/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • extensions/usAnnotation/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/basic-dev-mode/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/basic-test-mode/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/basic/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/longitudinal/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/microscopy/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/preclinical-4d/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/segmentation/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/tmtv/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • modes/usAnnotation/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • platform/app/.webpack/rules/extractStyleChunks.js is excluded by !**/.webpack/**
  • platform/app/.webpack/webpack.pwa.js is excluded by !**/.webpack/**
  • platform/app/.webpack/writePluginImportsFile.js is excluded by !**/.webpack/**
  • platform/cli/templates/extension/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • platform/cli/templates/mode/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • platform/core/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • platform/docs/bun.lockb is excluded by !**/bun.lockb
  • platform/docs/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • platform/i18n/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • platform/ui-next/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • platform/ui/.webpack/webpack.prod.js is excluded by !**/.webpack/**
  • platform/ui/a34f9d1faa5f3315.woff2 is excluded by !**/*.woff2
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (107)
  • .circleci/config.yml
  • .github/.dependabot.yaml
  • .github/workflows/build-docs.yml
  • .github/workflows/codeql.yml
  • .github/workflows/playwright.yml
  • .gitignore
  • .netlify/build-deploy-preview.sh
  • .netlify/package.json
  • .node-version
  • .npmrc
  • .scripts/dev.sh
  • Dockerfile
  • addOns/README.md
  • addOns/externals/devDependencies/CHANGELOG.md
  • addOns/externals/devDependencies/package.json
  • addOns/externals/dicom-microscopy-viewer/CHANGELOG.md
  • addOns/externals/dicom-microscopy-viewer/package.json
  • addOns/package.json
  • bunfig.toml
  • bunfig.update-lockfile.toml
  • extensions/cornerstone-dicom-pmap/package.json
  • extensions/cornerstone-dicom-rt/package.json
  • extensions/cornerstone-dicom-seg/package.json
  • extensions/cornerstone-dicom-seg/src/commandsModule.ts
  • extensions/cornerstone-dicom-seg/src/customizations/segmentationCustomization.ts
  • extensions/cornerstone-dicom-seg/src/getCustomizationModule.ts
  • extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts
  • extensions/cornerstone-dicom-seg/src/index.tsx
  • extensions/cornerstone-dicom-seg/src/utils/segLocalImageIds.ts
  • extensions/cornerstone-dicom-seg/src/utils/segmentationConfig.ts
  • extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx
  • extensions/cornerstone-dicom-sr/package.json
  • extensions/cornerstone-dynamic-volume/package.json
  • extensions/cornerstone/package.json
  • extensions/cornerstone/src/commandsModule.ts
  • extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
  • extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
  • extensions/cornerstone/src/utils/segmentationHandlers.ts
  • extensions/default/package.json
  • extensions/default/src/DicomLocalDataSource/index.js
  • extensions/default/src/DicomWebDataSource/index.ts
  • extensions/default/src/ViewerLayout/index.tsx
  • extensions/default/src/commandsModule.ts
  • extensions/default/src/customizations/reportDialogCustomization.tsx
  • extensions/default/src/utils/appendFrameQueryToImageId.js
  • extensions/default/src/utils/dicomWriter.ts
  • extensions/default/src/utils/registerNaturalizedDatasetForLocalWadouri.js
  • extensions/dicom-microscopy/package.json
  • extensions/dicom-pdf/package.json
  • extensions/dicom-video/package.json
  • extensions/measurement-tracking/package.json
  • extensions/test-extension/package.json
  • extensions/tmtv/package.json
  • extensions/usAnnotation/package.json
  • lerna.json
  • modes/basic-dev-mode/package.json
  • modes/basic-test-mode/package.json
  • modes/basic/package.json
  • modes/longitudinal/package.json
  • modes/microscopy/package.json
  • modes/preclinical-4d/package.json
  • modes/segmentation/package.json
  • modes/tmtv/package.json
  • modes/usAnnotation/package.json
  • netlify-lerna-cache.sh
  • netlify-lerna-restore.sh
  • netlify.toml
  • nx.json
  • package.json
  • platform/app/cypress.config.ts
  • platform/app/cypress/integration/measurement-tracking/OHIFSaveMeasurements.spec.js
  • platform/app/cypress/support/commands.js
  • platform/app/package.json
  • platform/app/public/config/default.js
  • platform/app/tailwind.config.js
  • platform/cli/package.json
  • platform/cli/src/commands/createPackage.js
  • platform/cli/src/commands/utils/getYarnInfo.js
  • platform/cli/src/commands/utils/uninstallNPMPackage.js
  • platform/cli/src/index.js
  • platform/cli/templates/extension/dependencies.json
  • platform/cli/templates/mode/dependencies.json
  • platform/core/package.json
  • platform/core/src/classes/MetadataProvider.ts
  • platform/core/src/utils/combineFrameInstance.ts
  • platform/core/src/utils/imageIdToURI.js
  • platform/docs/docs/migration-guide/3p12-to-3p13/build-tooling.md
  • platform/docs/docs/migration-guide/3p12-to-3p13/index.md
  • platform/docs/docs/migration-guide/3p12-to-3p13/node-version.md
  • platform/docs/docs/migration-guide/3p12-to-3p13/package-manager.md
  • platform/docs/docs/platform/extensions/index.md
  • platform/docs/docs/platform/extensions/pluginConfig.md
  • platform/docs/docs/platform/modes/index.md
  • platform/docs/package.json
  • platform/i18n/package.json
  • platform/ui-next/package.json
  • platform/ui/package.json
  • playwright.config.ts
  • pnpm-workspace.yaml
  • publish-package.mjs
  • publish-version.mjs
  • rsbuild.config.ts
  • tests/FreehandROI.spec.ts
  • tests/SegmentationPanel.spec.ts
  • tests/globalSetup.ts
  • tests/pages/RightPanelPageObject.ts
  • version.mjs
💤 Files with no reviewable changes (11)
  • addOns/README.md
  • bunfig.update-lockfile.toml
  • addOns/externals/devDependencies/package.json
  • platform/app/cypress/integration/measurement-tracking/OHIFSaveMeasurements.spec.js
  • nx.json
  • netlify-lerna-restore.sh
  • netlify-lerna-cache.sh
  • addOns/externals/dicom-microscopy-viewer/package.json
  • bunfig.toml
  • addOns/package.json
  • lerna.json
✅ Files skipped from review due to trivial changes (7)
  • .gitignore
  • .npmrc
  • .netlify/package.json
  • .node-version
  • platform/docs/docs/migration-guide/3p12-to-3p13/index.md
  • platform/docs/docs/platform/modes/index.md
  • platform/docs/docs/platform/extensions/pluginConfig.md
🚧 Files skipped from review as they are similar to previous changes (21)
  • extensions/cornerstone-dicom-seg/src/commandsModule.ts
  • extensions/cornerstone-dicom-seg/src/getCustomizationModule.ts
  • extensions/cornerstone/src/commandsModule.ts
  • extensions/cornerstone-dicom-seg/src/utils/segmentationConfig.ts
  • platform/app/public/config/default.js
  • extensions/cornerstone-dicom-seg/src/index.tsx
  • platform/core/src/utils/imageIdToURI.js
  • extensions/cornerstone-dicom-seg/src/customizations/segmentationCustomization.ts
  • platform/core/src/utils/combineFrameInstance.ts
  • extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx
  • extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
  • extensions/default/src/commandsModule.ts
  • platform/core/src/classes/MetadataProvider.ts
  • extensions/cornerstone/src/utils/segmentationHandlers.ts
  • extensions/default/src/utils/dicomWriter.ts
  • extensions/cornerstone-dicom-seg/src/utils/segLocalImageIds.ts
  • extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
  • extensions/default/src/customizations/reportDialogCustomization.tsx
  • extensions/default/src/DicomWebDataSource/index.ts
  • extensions/default/src/utils/registerNaturalizedDatasetForLocalWadouri.js
  • extensions/default/src/DicomLocalDataSource/index.js

Comment thread .github/workflows/build-docs.yml
Comment thread extensions/default/package.json
Comment thread extensions/default/src/utils/appendFrameQueryToImageId.js
Comment thread modes/basic-dev-mode/package.json
Comment thread platform/app/package.json Outdated
Comment on lines +10 to +12
OHIF 3.13 bumps the minimum supported Node.js runtime from 18 to **24**.
The `.node-version` file at the repository root and every workspace
package's `engines.node` field have been updated.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid saying every workspace package now declares engines.node.

That overstates the migration relative to the supplied manifests: platform/docs/package.json still has no engines block. Wording this as “the relevant runtime packages” or enumerating the updated workspaces would keep the guide accurate.

🤖 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/docs/docs/migration-guide/3p12-to-3p13/node-version.md` around lines
10 - 12, The sentence in node-version.md overstates changes by claiming "every
workspace package's `engines.node` field have been updated"; instead update the
wording to be accurate: mention that the repository root `.node-version` was
bumped to 24 and that only specific workspace packages had their `engines.node`
fields updated (e.g., list or say "relevant runtime packages" or enumerate the
updated workspaces), and avoid asserting that `platform/docs/package.json` or
all workspaces were changed.

Comment on lines +187 to 192
For the full set of fields (`packageName`, `version`, `default`, `directory`,
the `public` section) and the three ways a plugin can be located (in-tree,
out-of-tree `directory`, or installed dependency), see the
[pluginConfig.json reference](./pluginConfig.md).

The final registration and import of the extensions happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the generated import filename in this section.

This paragraph now points readers at the current pluginConfig.json flow, but the next sentence still refers to pluginImport.js. The surrounding migration docs describe pluginImports.js, so this filename looks stale and will send readers to the wrong generated file.

🧰 Tools
🪛 LanguageTool

[style] ~192-~192: The adverb ‘also’ is commonly used to connect clauses and isn’t usually used at the end of a phrase or before a conjunction. Consider replacing it with a more formal alternative.
Context: ...ed file pluginImport.js (this file is also for internal use only). After an exten...

(ALSO_AS_WELL)

🤖 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/docs/docs/platform/extensions/index.md` around lines 187 - 192,
Replace the stale generated filename `pluginImport.js` with the correct
`pluginImports.js` in the paragraph that describes the non-tracked internal
import file; update the sentence to read that the final registration and import
of extensions happen inside a non-tracked file `pluginImports.js` (internal use
only) so it matches the surrounding migration docs and the actual generated
filename.

Comment on lines +9 to 17
"exports": {
"./tailwind.config": "./tailwind.config.js",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
".": "./src/index.ts"
},
"files": [
"dist",
"README.md"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Publish the new ./tailwind.config export.

./tailwind.config now points at ./tailwind.config.js, but files still only whitelists dist and README.md. After publish, consumers importing @ohif/ui-next/tailwind.config will resolve an export that is missing from the tarball.

📦 Suggested fix
   "files": [
     "dist",
-    "README.md"
+    "README.md",
+    "tailwind.config.js"
   ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"exports": {
"./tailwind.config": "./tailwind.config.js",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
".": "./src/index.ts"
},
"files": [
"dist",
"README.md"
"exports": {
"./tailwind.config": "./tailwind.config.js",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
".": "./src/index.ts"
},
"files": [
"dist",
"README.md",
"tailwind.config.js"
],
🤖 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/ui-next/package.json` around lines 9 - 17, The package.json exposes
"./tailwind.config" via the "exports" map but the "files" array does not include
tailwind.config.js, so the file won't be published; update package.json to
include "tailwind.config.js" (or the path you used for the export) in the
"files" array so that the exported entry "./tailwind.config" is present in the
published tarball (refer to the "exports" entry "./tailwind.config" and the
"files" array when making the change).

Comment thread platform/ui/package.json
Comment on lines +227 to 231
// Retrying-friendly locator for `expect(...).toHaveCount(n)` — prefer this
// over the one-shot getSegmentCount() when asserting row counts.
rows: page.getByTestId('data-row'),
getSegmentCount: async () => {
return await page.getByTestId('data-row').count();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Scope segmentation rows to the panel instead of the whole page.

page.getByTestId('data-row') is the shared DataRow marker, and this page object also uses it for measurements. With tests/SegmentationPanel.spec.ts now asserting exact counts through panel.rows, any unrelated data-row elsewhere on the page can make the segmentation checks fail even when the panel is correct. Build rows and getSegmentCount() from a panel-scoped root locator instead.

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

In `@tests/pages/RightPanelPageObject.ts` around lines 227 - 231,
RightPanelPageObject's rows and getSegmentCount use page.getByTestId('data-row')
which matches rows across the whole page; scope them to the panel root instead.
Replace page.getByTestId('data-row') in the rows property and in
getSegmentCount() with a panel-scoped locator (e.g., use an existing root
locator like this.panel or this.root.getByTestId('data-row'); if none exists,
add a panelRoot =
page.getByTestId('segmentation-panel')/page.getByTestId('<panel-testid>') and
use panelRoot.getByTestId('data-row')). Update both the rows field and the
getSegmentCount method to use that panel-scoped locator.

wayfarer3130 and others added 5 commits June 12, 2026 16:26
Resolved version-bump conflicts: kept workspace:* for internal @ohif/* deps
and dcmjs 0.52.0 (feature branch convention), took master's beta.90 version
bumps, root version, and the playwright self-hosted-runner pnpm fix, and
dropped a duplicate productVersion in platform/app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	extensions/cornerstone-dicom-pmap/package.json
#	extensions/cornerstone-dicom-rt/package.json
#	extensions/cornerstone-dicom-seg/package.json
#	extensions/cornerstone-dicom-sr/package.json
#	extensions/cornerstone-dynamic-volume/package.json
#	extensions/cornerstone/package.json
#	extensions/default/package.json
#	extensions/dicom-microscopy/package.json
#	extensions/dicom-pdf/package.json
#	extensions/dicom-video/package.json
#	extensions/measurement-tracking/package.json
#	extensions/test-extension/package.json
#	extensions/tmtv/package.json
#	extensions/usAnnotation/package.json
#	modes/basic-dev-mode/package.json
#	modes/basic-test-mode/package.json
#	modes/basic/package.json
#	modes/longitudinal/package.json
#	modes/microscopy/package.json
#	modes/preclinical-4d/package.json
#	modes/segmentation/package.json
#	modes/tmtv/package.json
#	modes/usAnnotation/package.json
#	platform/app/package.json
#	platform/core/package.json
# Conflicts:
#	extensions/cornerstone-dicom-pmap/package.json
#	extensions/cornerstone-dicom-rt/package.json
#	extensions/cornerstone-dicom-seg/package.json
#	extensions/cornerstone-dicom-sr/package.json
#	extensions/cornerstone-dynamic-volume/package.json
#	extensions/cornerstone/package.json
#	extensions/default/package.json
#	extensions/dicom-microscopy/package.json
#	extensions/dicom-pdf/package.json
#	extensions/dicom-video/package.json
#	extensions/measurement-tracking/package.json
#	extensions/test-extension/package.json
#	extensions/tmtv/package.json
#	extensions/usAnnotation/package.json
#	modes/basic-dev-mode/package.json
#	modes/basic-test-mode/package.json
#	modes/basic/package.json
#	modes/longitudinal/package.json
#	modes/microscopy/package.json
#	modes/preclinical-4d/package.json
#	modes/segmentation/package.json
#	modes/tmtv/package.json
#	modes/usAnnotation/package.json
#	platform/app/package.json
#	platform/core/package.json

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

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

Inline comments:
In `@platform/app/package.json`:
- Line 3: The postcss configuration is broken due to inconsistent package
removals. In the root package.json devDependencies, add the missing
`autoprefixer` package that is referenced in rsbuild.config.ts postcss options,
and then either remove the obsolete postcss.config.js file entirely (if the
inline rsbuild configuration is sufficient) or update postcss.config.js to
remove all references to the missing packages cssnano, postcss-import, and
postcss-preset-env. Ensure that the postcss configuration sources are aligned -
either rely solely on the rsbuild.config.ts inline configuration or restore the
removed packages to support postcss.config.js.
🪄 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: 422723ea-f225-4e6b-b991-add3a8950acc

📥 Commits

Reviewing files that changed from the base of the PR and between 575d379 and 4155410.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • extensions/cornerstone-dicom-sr/package.json
  • extensions/cornerstone-dynamic-volume/package.json
  • extensions/cornerstone/package.json
  • extensions/default/package.json
  • extensions/dicom-pdf/package.json
  • extensions/dicom-video/package.json
  • extensions/measurement-tracking/package.json
  • extensions/test-extension/package.json
  • extensions/tmtv/package.json
  • platform/app/package.json
  • platform/core/package.json
  • pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (2)
  • extensions/test-extension/package.json
  • extensions/dicom-pdf/package.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • extensions/default/package.json
  • extensions/tmtv/package.json
  • extensions/dicom-video/package.json

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

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

Inline comments:
In `@platform/app/package.json`:
- Line 3: The postcss configuration is broken due to inconsistent package
removals. In the root package.json devDependencies, add the missing
`autoprefixer` package that is referenced in rsbuild.config.ts postcss options,
and then either remove the obsolete postcss.config.js file entirely (if the
inline rsbuild configuration is sufficient) or update postcss.config.js to
remove all references to the missing packages cssnano, postcss-import, and
postcss-preset-env. Ensure that the postcss configuration sources are aligned -
either rely solely on the rsbuild.config.ts inline configuration or restore the
removed packages to support postcss.config.js.
🪄 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: 422723ea-f225-4e6b-b991-add3a8950acc

📥 Commits

Reviewing files that changed from the base of the PR and between 575d379 and 4155410.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • extensions/cornerstone-dicom-sr/package.json
  • extensions/cornerstone-dynamic-volume/package.json
  • extensions/cornerstone/package.json
  • extensions/default/package.json
  • extensions/dicom-pdf/package.json
  • extensions/dicom-video/package.json
  • extensions/measurement-tracking/package.json
  • extensions/test-extension/package.json
  • extensions/tmtv/package.json
  • platform/app/package.json
  • platform/core/package.json
  • pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (2)
  • extensions/test-extension/package.json
  • extensions/dicom-pdf/package.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • extensions/default/package.json
  • extensions/tmtv/package.json
  • extensions/dicom-video/package.json
🛑 Comments failed to post (1)
platform/app/package.json (1)

3-3: ⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Rspack CSS configuration and any remaining postcss usage

echo "=== Rspack configuration files ==="
fd -e js -e ts -e json 'rspack|webpack' platform/app

echo -e "\n=== Remaining postcss references ==="
rg -n --type-add 'config:*.{js,json,ts}' --type=config 'postcss' platform/app

echo -e "\n=== CSS imports in source ==="
rg -n --type=typescript --type=javascript "\.css|\.scss|\.sass" platform/app/src | head -20

Repository: OHIF/Viewers

Length of output: 299


🏁 Script executed:

# Check postcss.config.js files and their content
echo "=== platform/app/postcss.config.js ==="
cat platform/app/postcss.config.js

echo -e "\n=== Root postcss.config.js ==="
cat postcss.config.js

echo -e "\n=== platform/app/package.json (checking devDependencies) ==="
rg -A 50 '"devDependencies"' platform/app/package.json

echo -e "\n=== Look for Rspack or build config in root ==="
ls -la | grep -E 'rspack|webpack|build'

echo -e "\n=== Check root level config files ==="
fd -e js -e json -e ts 'rspack|webpack|build' . --max-depth=2 | grep -v node_modules | head -20

Repository: OHIF/Viewers

Length of output: 1225


🏁 Script executed:

# Check root package.json for postcss dependencies
echo "=== Root package.json devDependencies (postcss related) ==="
rg -A 150 '"devDependencies"' package.json | grep -E 'postcss|cssnano'

echo -e "\n=== rsbuild.config.ts content ==="
head -100 rsbuild.config.ts

echo -e "\n=== Check for other build configs ==="
ls -la platform/app/*.config.* 2>/dev/null | head -20

echo -e "\n=== Look for webpack/rspack config in platform/app ==="
fd 'config' platform/app --max-depth 2

Repository: OHIF/Viewers

Length of output: 4907


🏁 Script executed:

# Verify postcss-loader presence and exact setup
echo "=== platform/app/package.json - check postcss-loader ==="
grep -i postcss platform/app/package.json

echo -e "\n=== Root package.json - postcss related ==="
grep -E 'postcss|cssnano' package.json

echo -e "\n=== Check if postcss.config.js is actually used ==="
rg -n 'postcss\.config' . --type-add 'config:*.{js,json,ts}' | grep -v node_modules

echo -e "\n=== Verify what postcss-loader version and which package ==="
cat package.json | grep -A 2 -B 2 postcss-loader

Repository: OHIF/Viewers

Length of output: 555


🏁 Script executed:

# Final verification: confirm postcss packages are actually removed
echo "=== Root package.json - all devDependencies to check for postcss packages ==="
rg -B 5 -A 100 '"devDependencies"' package.json | grep -E '^\s+"(postcss|cssnano|autoprefixer)' || echo "None found in root devDeps"

echo -e "\n=== Verify the exact packages removed from platform/app ==="
echo "The AI summary claimed removed: cssnano, postcss-import, postcss-preset-env"
echo "Let's check if they're in root package.json:"
rg '(cssnano|postcss-import|postcss-preset-env)' package.json

echo -e "\n=== Check if autoprefixer is available (used in rsbuild.config.ts) ==="
rg 'autoprefixer' package.json

Repository: OHIF/Viewers

Length of output: 419


Critical: CSS processing broken after postcss package removal.

The removal of cssnano, postcss-import, and postcss-preset-env has created a broken state:

  1. postcss.config.js still exists but references missing packages (cssnano, postcss-import, postcss-preset-env) that are no longer in dependencies.
  2. Missing autoprefixer dependency: rsbuild.config.ts specifies autoprefixer: {} in postcss options, but the autoprefixer package is not in root package.json devDependencies.
  3. Unclear configuration: postcss-loader is present in root devDependencies, but it's unclear whether postcss.config.js is being used or if the inline rsbuild config is sufficient.

Either remove the obsolete postcss.config.js and add the missing autoprefixer package, or restore the removed postcss packages if they're needed by the build.

🤖 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/app/package.json` at line 3, The postcss configuration is broken due
to inconsistent package removals. In the root package.json devDependencies, add
the missing `autoprefixer` package that is referenced in rsbuild.config.ts
postcss options, and then either remove the obsolete postcss.config.js file
entirely (if the inline rsbuild configuration is sufficient) or update
postcss.config.js to remove all references to the missing packages cssnano,
postcss-import, and postcss-preset-env. Ensure that the postcss configuration
sources are aligned - either rely solely on the rsbuild.config.ts inline
configuration or restore the removed packages to support postcss.config.js.

Comment on lines +1333 to +1342
settle();

try {
await applyRepresentation();
} catch (error) {
console.warn(
`Failed to apply segmentation representation for "${segmentationId}":`,
error
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 settle() resolves the outer promise before applyRepresentation() finishes

settle() calls resolve() on line 1333, which immediately unblocks the await in _addOverlayRepresentations. applyRepresentation() (line 1336) is then awaited concurrently in the subscriber callback — the outer caller has already moved on. This creates an asymmetry: the synchronous fast path (getSegmentation already truthy at line 1306) returns applyRepresentation() directly and the caller properly awaits it, while this async path resolves before addSegmentationRepresentation has run. Any work _addOverlayRepresentations defers until after its loop relies on the representation being attached, but for the first load it won't be.

Move settle() to after the await applyRepresentation() call, or use a try/finally to guarantee resolution regardless of outcome.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
Line: 1333-1342

Comment:
**`settle()` resolves the outer promise before `applyRepresentation()` finishes**

`settle()` calls `resolve()` on line 1333, which immediately unblocks the `await` in `_addOverlayRepresentations`. `applyRepresentation()` (line 1336) is then awaited concurrently in the subscriber callback — the outer caller has already moved on. This creates an asymmetry: the synchronous fast path (`getSegmentation` already truthy at line 1306) returns `applyRepresentation()` directly and the caller properly awaits it, while this async path resolves before `addSegmentationRepresentation` has run. Any work `_addOverlayRepresentations` defers until after its loop relies on the representation being attached, but for the first load it won't be.

Move `settle()` to after the `await applyRepresentation()` call, or use a try/finally to guarantee resolution regardless of outcome.

How can I resolve this? If you propose a fix, please make it concise.

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.

5 participants