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:
59 files changed
+2501
-322
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
Reference in new issue
Block a user