diff --git a/platform/core/package.json b/platform/core/package.json index 8fd794a86..8b7d19946 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -39,7 +39,7 @@ "dependencies": { "@babel/runtime": "^7.5.5", "ajv": "^6.10.0", - "dcmjs": "^0.8.3", + "dcmjs": "^0.12.0", "dicomweb-client": "^0.5.2", "immer": "6.0.2", "isomorphic-base64": "^1.0.2", diff --git a/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js b/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js index 4db4db76c..9f8a3129a 100644 --- a/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js +++ b/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js @@ -10,6 +10,13 @@ const findMostRecentStructuredReport = studies => { studies.forEach(study => { const allSeries = study.getSeries ? study.getSeries() : []; allSeries.forEach(series => { + // Skip series that may not have instances yet + // This can happen if we have retrieved just the initial + // details about the series via QIDO-RS, but not the full metadata + if (!series.instances.length) { + return; + } + if (isStructuredReportSeries(series)) { if ( !mostRecentStructuredReport || diff --git a/platform/core/src/classes/metadata/StudyMetadata.js b/platform/core/src/classes/metadata/StudyMetadata.js index 9cdcbd3bc..cd344e8cc 100644 --- a/platform/core/src/classes/metadata/StudyMetadata.js +++ b/platform/core/src/classes/metadata/StudyMetadata.js @@ -100,20 +100,32 @@ export class StudyMetadata extends Metadata { * Split a series metadata object into display sets * @param {Array} sopClassHandlerModules List of SOP Class Modules * @param {SeriesMetadata} series The series metadata object from which the display sets will be created - * @param {Array} [givenDisplaySets] An optional list to which the display sets will be appended * @returns {Array} The list of display sets created for the given series object */ _createDisplaySetsForSeries( sopClassHandlerModules, series, - givenDisplaySets ) { const study = this; - const displaySets = Array.isArray(givenDisplaySets) ? givenDisplaySets : []; + const displaySets = []; + const anyInstances = series.getInstanceCount() > 0; if (!anyInstances) { - return; + const displaySet = new ImageSet([]); + const seriesData = series.getData(); + + displaySet.setAttributes({ + displaySetInstanceUID: displaySet.uid, + SeriesInstanceUID: seriesData.SeriesInstanceUID, + SeriesDescription: seriesData.SeriesDescription, + SeriesNumber: seriesData.SeriesNumber, + Modality: seriesData.Modality, + }); + + displaySets.push(displaySet); + + return displaySets; } const sopClassUIDs = getSopClassUIDs(series); @@ -282,13 +294,14 @@ export class StudyMetadata extends Metadata { // Loop through the series (SeriesMetadata) this.forEachSeries( - series => - void this._createDisplaySetsForSeries( - sopClassHandlerModules, - series, - displaySets - ) - ); + series => { + const displaySetsForSeries = this._createDisplaySetsForSeries( + sopClassHandlerModules, + series, + ); + + displaySets.push(...displaySetsForSeries); + }); return sortDisplaySetList(displaySets); } @@ -304,13 +317,27 @@ export class StudyMetadata extends Metadata { * @returns {boolean} Returns true on success or false on failure (e.g., the series does not belong to this study) */ createAndAddDisplaySetsForSeries(sopClassHandlerModules, series) { - if (this.containsSeries(series)) { - this.setDisplaySets( - this._createDisplaySetsForSeries(sopClassHandlerModules, series) - ); - return true; + if (!this.containsSeries(series)) { + return false; } - return false; + + const displaySets = this._createDisplaySetsForSeries(sopClassHandlerModules, series) + + // Note: filtering in place because this._displaySets has writable: false + for (let i = this._displaySets.length - 1; i >= 0; i--) { + const displaySet = this._displaySets[i]; + if (displaySet.SeriesInstanceUID === series.getSeriesInstanceUID()) { + this._displaySets.splice(i, 1); + } + } + + displaySets.forEach(displaySet => { + this.addDisplaySet(displaySet); + }); + + this.sortDisplaySets(); + + return true; } /** @@ -319,6 +346,9 @@ export class StudyMetadata extends Metadata { */ setDisplaySets(displaySets) { if (Array.isArray(displaySets) && displaySets.length > 0) { + // TODO: This is weird, can we just switch it to writable: true? + this._displaySets.splice(0); + displaySets.forEach(displaySet => this.addDisplaySet(displaySet)); this.sortDisplaySets(); } @@ -407,6 +437,30 @@ export class StudyMetadata extends Metadata { return result; } + /** + * Update a series in the current study by SeriesInstanceUID. + * @param {String} SeriesInstanceUID The SeriesInstanceUID to be updated + * @param {SeriesMetadata} series The series to be added to the current study. + * @returns {boolean} Returns true on success, false otherwise. + */ + updateSeries(SeriesInstanceUID, series) { + const index = this._series.findIndex(series => { + return series.getSeriesInstanceUID() === SeriesInstanceUID; + }); + + if (index < 0) { + return false; + } + + if (!(series instanceof SeriesMetadata)) { + throw new Error('Series must be an instance of SeriesMetadata'); + } + + this._series[index] = series; + + return true; + } + /** * Find a series by index. * @param {number} index An integer representing a list index. diff --git a/platform/core/src/studies/services/wado/retrieveMetadataLoaderAsync.js b/platform/core/src/studies/services/wado/retrieveMetadataLoaderAsync.js index 2f224274b..74d18a79a 100644 --- a/platform/core/src/studies/services/wado/retrieveMetadataLoaderAsync.js +++ b/platform/core/src/studies/services/wado/retrieveMetadataLoaderAsync.js @@ -1,4 +1,5 @@ import { api } from 'dicomweb-client'; +import dcmjs from 'dcmjs'; import DICOMWeb from '../../../DICOMWeb/'; import RetrieveMetadataLoader from './retrieveMetadataLoader'; import { sortStudySeries, sortingCriteria } from '../../sortStudy'; @@ -8,6 +9,8 @@ import { addInstancesToStudy, } from './studyInstanceHelpers'; +const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary; + /** * Map series to an array of SeriesInstanceUID * @param {Arrays} series list of Series Instance UIDs @@ -101,15 +104,21 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader async preLoad() { const preLoaders = this.getPreLoaders(); - const result = await this.runLoaders(preLoaders); + + // seriesData is the result of the QIDO-RS Search For Series request + // It's an array of Objects containing DICOM Tag values at the Series level + const seriesData = await this.runLoaders(preLoaders); const seriesSorted = sortStudySeries( - result, + seriesData, sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria ); const seriesInstanceUIDsMap = mapStudySeries(seriesSorted); - return seriesInstanceUIDsMap; + return { + seriesInstanceUIDsMap, + seriesData + }; } async load(preLoadData) { @@ -118,7 +127,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader const seriesAsyncLoader = makeSeriesAsyncLoader( client, studyInstanceUID, - preLoadData + preLoadData.seriesInstanceUIDsMap ); const firstSeries = await seriesAsyncLoader.next(); @@ -126,16 +135,38 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader return { sopInstances: firstSeries.sopInstances, asyncLoader: seriesAsyncLoader, + seriesData: preLoadData.seriesData, }; } async posLoad(loadData) { const { server } = this; - const { sopInstances, asyncLoader } = loadData; + const { sopInstances, asyncLoader, seriesData } = loadData; const study = await createStudyFromSOPInstanceList(server, sopInstances); + // TODO: Should this be in a helper + const seriesDataNaturalized = seriesData.map(naturalizeDataset); + + seriesDataNaturalized.forEach((series, idx) => { + const seriesDataFromQIDO = { + SeriesInstanceUID: series.SeriesInstanceUID, + SeriesDescription: series.SeriesDescription, + SeriesNumber: series.SeriesNumber, + Modality: series.Modality, + instances: [] + }; + + if (study.series[idx]) { + study.series[idx] = Object.assign(seriesDataFromQIDO, study.series[idx]); + } else { + study.series[idx] = seriesDataFromQIDO; + } + + study.seriesMap[series.SeriesInstanceUID] = study.series[idx]; + }); + if (asyncLoader.hasNext()) { attachSeriesLoader(server, study, asyncLoader); } diff --git a/platform/ui/src/viewer/ViewportLoadingIndicator.js b/platform/ui/src/viewer/ViewportLoadingIndicator.js index 9bf8feb8f..9f44921f9 100644 --- a/platform/ui/src/viewer/ViewportLoadingIndicator.js +++ b/platform/ui/src/viewer/ViewportLoadingIndicator.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; export function ViewportLoadingIndicator(props) { return (