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

* feat: Update to use pnpm (#6031) [simulated squash-merge]

* feat: load DICOM SEG images via imageLoader

* feat: load DICOM SEG images via imageLoader

* PR review fixes

* Test fragment of compressed multiframes

* fix: Removed dependencies incorrectly

* Update frame as a targetted change to avoid stripping remaining params

* lock

* Update test to agree with the missing representation branch

* test(contour): contour color change coverage (#6042)

* test(contour): contour interactions delete segment (#6069)

---------

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>

* chore(version): Update package versions to 3.13.0-beta.98 [skip ci]

* feat(testing): OHIF Test Agent Skills (#5993)

* chore(version): Update package versions to 3.13.0-beta.99 [skip ci]

* test(contour): Add the ContourSegmentToggleLock.spec.ts test file to test contour locking (#6072)

* chore(version): Update package versions to 3.13.0-beta.100 [skip ci]

* chore(testing): Flock lock for playwright tests; Pin node version for OHIF; add more workers (#6099)

* update Cypress apt deps for Ubuntu Noble (drop libgconf-2-4, libasound2→libasound2t64)

* chore(version): Update package versions to 3.13.0-beta.101 [skip ci]

* Debug fixes

* perf(seg): pass explicit frame decode concurrency (16) to SEG loader

Pass an explicit concurrency value (SEG_FRAME_DECODE_CONCURRENCY = 16) into
createFromDicomSegImageId rather than relying on the adapter default, so the
SEG frame fetch/decode parallelism is set at the call site and is ready to
become configurable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(behaviours): add behaviours section + multiframe Part 10 prefetch proposal

Start a "Behaviours" docs section for documenting how the system and UI behave
end-to-end (observed behaviours, design proposals, and failure modes), with an
index README and a Docusaurus category.

First entry is the proposal for loading a multiframe SEG as a single Part 10
instance: prefetch the whole instance (gated by loadMultiframeAsPart10RaceTimeMs),
parse it with dcmjs (handling multipart/related vs raw DICOM), and register the
per-frame compressed pixels into the Cornerstone3D core image cache (the single
uniform frame registry) so the per-frame load path is served locally while the
standard decode path is unchanged. Best-effort: falls back to per-frame fetches
on any failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Pre-cache the image data using a full part10 file for performance

* Add customizations to specify type of segmentation save

* Add clearcache of the cacheData

* Bump @cornerstonejs/* pins 5.1.3 -> 5.4.10 to match libs/@cornerstonejs base

libs/@cornerstonejs (fix/use-imageLoader-for-seg) is based on the released
cs3d 5.4.10 (merge-base with origin/main is the 5.4.10 version bump), so pin
OHIF to that release. The local branch changes still reach the app via the
cs3d:link symlinks; these pins keep the lockfile/npm fallback aligned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix seg OOM

* fix: file meta garbage element, bogus NumberOfFrames, unguarded source-map-loader

- dicomWriter: drop the naturalized meta.TransferSyntaxUID assignment that
  dcmjs wrote as a garbage (0000,0000) element into every saved file's meta
  group (download / clipboard / local wadouri blob / local store); keep the
  hex 00020010 assignment, which is required for string-form _meta fallbacks.
- registerNaturalizedDatasetForLocalWadouri: keep the computed frame count
  local so single-frame IODs (SR, RTSTRUCT) no longer gain a bogus
  NumberOfFrames element in their serialized form.
- rsbuild.config: resolve source-map-loader opportunistically (it is not a
  project dependency; the rule only serves the gitignored cs3d-link
  workflow) so fresh clones no longer crash at config load.
- Regression tests for both writer fixes (mutation-checked).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* PR review comment fixes

* Update versions

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: diattamo <mmddiatta@gmail.com>
Co-authored-by: ohif-bot <danny.ri.brown+ohif-bot@gmail.com>
Co-authored-by: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-07-09 20:54:23 -04:00
1 parent b996070583
commit dbb8b1526e
59 files changed
+2501 -322

No files matched your search

+4 -4
View File
@@ -35,16 +35,16 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/core": "5.1.3",
"@cornerstonejs/dicom-image-loader": "5.1.3",
"@cornerstonejs/metadata": "5.1.3",
"@cornerstonejs/core": "5.4.12",
"@cornerstonejs/dicom-image-loader": "5.4.12",
"@cornerstonejs/metadata": "5.4.12",
"@ohif/ui": "workspace:*",
"cornerstone-math": "0.1.10",
"dicom-parser": "1.8.21"
},
"dependencies": {
"@babel/runtime": "7.29.7",
"dcmjs": "0.49.4",
"dcmjs": "0.52.0",
"dicomweb-client": "0.10.4",
"gl-matrix": "3.4.3",
"immutability-helper": "3.1.1",
+8 -18
View File
@@ -1,7 +1,7 @@
import queryString from 'query-string';
import dicomParser from 'dicom-parser';
import { utilities } from '@cornerstonejs/core';
import { imageIdToURI } from '../utils';
import { baseImageURIForMetadata } from '../utils/imageIdToURI';
import DicomMetadataStore from '../services/DicomMetadataStore';
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
import toNumber from '../utils/toNumber';
@@ -24,12 +24,12 @@ class MetadataProvider {
// This method is a fallback for when you don't have WADO-URI or WADO-RS.
// You can add instances fetched by any method by calling addInstance, and hook an imageId to point at it here.
// An example would be dicom hosted at some random site.
const imageURI = imageIdToURI(imageId);
const imageURI = baseImageURIForMetadata(imageId);
this.imageURIToUIDs.set(imageURI, uids);
}
addCustomMetadata(imageId, type, metadata) {
const imageURI = imageIdToURI(imageId);
const imageURI = baseImageURIForMetadata(imageId);
if (!this.customMetadata.has(type)) {
this.customMetadata.set(type, {});
}
@@ -76,7 +76,7 @@ class MetadataProvider {
// check inside custom metadata
if (this.customMetadata.has(query)) {
const customMetadata = this.customMetadata.get(query);
const imageURI = imageIdToURI(imageId);
const imageURI = baseImageURIForMetadata(imageId);
if (customMetadata[imageURI]) {
return customMetadata[imageURI];
}
@@ -445,6 +445,9 @@ class MetadataProvider {
if (imageId.includes('/frames')) {
return getInformationFromURL('/frames', '/');
}
if (imageId.includes('?frame=')) {
return getInformationFromURL('?frame=', '&');
}
if (imageId.includes('&frame=')) {
return getInformationFromURL('&frame=', '&');
}
@@ -473,20 +476,7 @@ class MetadataProvider {
};
}
// Maybe its a non-standard imageId
// check if the imageId starts with http:// or https:// using regex
// Todo: handle non http imageIds
let imageURI;
const urlRegex = /^(http|https|dicomfile):\/\//;
if (urlRegex.test(imageId)) {
imageURI = imageId;
} else {
imageURI = imageIdToURI(imageId);
}
// remove &frame=number from imageId
imageURI = imageURI.split('&frame=')[0];
const imageURI = baseImageURIForMetadata(imageId);
const uids = this.imageURIToUIDs.get(imageURI);
const frameNumber = this.getFrameInformationFromURL(imageId) || '1';
@@ -10,6 +10,12 @@ import { dicomSplit } from './dicomSplit';
* will be ignored.
* This can be safely called with an undefined frame in order to handle
* single frame data. (eg frame is undefined is the same as frame===1).
*
* Note: instances carry non-enumerable runtime props (frameNumber, imageId,
* url, wadoRoot, ...). This is intentional: dcmjs serialization skips them and
* spreads/copies (including of anything from `metaData.get('instance', ...)`)
* deliberately do not carry them — frameNumber in particular must not be
* copied onto other objects. Read runtime props off the original instance.
*/
const combineFrameInstance = (frame, instance) => {
const {
@@ -83,6 +89,9 @@ const combineFrameInstance = (frame, instance) => {
if (!instance._parentInstance) {
Object.defineProperty(instance, '_parentInstance', {
value: { ...instance },
enumerable: false,
writable: false,
configurable: false,
});
}
const sharedInstance = createCombinedValue(
@@ -102,7 +111,7 @@ const combineFrameInstance = (frame, instance) => {
Object.defineProperty(newInstance, 'frameNumber', {
value: frameNumber,
writable: true,
enumerable: true,
enumerable: false,
configurable: true,
});
return newInstance;
@@ -113,6 +122,9 @@ const combineFrameInstance = (frame, instance) => {
if (!instance._parentInstance) {
Object.defineProperty(instance, '_parentInstance', {
value: { ...instance },
enumerable: false,
writable: false,
configurable: false,
});
}
@@ -144,7 +156,7 @@ const combineFrameInstance = (frame, instance) => {
Object.defineProperty(newInstance, 'frameNumber', {
value: frameNumber,
writable: true,
enumerable: true,
enumerable: false,
configurable: true,
});
+17
View File
@@ -10,3 +10,20 @@ export default function imageIdToURI(imageId) {
return imageId.substring(colonIndex + 1);
}
/**
* Normalizes an imageId to the metadata lookup key (scheme stripped, frame query removed).
* Must match MetadataProvider.getUIDsFromImageID lookup behavior.
*/
export function baseImageURIForMetadata(imageId) {
const urlRegex = /^(http|https|dicomfile):\/\//;
let imageURI;
if (urlRegex.test(imageId)) {
imageURI = imageId;
} else {
imageURI = imageIdToURI(imageId);
}
return imageURI.split('&frame=')[0].split('?frame=')[0];
}