fix(MetadataProvider): Correctly assign imageId for multiframe images and remove unused frame information retrieval method (#5965)

This commit is contained in:
Alireza authored and GitHub committed 2026-07-10 15:35:09 -04:00
1 parent 2c54616136
commit 1eee3e6119
7 files changed
+369 -43

No files matched your search

@@ -0,0 +1,82 @@
import metadataProvider from './MetadataProvider';
beforeEach(() => {
(
metadataProvider as unknown as {
imageURIToUIDs: Map<string, unknown>;
}
).imageURIToUIDs.clear();
});
describe('MetadataProvider', () => {
it('uses the WADO frame query parameter as the frame number', () => {
expect(
metadataProvider.getUIDsFromImageID(
'dicomweb:http://localhost/wado?requestType=WADO&studyUID=study-wado&seriesUID=series-wado&objectUID=sop-wado&contentType=application/dicom&transferSyntax=*&frame=5'
)
).toEqual({
StudyInstanceUID: 'study-wado',
SeriesInstanceUID: 'series-wado',
SOPInstanceUID: 'sop-wado',
frameNumber: '5',
});
});
it('uses the frame query parameter for local multiframe imageIds registered by base URL', () => {
const baseImageId = 'blob:http://localhost/local-multiframe';
const uids = {
StudyInstanceUID: 'study-local',
SeriesInstanceUID: 'series-local',
SOPInstanceUID: 'sop-local',
};
metadataProvider.addImageIdToUIDs(baseImageId, uids);
expect(metadataProvider.getUIDsFromImageID(`${baseImageId}&frame=3`)).toEqual({
...uids,
frameNumber: '3',
});
});
it('prefers the frame query parameter over stored frame metadata', () => {
const baseImageId = 'blob:http://localhost/local-multiframe-with-frame-number';
const uids = {
StudyInstanceUID: 'study-local-with-frame-number',
SeriesInstanceUID: 'series-local-with-frame-number',
SOPInstanceUID: 'sop-local-with-frame-number',
frameNumber: '1',
};
metadataProvider.addImageIdToUIDs(baseImageId, uids);
expect(metadataProvider.getUIDsFromImageID(`${baseImageId}&frame=4`)).toEqual({
...uids,
frameNumber: '4',
});
});
it('prefers an exact frame imageId mapping before falling back to the base URL', () => {
const baseImageId = 'blob:http://localhost/local-multiframe-exact-frame';
metadataProvider.addImageIdToUIDs(baseImageId, {
StudyInstanceUID: 'study-base',
SeriesInstanceUID: 'series-base',
SOPInstanceUID: 'sop-base',
frameNumber: '1',
});
const frameImageId = `${baseImageId}&frame=3`;
metadataProvider.addImageIdToUIDs(frameImageId, {
StudyInstanceUID: 'study-frame',
SeriesInstanceUID: 'series-frame',
SOPInstanceUID: 'sop-frame',
frameNumber: '3',
});
expect(metadataProvider.getUIDsFromImageID(frameImageId)).toEqual({
StudyInstanceUID: 'study-frame',
SeriesInstanceUID: 'series-frame',
SOPInstanceUID: 'sop-frame',
frameNumber: '3',
});
});
});
+26 -37
View File
@@ -1,6 +1,7 @@
import queryString from 'query-string';
import dicomParser from 'dicom-parser';
import { utilities } from '@cornerstonejs/core';
import { utilities as csMetadataUtilities } from '@cornerstonejs/metadata';
import { baseImageURIForMetadata } from '../utils/imageIdToURI';
import DicomMetadataStore from '../services/DicomMetadataStore';
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
@@ -8,6 +9,7 @@ import toNumber from '../utils/toNumber';
import combineFrameInstance from '../utils/combineFrameInstance';
const { calibratedPixelSpacingMetadataProvider, getPixelSpacingInformation } = utilities;
const { getUriModule } = csMetadataUtilities;
class MetadataProvider {
private readonly imageURIToUIDs: Map<string, any> = new Map();
@@ -60,7 +62,22 @@ class MetadataProvider {
return;
}
return (frameNumber && combineFrameInstance(frameNumber, instance)) || instance;
const result = (frameNumber && combineFrameInstance(frameNumber, instance)) || instance;
// We reassign the imageId on the instance because multiframe images processed
// through combineFrameInstance will mistakenly get the first imageId.
// This happens because the DICOM web data store only keeps the first instance.
// Defined non-enumerable so spreading the instance into another object does
// not carry the imageId over (it belongs to this frame only).
if (result) {
Object.defineProperty(result, 'imageId', {
value: imageId,
writable: true,
enumerable: false,
configurable: true,
});
}
return result;
}
get(query, imageId, options = { fallback: false }) {
@@ -423,37 +440,6 @@ class MetadataProvider {
return metadata;
}
/**
* Retrieves the frameNumber information, depending on the url style
* wadors /frames/1
* wadouri &frame=1
* @param {*} imageId
* @returns
*/
getFrameInformationFromURL(imageId) {
function getInformationFromURL(informationString, separator) {
let result = '';
const splittedStr = imageId.split(informationString)[1];
if (splittedStr.includes(separator)) {
result = splittedStr.split(separator)[0];
} else {
result = splittedStr;
}
return result;
}
if (imageId.includes('/frames')) {
return getInformationFromURL('/frames', '/');
}
if (imageId.includes('?frame=')) {
return getInformationFromURL('?frame=', '&');
}
if (imageId.includes('&frame=')) {
return getInformationFromURL('&frame=', '&');
}
return;
}
getUIDsFromImageID(imageId) {
if (imageId.startsWith('wadors:')) {
const strippedImageId = imageId.split('/studies/')[1];
@@ -467,23 +453,26 @@ class MetadataProvider {
};
} else if (imageId.includes('?requestType=WADO')) {
const qs = queryString.parse(imageId);
const frameNumber = qs.frameNumber || qs.frame;
return {
StudyInstanceUID: qs.studyUID,
SeriesInstanceUID: qs.seriesUID,
SOPInstanceUID: qs.objectUID,
frameNumber: qs.frameNumber,
frameNumber,
};
}
const imageURI = baseImageURIForMetadata(imageId);
const uids = this.imageURIToUIDs.get(imageURI);
const frameNumber = this.getFrameInformationFromURL(imageId) || '1';
if (uids && frameNumber !== undefined) {
return { ...uids, frameNumber };
if (!uids) {
return;
}
return uids;
const frameNumber = getUriModule(imageId)?.framesString || uids.frameNumber || '1';
return { ...uids, frameNumber };
}
}