diff --git a/extensions/ohif-gradienthealth-extension/src/DicomJSONDataSource/index.js b/extensions/ohif-gradienthealth-extension/src/DicomJSONDataSource/index.js index e780fc6..5e4399a 100644 --- a/extensions/ohif-gradienthealth-extension/src/DicomJSONDataSource/index.js +++ b/extensions/ohif-gradienthealth-extension/src/DicomJSONDataSource/index.js @@ -12,13 +12,18 @@ import getImageId from '../DicomWebDataSource/utils/getImageId'; import _ from 'lodash'; const metadataProvider = classes.MetadataProvider; -const { datasetToBlob } = dcmjs.data; +const { datasetToBlob, DicomMetaDictionary } = dcmjs.data; const mappings = { studyInstanceUid: 'StudyInstanceUID', patientId: 'PatientID', }; +const GH_CUSTOM_TAGS = { + CustomOffsetTable: '60011002', + CustomOffsetTableLengths: '60011003', +}; + let _store = { urls: [], studyInstanceUIDMap: new Map(), // map of urls to array of study instance UIDs @@ -37,13 +42,23 @@ const getMetaDataByURL = url => { return _store.urls.find(metaData => metaData.url === url); }; -const getInstanceUrl = (url, prefix) => { - let modifiedUrl = prefix - ? url.replace( +const getInstanceUrl = (url, prefix, bucket, bucketPrefix) => { + let modifiedUrl = url; + + const schemaPresent = !!url.match(/^(dicomweb:|dicomzip:|wadouri:)/) + if (!schemaPresent) { + const filePath = url.split('studies/')[1]; + modifiedUrl = `dicomweb:https://storage.googleapis.com/${bucket}/${ + bucketPrefix ? bucketPrefix + '/' : '' + }studies/${filePath}`; + } + + modifiedUrl = prefix + ? modifiedUrl.replace( 'https://storage.googleapis.com', `https://storage.googleapis.com/${prefix}` ) - : url; + : modifiedUrl; const dicomwebRegex = /^dicomweb:/ modifiedUrl = modifiedUrl.includes(":zip//") @@ -53,13 +68,56 @@ const getInstanceUrl = (url, prefix) => { return modifiedUrl; } +const naturalizeMetadata = (metadata) => { + return { + ...DicomMetaDictionary.naturalizeDataset(metadata), + CustomOffsetTable: metadata[GH_CUSTOM_TAGS.CustomOffsetTable]?.Value, + CustomOffsetTableLengths: + metadata[GH_CUSTOM_TAGS.CustomOffsetTableLengths]?.Value, + }; +}; + +const mergeInstanceProperties = (instance) => { + return { + ...instance.metadata, + ...(instance.headers.start_byte && + instance.headers.end_byte && { + FileOffsets: { + startByte: instance.headers.start_byte, + endByte: instance.headers.end_byte, + }, + }), + }; +}; + const getProperty = (serieMetadata, property) => { return ( serieMetadata[property] || serieMetadata.instances[0].metadata[property] ); }; -const getMetadataFromRows = (rows, prefix, seriesuidArray) => { +const getMetadataFromRows = (data, prefix, seriesuidArray) => { + const rows = data.flatMap(({ metadata }) => + metadata.map((seriesMetadata) => ({ + ...seriesMetadata, + instances: seriesMetadata.instances.map((instance) => ({ + ...instance, + metadata: naturalizeMetadata(instance.metadata), + url: instance.url || instance.uri, + })), + })) + ); + const bucketMap = data.reduce( + (dataMap, { bucket, bucketPrefix, metadata }) => { + metadata.forEach(({ instances }) => + instances.forEach(({ url, uri }) => { + dataMap[url || uri] = { bucket, bucketPrefix }; + }) + ); + return dataMap; + }, + {} + ); // TODO: bq should not have dups let filteredRows = rows.map(row => { row.instances = _.uniqBy(row.instances, (x)=>x.url) @@ -74,7 +132,7 @@ const getMetadataFromRows = (rows, prefix, seriesuidArray) => { const rowsByStudy = Object.values( filteredRows.reduce((rowsByStudy, row) => { - const studyuid = row['StudyInstanceUID']; + const studyuid = getProperty(row, 'StudyInstanceUID'); if (!rowsByStudy[studyuid]) rowsByStudy[studyuid] = []; rowsByStudy[studyuid].push(row); return rowsByStudy; @@ -88,20 +146,27 @@ const getMetadataFromRows = (rows, prefix, seriesuidArray) => { const series = rows.map(row => { return { - SeriesInstanceUID: row['SeriesInstanceUID'], - Modality: row['Modality'], - SeriesDescription: row['SeriesDescription'] || 'No description', - StudyInstanceUID: row['StudyInstanceUID'], - SeriesNumber: row['SeriesNumber'], - SeriesDate: row['SeriesDate'], - SeriesTime: row['SeriesTime'], - NumInstances: isNaN(parseInt(row['NumInstances'])) + SeriesInstanceUID: getProperty(row, 'SeriesInstanceUID'), + Modality: getProperty(row, 'Modality'), + SeriesDescription: + getProperty(row, 'SeriesDescription') || 'No description', + StudyInstanceUID: getProperty(row, 'StudyInstanceUID'), + SeriesNumber: getProperty(row, 'SeriesNumber'), + SeriesDate: getProperty(row, 'SeriesDate'), + SeriesTime: getProperty(row, 'SeriesTime'), + NumInstances: isNaN(parseInt(getProperty(row, 'NumInstances'))) ? 0 - : parseInt(row['NumInstances']), - instances: row['instances'].map(instance => { + : parseInt(getProperty(row, 'NumInstances')), + instances: row['instances'].map((instance) => { + const url = instance.url; return { - metadata: instance.metadata, - url: getInstanceUrl(instance.url, prefix), + metadata: mergeInstanceProperties(instance), + url: getInstanceUrl( + url, + prefix, + bucketMap[url].bucket, + bucketMap[url].bucketPrefix + ), }; }), }; @@ -195,7 +260,7 @@ const filesFromStudyInstanceUID = async ({bucketName, prefix, studyuids, headers const files = res.items || []; const folders = res.prefixes || []; const series = folders.map(async (folderPath)=>{ - const objectName = `${folderPath}metadata`; + const objectName = `${folderPath}metadata.json`; const apiUrl = `https://storage.googleapis.com/storage/v1/b/${bucketName}/o/${encodeURIComponent(objectName)}?alt=media`; const response = await fetch(apiUrl, { headers }); return response.json() @@ -313,7 +378,7 @@ const storeDicomSeg = async (naturalizedReport, headers, displaySetService) => { const compressedFile = pako.gzip(JSON.stringify(segSeries)); return fetch( - `https://storage.googleapis.com/upload/storage/v1/b/${segBucket}/o?uploadType=media&name=${segPrefix}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/metadata&contentEncoding=gzip`, + `https://storage.googleapis.com/upload/storage/v1/b/${segBucket}/o?uploadType=media&name=${segPrefix}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/metadata.json&contentEncoding=gzip`, { method: 'POST', headers: { @@ -381,14 +446,20 @@ function createDicomJSONApi(dicomJsonConfig, servicesManager) { const studyMetadata = []; for (let i = 0; i < buckets.length; i++) { + const bucket = buckets[i], + bucketPrefix = query.get('bucket-prefix') || 'dicomweb'; const metadataPerBucket = await filesFromStudyInstanceUID({ - bucketName: buckets[i], - prefix: query.get('bucket-prefix') || 'dicomweb', + bucketName: bucket, + prefix: bucketPrefix, studyuids: query.getAll('StudyInstanceUID'), headers: UserAuthenticationService.getAuthorizationHeader(), }); - studyMetadata.push(...metadataPerBucket); + studyMetadata.push({ + bucket, + bucketPrefix, + metadata: metadataPerBucket.flatMap(e=>e), + }); } const data = getMetadataFromRows(