diff --git a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts index 79ac22b6b..ac6cfd84d 100644 --- a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts +++ b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts @@ -2,6 +2,7 @@ import dcmjs from 'dcmjs'; import pubSubServiceInterface from '../_shared/pubSubServiceInterface'; import createStudyMetadata from './createStudyMetadata'; +import addProxyFields from '../../utils/addProxyFields'; const EVENTS = { STUDY_ADDED: 'event::dicomMetadataStore:studyAdded', @@ -70,7 +71,7 @@ function _getStudy(StudyInstanceUID) { * @returns {*} A series object */ function _getSeries(StudyInstanceUID, SeriesInstanceUID) { - if(!StudyInstanceUID) { + if (!StudyInstanceUID) { const series = _model.studies.map(study => study.series).flat(); return series.find(aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID); } @@ -176,7 +177,7 @@ const BaseImplementation = { naturalizedDataset = dicomJSONDataset; } - const { StudyInstanceUID } = naturalizedDataset; + const { StudyInstanceUID, NumberOfFrames: numberOfFrames } = naturalizedDataset; let study = _model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); @@ -185,10 +186,19 @@ const BaseImplementation = { study = _model.studies[_model.studies.length - 1]; } + naturalizedDataset = numberOfFrames > 1 ? addProxyFields(naturalizedDataset) : naturalizedDataset; + study.addInstanceToSeries(naturalizedDataset); }, addInstances(instances, madeInClient = false) { - const { StudyInstanceUID, SeriesInstanceUID } = instances[0]; + const { StudyInstanceUID, SeriesInstanceUID, NumberOfFrames } = instances[0]; + // For multiframe images (NumberOfFrames > 1), wrap each instance with a metadata proxy + // to enable fallback access for key image plane fields. This allows fields like + // ImagePositionPatient, ImageOrientationPatient, and PixelSpacing to be transparently + // resolved from parent or shared metadata if they are not directly present on the instance directly + if (NumberOfFrames > 1) { + instances = instances.map(instance => addProxyFields(instance)); + } let study = _model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); diff --git a/platform/core/src/utils/addProxyFields.js b/platform/core/src/utils/addProxyFields.js new file mode 100644 index 000000000..c60eb9941 --- /dev/null +++ b/platform/core/src/utils/addProxyFields.js @@ -0,0 +1,57 @@ +/** + * fieldProxies has the set of fields which are proxied on a per-field basis + * to the child element. + */ +export const fieldProxies = [ + 'ImagePositionPatient', + 'ImageOrientationPatient', + 'PixelSpacing', + // more fields as necessary +]; + +const METADATA_PROXY_FLAG = Symbol('isMetadataProxy'); + +/** + * Adds proxy properties for a limited number of values which are sometimes + * defined in multiframe instance data. + * + * If a requested field is in `metadataFieldsToWrap`: + * - It first tries to return the value from the current instance. + * - If not found, it attempts to retrieve the value from the `_parentInstance`. + * - If still not found, it checks `_parentInstance._shared`. + * - If none exist, it returns `undefined`. + * + * For all other properties, it behaves like a regular property access. + * + * This allows graceful fallback access for DICOM metadata values that might be spread + * across nested or shared metadata structures. + * + * @param {Object} instance - The target instance object to wrap. + * @returns {Proxy} A proxy-wrapped instance with custom field resolution behavior. + */ +export function addProxyFields(instance) { + // Skip wrapping if already wrapped + if (!instance || instance[METADATA_PROXY_FLAG]) { + return instance; + } + + for(const fieldProxy of fieldProxies) { + if( fieldProxy in instance) { + continue; + } + Object.defineProperty(instance,fieldProxy, { + configurable: true, + enumerable: true, + get: () => { + return instance._parentInstance?.[fieldProxy] ?? instance._parentInstance?._shared?.[fieldProxy]; + } + }); + } + + // Mark this proxy to avoid double wrapping + Object.defineProperty(instance,METADATA_PROXY_FLAG, { value: true }); + + return instance; +} + +export default addProxyFields;