diff --git a/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js b/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js index cf3ecccf5..f94ea055c 100644 --- a/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js +++ b/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js @@ -22,6 +22,12 @@ const OHIFDicomHtmlSopClassHandler = { getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { const instance = series.getFirstInstance(); + const { + SeriesDate, + SeriesTime, + SeriesNumber, + } = instance._instance.metadata; + return { plugin: 'html', Modality: 'SR', @@ -31,6 +37,9 @@ const OHIFDicomHtmlSopClassHandler = { SOPInstanceUID: instance.getSOPInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(), StudyInstanceUID: study.getStudyInstanceUID(), + SeriesDate, + SeriesTime, + SeriesNumber, authorizationHeaders, }; }, diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js index cef4e49d1..909e39bf2 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js +++ b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js @@ -12,6 +12,12 @@ const DicomMicroscopySopClassHandler = { getDisplaySetFromSeries(series, study, dicomWebClient) { const instance = series.getFirstInstance(); + const { + ContentDate, + ContentTime, + SeriesNumber, + } = instance._instance.metadata; + // Note: We are passing the dicomweb client into each viewport! return { @@ -22,6 +28,9 @@ const DicomMicroscopySopClassHandler = { SOPInstanceUID: instance.getSOPInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(), StudyInstanceUID: study.getStudyInstanceUID(), + SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting. + SeriesTime: ContentTime, + SeriesNumber, }; }, }; diff --git a/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js b/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js index c07f1c39c..9550ed80f 100644 --- a/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js +++ b/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js @@ -12,6 +12,12 @@ const OHIFDicomPDFSopClassHandler = { getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { const instance = series.getFirstInstance(); + const { + ContentDate, + ContentTime, + SeriesNumber, + } = instance._instance.metadata; + return { plugin: 'pdf', Modality: 'DOC', @@ -21,6 +27,9 @@ const OHIFDicomPDFSopClassHandler = { SOPInstanceUID: instance.getSOPInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(), StudyInstanceUID: study.getStudyInstanceUID(), + SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting. + SeriesTime: ContentTime, + SeriesNumber, authorizationHeaders: authorizationHeaders, }; }, diff --git a/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js b/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js index 5095e82bf..0aae17761 100644 --- a/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js +++ b/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js @@ -27,6 +27,7 @@ const OHIFDicomRTStructSopClassHandler = { const { SeriesDate, SeriesTime, + SeriesNumber, SeriesDescription, FrameOfReferenceUID, SOPInstanceUID, @@ -53,6 +54,7 @@ const OHIFDicomRTStructSopClassHandler = { isLoaded: false, SeriesDate, SeriesTime, + SeriesNumber, SeriesDescription, }; diff --git a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js index a396ad92d..83f84ed42 100644 --- a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js +++ b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js @@ -28,6 +28,7 @@ export default function getSopClassHandlerModule({ servicesManager }) { const { SeriesDate, SeriesTime, + SeriesNumber, SeriesDescription, FrameOfReferenceUID, SOPInstanceUID, @@ -52,6 +53,7 @@ export default function getSopClassHandlerModule({ servicesManager }) { isLoaded: false, SeriesDate, SeriesTime, + SeriesNumber, SeriesDescription, }; diff --git a/platform/core/src/classes/OHIFStudyMetadataSource.js b/platform/core/src/classes/OHIFStudyMetadataSource.js index 4e81f4113..8c7e82b79 100644 --- a/platform/core/src/classes/OHIFStudyMetadataSource.js +++ b/platform/core/src/classes/OHIFStudyMetadataSource.js @@ -54,9 +54,6 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource { // Get Study display sets const displaySets = studyMetadata.createDisplaySets(); - // Set studyMetadata display sets - studyMetadata.setDisplaySets(displaySets); - OHIFStudyMetadataSource._updateStudyCollections(studyMetadata); resolve(studyMetadata); }) diff --git a/platform/core/src/classes/metadata/StudyMetadata.js b/platform/core/src/classes/metadata/StudyMetadata.js index c8d02cac7..06810710b 100644 --- a/platform/core/src/classes/metadata/StudyMetadata.js +++ b/platform/core/src/classes/metadata/StudyMetadata.js @@ -10,8 +10,8 @@ import { api } from 'dicomweb-client'; // - createStacks import { isImage } from '../../utils/isImage'; import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable'; -import isLowPriorityModality from '../../utils/isLowPriorityModality'; import errorHandler from '../../errorHandler'; +import isLowPriorityModality from '../../utils/isLowPriorityModality'; export class StudyMetadata extends Metadata { constructor(data, uid) { @@ -308,14 +308,10 @@ export class StudyMetadata extends Metadata { series ); - displaySets.push(...displaySetsForSeries); + displaySetsForSeries.forEach(ds => this._insertDisplaySet(ds)); }); - return sortDisplaySetList(displaySets); - } - - sortDisplaySets() { - sortDisplaySetList(this._displaySets); + return this._displaySets; } /** @@ -346,25 +342,9 @@ export class StudyMetadata extends Metadata { this.addDisplaySet(displaySet); }); - this.sortDisplaySets(); - return true; } - /** - * Set display sets - * @param {Array} displaySets Array of display sets (ImageSet[]) - */ - 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(); - } - } - /** * Add a single display set to the list * @param {Object} displaySet Display set object @@ -372,7 +352,7 @@ export class StudyMetadata extends Metadata { */ addDisplaySet(displaySet) { if (displaySet instanceof ImageSet || displaySet.sopClassModule) { - this._displaySets.push(displaySet); + this._insertDisplaySet(displaySet); return true; } return false; @@ -393,6 +373,96 @@ export class StudyMetadata extends Metadata { } } + /** + * Insert the displaySet so that the list has an increasing SeriesNumber, + * with the most recent series first for displaySets with the same SeriesNumber. + * + * If the displaySet is low priority, the same logic is applied, but is sorted within a sub list + * At the end of the list, where all low priority data is found. + */ + _insertDisplaySet(displaySet) { + const { SeriesNumber } = displaySet; + const displaySets = this._displaySets; + let insertIndex = displaySets.length; + let firstIndexWithSameSeriesNumber; + + // If low priority, start search from next low priority. + if (isLowPriorityModality(displaySet.Modality)) { + let startingIndex; + + // Find where the first low priority displaySet is. + for (let i = 0; i < displaySets.length; i++) { + if (isLowPriorityModality(displaySets[i].Modality)) { + startingIndex = i; + break; + } + } + + if (!startingIndex) { + startingIndex = displaySets.length; + } + + // Find the correct SeriesNumber location to insert within the low priority + // Modality displaySets + for (let i = startingIndex; i < displaySets.length; i++) { + if ( + displaySets[i].SeriesNumber === SeriesNumber && + !firstIndexWithSameSeriesNumber + ) { + firstIndexWithSameSeriesNumber = i; + } + + if (displaySets[i].SeriesNumber > SeriesNumber) { + insertIndex = i; + break; + } + } + } else { + // Find correct SeriesNumber to insert or where the low priority modalities start. + for (let i = 0; i < displaySets.length; i++) { + if ( + displaySets[i].SeriesNumber === SeriesNumber && + !firstIndexWithSameSeriesNumber + ) { + firstIndexWithSameSeriesNumber = i; + } + + if ( + displaySets[i].SeriesNumber > SeriesNumber || + isLowPriorityModality(displaySets[i].Modality) + ) { + insertIndex = i; + break; + } + } + } + + // If we have multiple displaySets with the same series number, find the insert position based on + // SeriesDate and SeriesTime. + if (firstIndexWithSameSeriesNumber !== undefined) { + // If no SeriesDate, is just a placeholder displaySet, just insert anywhere, it will be re-added later. + if (displaySet.SeriesDate) { + const seriesDateTime = `${displaySet.SeriesDate}${displaySet.SeriesTime}`; + + for (let i = firstIndexWithSameSeriesNumber; i < insertIndex; i++) { + const displaySetI = displaySets[i]; + + if ( + displaySetI.SeriesDate && + `${displaySetI.SeriesDate}${displaySetI.SeriesTime}` < + seriesDateTime + ) { + insertIndex = i; + break; + } + } + } + } + + this._displaySets.splice(insertIndex, 0, displaySet); + this.displaySets = this._displaySets; + } + /** * Search the associated display sets using the supplied callback as criteria. The callback is passed * two arguments: display set (an ImageSet instance) and index (the integer @@ -548,49 +618,6 @@ export class StudyMetadata extends Metadata { return this._series.indexOf(series); } - /** - * It sorts the series based on display sets order. Each series must be an instance - * of SeriesMetadata and each display sets must be an instance of ImageSet. - * Useful example of usage: - * Study data provided by backend does not sort series at all and client-side - * needs series sorted by the same criteria used for sorting display sets. - */ - sortSeriesByDisplaySets() { - // Object for mapping display sets' index by SeriesInstanceUID - const displaySetsMapping = {}; - - // Loop through each display set to create the mapping - this.forEachDisplaySet((displaySet, index) => { - if (!(displaySet instanceof ImageSet)) { - throw new OHIFError( - `StudyMetadata::sortSeriesByDisplaySets display set at index ${index} is not an instance of ImageSet` - ); - } - - // In case of multiframe studies, just get the first index occurence - if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) { - displaySetsMapping[displaySet.SeriesInstanceUID] = index; - } - }); - - // Clone of actual series - const actualSeries = this.getSeries(); - - actualSeries.forEach((series, index) => { - if (!(series instanceof SeriesMetadata)) { - throw new OHIFError( - `StudyMetadata::sortSeriesByDisplaySets series at index ${index} is not an instance of SeriesMetadata` - ); - } - - // Get the new series index - const seriesIndex = displaySetsMapping[series.getSeriesInstanceUID()]; - - // Update the series object with the new series position - this._series[seriesIndex] = series; - }); - } - /** * Compares the current study instance with another one. * @param {StudyMetadata} study An instance of the StudyMetadata class. @@ -865,53 +892,3 @@ function _getDisplaySetFromSopClassModule( } return displaySet; } - -/** - * Sort series primarily by Modality (i.e., series with references to other - * series like SEG, KO or PR are grouped in the end of the list) and then by - * series number: - * - * -------- - * | CT #3 | - * | CT #4 | - * | CT #5 | - * -------- - * | SEG #1 | - * | SEG #2 | - * -------- - * - * @param {*} a - DisplaySet - * @param {*} b - DisplaySet - */ - -function seriesSortingCriteria(a, b) { - const isLowPriorityA = isLowPriorityModality(a.Modality); - const isLowPriorityB = isLowPriorityModality(b.Modality); - if (!isLowPriorityA && isLowPriorityB) { - return -1; - } - if (isLowPriorityA && !isLowPriorityB) { - return 1; - } - return sortBySeriesNumber(a, b); -} - -/** - * Sort series by series number. Series with low - * @param {*} a - DisplaySet - * @param {*} b - DisplaySet - */ -function sortBySeriesNumber(a, b) { - const seriesNumberAIsGreaterOrUndefined = - a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber); - - return seriesNumberAIsGreaterOrUndefined ? 1 : -1; -} - -/** - * Sorts a list of display set objects - * @param {Array} list A list of display sets to be sorted - */ -function sortDisplaySetList(list) { - return list.sort(seriesSortingCriteria); -} diff --git a/platform/core/src/utils/isLowPriorityModality.js b/platform/core/src/utils/isLowPriorityModality.js index d2794a8ce..42a5c3040 100644 --- a/platform/core/src/utils/isLowPriorityModality.js +++ b/platform/core/src/utils/isLowPriorityModality.js @@ -1,4 +1,11 @@ -const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR']); +const LOW_PRIORITY_MODALITIES = Object.freeze([ + 'SEG', + 'DOC', + 'RTSTRUCT', + 'SR', + 'KO', + 'PR', +]); export default function isLowPriorityModality(Modality) { return LOW_PRIORITY_MODALITIES.includes(Modality); diff --git a/platform/viewer/src/connectedComponents/Viewer.js b/platform/viewer/src/connectedComponents/Viewer.js index 632d2909b..08d70d741 100644 --- a/platform/viewer/src/connectedComponents/Viewer.js +++ b/platform/viewer/src/connectedComponents/Viewer.js @@ -19,6 +19,7 @@ import UserManagerContext from '../context/UserManagerContext'; import AppContext from '../context/AppContext'; import './Viewer.css'; +import { finished } from 'stream'; class Viewer extends Component { static propTypes = { @@ -189,6 +190,7 @@ class Viewer extends Component { currentTimepointId, ]); } + this.setState({ thumbnails: _mapStudiesToThumbnails(studies), }); @@ -197,6 +199,7 @@ class Viewer extends Component { componentDidUpdate(prevProps) { const { studies, isStudyLoaded } = this.props; + if (studies !== prevProps.studies) { this.setState({ thumbnails: _mapStudiesToThumbnails(studies), @@ -355,7 +358,6 @@ export default withDialog(Viewer); * a mapping layer? * * TODO[react]: - * - Add sorting of display sets * - Add showStackLoadingProgressBar option * * @param {Study[]} studies @@ -369,9 +371,9 @@ const _mapStudiesToThumbnails = function(studies) { const { displaySetInstanceUID, SeriesDescription, - SeriesNumber, InstanceNumber, numImageFrames, + SeriesNumber, } = displaySet; let imageId; @@ -395,9 +397,9 @@ const _mapStudiesToThumbnails = function(studies) { altImageText, displaySetInstanceUID, SeriesDescription, - SeriesNumber, InstanceNumber, numImageFrames, + SeriesNumber, }; }); diff --git a/platform/viewer/src/connectedComponents/ViewerLocalFileData.js b/platform/viewer/src/connectedComponents/ViewerLocalFileData.js index c0e93d979..9d211f2d8 100644 --- a/platform/viewer/src/connectedComponents/ViewerLocalFileData.js +++ b/platform/viewer/src/connectedComponents/ViewerLocalFileData.js @@ -77,7 +77,8 @@ class ViewerLocalFileData extends Component { study.displaySets = study.displaySets || studyMetadata.createDisplaySets(sopClassHandlerModules); - studyMetadata.setDisplaySets(study.displaySets); + + debugger; studyMetadata.forEachDisplaySet(displayset => { displayset.localFile = true; diff --git a/platform/viewer/src/connectedComponents/ViewerRetrieveStudyData.js b/platform/viewer/src/connectedComponents/ViewerRetrieveStudyData.js index 5a4646ff0..1d003cf9e 100644 --- a/platform/viewer/src/connectedComponents/ViewerRetrieveStudyData.js +++ b/platform/viewer/src/connectedComponents/ViewerRetrieveStudyData.js @@ -47,7 +47,6 @@ const _promoteList = (study, studyMetadata, filters, isFilterStrategy) => { let promoted = false; // Promote only if no filter should be applied if (!isFilterStrategy) { - _sortStudyDisplaySet(study, studyMetadata); promoted = _promoteStudyDisplaySet(study, studyMetadata, filters); } @@ -193,12 +192,6 @@ const _updateStudyDisplaySets = (study, studyMetadata) => { if (study.derivedDisplaySets) { studyMetadata._addDerivedDisplaySets(study.derivedDisplaySets); } - - studyMetadata.setDisplaySets(study.displaySets); -}; - -const _sortStudyDisplaySet = (study, studyMetadata) => { - studyMetadata.sortDisplaySets(study.displaySets); }; const _thinStudyData = study => { diff --git a/platform/viewer/src/routes/StandaloneRouting.js b/platform/viewer/src/routes/StandaloneRouting.js index b2a3038fa..82ee9fa54 100644 --- a/platform/viewer/src/routes/StandaloneRouting.js +++ b/platform/viewer/src/routes/StandaloneRouting.js @@ -194,7 +194,6 @@ const _mapStudiesToNewFormat = studies => { study.displaySets = study.displaySets || studyMetadata.createDisplaySets(sopClassHandlerModules); - studyMetadata.setDisplaySets(study.displaySets); studyMetadataManager.add(studyMetadata); uniqueStudyUIDs.add(study.StudyInstanceUID);