diff --git a/Packages/ohif-study-list/client/lib/getStudyMetadata.js b/Packages/ohif-study-list/client/lib/getStudyMetadata.js index 52a90748e..f565bbae1 100644 --- a/Packages/ohif-study-list/client/lib/getStudyMetadata.js +++ b/Packages/ohif-study-list/client/lib/getStudyMetadata.js @@ -59,6 +59,9 @@ getStudyMetadata = function(studyInstanceUid, doneCallback, failCallback) { // and instance number in ascending order OHIF.viewerbase.sortStudy(study); + // Updates WADO-RS metaDataManager + OHIF.viewerbase.updateMetaDataManager(study); + // Add additional metadata to our study from the studylist var studylistStudy = StudyListStudies.findOne({ studyInstanceUid: study.studyInstanceUid diff --git a/Packages/ohif-study-list/server/services/wado/retrieveMetadata.js b/Packages/ohif-study-list/server/services/wado/retrieveMetadata.js index 3738560bc..8d3695c84 100644 --- a/Packages/ohif-study-list/server/services/wado/retrieveMetadata.js +++ b/Packages/ohif-study-list/server/services/wado/retrieveMetadata.js @@ -169,6 +169,26 @@ function getFrameIncrementPointer(element) { return frameIncrementPointerNames[value]; } +function getRadiopharmaceuticalInfo(instance) { + const modality = DICOMWeb.getString(instance['00080060']); + + if (modality !== 'PT') { + return; + } + + const radiopharmaceuticalInfo = instance['00540016']; + if ((radiopharmaceuticalInfo === undefined) || !radiopharmaceuticalInfo.Value || !radiopharmaceuticalInfo.Value.length) { + return; + } + + const firstPetRadiopharmaceuticalInfo = radiopharmaceuticalInfo.Value[0]; + return { + radiopharmaceuticalStartTime: DICOMWeb.getString(firstPetRadiopharmaceuticalInfo['00181072']), + radionuclideTotalDose: DICOMWeb.getNumber(firstPetRadiopharmaceuticalInfo['00181074']), + radionuclideHalfLife: DICOMWeb.getNumber(firstPetRadiopharmaceuticalInfo['00181075']) + }; +} + /** * Parses result data from a WADO search into Study MetaData * Returns an object populated with study metadata, including the @@ -196,6 +216,9 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { seriesList: seriesList, patientName: DICOMWeb.getName(anInstance['00100010']), patientId: DICOMWeb.getString(anInstance['00100020']), + patientAge: DICOMWeb.getNumber(anInstance['00101010']), + patientSize: DICOMWeb.getNumber(anInstance['00101020']), + patientWeight: DICOMWeb.getNumber(anInstance['00101030']), accessionNumber: DICOMWeb.getString(anInstance['00080050']), studyDate: DICOMWeb.getString(anInstance['00080020']), modalities: DICOMWeb.getString(anInstance['00080061']), @@ -214,6 +237,8 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { modality: DICOMWeb.getString(instance['00080060']), seriesInstanceUid: seriesInstanceUid, seriesNumber: DICOMWeb.getNumber(instance['00200011']), + seriesDate: DICOMWeb.getString(instance['00080021']), + seriesTime: DICOMWeb.getString(instance['00080031']), instances: [] }; seriesMap[seriesInstanceUid] = series; @@ -269,11 +294,12 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']), echoNumber: DICOMWeb.getString(instance['00180086']), contrastBolusAgent: DICOMWeb.getString(instance['00180010']), + radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance), baseWadoRsUri: baseWadoRsUri, wadouri: WADOProxy.convertURL(wadouri, server.requestOptions), wadorsuri: WADOProxy.convertURL(wadorsuri), imageRendering: server.imageRendering, - thumbnailRendering: server.thumbnailRendering, + thumbnailRendering: server.thumbnailRendering }; // Get additional information if the instance uses "PALETTE COLOR" photometric interpretation diff --git a/Packages/ohif-viewerbase/client/index.js b/Packages/ohif-viewerbase/client/index.js index b2a04b1a3..0cc115d49 100644 --- a/Packages/ohif-viewerbase/client/index.js +++ b/Packages/ohif-viewerbase/client/index.js @@ -59,6 +59,10 @@ Viewerbase.updateAllViewports = updateAllViewports; import { sortStudy } from './lib/sortStudy'; Viewerbase.sortStudy = sortStudy; +// updateMetaDataManager +import { updateMetaDataManager } from './lib/updateMetaDataManager'; +Viewerbase.updateMetaDataManager = updateMetaDataManager; + // updateOrientationMarkers import { updateOrientationMarkers } from './lib/updateOrientationMarkers'; Viewerbase.updateOrientationMarkers = updateOrientationMarkers; diff --git a/Packages/ohif-viewerbase/client/lib/classes/metadata/WadoRsMetaDataBuilder.js b/Packages/ohif-viewerbase/client/lib/classes/metadata/WadoRsMetaDataBuilder.js new file mode 100644 index 000000000..98d158b2e --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/classes/metadata/WadoRsMetaDataBuilder.js @@ -0,0 +1,48 @@ +export class WadoRsMetaDataBuilder { + constructor() { + this.tags = {}; + } + + addTag(tag, value, multi) { + this.tags[tag] = { + tag, + value, + multi + }; + + return this; + } + + toJSON() { + const json = {}; + const keys = Object.keys(this.tags); + + keys.forEach(key => { + if (!this.tags.hasOwnProperty(key)) { + return; + } + + const tag = this.tags[key]; + const multi = !!tag.multi; + let value = tag.value; + + if ((value == null) || ((value.length === 1) && (value[0] == null))) { + return; + } + + if ((typeof value === 'string') && multi) { + value = value.split('\\'); + } + + if (!_.isArray(value)) { + value = [value]; + } + + json[key] = { + Value: value + }; + }); + + return json; + } +} diff --git a/Packages/ohif-viewerbase/client/lib/getWADORSImageId.js b/Packages/ohif-viewerbase/client/lib/getWADORSImageId.js index 37702ae42..a5fe44d6b 100644 --- a/Packages/ohif-viewerbase/client/lib/getWADORSImageId.js +++ b/Packages/ohif-viewerbase/client/lib/getWADORSImageId.js @@ -1,66 +1,6 @@ import { OHIF } from 'meteor/ohif:core'; -import { _ } from 'meteor/underscore'; -class ImageMetadataBuilder { - constructor() { - this.tags = {}; - } - - addTag(tag, value, multi) { - this.tags[tag] = { - tag, - value, - multi - } - - return this; - } - - toJSON() { - const json = {}; - const keys = Object.keys(this.tags); - - keys.forEach(key => { - if(!this.tags.hasOwnProperty(key)) { - return; - } - - const tag = this.tags[key]; - const multi = !!tag.multi; - let value = tag.value; - - if((value == null) || ((value.length === 1) && (value[0] == null))) { - return; - } - - if((typeof value === 'string') && multi) { - value = value.split('\\'); - } - - if(!_.isArray(value)) { - value = [value]; - } - - json[key] = { - Value: value - }; - }); - - return json; - } -} - -function formatWADORSImageUrl(wadorsuri, frame) { - // We need to sum 1 because WADO-RS frame number is 1-based - frame = (frame || 0) + 1; - - // Replaces /frame/1 by /frame/{frame} - // TODO: Maybe should be better to export the WADOProxy to be able to use it on client - // Example: WADOProxy.convertURL(baseWadoRsUri + '/frame/' + frame) - wadorsuri = wadorsuri.replace(/(%2Fframes%2F)(\d+)/, `$1${frame}`); - - return Meteor.absoluteUrl(wadorsuri); -} +import { getWADORSImageUrl } from './getWADORSImageUrl'; /** * Obtain an imageId for Cornerstone based on the WADO-RS scheme @@ -69,51 +9,14 @@ function formatWADORSImageUrl(wadorsuri, frame) { * @returns {string} The imageId to be used by Cornerstone */ export function getWADORSImageId(instance, frame) { - const uri = formatWADORSImageUrl(instance.wadorsuri, frame); + const uri = getWADORSImageUrl(instance, frame); + + if (!uri) { + return; + } + const imageId = `wadors:${uri}`; - - const imageMetadata = new ImageMetadataBuilder() - .addTag('00080016', instance.sopClassUid) - .addTag('00080018', instance.sopInstanceUid) - .addTag('00180050', instance.sliceThickness) - .addTag('00200013', instance.instanceNumber) - .addTag('00200032', instance.imagePositionPatient, true) - .addTag('00200037', instance.imageOrientationPatient, true) - .addTag('00200052', instance.frameOfReferenceUID) - .addTag('00201041', instance.sliceLocation) - .addTag('00280002', instance.samplesPerPixel) - .addTag('00280004', instance.photometricInterpretation) - .addTag('00280006', instance.planarConfiguration) - .addTag('00280010', instance.rows) - .addTag('00280011', instance.columns) - .addTag('00280030', instance.pixelSpacing, true) - .addTag('00280034', instance.pixelAspectRatio, true) - .addTag('00280100', instance.bitsAllocated) - .addTag('00280101', instance.bitsStored) - .addTag('00280102', instance.highBit) - .addTag('00280103', instance.pixelRepresentation) - .addTag('00280106', instance.smallestPixelValue) - .addTag('00280107', instance.largestPixelValue) - .addTag('00281050', instance.windowCenter, true) - .addTag('00281051', instance.windowWidth, true) - .addTag('00281052', instance.rescaleIntercept) - .addTag('00281053', instance.rescaleSlope) - .addTag('00281054', instance.rescaleType) - .addTag('00281101', instance.redPaletteColorLookupTableDescriptor) - .addTag('00281102', instance.greenPaletteColorLookupTableDescriptor) - .addTag('00281103', instance.bluePaletteColorLookupTableDescriptor) - .addTag('00281201', instance.redPaletteColorLookupTableData) - .addTag('00281202', instance.greenPaletteColorLookupTableData) - .addTag('00281203', instance.bluePaletteColorLookupTableData) - .toJSON(); - - _.extend(imageMetadata, { - uri: uri, - instance: instance - }); - - cornerstoneWADOImageLoader.wadors.metaDataManager.add(imageId, imageMetadata); - OHIF.log.info('WADO-RS ImageID: ' + imageId); + return imageId; }; diff --git a/Packages/ohif-viewerbase/client/lib/getWADORSImageUrl.js b/Packages/ohif-viewerbase/client/lib/getWADORSImageUrl.js new file mode 100644 index 000000000..b21aaccd5 --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/getWADORSImageUrl.js @@ -0,0 +1,17 @@ +export function getWADORSImageUrl(instance, frame) { + let wadorsuri = instance.wadorsuri; + + if (!wadorsuri) { + return; + } + + // We need to sum 1 because WADO-RS frame number is 1-based + frame = (frame || 0) + 1; + + // Replaces /frame/1 by /frame/{frame} + // TODO: Maybe should be better to export the WADOProxy to be able to use it on client + // Example: WADOProxy.convertURL(baseWadoRsUri + '/frame/' + frame) + wadorsuri = wadorsuri.replace(/(%2Fframes%2F)(\d+)/, `$1${frame}`); + + return Meteor.absoluteUrl(wadorsuri); +} diff --git a/Packages/ohif-viewerbase/client/lib/updateMetaDataManager.js b/Packages/ohif-viewerbase/client/lib/updateMetaDataManager.js new file mode 100644 index 000000000..b847f68a7 --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/updateMetaDataManager.js @@ -0,0 +1,87 @@ +import { _ } from 'meteor/underscore'; +import { getWADORSImageId } from './getWADORSImageId'; +import { WadoRsMetaDataBuilder } from './classes/metadata/WadoRsMetaDataBuilder'; + +function getRadiopharmaceuticalInfoMetaData(instance) { + const radiopharmaceuticalInfo = instance.radiopharmaceuticalInfo; + + if ((instance.modality !== 'PT') || !radiopharmaceuticalInfo) { + return; + } + + return new WadoRsMetaDataBuilder() + .addTag('00181072', radiopharmaceuticalInfo.radiopharmaceuticalStartTime) + .addTag('00181074', radiopharmaceuticalInfo.radionuclideTotalDose) + .addTag('00181075', radiopharmaceuticalInfo.radionuclideHalfLife) + .toJSON(); +} + +const getWadoRsInstanceMetaData = (study, series, instance) => { + return new WadoRsMetaDataBuilder() + .addTag('00080016', instance.sopClassUid) + .addTag('00080018', instance.sopInstanceUid) + .addTag('00080021', series.seriesDate) + .addTag('00080031', series.seriesTime) + .addTag('00080060', instance.modality) + .addTag('00101010', study.patientAge) + .addTag('00101020', study.patientSize) + .addTag('00101030', study.patientWeight) + .addTag('00180050', instance.sliceThickness) + .addTag('0020000e', series.seriesInstanceUid) + .addTag('00200011', series.seriesNumber) + .addTag('0020000d', study.studyInstanceUid) + .addTag('00200013', instance.instanceNumber) + .addTag('00200032', instance.imagePositionPatient, true) + .addTag('00200037', instance.imageOrientationPatient, true) + .addTag('00200052', instance.frameOfReferenceUID) + .addTag('00201041', instance.sliceLocation) + .addTag('00280002', instance.samplesPerPixel) + .addTag('00280004', instance.photometricInterpretation) + .addTag('00280006', instance.planarConfiguration) + .addTag('00280010', instance.rows) + .addTag('00280011', instance.columns) + .addTag('00280030', instance.pixelSpacing, true) + .addTag('00280034', instance.pixelAspectRatio, true) + .addTag('00280100', instance.bitsAllocated) + .addTag('00280101', instance.bitsStored) + .addTag('00280102', instance.highBit) + .addTag('00280103', instance.pixelRepresentation) + .addTag('00280106', instance.smallestPixelValue) + .addTag('00280107', instance.largestPixelValue) + .addTag('00281050', instance.windowCenter, true) + .addTag('00281051', instance.windowWidth, true) + .addTag('00281052', instance.rescaleIntercept) + .addTag('00281053', instance.rescaleSlope) + .addTag('00281054', instance.rescaleType) + .addTag('00281101', instance.redPaletteColorLookupTableDescriptor) + .addTag('00281102', instance.greenPaletteColorLookupTableDescriptor) + .addTag('00281103', instance.bluePaletteColorLookupTableDescriptor) + .addTag('00281201', instance.redPaletteColorLookupTableData) + .addTag('00281202', instance.greenPaletteColorLookupTableData) + .addTag('00281203', instance.bluePaletteColorLookupTableData) + .addTag('00540016', getRadiopharmaceuticalInfoMetaData(instance)) + .toJSON(); +}; + +export function updateMetaDataManager(study) { + study.seriesList.forEach(series => { + series.instances.forEach(instance => { + // Cache just images that are going to be loaded via WADO-RS + if ((instance.imageRendering !== 'wadors') && (instance.thumbnailRendering !== 'wadors')) { + OHIF.log.info(`Skiping cornerstone metaDataManager update (${imageId})`); + return; + } + + const metaData = getWadoRsInstanceMetaData(study, series, instance); + const numberOfFrames = instance.numberOfFrames || 1; + + // We can share the same metaData with all frames because it doesn't have + // any frame specific data, such as frameNumber, pixelData, offset, etc. + // WADO-RS frame number is 1-based + for (let frameNumber = 0; frameNumber < numberOfFrames; frameNumber++) { + const imageId = getWADORSImageId(instance, frameNumber); + cornerstoneWADOImageLoader.wadors.metaDataManager.add(imageId, metaData); + } + }); + }); +}