diff --git a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/imagePixelSpacingMetaDataProvider.js b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/imagePixelSpacingMetaDataProvider.js new file mode 100644 index 00000000..6a2f283d --- /dev/null +++ b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/imagePixelSpacingMetaDataProvider.js @@ -0,0 +1,193 @@ +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader' +function parseImageId(imageId) { + // build a url by parsing out the url scheme and frame index from the imageId + const firstColonIndex = imageId.indexOf(':'); + + let url = imageId.substring(firstColonIndex + 1); + const frameIndex = url.indexOf('frame='); + + let frame; + + if (frameIndex !== -1) { + const frameStr = url.substr(frameIndex + 6); + + frame = parseInt(frameStr, 10); + url = url.substr(0, frameIndex - 1); + } + + return { + scheme: imageId.substr(0, firstColonIndex), + url, + frame, + }; +} +function getNumberValues(dataSet, tag, minimumLength) { + const values = []; + const valueAsString = dataSet.string(tag); + + if (!valueAsString) { + return; + } + const split = valueAsString.split('\\'); + + if (minimumLength && split.length < minimumLength) { + return; + } + for (let i = 0; i < split.length; i++) { + values.push(parseFloat(split[i])); + } + + return values; +} +function getImageTypeSubItemFromDataset(dataSet, index) { + const imageType = dataSet.string('x00080008'); + if (imageType) { + const subTypes = imageType.split('\\'); + if (subTypes.length > index) { + return subTypes[index]; + } + } + return undefined; +} +function isNMReconstructable(imageSubType) { + return imageSubType === 'RECON TOMO' || imageSubType === 'RECON GATED TOMO'; +} +function extractOrientationFromNMMultiframeDataset(dataSet) { + let imageOrientationPatient; + const modality = dataSet.string('x00080060'); + if (modality?.includes('NM')) { + const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2); + if (imageSubType && isNMReconstructable(imageSubType)) { + if (dataSet.elements.x00540022) { + imageOrientationPatient = getNumberValues(dataSet.elements.x00540022.items[0].dataSet, 'x00200037', 6); + } + } + } + return imageOrientationPatient; +} +function extractOrientationFromDataset(dataSet) { + let imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6); + if (!imageOrientationPatient && dataSet.elements.x00209116) { + imageOrientationPatient = getNumberValues(dataSet.elements.x00209116.items[0].dataSet, 'x00200037', 6); + } + if (!imageOrientationPatient) { + imageOrientationPatient = + extractOrientationFromNMMultiframeDataset(dataSet); + } + return imageOrientationPatient; +} +function extractPositionFromNMMultiframeDataset(dataSet) { + let imagePositionPatient; + const modality = dataSet.string('x00080060'); + if (modality?.includes('NM')) { + const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2); + if (imageSubType && isNMReconstructable(imageSubType)) { + if (dataSet.elements.x00540022) { + imagePositionPatient = getNumberValues(dataSet.elements.x00540022.items[0].dataSet, 'x00200032', 3); + } + } + } + return imagePositionPatient; +} +function extractPositionFromDataset(dataSet) { + let imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3); + if (!imagePositionPatient && dataSet.elements.x00209113) { + imagePositionPatient = getNumberValues(dataSet.elements.x00209113.items[0].dataSet, 'x00200032', 3); + } + if (!imagePositionPatient) { + imagePositionPatient = extractPositionFromNMMultiframeDataset(dataSet); + } + return imagePositionPatient; +} +function extractSliceThicknessFromDataset(dataSet) { + let sliceThickness; + if (dataSet.elements.x00180050) { + sliceThickness = dataSet.floatString('x00180050'); + } + else if (dataSet.elements.x00289110 && + dataSet.elements.x00289110.items.length && + dataSet.elements.x00289110.items[0].dataSet.elements.x00180050) { + sliceThickness = + dataSet.elements.x00289110.items[0].dataSet.floatString('x00180050'); + } + return sliceThickness; +} +function extractSpacingFromDataset(dataSet) { + let rowPixelSpacing = null + let columnPixelSpacing = null + let pixelSpacing = getNumberValues(dataSet, 'x00280030', 2); + const imagePixelSpacing = getNumberValues(dataSet, 'x00181164', 2); + const estimatedRadiographicMagnificationFactor = getNumberValues(dataSet, 'x00181114', 2); + if (pixelSpacing) { + rowPixelSpacing = pixelSpacing[0] + columnPixelSpacing = pixelSpacing[1] + } else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) { + rowPixelSpacing = imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0]; + columnPixelSpacing = imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1]; + } else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) { + rowPixelSpacing = imagePixelSpacing[0]; + columnPixelSpacing = imagePixelSpacing[1]; + } + return rowPixelSpacing !== null ? [rowPixelSpacing, columnPixelSpacing] : undefined +} +function metaDataProvider(type, imageId) { + const parsedImageId = parseImageId(imageId); + const dataSet = cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url) + + + if (!dataSet) { + return; + } + if (type === 'imagePlaneModule') { + const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6); + const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3); + const pixelSpacing = extractSpacingFromDataset(dataSet); + + let rowCosines = null; + + let columnCosines = null; + + if (imageOrientationPatient) { + rowCosines = [ + parseFloat(imageOrientationPatient[0]), + parseFloat(imageOrientationPatient[1]), + parseFloat(imageOrientationPatient[2]), + ]; + columnCosines = [ + parseFloat(imageOrientationPatient[3]), + parseFloat(imageOrientationPatient[4]), + parseFloat(imageOrientationPatient[5]), + ]; + } + return { + frameOfReferenceUID: dataSet.string('x00200052'), + rows: dataSet.uint16('x00280010'), + columns: dataSet.uint16('x00280011'), + imageOrientationPatient, + rowCosines, + columnCosines, + imagePositionPatient, + sliceThickness: dataSet.floatString('x00180050'), + sliceLocation: dataSet.floatString('x00201041'), + pixelSpacing: pixelSpacing, + rowPixelSpacing: pixelSpacing ? pixelSpacing[0] : null, + columnPixelSpacing: pixelSpacing ? pixelSpacing[1] : null, + }; + } + if (type === 'nmMultiframeGeometryModule') { + const modality = dataSet.string('x00080060'); + const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2); + return { + modality, + imageType: dataSet.string('x00080008'), + imageSubType, + imageOrientationPatient: extractOrientationFromDataset(dataSet), + imagePositionPatient: extractPositionFromDataset(dataSet), + sliceThickness: extractSliceThicknessFromDataset(dataSet), + pixelSpacing: extractSpacingFromDataset(dataSet), + numberOfFrames: dataSet.uint16('x00280008'), + isNMReconstructable: isNMReconstructable(imageSubType) && modality.includes('NM'), + }; + } +} +export default metaDataProvider; \ No newline at end of file diff --git a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initProviders.js b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initProviders.js index 06beeca1..43133125 100644 --- a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initProviders.js +++ b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initProviders.js @@ -1,6 +1,6 @@ import * as cornerstone3D from '@cornerstonejs/core' import ptScalingMetaDataProvider from './ptScalingMetaDataProvider' - +import imagePixelSpacingMetaDataProvider from './imagePixelSpacingMetaDataProvider' const { calibratedPixelSpacingMetadataProvider } = cornerstone3D.utilities export default function initProviders() { @@ -14,4 +14,8 @@ export default function initProviders() { ), 11000 ) + cornerstone3D.metaData.addProvider( + imagePixelSpacingMetaDataProvider, + 11000 + ) }