diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 84dc9e3b8..eae3942a5 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -36,7 +36,7 @@ "cornerstone-math": "^0.1.9", "cornerstone-tools": "^6.0.6", "cornerstone-wado-image-loader": "^4.1.0", - "dcmjs": "0.19.8", + "dcmjs": "0.19.9", "dicom-parser": "^1.8.11", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-html/package.json b/extensions/dicom-html/package.json index 0c1c38560..e3b67eb79 100644 --- a/extensions/dicom-html/package.json +++ b/extensions/dicom-html/package.json @@ -29,7 +29,7 @@ }, "peerDependencies": { "@ohif/core": "^0.50.0", - "dcmjs": "0.19.8", + "dcmjs": "0.19.9", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-rt/package.json b/extensions/dicom-rt/package.json index 19fd73c5a..952729f40 100644 --- a/extensions/dicom-rt/package.json +++ b/extensions/dicom-rt/package.json @@ -31,7 +31,7 @@ "@ohif/core": "^0.50.0", "cornerstone-core": "^2.6.1", "cornerstone-tools": "^6.0.6", - "dcmjs": "0.19.8", + "dcmjs": "0.19.9", "gl-matrix": "^3.3.0", "prop-types": "^15.6.2", "react": "^16.8.6", diff --git a/extensions/dicom-segmentation/package.json b/extensions/dicom-segmentation/package.json index 0dbdb371f..86b39bea0 100644 --- a/extensions/dicom-segmentation/package.json +++ b/extensions/dicom-segmentation/package.json @@ -31,7 +31,7 @@ "@ohif/core": "^0.50.0", "cornerstone-core": "^2.6.1", "cornerstone-tools": "^6.0.6", - "dcmjs": "0.19.8", + "dcmjs": "0.19.9", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js index bf7ce2959..d54d0237e 100644 --- a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js +++ b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js @@ -226,7 +226,6 @@ const SegmentationPanel = ({ }; }, [ activeIndex, - updateSegmentationComboBox, viewports, ]); @@ -234,23 +233,9 @@ const SegmentationPanel = ({ const index = e.detail.activatedLabelmapIndex; if (index !== -1) { setState(state => ({ ...state, selectedSegmentation: index })); - } else { - cleanSegmentationComboBox(); } }; - const cleanSegmentationComboBox = () => { - setState(state => ({ - ...state, - segmentsHidden: [], - segmentNumbers: [], - labelMapList: [], - segmentList: [], - isDisabled: true, - selectedSegmentation: -1, - })); - }; - const refreshSegmentations = () => { const activeViewport = getActiveViewport(); const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID; @@ -709,7 +694,7 @@ const SegmentationPanel = ({ count={state.segmentList.length} isVisible={ state.segmentsHidden.filter(isHidden => isHidden === true).length < - state.segmentNumbers.length + state.segmentNumbers.length && state.segmentNumbers.length > 0 } onVisibilityChange={onVisibilityChangeHandler} > diff --git a/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js b/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js index 1bda8d2e2..309300819 100644 --- a/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js +++ b/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js @@ -47,14 +47,23 @@ export default async function setActiveLabelmap( return labelmapIndex; } - if (!displaySet.isLoaded) { + if (!displaySet.isLoaded && !displaySet.loadError) { try { await displaySet.load(referencedDisplaySet, studies); } catch (error) { displaySet.isLoaded = false; displaySet.loadError = true; + displaySet.segLoadErrorMessagge = error.message; onDisplaySetLoadFailure(error); + /* + * TODO: Improve the way we notify parts of the app + * that depends on derived display sets to be loaded. + * (Implement pubsub for better tracking of derived display sets) + */ + const event = new CustomEvent('segmentationLoadingError'); + document.dispatchEvent(event); + return -1; } } diff --git a/extensions/dicom-tag-browser/package.json b/extensions/dicom-tag-browser/package.json index 3596fdb04..6821b3507 100644 --- a/extensions/dicom-tag-browser/package.json +++ b/extensions/dicom-tag-browser/package.json @@ -28,7 +28,7 @@ }, "peerDependencies": { "@ohif/core": "^2.6.0", - "dcmjs": "0.19.8", + "dcmjs": "0.19.9", "react": "^16.8.6" }, "dependencies": { diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json index 8efe96f88..98a2aeacc 100644 --- a/extensions/vtk/package.json +++ b/extensions/vtk/package.json @@ -35,7 +35,7 @@ "cornerstone-core": "^2.6.1", "cornerstone-tools": "^6.0.6", "cornerstone-wado-image-loader": "^4.1.0", - "dcmjs": "0.19.8", + "dcmjs": "0.19.9", "dicom-parser": "^1.8.11", "i18next": "^17.0.3", "i18next-browser-languagedetector": "^3.0.1", diff --git a/platform/core/package.json b/platform/core/package.json index f8dffc046..f362e607f 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.19.8", + "dcmjs": "0.19.9", "dicomweb-client": "^0.8.3", "immer": "9.0.12", "isomorphic-base64": "^1.0.2", diff --git a/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js b/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js index eaaca285a..631f462c8 100644 --- a/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js +++ b/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js @@ -49,7 +49,12 @@ import studyMetadataManager from './studyMetadataManager'; * @param {object} snackbar * @returns void */ -async function loadAndCacheDerivedDisplaySets(referencedDisplaySet, studies, logger, snackbar) { +async function loadAndCacheDerivedDisplaySets( + referencedDisplaySet, + studies, + logger, + snackbar +) { const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet; const promises = []; const studyMetadata = studyMetadataManager.get(StudyInstanceUID); @@ -80,60 +85,111 @@ async function loadAndCacheDerivedDisplaySets(referencedDisplaySet, studies, log }); // For each type, see if any are loaded, if not load the most recent. - await Promise.all(Object.keys(displaySetsPerModality).map(async (key) => { - const displaySets = displaySetsPerModality[key]; + await Promise.all( + Object.keys(displaySetsPerModality).map(async key => { + const displaySets = displaySetsPerModality[key]; - const isLoaded = displaySets.some(displaySet => displaySet.isLoaded); - if (isLoaded) { - return; - } - - if (displaySets.some(displaySet => displaySet.loadError)) { - return; - } - - // find most recent and load it. - let recentDateTime = 0; - let recentDisplaySet = displaySets[0]; - - displaySets.forEach(displaySet => { - const dateTime = Number( - `${displaySet.SeriesDate}${displaySet.SeriesTime}` - ); - if (dateTime > recentDateTime) { - recentDateTime = dateTime; - recentDisplaySet = displaySet; + const isLoaded = displaySets.some(displaySet => displaySet.isLoaded); + if (isLoaded) { + return; } - }); - try { - if (recentDisplaySet.hasOwnProperty('getSourceDisplaySet') && - typeof recentDisplaySet.getSourceDisplaySet === 'function') { - await recentDisplaySet.getSourceDisplaySet(studies); - } else { - await recentDisplaySet.load(referencedDisplaySet, studies); + if (displaySets.some(displaySet => displaySet.loadError)) { + return; } - } catch (error) { - recentDisplaySet.isLoaded = false; - recentDisplaySet.loadError = true; - logger.error({ error, message: error.message }); - snackbar.show({ - title: 'Error loading derived display set:', - message: error.message, - type: 'error', - error, - autoClose: false, + + // find most recent and load it. + let recentDateTime = 0; + let recentDisplaySet = displaySets[0]; + + displaySets.forEach(displaySet => { + const dateTime = Number( + `${displaySet.SeriesDate}${displaySet.SeriesTime}` + ); + if (dateTime > recentDateTime) { + recentDateTime = dateTime; + recentDisplaySet = displaySet; + } }); - } - })); + + try { + if ( + recentDisplaySet.hasOwnProperty('getSourceDisplaySet') && + typeof recentDisplaySet.getSourceDisplaySet === 'function' + ) { + if (recentDisplaySet.Modality === 'SEG' && logger) { + const onDisplaySetLoadFailureHandler = error => { + logger.error({ error, message: error.message }); + snackbar.show({ + title: 'DICOM Segmentation Loader', + message: error.message, + type: 'error', + autoClose: true, + }); + }; + + let activatedLabelmapIndex = -1; + while (activatedLabelmapIndex == -1) { + const { + referencedDisplaySet, + activatedLabelmapPromise, + } = await recentDisplaySet.getSourceDisplaySet( + studies, + true, + onDisplaySetLoadFailureHandler + ); + + activatedLabelmapIndex = await activatedLabelmapPromise; + const selectionFired = new CustomEvent( + 'extensiondicomsegmentationsegselected', + { + detail: { activatedLabelmapIndex: activatedLabelmapIndex }, + } + ); + document.dispatchEvent(selectionFired); + + const lastDateTime = Number( + `${recentDisplaySet.SeriesDate}${recentDisplaySet.SeriesTime}` + ); + recentDateTime = 0; + displaySets.forEach(displaySet => { + const dateTime = Number( + `${displaySet.SeriesDate}${displaySet.SeriesTime}` + ); + if (dateTime > recentDateTime && dateTime < lastDateTime) { + recentDateTime = dateTime; + recentDisplaySet = displaySet; + } + }); + } + } else { + await recentDisplaySet.getSourceDisplaySet(studies); + } + } else { + await recentDisplaySet.load(referencedDisplaySet, studies); + } + } catch (error) { + recentDisplaySet.isLoaded = false; + recentDisplaySet.loadError = true; + logger.error({ error, message: error.message }); + snackbar.show({ + title: 'Error loading derived display set:', + message: error.message, + type: 'error', + error, + autoClose: false, + }); + } + }) + ); /* - * TODO: Improve the way we notify parts of the app - * that depends on derived display sets to be loaded. - * (Implement pubsub for better tracking of derived display sets) - */ + * TODO: Improve the way we notify parts of the app + * that depends on derived display sets to be loaded. + * (Implement pubsub for better tracking of derived display sets) + */ const event = new CustomEvent('deriveddisplaysetsloadedandcached'); document.dispatchEvent(event); -}; +} export default loadAndCacheDerivedDisplaySets; diff --git a/platform/viewer/package.json b/platform/viewer/package.json index 2f8344669..3757cdc61 100644 --- a/platform/viewer/package.json +++ b/platform/viewer/package.json @@ -66,7 +66,7 @@ "cornerstone-math": "^0.1.9", "cornerstone-tools": "^6.0.6", "cornerstone-wado-image-loader": "^4.1.0", - "dcmjs": "0.19.8", + "dcmjs": "0.19.9", "dicom-parser": "^1.8.11", "dicomweb-client": "^0.8.3", "hammerjs": "^2.0.8", diff --git a/platform/viewer/src/connectedComponents/Viewer.js b/platform/viewer/src/connectedComponents/Viewer.js index 8289c367f..54b6f48bf 100644 --- a/platform/viewer/src/connectedComponents/Viewer.js +++ b/platform/viewer/src/connectedComponents/Viewer.js @@ -105,6 +105,11 @@ class Viewer extends Component { if (this.props.dialog) { this.props.dialog.dismissAll(); } + + document.removeEventListener( + 'segmentationLoadingError', + this._updateThumbnails + ); } retrieveTimepoints = filter => { @@ -212,6 +217,12 @@ class Viewer extends Component { ), }); } + + document.addEventListener( + 'segmentationLoadingError', + this._updateThumbnails.bind(this), + false + ); } componentDidUpdate(prevProps) { @@ -255,6 +266,20 @@ class Viewer extends Component { } } + _updateThumbnails() { + const { studies, activeViewportIndex, viewports } = this.props; + + const activeViewport = viewports[activeViewportIndex]; + const activeDisplaySetInstanceUID = activeViewport + ? activeViewport.displaySetInstanceUID + : undefined; + + this.setState({ + thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID), + activeDisplaySetInstanceUID, + }); + } + _getActiveViewport() { return this.props.viewports[this.props.activeViewportIndex]; } @@ -461,9 +486,7 @@ const _checkForDerivedDisplaySets = async function(displaySet, study) { /** * Async function to check if there are any inconsistences in the series. * - * For segmentation checks that the geometry is consistent with the source images: - * 1) no frames out of plane; - * 2) have the same width and height. + * For segmentation returns any error during loading. * * For reconstructable 3D volume: * 1) Is series multiframe? @@ -477,10 +500,7 @@ const _checkForDerivedDisplaySets = async function(displaySet, study) { * @param {*object} displaySet * @returns {[string]} an array of strings containing the warnings */ -const _checkForSeriesInconsistencesWarnings = async function( - displaySet, - studies -) { +const _checkForSeriesInconsistencesWarnings = async function(displaySet) { if (displaySet.inconsistencyWarnings) { // warnings already checked and cached in displaySet return displaySet.inconsistencyWarnings; @@ -546,98 +566,14 @@ const _checkForSeriesInconsistencesWarnings = async function( if (displaySet.isModalitySupported === false) { inconsistencyWarnings.push('The datasets modality is not supported.'); } + displaySet.inconsistencyWarnings = inconsistencyWarnings; } else { - const segMetadata = displaySet.metadata; - if (!segMetadata) { - return inconsistencyWarnings; - } - - const { referencedDisplaySet } = displaySet.getSourceDisplaySet( - studies, - false - ); - if (!referencedDisplaySet) { - return inconsistencyWarnings; - } - - const imageIds = referencedDisplaySet.images.map(image => - image.getImageId() - ); - if (!imageIds || imageIds.length === 0) { - return inconsistencyWarnings; - } - - for ( - let i = 0, - groupsLen = segMetadata.PerFrameFunctionalGroupsSequence.length; - i < groupsLen; - ++i - ) { - const PerFrameFunctionalGroups = - segMetadata.PerFrameFunctionalGroupsSequence[i]; - if (!PerFrameFunctionalGroups) { - continue; - } - - let SourceImageSequence = undefined; - if (segMetadata.SourceImageSequence) { - SourceImageSequence = segMetadata.SourceImageSequence[i]; - } else if (PerFrameFunctionalGroups.DerivationImageSequence) { - SourceImageSequence = - PerFrameFunctionalGroups.DerivationImageSequence.SourceImageSequence; - } - if (!SourceImageSequence) { - if (inconsistencyWarnings.length === 0) { - const warningMessage = - 'The segmentation ' + - 'has frames out of plane respect to the source images.'; - inconsistencyWarnings.push(warningMessage); - } - continue; - } - - const { ReferencedSOPInstanceUID } = SourceImageSequence; - - const imageId = imageIds.find(imageId => { - const sopCommonModule = cornerstone.metaData.get( - 'sopCommonModule', - imageId - ); - if (!sopCommonModule) { - return; - } - - return sopCommonModule.sopInstanceUID === ReferencedSOPInstanceUID; - }); - - if (!imageId) { - continue; - } - - const sourceImageMetadata = cornerstone.metaData.get('instance', imageId); - if ( - segMetadata.Rows !== sourceImageMetadata.Rows || - segMetadata.Columns !== sourceImageMetadata.Columns - ) { - const warningMessage = - 'The segmentation ' + - 'has frames with different geometry ' + - 'dimensions (Rows and Columns) respect to the source images.'; - inconsistencyWarnings.push(warningMessage); - break; - } - } - - if (inconsistencyWarnings.length !== 0) { - const warningMessage = - 'The segmentation format is not supported yet. ' + - 'The segmentation data (segments) could not be loaded.'; - inconsistencyWarnings.push(warningMessage); + if (displaySet.loadError) { + inconsistencyWarnings.push(displaySet.segLoadErrorMessagge); + displaySet.inconsistencyWarnings = inconsistencyWarnings; } } - // cache the warnings - displaySet.inconsistencyWarnings = inconsistencyWarnings; return inconsistencyWarnings; }; @@ -736,10 +672,7 @@ const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) { altImageText = displaySet.Modality ? displaySet.Modality : 'UN'; } - const hasWarnings = _checkForSeriesInconsistencesWarnings( - displaySet, - studies - ); + const hasWarnings = _checkForSeriesInconsistencesWarnings(displaySet); const hasDerivedDisplaySets = _checkForDerivedDisplaySets( displaySet, diff --git a/platform/viewer/src/connectedComponents/ViewerMain.js b/platform/viewer/src/connectedComponents/ViewerMain.js index 640864d01..c71617177 100644 --- a/platform/viewer/src/connectedComponents/ViewerMain.js +++ b/platform/viewer/src/connectedComponents/ViewerMain.js @@ -154,12 +154,25 @@ class ViewerMain extends Component { }); }; - const { referencedDisplaySet } = displaySet.getSourceDisplaySet( + const { + referencedDisplaySet, + activatedLabelmapPromise, + } = displaySet.getSourceDisplaySet( this.props.studies, true, onDisplaySetLoadFailureHandler ); displaySet = referencedDisplaySet; + + activatedLabelmapPromise.then(activatedLabelmapIndex => { + const selectionFired = new CustomEvent( + 'extensiondicomsegmentationsegselected', + { + detail: { activatedLabelmapIndex: activatedLabelmapIndex }, + } + ); + document.dispatchEvent(selectionFired); + }); } else { displaySet = displaySet.getSourceDisplaySet(this.props.studies); } diff --git a/platform/viewer/src/googleCloud/DicomStorePickerModal.js b/platform/viewer/src/googleCloud/DicomStorePickerModal.js index 093408a89..02ba13849 100644 --- a/platform/viewer/src/googleCloud/DicomStorePickerModal.js +++ b/platform/viewer/src/googleCloud/DicomStorePickerModal.js @@ -36,7 +36,7 @@ function DicomStorePickerModal({ user, url, }, - onClose + onClose, }); } }; diff --git a/yarn.lock b/yarn.lock index 610043376..8a8fd59d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5449,10 +5449,10 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dcmjs@0.19.8: - version "0.19.8" - resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.19.8.tgz#9be7471f8f2e20d4e09489b5f0c51dfa56621ec1" - integrity sha512-po/GWh7pXF+diO/umpqOfpF0Kk7MzAmhFlscOR+KchCIZbN0pmt2npFT3p8jMii/FBre++k+8PIvFa/LKh0P2A== +dcmjs@0.19.9: + version "0.19.9" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.19.9.tgz#be500101b1f75a28ea9eb7ef845a8ed197355e76" + integrity sha512-V+0tND+SPX5e7WFeLzeoooaTsZbiKmEP2i3AyERsojaXXfaDV5E+pta5IqId4Ok1yfRT5qfTFwLGstGT6nLplA== dependencies: "@babel/polyfill" "^7.8.3" "@babel/runtime" "^7.8.4"