Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { metaData, eventTarget, utilities as csUtils } from '@cornerstonejs/core
import { CONSTANTS, segmentation as cstSegmentation } from '@cornerstonejs/tools';
import { adaptersSEG, Enums } from '@cornerstonejs/adapters';
import { dicomLoaderService } from '@ohif/extension-cornerstone';
import dcmjs from 'dcmjs';

import { SOPClassHandlerId } from './id';

const { DicomMetaDictionary } = dcmjs.data;
const { naturalizeDataset } = DicomMetaDictionary;
import { dicomlabToRGB } from './utils/dicomlabToRGB';
import { getSegmentationParserType } from './utils/segmentationConfig';
import {
Expand Down Expand Up @@ -460,15 +464,70 @@ async function _loadSegments({
true;

/**
* Check if the metadata has PerFrameFunctionalGroupsSequence.
* Some DICOMweb servers (like IDC's static WADO) omit this sequence from JSON metadata
* for large SEGs to save space. When missing, we need to fetch the full DICOM file
* and use the buffer-based loader which parses the complete metadata from the file.
* Some DICOMweb servers (e.g. IDC's static WADO) omit large sequences like
* PerFrameFunctionalGroupsSequence from the JSON metadata to save bandwidth.
* The metadata-based loader (createFromDicomSegImageId) requires this sequence
* to map frames to segments.
*
* Loading strategy (in order of preference):
* 1. If PerFrameFunctionalGroupsSequence is inline (array) → use metadata-based loader
* 2. If PerFrameFunctionalGroupsSequence has BulkDataURI → fetch bulk data, then use metadata-based loader
* 3. If bulk data fetch fails or no BulkDataURI → fall back to buffer-based loader (full Part 10 file)
*/
const hasPerFrameFunctionalGroups =
let hasPerFrameFunctionalGroups =
Array.isArray(instance.PerFrameFunctionalGroupsSequence) &&
instance.PerFrameFunctionalGroupsSequence.length > 0;

/**
* Check if PerFrameFunctionalGroupsSequence is available via bulk data.
* Some servers return it as { BulkDataURI: '...' } instead of inline array.
*/
const perFrameValue = instance.PerFrameFunctionalGroupsSequence as
| unknown[]
| { BulkDataURI?: string; retrieveBulkData?: () => Promise<ArrayBuffer> }
| undefined;

if (
!hasPerFrameFunctionalGroups &&
perFrameValue &&
typeof perFrameValue === 'object' &&
!Array.isArray(perFrameValue) &&
(perFrameValue.BulkDataURI || typeof perFrameValue.retrieveBulkData === 'function')
) {
try {
let buffer: ArrayBuffer | undefined;

if (typeof perFrameValue.retrieveBulkData === 'function') {
buffer = await perFrameValue.retrieveBulkData();
} else if (perFrameValue.BulkDataURI && dataSource.retrieve?.bulkDataURI) {
buffer = await dataSource.retrieve.bulkDataURI({
StudyInstanceUID: instance.StudyInstanceUID,
BulkDataURI: perFrameValue.BulkDataURI,
});
}

if (buffer && buffer.byteLength > 0) {
/**
* Parse the bulk data as JSON. The server returns the sequence as a JSON array
* following the DICOMweb JSON model (denaturalized form).
*/
const jsonText = new TextDecoder().decode(buffer);
const denaturalizedSequence = JSON.parse(jsonText);

if (Array.isArray(denaturalizedSequence) && denaturalizedSequence.length > 0) {
/** Naturalize each item in the sequence to match OHIF's internal format */
const naturalizedSequence = denaturalizedSequence.map(item => naturalizeDataset(item));

/** Update the instance metadata with the fetched sequence */
instance.PerFrameFunctionalGroupsSequence = naturalizedSequence;
hasPerFrameFunctionalGroups = true;
}
}
} catch {
/** Bulk data fetch failed; fall back to buffer-based loader */
}
}

let results;
let prefetch;

Expand All @@ -477,13 +536,7 @@ async function _loadSegments({
* Fallback: PerFrameFunctionalGroupsSequence is missing from metadata.
* Fetch the full DICOM file and use createFromDICOMSegBuffer which parses
* the complete metadata directly from the DICOM binary.
* This was the loading method used in OHIF 3.12 and earlier.
*/
log.info(
SEG_LOAD_LOG_PREFIX,
'PerFrameFunctionalGroupsSequence missing from metadata, using buffer-based loader'
);

try {
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(
segDisplaySet,
Expand Down
Loading