diff --git a/.prettierrc b/.prettierrc index b80ec6b3..c45c0f13 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,5 +4,6 @@ "proseWrap": "always", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "endOfLine":"auto" } diff --git a/src/imageLoader/wadors/combineFrameInstance.js b/src/imageLoader/wadors/combineFrameInstance.js new file mode 100644 index 00000000..a841fcfd --- /dev/null +++ b/src/imageLoader/wadors/combineFrameInstance.js @@ -0,0 +1,83 @@ +import getTagValue from './getTagValue.js'; + +function getFrameInformation( + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + frameNumber +) { + const shared = ( + SharedFunctionalGroupsSequence + ? Object.values(SharedFunctionalGroupsSequence[0]) + : [] + ) + .map((it) => it[0]) + .filter((it) => it !== undefined && typeof it === 'object'); + const perFrame = ( + PerFrameFunctionalGroupsSequence + ? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1]) + : [] + ) + .map((it) => it.Value[0]) + .filter((it) => it !== undefined && typeof it === 'object'); + + return { + shared, + perFrame, + }; +} + +function getMultiframeInformation(metaData) { + let { + 52009230: PerFrameFunctionalGroupsSequence, + 52009229: SharedFunctionalGroupsSequence, + '00280008': NumberOfFrames, + // eslint-disable-next-line prefer-const + ...rest + } = metaData; + + PerFrameFunctionalGroupsSequence = getTagValue( + PerFrameFunctionalGroupsSequence, + false + ); + SharedFunctionalGroupsSequence = getTagValue( + SharedFunctionalGroupsSequence, + false + ); + NumberOfFrames = getTagValue(NumberOfFrames); + + return { + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + NumberOfFrames, + rest, + }; +} +// function that retrieves specific frame metadata information from multiframe +// metadata +function combineFrameInstance(frameNumber, instance) { + const { + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + NumberOfFrames, + rest, + } = getMultiframeInformation(instance); + + if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) { + const { shared, perFrame } = getFrameInformation( + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + frameNumber + ); + + return Object.assign( + rest, + { '00280008': NumberOfFrames }, + ...Object.values(shared), + ...Object.values(perFrame) + ); + } + + return instance; +} + +export { combineFrameInstance, getMultiframeInformation, getFrameInformation }; diff --git a/src/imageLoader/wadors/getTagValue.js b/src/imageLoader/wadors/getTagValue.js new file mode 100644 index 00000000..14e48a13 --- /dev/null +++ b/src/imageLoader/wadors/getTagValue.js @@ -0,0 +1,11 @@ +export default function getTagValue(tag, justElement = true) { + if (tag && tag.Value) { + if (tag.Value[0] && justElement) { + return tag.Value[0]; + } + + return tag.Value; + } + + return tag; +} diff --git a/src/imageLoader/wadors/metaData/fixNMMetadata.js b/src/imageLoader/wadors/metaData/fixNMMetadata.js new file mode 100644 index 00000000..db233da8 --- /dev/null +++ b/src/imageLoader/wadors/metaData/fixNMMetadata.js @@ -0,0 +1,16 @@ +import getTagValue from '../getTagValue.js'; + +export default function fixNMMetadata(metaData) { + if (!metaData['00200032'] || metaData['00200037']) { + // adjust metadata in case of multiframe NM data, as the dicom tags + // 00200032 and 00200037 could be found only in the dicom tag 00540022 + const detectorInformationSequence = getTagValue(metaData['00540022']); + + if (detectorInformationSequence) { + metaData['00200032'] = detectorInformationSequence['00200032']; + metaData['00200037'] = detectorInformationSequence['00200037']; + } + } + + return metaData; +} diff --git a/src/imageLoader/wadors/metaData/metaDataProvider.js b/src/imageLoader/wadors/metaData/metaDataProvider.js index c55adf2a..17e40955 100644 --- a/src/imageLoader/wadors/metaData/metaDataProvider.js +++ b/src/imageLoader/wadors/metaData/metaDataProvider.js @@ -1,12 +1,53 @@ import external from '../../../externalModules.js'; import getNumberValues from './getNumberValues.js'; -import getValue from './getValue.js'; import getNumberValue from './getNumberValue.js'; import getOverlayPlaneModule from './getOverlayPlaneModule.js'; import metaDataManager from '../metaDataManager.js'; +import getValue from './getValue.js'; +//import fixNMMetadata from './fixNMMetadata.js'; +import { + getMultiframeInformation, + getFrameInformation, +} from '../combineFrameInstance.js'; +import multiframeMetadata from '../retrieveMultiframeMetadata.js'; function metaDataProvider(type, imageId) { + if (type === 'multiframeModule') { + // the get function removes the PerFrameFunctionalGroupsSequence + const { metadata, frame } = + multiframeMetadata.retrieveMultiframeMetadata(imageId); + + if (!metadata) { + return; + } + const { + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + NumberOfFrames, + } = getMultiframeInformation(metadata); + + if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) { + const { shared, perFrame } = getFrameInformation( + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + frame + ); + + return { + NumberOfFrames, + //PerFrameFunctionalGroupsSequence, + PerFrameFunctionalInformation: perFrame, + SharedFunctionalInformation: shared, + }; + } + + return { + NumberOfFrames, + //PerFrameFunctionalGroupsSequence, + }; + } const { dicomParser } = external; + const metaData = metaDataManager.get(imageId); if (!metaData) { @@ -38,6 +79,7 @@ function metaDataProvider(type, imageId) { } if (type === 'imagePlaneModule') { + //metaData = fixNMMetadata(metaData); const imageOrientationPatient = getNumberValues(metaData['00200037'], 6); const imagePositionPatient = getNumberValues(metaData['00200032'], 3); const pixelSpacing = getNumberValues(metaData['00280030'], 2); diff --git a/src/imageLoader/wadors/metaDataManager.js b/src/imageLoader/wadors/metaDataManager.js index 468c3696..be60df6c 100644 --- a/src/imageLoader/wadors/metaDataManager.js +++ b/src/imageLoader/wadors/metaDataManager.js @@ -1,17 +1,50 @@ import imageIdToURI from '../imageIdToURI.js'; +import { combineFrameInstance } from './combineFrameInstance.js'; +import multiframeMetadata from './retrieveMultiframeMetadata.js'; let metadataByImageURI = []; function add(imageId, metadata) { const imageURI = imageIdToURI(imageId); + metadata.isMultiframe = multiframeMetadata.isMultiframe(metadata); + metadataByImageURI[imageURI] = metadata; } +// multiframes images will have only one imageid returned by the dicomweb +// client and registered in metadataByImageURI for all the n frames. If an +// iamgeid does not have metadata, or it does not have at all, or the imageid +// belongs to a frame, not registered in metadataByImageURI function get(imageId) { const imageURI = imageIdToURI(imageId); - return metadataByImageURI[imageURI]; + // dealing first with the non multiframe information + let metadata = metadataByImageURI[imageURI]; + + if (metadata) { + if (!metadata.isMultiframe) { + return metadata; + } + } + + let frame = 1; + + if (!metadata) { + // in this case it could indicate a multiframe imageid + // Try to get the first frame metadata, where is stored the multiframe info + const firstFrameInfo = + multiframeMetadata._retrieveMultiframeMetadata(imageURI); + + metadata = firstFrameInfo.metadata; + frame = firstFrameInfo.frame; + } + + if (metadata) { + metadata = combineFrameInstance(frame, metadata); + } + + return metadata; } function remove(imageId) { @@ -24,6 +57,8 @@ function purge() { metadataByImageURI = []; } +export { metadataByImageURI }; + export default { add, get, diff --git a/src/imageLoader/wadors/retrieveMultiframeMetadata.js b/src/imageLoader/wadors/retrieveMultiframeMetadata.js new file mode 100644 index 00000000..9343487e --- /dev/null +++ b/src/imageLoader/wadors/retrieveMultiframeMetadata.js @@ -0,0 +1,39 @@ +import getValue from './metaData/getValue.js'; +import imageIdToURI from '../imageIdToURI.js'; +import { metadataByImageURI } from './metaDataManager.js'; + +// get metadata information for the first frame +function _retrieveMultiframeMetadata(imageURI) { + const lastSlashIdx = imageURI.indexOf('/frames/') + 8; + // imageid string without frame number + const imageIdFrameless = imageURI.slice(0, lastSlashIdx); + // calculating frame number + const frame = parseInt(imageURI.slice(lastSlashIdx), 10); + // retrieving the frame 1 that contains multiframe information + + const metadata = metadataByImageURI[`${imageIdFrameless}1`]; + + return { + metadata, + frame, + }; +} + +function retrieveMultiframeMetadata(imageId) { + const imageURI = imageIdToURI(imageId); + + return _retrieveMultiframeMetadata(imageURI); +} + +function isMultiframe(metadata) { + // Checks if dicomTag NumberOf Frames exists and it is greater than one + const numberOfFrames = getValue(metadata['00280008']); + + return numberOfFrames && numberOfFrames > 1; +} + +export default { + _retrieveMultiframeMetadata, + retrieveMultiframeMetadata, + isMultiframe, +}; diff --git a/src/imageLoader/wadouri/combineFrameInstanceDataset.js b/src/imageLoader/wadouri/combineFrameInstanceDataset.js new file mode 100644 index 00000000..806b806a --- /dev/null +++ b/src/imageLoader/wadouri/combineFrameInstanceDataset.js @@ -0,0 +1,123 @@ +function getDirectFrameInformation(dataSet, frame) { + if (!dataSet) { + return; + } + + const { + NumberOfFrames, + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + } = getMultiframeInformation(dataSet); + + if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) { + const { shared, perFrame } = getFrameInformation( + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + frame + ); + + return { + NumberOfFrames, + PerFrameFunctionalInformation: perFrame, + SharedFunctionalInformation: shared, + }; + } + + return { + NumberOfFrames, + }; +} + +function getFrameInformation( + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + frameNumber +) { + const shared = {}; + + (SharedFunctionalGroupsSequence + ? Object.values(SharedFunctionalGroupsSequence.items[0].dataSet.elements) + : [] + ).map((it) => (shared[it.tag] = it)); + + const perFrame = {}; + + (PerFrameFunctionalGroupsSequence + ? Object.values( + PerFrameFunctionalGroupsSequence.items[frameNumber - 1].dataSet.elements + ) + : [] + ).map((it) => (perFrame[it.tag] = it)); + + return { + shared, + perFrame, + }; +} + +function getMultiframeInformation(dataSet) { + if (!dataSet) { + return; + } + const { elements, ...otherAttributtes } = dataSet; + const { + x52009230: PerFrameFunctionalGroupsSequence, + x52009229: SharedFunctionalGroupsSequence, + ...otherElements + } = elements; + + const NumberOfFrames = dataSet.intString('x00280008'); + + return { + NumberOfFrames, + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + otherElements, + otherAttributtes, + }; +} + +// function that retrieves specific frame metadata information from multiframe +// metadata +function combineFrameInstanceDataset(frameNumber, dataSet) { + if (!dataSet) { + return; + } + + const { + NumberOfFrames, + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + otherElements, + otherAttributtes, + } = getMultiframeInformation(dataSet); + + if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) { + const { shared, perFrame } = getFrameInformation( + PerFrameFunctionalGroupsSequence, + SharedFunctionalGroupsSequence, + frameNumber + ); + + // creating a new copy of the dataset to remove the two multiframe dicom tags + const newDataset = { + ...otherAttributtes, + elements: { + ...otherElements, + ...shared, + ...perFrame, + }, + }; + + return newDataset; + } + + return dataSet; +} + +export { + combineFrameInstanceDataset, + getMultiframeInformation, + getFrameInformation, + getDirectFrameInformation, +}; diff --git a/src/imageLoader/wadouri/dataSetCacheManager.js b/src/imageLoader/wadouri/dataSetCacheManager.js index fc4e9340..31d60539 100644 --- a/src/imageLoader/wadouri/dataSetCacheManager.js +++ b/src/imageLoader/wadouri/dataSetCacheManager.js @@ -1,5 +1,7 @@ import external from '../../externalModules.js'; import { xhrRequest } from '../internal/index.js'; +import { combineFrameInstanceDataset } from './combineFrameInstanceDataset.js'; +import multiframeDataset from './retrieveMultiframeDataset.js'; import dataSetFromPartialContent from './dataset-from-partial-content.js'; /** @@ -20,11 +22,18 @@ function isLoaded(uri) { } function get(uri) { - if (!loadedDataSets[uri]) { - return; + let dataSet; + + if (uri.includes('&frame=')) { + const { frame, dataSet: multiframeDataSet } = + multiframeDataset.retrieveMultiframeDataset(uri); + + dataSet = combineFrameInstanceDataset(frame, multiframeDataSet); + } else if (loadedDataSets[uri]) { + dataSet = loadedDataSets[uri].dataSet; } - return loadedDataSets[uri].dataSet; + return dataSet; } function update(uri, dataSet) { @@ -174,7 +183,7 @@ function unload(uri) { } } -export function getInfo() { +function getInfo() { return { cacheSizeInBytes, numberOfDataSetsCached: Object.keys(loadedDataSets).length, @@ -188,6 +197,8 @@ function purge() { cacheSizeInBytes = 0; } +export { loadedDataSets }; + export default { isLoaded, load, diff --git a/src/imageLoader/wadouri/metaData/metaDataProvider.js b/src/imageLoader/wadouri/metaData/metaDataProvider.js index ce0bf31a..ed516841 100644 --- a/src/imageLoader/wadouri/metaData/metaDataProvider.js +++ b/src/imageLoader/wadouri/metaData/metaDataProvider.js @@ -6,11 +6,31 @@ import getImagePixelModule from './getImagePixelModule.js'; import getOverlayPlaneModule from './getOverlayPlaneModule.js'; import getLUTs from './getLUTs.js'; import getModalityLUTOutputPixelRepresentation from './getModalityLUTOutputPixelRepresentation.js'; +import { getDirectFrameInformation } from '../combineFrameInstanceDataset.js'; +import multiframeDataset from '../retrieveMultiframeDataset.js'; function metaDataProvider(type, imageId) { - const { dicomParser } = external; const parsedImageId = parseImageId(imageId); + if (type === 'multiframeModule') { + const multiframeData = multiframeDataset.retrieveMultiframeDataset( + parsedImageId.url + ); + + if (!multiframeData.dataSet) { + return; + } + + const multiframeInfo = getDirectFrameInformation( + multiframeData.dataSet, + multiframeData.frame + ); + + return multiframeInfo; + } + + const { dicomParser } = external; + const dataSet = dataSetCacheManager.get(parsedImageId.url); if (!dataSet) { diff --git a/src/imageLoader/wadouri/retrieveMultiframeDataset.js b/src/imageLoader/wadouri/retrieveMultiframeDataset.js new file mode 100644 index 00000000..942104f0 --- /dev/null +++ b/src/imageLoader/wadouri/retrieveMultiframeDataset.js @@ -0,0 +1,75 @@ +import { loadedDataSets } from './dataSetCacheManager.js'; + +function _get(uri) { + if (!loadedDataSets[uri]) { + return; + } + + return loadedDataSets[uri]; +} + +function isMultiframeDataset(uri) { + const dataSet = _get(uri); + + return _isMultiframeDataset(dataSet); +} + +function _isMultiframeDataset(dataSet) { + // Checks if dicomTag NumberOf Frames exists and it is greater than one + if (!dataSet) { + return false; + } + + const numberOfFrames = dataSet.intString('x00280008'); + + return numberOfFrames && numberOfFrames > 1; +} + +function retrieveFrameParameterIndex(uri) { + return uri.indexOf('&frame='); +} + +function retrieveMultiframeDataset(uri) { + const frameParameterIndex = retrieveFrameParameterIndex(uri); + const multiframeURI = + frameParameterIndex === -1 ? uri : uri.slice(0, frameParameterIndex); + const frame = parseInt(uri.slice(frameParameterIndex + 7), 10) || 1; + + let dataSet; + + if (loadedDataSets[multiframeURI]) { + dataSet = loadedDataSets[multiframeURI].dataSet; + } else { + dataSet = undefined; + } + + return { + dataSet, + frame, + }; +} + +function generateMultiframeWADOURIs(uri) { + const listWADOURIs = []; + + const dataSet = _get(uri); + + if (_isMultiframeDataset(dataSet)) { + const numberOfFrames = dataSet.intString('x00280008'); + + for (let i = 1; i <= numberOfFrames; i++) { + listWADOURIs.push(`${uri}&frame=${i}`); + } + } else { + listWADOURIs.push(uri); + } + + return listWADOURIs; +} + +export default { + _get, + generateMultiframeWADOURIs, + retrieveMultiframeDataset, + isMultiframeDataset, +};