From 8bc12a37d0353160ae5ea4624dc0b244b7d59c07 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 5 Oct 2023 23:39:56 -0400 Subject: [PATCH] fix(bugs): fixing lots of bugs regarding release candidate (#3700) --- .webpack/webpack.base.js | 1 + extensions/cornerstone/src/commandsModule.ts | 37 +++++++++++------ .../src/getHangingProtocolModule.ts | 13 +----- .../CornerstoneCacheService.ts | 4 +- .../utils/StaticWadoClient.ts | 2 +- .../default/src/Panels/PanelStudyBrowser.tsx | 7 ---- extensions/default/src/commandsModule.ts | 28 ++++++++++++- platform/app/src/routes/DataSourceWrapper.tsx | 3 +- .../DicomMetadataStore/DicomMetadataStore.ts | 2 +- .../createSeriesMetadata.js | 21 +++++++++- .../DicomMetadataStore/createStudyMetadata.js | 40 ++++--------------- .../services/ToolBarService/ToolbarService.ts | 16 +++++++- .../configuration/dataSources/dicom-json.md | 11 +++++ .../configuration/dataSources/static-files.md | 4 +- .../docs/deployment/build-for-production.md | 2 + platform/docs/docs/deployment/iframe.md | 4 +- 16 files changed, 119 insertions(+), 76 deletions(-) diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index 985690bd32b..004c73ece8e 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -35,6 +35,7 @@ dotenv.config(); const defineValues = { /* Application */ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + 'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG), 'process.env.DEBUG': JSON.stringify(process.env.DEBUG), 'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'), 'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM), diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts index c051cae884c..fe18fbac169 100644 --- a/extensions/cornerstone/src/commandsModule.ts +++ b/extensions/cornerstone/src/commandsModule.ts @@ -28,7 +28,6 @@ function commandsModule({ toolGroupService, cineService, toolbarService, - stateSyncService, uiDialogService, cornerstoneViewportService, uiNotificationService, @@ -155,7 +154,7 @@ function commandsModule({ * @param props - containing the updates to apply * @param props.measurementKey - chooses the measurement key to apply the * code to. This will typically be finding or site to apply a - * finind code or a findingSites code. + * finding code or a findingSites code. * @param props.code - A coding scheme value from DICOM, including: * * CodeValue - the language independent code, for example '1234' * * CodingSchemeDesignator - the issue of the code value @@ -225,6 +224,25 @@ function commandsModule({ arrowTextCallback: ({ callback, data }) => { callInputDialog(uiDialogService, data, callback); }, + cleanUpCrosshairs: () => { + // if the crosshairs tool is active, deactivate it and set window level active + // since we are going back to main non-mpr HP + const activeViewportToolGroup = toolGroupService.getToolGroup(null); + + if (activeViewportToolGroup._toolInstances?.Crosshairs?.mode === Enums.ToolModes.Active) { + actions.toolbarServiceRecordInteraction({ + interactionType: 'tool', + commands: [ + { + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + }); + } + }, toggleCine: () => { const { viewports } = viewportGridService.getState(); const { isCineEnabled } = cineService.getState(); @@ -294,18 +312,8 @@ function commandsModule({ } const toolGroup = toolGroupService.getToolGroup(toolGroupId); - const toolGroupViewportIds = toolGroup?.getViewportIds?.(); - // if toolGroup has been destroyed, or its viewports have been removed - if (!toolGroupViewportIds || !toolGroupViewportIds.length) { - return; - } - - const filteredViewports = Array.from(viewports.values()).filter(viewport => { - return toolGroupViewportIds.includes(viewport.viewportId); - }); - - if (!filteredViewports.length) { + if (!toolGroup) { return; } @@ -708,6 +716,9 @@ function commandsModule({ setToolbarToggled: { commandFn: actions.setToolbarToggled, }, + cleanUpCrosshairs: { + commandFn: actions.cleanUpCrosshairs, + }, }; return { diff --git a/extensions/cornerstone/src/getHangingProtocolModule.ts b/extensions/cornerstone/src/getHangingProtocolModule.ts index 15bfaa44c6a..81c2132deb3 100644 --- a/extensions/cornerstone/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone/src/getHangingProtocolModule.ts @@ -24,18 +24,7 @@ const mpr: Types.HangingProtocol.Protocol = { // Turns off crosshairs when switching out of MPR mode onProtocolExit: [ { - commandName: 'toolbarServiceRecordInteraction', - commandOptions: { - interactionType: 'tool', - commands: [ - { - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - }, + commandName: 'cleanUpCrosshairs', }, ], }, diff --git a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts index a2fc8976013..a3db943ab27 100644 --- a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts +++ b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts @@ -224,9 +224,11 @@ class CornerstoneCacheService { const segDisplaySetInstanceUID = segmentation.displaySetInstanceUID; const segDisplaySet = displaySetService.getDisplaySetByUID(segDisplaySetInstanceUID); + const instance = segDisplaySet.instances?.[0] || segDisplaySet.instance; + const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( viewportDisplaySetInstanceUIDs, - segDisplaySet.instances[0].FrameOfReferenceUID + instance.FrameOfReferenceUID ); if (shouldDisplaySeg) { diff --git a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts index 6d790cd7c7a..1ba2aad9069 100644 --- a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts +++ b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts @@ -160,7 +160,7 @@ export default class StaticWadoClient extends api.DICOMwebClient { if (!valueElem) { return false; } - if (valueElem.vr == 'DA') { + if (valueElem.vr === 'DA' && valueElem.Value?.[0]) { return this.compareDateRange(testValue, valueElem.Value[0]); } const value = valueElem.Value; diff --git a/extensions/default/src/Panels/PanelStudyBrowser.tsx b/extensions/default/src/Panels/PanelStudyBrowser.tsx index 7498dc33475..e1b65d40197 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.tsx @@ -33,7 +33,6 @@ function PanelStudyBrowser({ const [studyDisplayList, setStudyDisplayList] = useState([]); const [displaySets, setDisplaySets] = useState([]); const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({}); - const isMounted = useRef(true); const onDoubleClickThumbnailHandler = displaySetInstanceUID => { let updatedViewports = []; @@ -120,17 +119,11 @@ function PanelStudyBrowser({ } // When the image arrives, render it and store the result in the thumbnailImgSrcMap newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId); - if (!isMounted.current) { - return; - } setThumbnailImageSrcMap(prevState => { return { ...prevState, ...newImageSrcEntry }; }); }); - return () => { - isMounted.current = false; - }; }, [StudyInstanceUIDs, dataSource, displaySetService, getImageSrc]); // ~~ displaySets diff --git a/extensions/default/src/commandsModule.ts b/extensions/default/src/commandsModule.ts index 2780c31ad04..3f22e35895b 100644 --- a/extensions/default/src/commandsModule.ts +++ b/extensions/default/src/commandsModule.ts @@ -170,6 +170,7 @@ const commandsModule = ({ stageIndex, reset = false, }: HangingProtocolParams): boolean => { + const primaryToolBeforeHPChange = toolbarService.getActivePrimaryTool(); try { // Stores in the state the display set selector id to displaySetUID mapping // Pass in viewportId for the active viewport. This item will get set as @@ -241,7 +242,29 @@ const commandsModule = ({ stateSyncService.store(stateSyncReduce); // This is a default action applied const { protocol } = hangingProtocolService.getActiveProtocol(); - actions.toggleHpTools(protocol); + actions.toggleHpTools(); + + // try to use the same tool in the new hanging protocol stage + const primaryButton = toolbarService.getButton(primaryToolBeforeHPChange); + if (primaryButton) { + // is there any type of interaction on this button, if not it might be in the + // items. This is a bit of a hack, but it works for now. + + let interactionType = primaryButton.props?.interactionType; + + if (!interactionType && primaryButton.props?.items) { + const firstItem = primaryButton.props.items[0]; + interactionType = firstItem.props?.interactionType || firstItem.props?.type; + } + + if (interactionType) { + toolbarService.recordInteraction({ + interactionType, + ...primaryButton.props, + }); + } + } + // Send the notification about updating the state if (protocolId !== hpInfo.protocolId) { // The old protocol callbacks are used for turning off things @@ -253,7 +276,8 @@ const commandsModule = ({ commandsManager.run(protocol.callbacks?.onProtocolEnter); return true; } catch (e) { - actions.toggleHpTools(hangingProtocolService.getActiveProtocol()); + console.error(e); + actions.toggleHpTools(); uiNotificationService.show({ title: 'Apply Hanging Protocol', message: 'The hanging protocol could not be applied.', diff --git a/platform/app/src/routes/DataSourceWrapper.tsx b/platform/app/src/routes/DataSourceWrapper.tsx index c993427ddab..c9d1d693692 100644 --- a/platform/app/src/routes/DataSourceWrapper.tsx +++ b/platform/app/src/routes/DataSourceWrapper.tsx @@ -188,7 +188,8 @@ function DataSourceWrapper(props) { !isSamePage || (!isLoading && (newOffset !== previousOffset || isLocationUpdated)); if (isDataInvalid) { - getData().catch(() => { + getData().catch(e => { + console.error(e); // If there is a data source configuration API, then the Worklist will popup the dialog to attempt to configure it // and attempt to resolve this issue. if (dataSource.getConfig().configurationAPI) { diff --git a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts index f63508c9756..841e72e26c1 100644 --- a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts +++ b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts @@ -74,7 +74,7 @@ function _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) { return; } - return series.instances.find(instance => instance.SOPInstanceUID === SOPInstanceUID); + return series.getInstance(SOPInstanceUID); } function _getInstanceByImageId(imageId) { diff --git a/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js b/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js index 51bbf0a5430..1dc6330dbcc 100644 --- a/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js +++ b/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js @@ -1,9 +1,26 @@ -function createSeriesMetadata(instances) { - const { SeriesInstanceUID } = instances[0]; +function createSeriesMetadata(SeriesInstanceUID) { + const instances = []; + const instancesMap = new Map(); return { SeriesInstanceUID, instances, + addInstance: function (newInstance) { + this.addInstances([newInstance]); + }, + addInstances: function (newInstances) { + for (let i = 0, len = newInstances.length; i < len; i++) { + const instance = newInstances[i]; + + if (!instancesMap.has(instance.SOPInstanceUID)) { + instancesMap.set(instance.SOPInstanceUID, instance); + instances.push(instance); + } + } + }, + getInstance: function (SOPInstanceUID) { + return instancesMap.get(SOPInstanceUID); + }, }; } diff --git a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js index 91b491afc39..ee7415b9ac5 100644 --- a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js +++ b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js @@ -8,54 +8,29 @@ function createStudyMetadata(StudyInstanceUID) { isLoaded: false, series: [], /** - * * @param {object} instance - * @returns {bool} true if series were added; false if series already exist */ addInstanceToSeries: function (instance) { - const { SeriesInstanceUID } = instance; - if (!this.StudyDescription) { - this.StudyDescription = instance.StudyDescription; - } - const existingSeries = this.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID); - - if (existingSeries) { - existingSeries.instances.push(instance); - } else { - const series = createSeriesMetadata([instance]); - this.series.push(series); - const { Modality } = series; - if (this.ModalitiesInStudy.indexOf(Modality) === -1) { - this.ModalitiesInStudy.push(Modality); - } - } + this.addInstancesToSeries([instance]); }, /** - * * @param {object[]} instances * @param {string} instances[].SeriesInstanceUID * @param {string} instances[].StudyDescription - * @returns {bool} true if series were added; false if series already exist */ addInstancesToSeries: function (instances) { const { SeriesInstanceUID } = instances[0]; if (!this.StudyDescription) { this.StudyDescription = instances[0].StudyDescription; } - const existingSeries = this.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID); + let series = this.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID); - if (existingSeries) { - // Only add instances not already present, so generate a map - // of existing instances and filter the to add by things - // already present. - const sopMap = {}; - existingSeries.instances.forEach(it => (sopMap[it.SOPInstanceUID] = it)); - const newInstances = instances.filter(it => !sopMap[it.SOPInstanceUID]); - existingSeries.instances.push(...newInstances); - } else { - const series = createSeriesMetadata(instances); + if (!series) { + const series = createSeriesMetadata(SeriesInstanceUID); this.series.push(series); } + + series.addInstances(instances); }, setSeriesMetadata: function (SeriesInstanceUID, seriesMetadata) { @@ -64,7 +39,8 @@ function createStudyMetadata(StudyInstanceUID) { if (existingSeries) { existingSeries = Object.assign(existingSeries, seriesMetadata); } else { - this.series.push(Object.assign({ instances: [] }, seriesMetadata)); + const series = createSeriesMetadata(SeriesInstanceUID); + this.series.push(Object.assign(series, seriesMetadata)); } }, }; diff --git a/platform/core/src/services/ToolBarService/ToolbarService.ts b/platform/core/src/services/ToolBarService/ToolbarService.ts index 908af18be83..a39d3eca58b 100644 --- a/platform/core/src/services/ToolBarService/ToolbarService.ts +++ b/platform/core/src/services/ToolBarService/ToolbarService.ts @@ -115,7 +115,13 @@ export default class ToolbarService extends PubSubService { return; } const commandsManager = this._commandsManager; - const { groupId, itemId, interactionType, commands } = interaction; + const { groupId, itemId, commands, type } = interaction; + let { interactionType } = interaction; + + // if not interaction type, assume the type can be used + if (!interactionType) { + interactionType = type; + } switch (interactionType) { case 'action': { @@ -228,6 +234,10 @@ export default class ToolbarService extends PubSubService { return activeTools; } + getActivePrimaryTool() { + return this.state.primaryToolId; + } + /** Sets the toggle state of a button to the isToggled state */ public setToggled(id: string, isToggled: boolean): void { if (isToggled) { @@ -259,7 +269,9 @@ export default class ToolbarService extends PubSubService { } for (const buttonId of Object.keys(this.buttons)) { const { primary, items } = this.buttons[buttonId].props || {}; - if (primary?.id === id) { return primary; } + if (primary?.id === id) { + return primary; + } const found = items?.find(childButton => childButton.id === id); if (found) { return found; diff --git a/platform/docs/docs/configuration/dataSources/dicom-json.md b/platform/docs/docs/configuration/dataSources/dicom-json.md index 08cbac7522f..20b0036d057 100644 --- a/platform/docs/docs/configuration/dataSources/dicom-json.md +++ b/platform/docs/docs/configuration/dataSources/dicom-json.md @@ -153,3 +153,14 @@ directly from Your public folder should look like this: ![](../../assets/img/dicom-json-public.png) + + +:::note +When hosting the DICOM JSON files, it is important to be aware that certain providers +do not automatically handle the 404 error and fallback to index.html. For example, Netlify +handles this, but Azure does not. Consequently, when you attempt to access a link with a +specific URL, a 404 error will be displayed. + +This issue also occurs locally, where the http-server does not handle it. However, +if you utilize the `serve` package (npx serve ./dist -l 8080 -s), it effectively addresses this problem. +::: diff --git a/platform/docs/docs/configuration/dataSources/static-files.md b/platform/docs/docs/configuration/dataSources/static-files.md index 820e20a1a2b..aa69d4bbafb 100644 --- a/platform/docs/docs/configuration/dataSources/static-files.md +++ b/platform/docs/docs/configuration/dataSources/static-files.md @@ -18,13 +18,15 @@ It can be compiled with Java and Gradle, and then run against a set of dicom, in the example located in /dicom/study1 outputting to /dicomweb, and then a server run against that data, like this: -``` +```bash git clone https://github.com/OHIF/static-wado cd static-wado ./gradlew installDist StaticWado/build/install/StaticWado/bin/StaticWado -d /dicomweb /dicom/study1 cd /dicomweb npx http-server -p 5000 --cors -g + +# you can use npx serve ./dist -l 8080 -s as an alternative to http-server ``` There is then a dev environment in the platform/app directory which can be diff --git a/platform/docs/docs/deployment/build-for-production.md b/platform/docs/docs/deployment/build-for-production.md index 217f38da28a..7da884abdd5 100644 --- a/platform/docs/docs/deployment/build-for-production.md +++ b/platform/docs/docs/deployment/build-for-production.md @@ -106,6 +106,8 @@ yarn global add http-server # Serve the files in our current directory # Accessible at: `http://localhost:8080` npx http-server ./dist + +# you can use npx serve ./dist -l 8080 -s as an alternative to http-server ``` :::caution diff --git a/platform/docs/docs/deployment/iframe.md b/platform/docs/docs/deployment/iframe.md index c1c0fb87068..50ae28cec8b 100644 --- a/platform/docs/docs/deployment/iframe.md +++ b/platform/docs/docs/deployment/iframe.md @@ -44,8 +44,10 @@ Download the index.html and the build (against the /ohif/ path) from [here](http Then run the -``` +```bash npx http-server unzipped-folder + +# you can use npx serve ./dist -l 8080 -s as an alternative to http-server ``` You should be able to see