diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md index 8c4cab1fb00..8d4faac76e4 100644 --- a/.github/ISSUE_TEMPLATE/---bug-report.md +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -2,23 +2,21 @@ name: "\U0001F41B Bug report" about: Create a report to help us improve title: '' -labels: 'Community: Report :bug:, Awaiting Reproduction, Triage :white_flag:' +labels: 'Community: Report :bug:, Awaiting Reproduction' assignees: '' --- -> **Before Creating an issue** -> -> - Are you running the latest version? -> - Are you reporting to the correct repository? -> - Did you search existing issues? - -## Bug Report + + + + + ### Describe the Bug _A clear and concise description of what the bug is._ -### What steps can we follow to reproduce the bug? +### Steps to Reproduce: 1. First step 2. Second step @@ -28,6 +26,15 @@ _A clear and concise description of what the bug is._ Please use code blocks to show formatted errors or code snippets ``` +### The current behavior + +_A clear and concise description of what happens instead of the expected +behavior._ + +### The expected behavior + +_A clear and concise description of what you expected to happen._ + > :warning: Reports we cannot reproduce are at risk of being marked stale and > closed. The more information you can provide, the more likely we are to look > into and address your issue. diff --git a/.github/ISSUE_TEMPLATE/---feature-request.md b/.github/ISSUE_TEMPLATE/---feature-request.md index d0cabe09f63..cff3c6a96f2 100644 --- a/.github/ISSUE_TEMPLATE/---feature-request.md +++ b/.github/ISSUE_TEMPLATE/---feature-request.md @@ -2,19 +2,28 @@ name: "\U0001F680 Feature request" about: Suggest an idea for this project title: '' -labels: 'Community: Request :hand:, Triage :white_flag:' +labels: 'Community: Request :hand:' assignees: '' --- -> :hand: Many people requests features. Tell us why yours is important to the + + + + + +### Request -## Request + **What feature or change would you like to see made?** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4a5623153a3..9b9fa9d7aff 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,14 +1,91 @@ -### PR Checklist + + + + -- [ ] Brief description of changes -- [ ] Links to any relevant issues -- [ ] Required status checks are passing -- [ ] User cases if changes impact the user's experience -- [ ] `@mention` a maintainer to request a review + + +### Context + + + +### Changes & Results + + + +### Testing + + + +### Checklist + +#### PR +https://semantic-release.gitbook.io/semantic-release/#how-does-it-work + +Examples: +Please note the letter casing in the provided examples (upper or lower). + +- feat(MeasurementService): add ... +- fix(Toolbar): fix ... +- docs(Readme): update ... +- style(Whitespace): fix ... +- refactor(ExtensionManager): ... +- test(HangingProtocol): Add test ... +- chore(git): update ... +- perf(VolumeLoader): ... + +You don't need to have each commit within the Pull Request follow the rule, +but the PR title must comply with it, as it will be used as the commit message +after the commits are squashed. +--> + +- [] My Pull Request title is descriptive, accurate and follows the + semantic-release format and guidelines. + +#### Code + +- [] My code has been well-documented (function documentation, inline comments, + etc.) + +#### Public Documentation Updates + + + +- [] The documentation page has been updated as necessary for any public API + additions or removals. + +#### Tested Environment + +- [] "OS: +- [] "Node version: +- [] "Browser: + [blog]: https://circleci.com/blog/triggering-trusted-ci-jobs-on-untrusted-forks/ diff --git a/.github/stale.yml b/.github/stale.yml index 0d11ecb9117..4d6cdd4e356 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -4,23 +4,17 @@ # Number of days of inactivity before an issue becomes stale daysUntilStale: 180 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 9000 +daysUntilClose: 60 # Issues with these labels will never be considered stale exemptLabels: - - 'Story :raised_hands:' - 'Bug: Verified :bug:' - - 'Task: CI/Tooling :robot:' - - 'Task: Docs 📖' - - 'Task: Docs :book:' - - 'Task: Refactor :hammer_and_wrench:' - - 'Task: Tests :microscope:' - 'PR: Awaiting Review 👀' - - 'Triage :white_flag:' - - 'Extension: Discussion' - 'Announcement 🎉' - 'IDC:priority' - 'IDC:candidate' - 'IDC:collaboration' + - 'Community: Request :hand:' + - 'Community: Report :bug:' # Label to use when marking an issue as stale staleLabel: 'Stale :baguette_bread:' # Comment to post when marking an issue as stale. Set to `false` to disable diff --git a/.gitignore b/.gitignore index 8e0d39f1408..a2ac5b0fd32 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,8 @@ screenshots/ # Locize settings .locize + +# firebase +firebase.json +.firebase/ +.firebaserc diff --git a/extensions/cornerstone/CHANGELOG.md b/extensions/cornerstone/CHANGELOG.md index f64c340d7a4..1e1530327f7 100644 --- a/extensions/cornerstone/CHANGELOG.md +++ b/extensions/cornerstone/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.12.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.15...@ohif/extension-cornerstone@2.12.16) (2022-12-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +## [2.12.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.14...@ohif/extension-cornerstone@2.12.15) (2022-09-20) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +## [2.12.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.13...@ohif/extension-cornerstone@2.12.14) (2022-09-01) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + ## [2.12.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.12...@ohif/extension-cornerstone@2.12.13) (2022-06-30) **Note:** Version bump only for package @ohif/extension-cornerstone diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index e7d1af41445..91e94a6d300 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone", - "version": "2.12.13", + "version": "2.12.16", "description": "OHIF extension for Cornerstone", "author": "OHIF", "license": "MIT", @@ -35,8 +35,8 @@ "cornerstone-core": "^2.6.1", "cornerstone-math": "^0.1.9", "cornerstone-tools": "^6.0.6", - "cornerstone-wado-image-loader": "^4.1.0", - "dcmjs": "0.19.9", + "cornerstone-wado-image-loader": "^4.2.1", + "dcmjs": "0.29.3", "dicom-parser": "^1.8.11", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/cornerstone/src/components/OHIFCornerstoneViewportOverlay.css b/extensions/cornerstone/src/components/OHIFCornerstoneViewportOverlay.css index dca6f30d642..cfd6e967b3c 100644 --- a/extensions/cornerstone/src/components/OHIFCornerstoneViewportOverlay.css +++ b/extensions/cornerstone/src/components/OHIFCornerstoneViewportOverlay.css @@ -34,7 +34,7 @@ left: 20px; } .OHIFCornerstoneViewportOverlay .bottom-left3 { - bottom: 110px; + bottom: 160px; left: -20px; } .OHIFCornerstoneViewportOverlay .bottom-right { @@ -71,3 +71,35 @@ width: 18px; height: 18px; } + +#SRlabelcontainer { + position: relative; +} + +#parent-button { + position: relative; +} + +#parent-button.haschild::before { + content: ""; + position: absolute; + left: -15px; + top: 50%; + transform: translateY(-50%); + width: 0; + height: 0; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 9px solid white; +} + +#child-button { + position: absolute; + width: 100%; + max-height: 30; + overflow: hidden; + overflow-wrap: break-word; + white-space: normal; + left: 20px; + transition: height 0.3s; +} diff --git a/extensions/cornerstone/src/components/OHIFCornerstoneViewportOverlay.js b/extensions/cornerstone/src/components/OHIFCornerstoneViewportOverlay.js index d344efec35c..b0543cfda77 100644 --- a/extensions/cornerstone/src/components/OHIFCornerstoneViewportOverlay.js +++ b/extensions/cornerstone/src/components/OHIFCornerstoneViewportOverlay.js @@ -147,49 +147,66 @@ class OHIFCornerstoneViewportOverlay extends PureComponent { const SRLabelsOn = SRLabels && SRLabels.length !== 0 ? true : false; /**/ - const getSRLabelsContent = SRLabels => { - if (Array.isArray(SRLabels)) { - const listedSRLabels = SRLabels.map((SRLabel, index) => { - const color = SRLabel.labels.color; - return ( - SRLabel.labels.visible && ( - 0 ? true : false; + } + if (childButton) { + parentButton.classList.add('haschild'); + } + const color = singleLabel.color; + + const handleOnClick = () => { + if (childButton) { + if (childButton.style.height == '0px') { + childButton.style.height = '50px'; + } else { + childButton.style.height = '0px'; + } + } + }; + + return ( + singleLabel.visible && ( +
+ + {childLabels ? ( +
- - ) - ); - }); + ) : null} +
+ ) + ); + } + + if (Array.isArray(SRLabels)) { + const listedSRLabels = SRLabels.map(SRLabel => SRLabel.labels) + .flatMap(labels => labels) + .map((label, index) => srLabelMapper(label, index)); return
    {listedSRLabels}
; } else { diff --git a/extensions/debugging/src/DebugReportModal.js b/extensions/debugging/src/DebugReportModal.js index 2727f4fdd4e..6602912601c 100644 --- a/extensions/debugging/src/DebugReportModal.js +++ b/extensions/debugging/src/DebugReportModal.js @@ -34,7 +34,7 @@ const DubugReportModal = ({ // App version body += '== App ==\n'; - body += `version\t${window.version}\n\n`; + body += `version\t${window.version} (fork: IDC2servers)\n\n`; // Extensions Versions @@ -210,7 +210,7 @@ const getAppVersion = () => { Version - {window.version} + {window.version} (fork: IDC2servers) ); diff --git a/extensions/dicom-html/CHANGELOG.md b/extensions/dicom-html/CHANGELOG.md index ab97f15abc4..848f4b32d8f 100644 --- a/extensions/dicom-html/CHANGELOG.md +++ b/extensions/dicom-html/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.3.24](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.23...@ohif/extension-dicom-html@1.3.24) (2022-12-21) + +**Note:** Version bump only for package @ohif/extension-dicom-html + + + + + +## [1.3.23](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.22...@ohif/extension-dicom-html@1.3.23) (2022-09-01) + +**Note:** Version bump only for package @ohif/extension-dicom-html + + + + + ## [1.3.22](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.21...@ohif/extension-dicom-html@1.3.22) (2022-06-30) **Note:** Version bump only for package @ohif/extension-dicom-html diff --git a/extensions/dicom-html/package.json b/extensions/dicom-html/package.json index f5828d8fe36..4b8ecea5dd7 100644 --- a/extensions/dicom-html/package.json +++ b/extensions/dicom-html/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-html", - "version": "1.3.22", + "version": "1.3.24", "description": "OHIF extension for rendering structured reports to HTML", "author": "OHIF", "license": "MIT", @@ -29,7 +29,7 @@ }, "peerDependencies": { "@ohif/core": "^0.50.0", - "dcmjs": "0.19.9", + "dcmjs": "0.29.3", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js b/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js index 06634dc4ee0..881bbbb3fea 100644 --- a/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js +++ b/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js @@ -36,7 +36,7 @@ const OHIFDicomHtmlSopClassHandler = { plugin: 'html', Modality: 'SR', displaySetInstanceUID: utils.guid(), - wadoRoot: study.getData().wadoRoot, + wadoRoot: instance.getData().wadoRoot, wadoUri: instance.getData().wadouri, SOPInstanceUID: instance.getSOPInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(), diff --git a/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js b/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js index 452b843bdc0..e1739a45906 100644 --- a/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js +++ b/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js @@ -24,7 +24,7 @@ const OHIFDicomPDFSopClassHandler = { plugin: 'pdf', Modality: 'DOC', displaySetInstanceUID: utils.guid(), - wadoRoot: study.getData().wadoRoot, + wadoRoot: instance.getData().wadoRoot, wadoUri: instance.getData().wadouri, SOPInstanceUID: instance.getSOPInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(), diff --git a/extensions/dicom-rt/CHANGELOG.md b/extensions/dicom-rt/CHANGELOG.md index 8535b458b67..3ecc1e2dac7 100644 --- a/extensions/dicom-rt/CHANGELOG.md +++ b/extensions/dicom-rt/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.7.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.13...@ohif/extension-dicom-rt@0.7.14) (2022-12-21) + +**Note:** Version bump only for package @ohif/extension-dicom-rt + + + + + +## [0.7.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.12...@ohif/extension-dicom-rt@0.7.13) (2022-09-01) + +**Note:** Version bump only for package @ohif/extension-dicom-rt + + + + + +## [0.7.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.11...@ohif/extension-dicom-rt@0.7.12) (2022-08-31) + +**Note:** Version bump only for package @ohif/extension-dicom-rt + + + + + ## [0.7.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.10...@ohif/extension-dicom-rt@0.7.11) (2022-04-05) **Note:** Version bump only for package @ohif/extension-dicom-rt diff --git a/extensions/dicom-rt/package.json b/extensions/dicom-rt/package.json index 587359c6eb8..e158d439dc6 100644 --- a/extensions/dicom-rt/package.json +++ b/extensions/dicom-rt/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-rt", - "version": "0.7.11", + "version": "0.7.14", "description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.", "author": "OHIF", "license": "MIT", @@ -31,7 +31,7 @@ "@ohif/core": "^0.50.0", "cornerstone-core": "^2.6.1", "cornerstone-tools": "^6.0.6", - "dcmjs": "0.19.9", + "dcmjs": "0.29.3", "gl-matrix": "^3.3.0", "prop-types": "^15.6.2", "react": "^16.8.6", diff --git a/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js b/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js index 3a5f836d4d7..f75c335a1fd 100644 --- a/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js +++ b/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js @@ -40,7 +40,7 @@ const OHIFDicomRTStructSopClassHandler = { const rtStructDisplaySet = { Modality: 'RTSTRUCT', displaySetInstanceUID: utils.guid(), - wadoRoot: study.getData().wadoRoot, + wadoRoot: instance.getData().wadoRoot, wadoUri: instance.getData().wadouri, SOPInstanceUID, SeriesInstanceUID, diff --git a/extensions/dicom-rt/src/components/RTPanel/RTPanel.js b/extensions/dicom-rt/src/components/RTPanel/RTPanel.js index c9da41006e7..f0bfa02c246 100644 --- a/extensions/dicom-rt/src/components/RTPanel/RTPanel.js +++ b/extensions/dicom-rt/src/components/RTPanel/RTPanel.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import cornerstoneTools from 'cornerstone-tools'; import cornerstone from 'cornerstone-core'; diff --git a/extensions/dicom-segmentation/CHANGELOG.md b/extensions/dicom-segmentation/CHANGELOG.md index 3624b80c792..f1c4ff91d7b 100644 --- a/extensions/dicom-segmentation/CHANGELOG.md +++ b/extensions/dicom-segmentation/CHANGELOG.md @@ -3,6 +3,92 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.7.21](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.20...@ohif/extension-dicom-segmentation@0.7.21) (2023-01-17) + +**Note:** Version bump only for package @ohif/extension-dicom-segmentation + + + + + +## [0.7.20](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.19...@ohif/extension-dicom-segmentation@0.7.20) (2023-01-10) + +**Note:** Version bump only for package @ohif/extension-dicom-segmentation + + + + + +## [0.7.19](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.18...@ohif/extension-dicom-segmentation@0.7.19) (2022-12-21) + +**Note:** Version bump only for package @ohif/extension-dicom-segmentation + + + + + +## [0.7.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.17...@ohif/extension-dicom-segmentation@0.7.18) (2022-12-21) + +**Note:** Version bump only for package @ohif/extension-dicom-segmentation + + + + + +## [0.7.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.16...@ohif/extension-dicom-segmentation@0.7.17) (2022-12-12) + + +### Bug Fixes + +* [#2964](https://github.com/OHIF/Viewers/issues/2964) Reword message for segmentation error loading due to orientation tolerance ([#3017](https://github.com/OHIF/Viewers/issues/3017)) ([597ac11](https://github.com/OHIF/Viewers/commit/597ac11c4daee1b1e14148804551e20611cfef08)) + + + + + +## [0.7.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.15...@ohif/extension-dicom-segmentation@0.7.16) (2022-10-28) + + +### Bug Fixes + +* [#2964](https://github.com/OHIF/Viewers/issues/2964) Update message for segmentation error loading due to orientation tolerance ([#2982](https://github.com/OHIF/Viewers/issues/2982)) ([545161d](https://github.com/OHIF/Viewers/commit/545161d5e8d92bb6652f17dc24a36dac8aa7b3a4)) + + + + + +## [0.7.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.14...@ohif/extension-dicom-segmentation@0.7.15) (2022-09-19) + +**Note:** Version bump only for package @ohif/extension-dicom-segmentation + + + + + +## [0.7.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.13...@ohif/extension-dicom-segmentation@0.7.14) (2022-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-segmentation + + + + + +## [0.7.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.12...@ohif/extension-dicom-segmentation@0.7.13) (2022-09-01) + +**Note:** Version bump only for package @ohif/extension-dicom-segmentation + + + + + +## [0.7.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.11...@ohif/extension-dicom-segmentation@0.7.12) (2022-08-31) + +**Note:** Version bump only for package @ohif/extension-dicom-segmentation + + + + + ## [0.7.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.10...@ohif/extension-dicom-segmentation@0.7.11) (2022-04-05) **Note:** Version bump only for package @ohif/extension-dicom-segmentation diff --git a/extensions/dicom-segmentation/package.json b/extensions/dicom-segmentation/package.json index 5d4897dee0d..dabb6d4e3c3 100644 --- a/extensions/dicom-segmentation/package.json +++ b/extensions/dicom-segmentation/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-segmentation", - "version": "0.7.11", + "version": "0.7.21", "description": "OHIF extension for viewing segmentations in the 2D MPR view", "author": "OHIF", "license": "MIT", @@ -31,7 +31,7 @@ "@ohif/core": "^0.50.0", "cornerstone-core": "^2.6.1", "cornerstone-tools": "^6.0.6", - "dcmjs": "0.19.9", + "dcmjs": "0.29.3", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-segmentation/src/commandsModule.js b/extensions/dicom-segmentation/src/commandsModule.js new file mode 100644 index 00000000000..02f5f07040a --- /dev/null +++ b/extensions/dicom-segmentation/src/commandsModule.js @@ -0,0 +1,100 @@ +import csTools from 'cornerstone-tools'; +import cs from 'cornerstone-core'; +import OHIF from '@ohif/core'; + +import DICOMSegTempCrosshairsTool from './tools/DICOMSegTempCrosshairsTool'; +import refreshViewports from './utils/refreshViewports'; + +const { studyMetadataManager } = OHIF.utils; + +const commandsModule = ({ commandsManager }) => { + const actions = { + jumpToFirstSegment: ({ viewports }) => { + try { + const { activeViewportIndex, viewportSpecificData } = viewports; + const viewport = viewportSpecificData[activeViewportIndex]; + const { StudyInstanceUID, displaySetInstanceUID } = viewport; + const studyMetadata = studyMetadataManager.get(StudyInstanceUID); + const firstImageId = studyMetadata.getFirstImageId( + displaySetInstanceUID + ); + + const module = csTools.getModule('segmentation'); + const brushStackState = module.state.series[firstImageId]; + const { labelmaps3D, activeLabelmapIndex } = brushStackState; + const { labelmaps2D } = labelmaps3D[activeLabelmapIndex]; + + const firstLabelMap2D = labelmaps2D.find(value => !!value); + const firstSegment = firstLabelMap2D.segmentsOnLabelmap[0]; + const segmentNumber = firstSegment; + + const validIndexList = []; + labelmaps2D.forEach((labelMap2D, index) => { + if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) { + validIndexList.push(index); + } + }); + + const avg = array => array.reduce((a, b) => a + b) / array.length; + const average = avg(validIndexList); + const closest = validIndexList.reduce((prev, curr) => { + return Math.abs(curr - average) < Math.abs(prev - average) + ? curr + : prev; + }); + + const enabledElements = cs.getEnabledElements(); + const element = enabledElements[activeViewportIndex].element; + + const toolState = csTools.getToolState(element, 'stack'); + if (!toolState) return; + + const imageIds = toolState.data[0].imageIds; + const imageId = imageIds[closest]; + const frameIndex = imageIds.indexOf(imageId); + const SOPInstanceUID = cs.metaData.get('SOPInstanceUID', imageId); + + cs.getEnabledElements().forEach(enabledElement => { + cs.updateImage(enabledElement.element); + }); + + DICOMSegTempCrosshairsTool.addCrosshair( + element, + imageId, + segmentNumber + ); + + cs.getEnabledElements().forEach(enabledElement => { + cs.updateImage(enabledElement.element); + }); + + const refreshViewports = false; + + commandsManager.runCommand('jumpToImage', { + StudyInstanceUID, + SOPInstanceUID, + frameIndex, + activeViewportIndex, + refreshViewports, + }); + } catch (error) { + console.log('Error in moving to the first segment slice'); + } + }, + }; + + const definitions = { + jumpToFirstSegment: { + commandFn: actions.jumpToFirstSegment, + storeContexts: ['viewports'], + options: {}, + }, + }; + + return { + definitions, + defaultContext: 'VIEWER', + }; +}; + +export default commandsModule; diff --git a/extensions/dicom-segmentation/src/components/SegmentItem/SegmentItem.css b/extensions/dicom-segmentation/src/components/SegmentItem/SegmentItem.css index 645e48c83fe..f0b28428b35 100644 --- a/extensions/dicom-segmentation/src/components/SegmentItem/SegmentItem.css +++ b/extensions/dicom-segmentation/src/components/SegmentItem/SegmentItem.css @@ -62,8 +62,8 @@ } .dcmseg-segment-item .segment-label span { - overflow-wrap: normal; - white-space: nowrap; + overflow-wrap: break-word; + white-space: normal; overflow: hidden; max-width: calc(100% - 40px); /* calc(100% - 50px); 20px = eye icon */ text-overflow: ellipsis; @@ -71,6 +71,7 @@ .dcmseg-segment-item .segment-label .eye-icon { cursor: pointer; + min-width: 20px; color: var(--active-color); } diff --git a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js index d54d0237e67..80b8da540cd 100644 --- a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js +++ b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import cornerstoneTools from 'cornerstone-tools'; import cornerstone from 'cornerstone-core'; @@ -36,6 +36,7 @@ const { studyMetadataManager } = utils; * @param {Function} props.onConfigurationChange - Configuration change handler * @param {Function} props.activeContexts - List of active application contexts * @param {Function} props.contexts - List of available application contexts + * @param {Function} props.servicesManager - Services manager * @returns component */ const SegmentationPanel = ({ @@ -50,6 +51,7 @@ const SegmentationPanel = ({ onSelectedSegmentationChange, activeContexts = [], contexts = {}, + servicesManager, }) => { const isVTK = () => activeContexts.includes(contexts.VTK); const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE); @@ -59,6 +61,9 @@ const SegmentationPanel = ({ * store with context to make these kind of things less blurry. */ const { configuration } = cornerstoneTools.getModule('segmentation'); + if (configuration.segsTolerance === undefined) { + configuration.segsTolerance = 1e-2; + } const DEFAULT_BRUSH_RADIUS = configuration.radius || 10; /* @@ -87,6 +92,25 @@ const SegmentationPanel = ({ return studyMetadata.getFirstImageId(displaySetInstanceUID); }; + const getAllSegDisplaySets = () => { + const { StudyInstanceUID } = getActiveViewport(); + const studyMetadata = studyMetadataManager.get(StudyInstanceUID); + return studyMetadata.getDerivedDatasets({ + Modality: 'SEG', + }); + }; + + const updateSegDisplaySetsTolerance = tolerance => { + const segDisplaySets = getAllSegDisplaySets(); + segDisplaySets.forEach(segDisplaySet => { + // update tol value + segDisplaySet.tolerance = tolerance; + // reset load flags for allowing retry for seg parsing. + segDisplaySet.isLoaded = false; + segDisplaySet.loadError = false; + }); + }; + const getActiveLabelMaps3D = () => { const { labelmaps3D, activeLabelmapIndex } = getBrushStackState(); return labelmaps3D[activeLabelmapIndex]; @@ -224,10 +248,7 @@ const SegmentationPanel = ({ ) ); }; - }, [ - activeIndex, - viewports, - ]); + }, [activeIndex, viewports]); const updateSegmentationComboBox = e => { const index = e.detail.activatedLabelmapIndex; @@ -306,14 +327,18 @@ const SegmentationPanel = ({ labelmapIndex, originLabelMapIndex, hasOverlapping, - SeriesDate, - SeriesTime, + metadata, } = displaySet; /* Map to display representation */ - const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0]; + const dateStr = `${metadata.ContentDate}:${metadata.ContentTime}`.split( + '.' + )[0]; const date = moment(dateStr, 'YYYYMMDD:HHmmss'); - const displayDate = date.format('ddd, MMM Do YYYY, h:mm:ss a'); + let displayDate = date.format('ddd, MMM Do YYYY, h:mm:ss a'); + if (displayDate === 'Invalid date') { + displayDate = ' '; + } const displayDescription = displaySet.SeriesDescription; return { @@ -601,31 +626,25 @@ const SegmentationPanel = ({ configuration.outlineWidth = newConfiguration.outlineWidth; configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive; configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive; + configuration.segsTolerance = newConfiguration.segsTolerance; onConfigurationChange(newConfiguration); + updateSegDisplaySetsTolerance(configuration.segsTolerance); refreshViewports(); }; const onVisibilityChangeHandler = isVisible => { let segmentsHidden = []; + const labelmap3D = getActiveLabelMaps3D(); + state.segmentNumbers.forEach(segmentNumber => { if (isVTK()) { onSegmentVisibilityChange(segmentNumber, isVisible); } - /** Get all labelmaps with this segmentNumber (overlapping segments) */ - const { labelmaps3D } = getBrushStackState(); - const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => { - return labelmaps2D.some(({ segmentsOnLabelmap }) => - segmentsOnLabelmap.includes(segmentNumber) - ); - }); - - possibleLabelMaps3D.forEach(labelmap3D => { - labelmap3D.segmentsHidden[segmentNumber] = !isVisible; - segmentsHidden = [ - ...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]), - ]; - }); + labelmap3D.segmentsHidden[segmentNumber] = !isVisible; + segmentsHidden = [ + ...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]), + ]; }); setState(state => ({ ...state, segmentsHidden })); @@ -650,6 +669,7 @@ const SegmentationPanel = ({ configuration={configuration} onBack={() => setState(state => ({ ...state, showSettings: false }))} onChange={updateConfiguration} + servicesManager={servicesManager} /> ); } else { @@ -694,7 +714,7 @@ const SegmentationPanel = ({ count={state.segmentList.length} isVisible={ state.segmentsHidden.filter(isHidden => isHidden === true).length < - state.segmentNumbers.length && state.segmentNumbers.length > 0 + state.segmentNumbers.length && state.segmentNumbers.length > 0 } onVisibilityChange={onVisibilityChangeHandler} > diff --git a/extensions/dicom-segmentation/src/components/SegmentationSettings/SegmentationSettings.js b/extensions/dicom-segmentation/src/components/SegmentationSettings/SegmentationSettings.js index 3d637b80fae..00fe943af6d 100644 --- a/extensions/dicom-segmentation/src/components/SegmentationSettings/SegmentationSettings.js +++ b/extensions/dicom-segmentation/src/components/SegmentationSettings/SegmentationSettings.js @@ -4,7 +4,7 @@ import { Range } from '@ohif/ui'; import './SegmentationSettings.css'; -const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields = [] }) => { +const SegmentationSettings = ({ configuration, onBack, onChange, servicesManager, disabledFields = [] }) => { const [state, setState] = useState({ renderFill: configuration.renderFill, renderOutline: configuration.renderOutline, @@ -13,7 +13,8 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields outlineAlpha: configuration.outlineAlpha, outlineWidth: configuration.outlineWidth, fillAlphaInactive: configuration.fillAlphaInactive, - outlineAlphaInactive: configuration.outlineAlphaInactive + outlineAlphaInactive: configuration.outlineAlphaInactive, + segsTolerance: configuration.segsTolerance, }); useEffect(() => { @@ -28,6 +29,32 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields setState(state => ({ ...state, [field]: value })); }; + const once = fn => (...args) => { + if (!fn) return; + fn(...args); + fn = null; + }; + + const segTolValue = document.getElementById('segToleranceValue'); + if (segTolValue) { + segTolValue.onchange = once(function() { + const { UINotificationService, LoggerService } = servicesManager.services; + + const error = new Error( + 'Segmentation loader tolerance changed.\ + This operation can potentially generate errors in the Segmentation parsing.' + ); + + LoggerService.error({ error, message: error.message }); + UINotificationService.show({ + title: 'Segmentation panel', + message: error.message, + type: 'warning', + autoClose: true, + }); + }); +} + const toFloat = value => parseFloat(value / 100).toFixed(2); return ( @@ -133,6 +160,31 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields )} )} +
+ +
); }; @@ -169,11 +221,12 @@ SegmentationSettings.propTypes = { renderFill: PropTypes.bool.isRequired, renderOutline: PropTypes.bool.isRequired, shouldRenderInactiveLabelmaps: PropTypes.bool.isRequired, - fillAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */ - outlineAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */ + fillAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + outlineAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, outlineWidth: PropTypes.number.isRequired, - fillAlphaInactive: PropTypes.number.isRequired, - outlineAlphaInactive: PropTypes.number.isRequired, + fillAlphaInactive: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + outlineAlphaInactive: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + segsTolerance: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, }).isRequired, onBack: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, diff --git a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js index 8adcd68d492..8f782180707 100644 --- a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js +++ b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js @@ -44,7 +44,7 @@ export default function getSopClassHandlerModule({ servicesManager }) { const segDisplaySet = { Modality: 'SEG', displaySetInstanceUID: utils.guid(), - wadoRoot: study.getData().wadoRoot, + wadoRoot: instance.getData().wadoRoot, wadoUri: instance.getData().wadouri, SOPInstanceUID, SeriesInstanceUID, @@ -62,6 +62,7 @@ export default function getSopClassHandlerModule({ servicesManager }) { SeriesNumber, SeriesDescription, metadata, + tolerance: 1e-2, }; segDisplaySet.getSourceDisplaySet = function( @@ -93,7 +94,7 @@ export default function getSopClassHandlerModule({ servicesManager }) { referencedDisplaySet.SeriesInstanceUID ); - const results = await _parseSeg(segArrayBuffer, imageIds); + const results = await _parseSeg(segArrayBuffer, imageIds, segDisplaySet.tolerance); if (results === undefined) { return; } @@ -149,10 +150,13 @@ export default function getSopClassHandlerModule({ servicesManager }) { }; } -function _parseSeg(arrayBuffer, imageIds) { - const skipOverlapping = false; - const tolerance = 1e-2; - const cornerstoneToolsVersion = 4; +function _parseSeg( + arrayBuffer, + imageIds, + tolerance = 1e-2, + skipOverlapping = false, + cornerstoneToolsVersion = 4 +) { return dcmjs.adapters.Cornerstone.Segmentation.generateToolState( imageIds, arrayBuffer, diff --git a/extensions/dicom-segmentation/src/index.js b/extensions/dicom-segmentation/src/index.js index 17bd246b7e0..dc353c1104e 100644 --- a/extensions/dicom-segmentation/src/index.js +++ b/extensions/dicom-segmentation/src/index.js @@ -6,6 +6,7 @@ import toolbarModule from './toolbarModule.js'; import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js'; import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js'; import { version } from '../package.json'; +import commandsModule from './commandsModule.js'; const { studyMetadataManager } = OHIF.utils; export default { @@ -32,12 +33,18 @@ export default { const ExtendedSegmentationPanel = props => { const { activeContexts } = api.hooks.useAppContext(); - const onDisplaySetLoadFailureHandler = error => { - LoggerService.error({ error, message: error.message }); + const message = + error.message.includes('orthogonal') || + error.message.includes('oblique') + ? 'The segmentation has been detected as non coplanar,\ + If you really think it is coplanar,\ + please adjust the tolerance in the segmentation panel settings (at your own peril!)' + : error.message; + LoggerService.error({ error, message }); UINotificationService.show({ title: 'DICOM Segmentation Loader', - message: error.message, + message, type: 'error', autoClose: false, }); @@ -78,6 +85,7 @@ export default { onConfigurationChange={onConfigurationChangeHandler} onSelectedSegmentationChange={onSelectedSegmentationChangeHandler} onDisplaySetLoadFailure={onDisplaySetLoadFailureHandler} + servicesManager={servicesManager} /> ); }; @@ -114,6 +122,15 @@ export default { }); }; + const onSegmentationsCompletelyLoaded = () => { + commandsManager.runCommand('jumpToFirstSegment'); + }; + + document.addEventListener( + 'segseriesselected', + onSegmentationsCompletelyLoaded + ); + document.addEventListener( 'extensiondicomsegmentationsegloaded', onSegmentationsLoaded @@ -175,5 +192,8 @@ export default { defaultContext: ['VIEWER'], }; }, + getCommandsModule({ commandsManager, servicesManager }) { + return commandsModule({ commandsManager, servicesManager }); + }, getSopClassHandlerModule, }; diff --git a/extensions/dicom-tag-browser/CHANGELOG.md b/extensions/dicom-tag-browser/CHANGELOG.md index d7c4d83de9d..eef68137903 100644 --- a/extensions/dicom-tag-browser/CHANGELOG.md +++ b/extensions/dicom-tag-browser/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.25](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.24...@ohif/extension-dicom-tag-browser@0.2.25) (2022-12-21) + +**Note:** Version bump only for package @ohif/extension-dicom-tag-browser + + + + + +## [0.2.24](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.23...@ohif/extension-dicom-tag-browser@0.2.24) (2022-09-01) + +**Note:** Version bump only for package @ohif/extension-dicom-tag-browser + + + + + ## [0.2.23](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.22...@ohif/extension-dicom-tag-browser@0.2.23) (2022-04-06) **Note:** Version bump only for package @ohif/extension-dicom-tag-browser diff --git a/extensions/dicom-tag-browser/package.json b/extensions/dicom-tag-browser/package.json index 88d362c5ed9..ed0ed8d8b7a 100644 --- a/extensions/dicom-tag-browser/package.json +++ b/extensions/dicom-tag-browser/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-tag-browser", - "version": "0.2.23", + "version": "0.2.25", "description": "OHIF extension for checking DICOM headers.", "author": "OHIF", "license": "MIT", @@ -28,7 +28,7 @@ }, "peerDependencies": { "@ohif/core": "^2.6.0", - "dcmjs": "0.19.9", + "dcmjs": "0.29.3", "react": "^16.8.6" }, "dependencies": { diff --git a/extensions/vtk/CHANGELOG.md b/extensions/vtk/CHANGELOG.md index 739c3cd89e1..fbb5e77dcab 100644 --- a/extensions/vtk/CHANGELOG.md +++ b/extensions/vtk/CHANGELOG.md @@ -43,6 +43,110 @@ All notable changes to this project will be documented in this file. See +## [1.12.41](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.40...@ohif/extension-vtk@1.12.41) (2022-12-21) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.40](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.39...@ohif/extension-vtk@1.12.40) (2022-12-12) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.39](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.38...@ohif/extension-vtk@1.12.39) (2022-11-12) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.38](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.37...@ohif/extension-vtk@1.12.38) (2022-11-02) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.37](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.36...@ohif/extension-vtk@1.12.37) (2022-10-28) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.36](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.35...@ohif/extension-vtk@1.12.36) (2022-10-25) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.35](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.34...@ohif/extension-vtk@1.12.35) (2022-09-26) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.34](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.33...@ohif/extension-vtk@1.12.34) (2022-09-20) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.33](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.32...@ohif/extension-vtk@1.12.33) (2022-09-19) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.32](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.31...@ohif/extension-vtk@1.12.32) (2022-09-12) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.31](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.30...@ohif/extension-vtk@1.12.31) (2022-09-02) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.30](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.29...@ohif/extension-vtk@1.12.30) (2022-09-01) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [1.12.29](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.28...@ohif/extension-vtk@1.12.29) (2022-08-31) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + ## [1.12.28](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.27...@ohif/extension-vtk@1.12.28) (2022-08-24) **Note:** Version bump only for package @ohif/extension-vtk diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json index cd7d43ab3e6..60eeed53ee6 100644 --- a/extensions/vtk/package.json +++ b/extensions/vtk/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-vtk", - "version": "1.12.28", + "version": "1.12.41", "description": "OHIF extension for VTK.js", "author": "OHIF", "license": "MIT", @@ -34,8 +34,8 @@ "@ohif/ui": "^0.50.0", "cornerstone-core": "^2.6.1", "cornerstone-tools": "^6.0.6", - "cornerstone-wado-image-loader": "^4.1.0", - "dcmjs": "0.19.9", + "cornerstone-wado-image-loader": "^4.2.1", + "dcmjs": "0.29.3", "dicom-parser": "^1.8.11", "i18next": "^17.0.3", "i18next-browser-languagedetector": "^3.0.1", @@ -50,13 +50,13 @@ "dependencies": { "@babel/runtime": "^7.5.5", "lodash.throttle": "^4.1.1", - "react-vtkjs-viewport": "^0.14.4" + "react-vtkjs-viewport": "^0.14.5" }, "devDependencies": { - "@ohif/core": "^2.16.23", + "@ohif/core": "^2.16.35", "@ohif/ui": "^1.10.10", "cornerstone-tools": "^6.0.6", - "cornerstone-wado-image-loader": "^4.1.0", + "cornerstone-wado-image-loader": "^4.2.1", "dicom-parser": "^1.8.11", "gh-pages": "^2.0.1", "i18next": "^17.0.3", diff --git a/idc-assets/app-config-template.js b/idc-assets/app-config-template.js index 3c8b0db4f0f..60abb40e821 100644 --- a/idc-assets/app-config-template.js +++ b/idc-assets/app-config-template.js @@ -2,7 +2,7 @@ window.config = { // default: '/' routerBasename: '/', extensions: [], - disableMeasurementPanel: true, + disableMeasurementPanel: false, splitQueryParameterCalls: true, disableServersCache: true, showStudyList: false, @@ -31,6 +31,27 @@ window.config = { ], }, + enableGoogleCloudAdapter: true, + healthcareApiEndpoint: 'https://healthcare.googleapis.com/v1', + oidc: [ + { + // ~ REQUIRED + // Authorization Server URL + authority: 'https://accounts.google.com', + client_id: + '70161151675-72395655qs19cra281h4nl7a63q3t76q.apps.googleusercontent.com', + redirect_uri: '/callback', // `OHIFStandaloneViewer.js` + response_type: 'id_token token', + scope: + 'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid + // ~ OPTIONAL + post_logout_redirect_uri: '/logout-redirect.html', + revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=', + automaticSilentRenew: true, + revokeAccessTokenOnSignout: true, + }, + ], + // Extensions should be able to suggest default values for these? // Or we can require that these be explicitly set hotkeys: [ diff --git a/idc-assets/public-config-template.js b/idc-assets/public-config-template.js index e6b6e1b715c..98bef723fab 100644 --- a/idc-assets/public-config-template.js +++ b/idc-assets/public-config-template.js @@ -4,7 +4,7 @@ window.config = { enableGoogleCloudAdapter: true, studyListFunctionsEnabled: true, filterQueryParam: true, - disableMeasurementPanel: true, + disableMeasurementPanel: false, splitQueryParameterCalls: true, servers: { // This is an array, but we'll only use the first entry for now @@ -32,4 +32,4 @@ window.config = { label: 'Download', keys: ['d'] }], -} \ No newline at end of file +} diff --git a/platform/core/CHANGELOG.md b/platform/core/CHANGELOG.md index 84f6b533451..aacc0c91217 100644 --- a/platform/core/CHANGELOG.md +++ b/platform/core/CHANGELOG.md @@ -3,6 +3,114 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.16.35](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.34...@ohif/core@2.16.35) (2022-12-21) + +**Note:** Version bump only for package @ohif/core + + + + + +## [2.16.34](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.33...@ohif/core@2.16.34) (2022-12-12) + + +### Bug Fixes + +* [#2964](https://github.com/OHIF/Viewers/issues/2964) Reword message for segmentation error loading due to orientation tolerance ([#3017](https://github.com/OHIF/Viewers/issues/3017)) ([597ac11](https://github.com/OHIF/Viewers/commit/597ac11c4daee1b1e14148804551e20611cfef08)) + + + + + +## [2.16.33](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.32...@ohif/core@2.16.33) (2022-11-12) + + +### Bug Fixes + +* handle missing ReferencedInstanceSequence attribute ([#2786](https://github.com/OHIF/Viewers/issues/2786)) ([4473807](https://github.com/OHIF/Viewers/commit/44738078dbd8dc6b27d2d669e25417e37371f48d)) + + + + + +## [2.16.32](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.31...@ohif/core@2.16.32) (2022-11-02) + +**Note:** Version bump only for package @ohif/core + + + + + +## [2.16.31](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.30...@ohif/core@2.16.31) (2022-10-28) + + +### Bug Fixes + +* [#2964](https://github.com/OHIF/Viewers/issues/2964) Update message for segmentation error loading due to orientation tolerance ([#2982](https://github.com/OHIF/Viewers/issues/2982)) ([545161d](https://github.com/OHIF/Viewers/commit/545161d5e8d92bb6652f17dc24a36dac8aa7b3a4)) + + + + + +## [2.16.30](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.29...@ohif/core@2.16.30) (2022-10-25) + + +### Bug Fixes + +* 2965 Correct Parsing Logic for Qualitative Instance Level SR ([#2972](https://github.com/OHIF/Viewers/issues/2972)) ([f7db74a](https://github.com/OHIF/Viewers/commit/f7db74a84376598065b6fa2c00cd5735415ff651)) + + + + + +## [2.16.29](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.28...@ohif/core@2.16.29) (2022-09-20) + +**Note:** Version bump only for package @ohif/core + + + + + +## [2.16.28](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.27...@ohif/core@2.16.28) (2022-09-19) + +**Note:** Version bump only for package @ohif/core + + + + + +## [2.16.27](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.26...@ohif/core@2.16.27) (2022-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +## [2.16.26](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.25...@ohif/core@2.16.26) (2022-09-02) + +**Note:** Version bump only for package @ohif/core + + + + + +## [2.16.25](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.24...@ohif/core@2.16.25) (2022-09-01) + +**Note:** Version bump only for package @ohif/core + + + + + +## [2.16.24](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.23...@ohif/core@2.16.24) (2022-08-31) + +**Note:** Version bump only for package @ohif/core + + + + + ## [2.16.23](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.22...@ohif/core@2.16.23) (2022-08-24) **Note:** Version bump only for package @ohif/core diff --git a/platform/core/package.json b/platform/core/package.json index 5ee05e21e98..961e0e1465c 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/core", - "version": "2.16.23", + "version": "2.16.35", "description": "Generic business logic for web-based medical imaging applications", "author": "OHIF Core Team", "license": "MIT", @@ -33,13 +33,13 @@ "peerDependencies": { "cornerstone-core": "^2.6.1", "cornerstone-tools": "^6.0.6", - "cornerstone-wado-image-loader": "^4.1.0", + "cornerstone-wado-image-loader": "^4.2.1", "dicom-parser": "^1.8.11" }, "dependencies": { "@babel/runtime": "^7.5.5", "ajv": "^6.10.0", - "dcmjs": "0.19.9", + "dcmjs": "0.29.3", "dicomweb-client": "^0.8.3", "immer": "9.0.12", "isomorphic-base64": "^1.0.2", diff --git a/platform/core/src/DICOMSR/SCOORD3D/enums.js b/platform/core/src/DICOMSR/SCOORD3D/enums.js index ac987141852..ed946cf68b0 100644 --- a/platform/core/src/DICOMSR/SCOORD3D/enums.js +++ b/platform/core/src/DICOMSR/SCOORD3D/enums.js @@ -7,7 +7,8 @@ export const CodeNameCodeSequenceValues = { TrackingUniqueIdentifier: '112040', TrackingIdentifier: '112039', Finding: '121071', - FindingSite: 'G-C0E3', // SRT + FindingSite: '363698007', + TopographicalModifier: '106233006', CornerstoneFreeText: 'CORNERSTONEFREETEXT', // CST4 Score: '246262008', }; @@ -20,6 +21,7 @@ export const RELATIONSHIP_TYPE = { export const CodingSchemeDesignators = { SRT: 'SRT', cornerstoneTools4: 'CST4', + SCT: 'SCT', }; export default { diff --git a/platform/core/src/DICOMSR/SCOORD3D/parseSCOORD3D.js b/platform/core/src/DICOMSR/SCOORD3D/parseSCOORD3D.js index ce90c63395c..e34f03f805b 100644 --- a/platform/core/src/DICOMSR/SCOORD3D/parseSCOORD3D.js +++ b/platform/core/src/DICOMSR/SCOORD3D/parseSCOORD3D.js @@ -1,6 +1,5 @@ import { ImageSet } from '../../classes'; import getMeasurements from './utils/getMeasurements'; -import getReferencedImagesList from './utils/getReferencedImagesList'; import isRehydratable from './utils/isRehydratable'; import addMeasurement from './utils/addMeasurement'; @@ -12,15 +11,14 @@ const parseSCOORD3D = ({ servicesManager, displaySets }) => { ds => ds.Modality !== 'SR' && ds.Modality !== 'SEG' && - ds.Modality !== 'RTSTRUCT' && - ds.Modality !== 'RTDOSE' + ds.Modality !== 'RTSTRUCT' ); imageDisplaySets.forEach(imageDisplaySet => { imageDisplaySet.SRLabels = []; }); - srDisplaySets.forEach(srDisplaySet => { + srDisplaySets.forEach((srDisplaySet, index) => { const firstInstance = srDisplaySet.metadata; if (!firstInstance) { return; @@ -28,8 +26,11 @@ const parseSCOORD3D = ({ servicesManager, displaySets }) => { const { ContentSequence } = firstInstance; - srDisplaySet.referencedImages = getReferencedImagesList(ContentSequence); - srDisplaySet.measurements = getMeasurements(ContentSequence); + srDisplaySet.measurements = getMeasurements( + ContentSequence, + srDisplaySet.SeriesInstanceUID, + index + ); const mappings = MeasurementService.getSourceMappings( 'CornerstoneTools', '4' @@ -133,7 +134,21 @@ const checkIfCanAddMeasurementsToDisplaySet = ( const SOPInstanceUIDs = images.map(i => i.SOPInstanceUID); const colors = new Map(); measurements.forEach(measurement => { - const { coords } = measurement; + const { coords, labels } = measurement; + labels.forEach((label, labelIndex) => { + const key = + measurement.labels[labelIndex].label + + measurement.labels[labelIndex].value; + let color = colors.get(key); + if (!color) { + color = 'hsla(' + Math.floor(Math.random() * 360) + ', 70%, 30%, 1)'; + colors.set(key, color); + } + measurement.labels[labelIndex].color = color; + measurement.isSRText = true; + }); + let areLabelsAdded = false; + coords.forEach((coord, index) => { if (coord.ReferencedSOPSequence !== undefined) { const imageIndex = SOPInstanceUIDs.findIndex( @@ -150,26 +165,15 @@ const checkIfCanAddMeasurementsToDisplaySet = ( const imageMetadata = images[imageIndex].getData().metadata; if (coord.GraphicType === 'TEXT') { - const key = - measurement.labels[index].label + measurement.labels[index].value; - let color = colors.get(key); - if (!color) { - // random dark color - color = - 'hsla(' + Math.floor(Math.random() * 360) + ', 70%, 30%, 1)'; - colors.set(key, color); + if (!areLabelsAdded) { + imageDisplaySet.SRLabels.push({ + ReferencedSOPInstanceUID: + coord.ReferencedSOPSequence.ReferencedSOPInstanceUID, + SeriesInstanceUID: srDisplaySet.SeriesInstanceUID, + labels: measurement.labels, + }); + areLabelsAdded = true; } - - measurement.labels[index].color = color; - measurement.isSRText = true; - measurement.labels[index].visible = true; - - imageDisplaySet.SRLabels.push({ - ReferencedSOPInstanceUID: - coord.ReferencedSOPSequence.ReferencedSOPInstanceUID, - labels: measurement.labels[index], - }); - if (index === 0) { addMeasurement( measurement, diff --git a/platform/core/src/DICOMSR/SCOORD3D/utils/addMeasurement.js b/platform/core/src/DICOMSR/SCOORD3D/utils/addMeasurement.js index c8a69ff058d..e25d55a9eb5 100644 --- a/platform/core/src/DICOMSR/SCOORD3D/utils/addMeasurement.js +++ b/platform/core/src/DICOMSR/SCOORD3D/utils/addMeasurement.js @@ -30,6 +30,8 @@ export default function addMeasurement( renderableData: {}, labels: measurement.labels, isSRText: measurement.isSRText, + isVisible: measurement.isVisible, + srSeriesInstanceUID: measurement.seriesInstanceUID, }; measurement.coords.forEach(coord => { diff --git a/platform/core/src/DICOMSR/SCOORD3D/utils/getMeasurements.js b/platform/core/src/DICOMSR/SCOORD3D/utils/getMeasurements.js index a75b3ed45f1..9b747441069 100644 --- a/platform/core/src/DICOMSR/SCOORD3D/utils/getMeasurements.js +++ b/platform/core/src/DICOMSR/SCOORD3D/utils/getMeasurements.js @@ -1,9 +1,12 @@ import { CodeNameCodeSequenceValues } from '../enums'; import getSequenceAsArray from './getSequenceAsArray'; -import getMergedContentSequencesByTrackingUniqueIdentifiers from './getMergedContentSequencesByTrackingUniqueIdentifiers'; import processMeasurement from './processMeasurement'; -const getMeasurements = ImagingMeasurementReportContentSequence => { +const getMeasurements = ( + ImagingMeasurementReportContentSequence, + SRSeriesInstanceUID, + index +) => { const ImagingMeasurements = ImagingMeasurementReportContentSequence.find( item => item.ConceptNameCodeSequence.CodeValue === @@ -18,16 +21,20 @@ const getMeasurements = ImagingMeasurementReportContentSequence => { CodeNameCodeSequenceValues.MeasurementGroup ); - /* const mergedContentSequencesByTrackingUniqueIdentifiers = getMergedContentSequencesByTrackingUniqueIdentifiers( - MeasurementGroups - );*/ - let measurements = []; MeasurementGroups.forEach(MeasurementGroup => { const contentSequence = MeasurementGroup.ContentSequence; const measurement = processMeasurement(contentSequence); if (measurement) { + measurement.seriesInstanceUID = SRSeriesInstanceUID; + if (index === 0) { + measurement.isVisible = true; + measurement.labels.forEach(label => (label.visible = true)); + } else { + measurement.isVisible = false; + measurement.labels.forEach(label => (label.visible = false)); + } measurements.push(measurement); } }); diff --git a/platform/core/src/DICOMSR/SCOORD3D/utils/getReferencedImagesList.js b/platform/core/src/DICOMSR/SCOORD3D/utils/getReferencedImagesList.js deleted file mode 100644 index 2b08147163a..00000000000 --- a/platform/core/src/DICOMSR/SCOORD3D/utils/getReferencedImagesList.js +++ /dev/null @@ -1,43 +0,0 @@ -import getSequenceAsArray from './getSequenceAsArray'; -import { CodeNameCodeSequenceValues } from '../enums'; - -const getReferencedImagesList = ImagingMeasurementReportContentSequence => { - const referencedImages = []; - - const ImageLibrary = ImagingMeasurementReportContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImageLibrary - ); - - if (!ImageLibrary.ContentSequence) { - return referencedImages; - } - - const ImageLibraryGroup = getSequenceAsArray( - ImageLibrary.ContentSequence - ).find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImageLibraryGroup - ); - - getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => { - const { ReferencedSOPSequence } = item; - if (ReferencedSOPSequence) { - const { - ReferencedSOPClassUID, - ReferencedSOPInstanceUID, - } = ReferencedSOPSequence; - - referencedImages.push({ - ReferencedSOPClassUID, - ReferencedSOPInstanceUID, - }); - } - }); - - return referencedImages; -}; - -export default getReferencedImagesList; diff --git a/platform/core/src/DICOMSR/SCOORD3D/utils/processNonGeometricallyDefinedMeasurement.js b/platform/core/src/DICOMSR/SCOORD3D/utils/processNonGeometricallyDefinedMeasurement.js index f1f0bc09dcc..28fcf64c819 100644 --- a/platform/core/src/DICOMSR/SCOORD3D/utils/processNonGeometricallyDefinedMeasurement.js +++ b/platform/core/src/DICOMSR/SCOORD3D/utils/processNonGeometricallyDefinedMeasurement.js @@ -37,7 +37,7 @@ const processNonGeometricallyDefinedMeasurement = contentSequence => { const FindingSites = contentSequence.filter( item => item.ConceptNameCodeSequence.CodingSchemeDesignator === - CodingSchemeDesignators.SRT && + CodingSchemeDesignators.SCT && item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSite ); @@ -62,8 +62,6 @@ const processNonGeometricallyDefinedMeasurement = contentSequence => { value: Finding.ConceptCodeSequence.CodeMeaning, }); } - - // TODO -> Eventually hopefully support SNOMED or some proper code library, just free text for now. if (FindingSites.length) { const cornerstoneFreeTextFindingSite = FindingSites.find( FindingSite => @@ -122,11 +120,14 @@ const processNonGeometricallyDefinedMeasurement = contentSequence => { if (NUMContentItems.length === 0 && IMAGEContentItem) { CODEContentItems.forEach(item => { - const { ConceptCodeSequence, ConceptNameCodeSequence } = item; + const { + ConceptCodeSequence, + ConceptNameCodeSequence, + ContentSequence, + } = item; if (!ConceptCodeSequence || !ConceptNameCodeSequence) { console.warn(`Graphic missing, skipping annotation.`); - return; } @@ -147,7 +148,23 @@ const processNonGeometricallyDefinedMeasurement = contentSequence => { ConceptNameCodeSequence.CodingSchemeDesignator, value: ConceptCodeSequence.CodeMeaning, valueCodingSchemeDesignator: ConceptCodeSequence.CodingSchemeDesignator, + children: [], }); + + if (ContentSequence) { + ContentSequence.forEach(childItem => { + const { ConceptCodeSequence, ConceptNameCodeSequence } = childItem; + const lastIndex = measurement.labels.length - 1; + measurement.labels[lastIndex].children.push({ + label: ConceptNameCodeSequence.CodeMeaning, + labelCodingSchemeDesignator: + ConceptNameCodeSequence.CodingSchemeDesignator, + value: ConceptCodeSequence.CodeMeaning, + valueCodingSchemeDesignator: + ConceptCodeSequence.CodingSchemeDesignator, + }); + }); + } }); } diff --git a/platform/core/src/DICOMSR/dataExchange.js b/platform/core/src/DICOMSR/dataExchange.js index 0d2b66630e1..8006b75faea 100644 --- a/platform/core/src/DICOMSR/dataExchange.js +++ b/platform/core/src/DICOMSR/dataExchange.js @@ -7,14 +7,6 @@ import { } from './handleStructuredReport'; import findMostRecentStructuredReport from './utils/findMostRecentStructuredReport'; -/** - * - * @typedef serverType - * @property {string} type - type of the server - * @property {string} wadoRoot - server wado root url - * - */ - /** * Function to be registered into MeasurementAPI to retrieve measurements from DICOM Structured Reports * @@ -32,7 +24,6 @@ const retrieveMeasurements = (server, external = {}) => { const serverUrl = server.wadoRoot; const studies = utils.studyMetadataManager.all(); - const latestSeries = findMostRecentStructuredReport(studies); if (!latestSeries) return Promise.resolve({}); diff --git a/platform/core/src/DICOMSR/handleStructuredReport.js b/platform/core/src/DICOMSR/handleStructuredReport.js index d3306fa5a70..167bc405e47 100644 --- a/platform/core/src/DICOMSR/handleStructuredReport.js +++ b/platform/core/src/DICOMSR/handleStructuredReport.js @@ -35,11 +35,11 @@ const retrieveMeasurementFromSR = async ( const dicomWeb = new api.DICOMwebClient(config); - const instance = series.getFirstInstance(); + const instance = series.instances[0]; const options = { - studyInstanceUID: instance.getStudyInstanceUID(), - seriesInstanceUID: instance.getSeriesInstanceUID(), - sopInstanceUID: instance.getSOPInstanceUID(), + studyInstanceUID: instance.metadata.StudyInstanceUID, + seriesInstanceUID: instance.metadata.SeriesInstanceUID, + sopInstanceUID: instance.metadata.SOPInstanceUID, }; const part10SRArrayBuffer = await dicomWeb.retrieveInstance(options); diff --git a/platform/core/src/DICOMSR/parseDicomStructuredReport.js b/platform/core/src/DICOMSR/parseDicomStructuredReport.js index ede35c8ec21..397f24399af 100644 --- a/platform/core/src/DICOMSR/parseDicomStructuredReport.js +++ b/platform/core/src/DICOMSR/parseDicomStructuredReport.js @@ -22,16 +22,32 @@ const parseDicomStructuredReport = ( displaySets, external ) => { - if (external && external.servicesManager) { - parseSCOORD3D({ servicesManager: external.servicesManager, displaySets }); - } - // Get the dicom data as an Object const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer); const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset( dicomData.dict ); + const { + LoggerService, + UINotificationService, + } = external.servicesManager.services; + if (external && external.servicesManager) { + try { + parseSCOORD3D({ servicesManager: external.servicesManager, displaySets }); + } catch (error) { + const seriesDescription = dataset.SeriesDescription || ''; + LoggerService.error({ error, message: error.message }); + UINotificationService.show({ + title: `Failed to parse ${seriesDescription} SR display set`, + message: error.message, + type: 'error', + autoClose: false, + }); + return; + } + } + const { MeasurementReport } = dcmjs.adapters.Cornerstone; let storedMeasurementByToolType; @@ -110,11 +126,12 @@ const parseDicomStructuredReport = ( ); } catch (error) { const seriesDescription = dataset.SeriesDescription || ''; - LogManager.publish(LogManager.EVENTS.OnLog, { + LoggerService.error({ error, message: error.message }); + UINotificationService.show({ title: `Failed to parse ${seriesDescription} measurement report`, - type: 'warning', - message: error.message || '', - notify: true, + message: error.message, + type: 'error', + autoClose: false, }); return; } diff --git a/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js b/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js index 8c9bd9cec8d..c3a5c79bcce 100644 --- a/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js +++ b/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js @@ -8,12 +8,13 @@ const findMostRecentStructuredReport = studies => { let mostRecentStructuredReport; studies.forEach(study => { - const allSeries = study.getSeries ? study.getSeries() : []; + const allData = study.getData ? study.getData() : []; + const allSeries = allData.series; 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 || series.getInstanceCount() === 0) { + if (!series || !series.instances || series.instances.length === 0) { return; } @@ -44,8 +45,8 @@ const isStructuredReportSeries = series => { '1.2.840.10008.5.1.4.1.1.88.34', // COMPREHENSIVE_3D_SR ]; - const firstInstance = series.getFirstInstance(); - const SOPClassUID = firstInstance.getData().metadata.SOPClassUID; + const firstInstance = series.instances[0]; + const SOPClassUID = firstInstance.metadata.SOPClassUID; return supportedSopClassUIDs.includes(SOPClassUID); }; @@ -59,9 +60,9 @@ const isStructuredReportSeries = series => { */ const compareSeriesDate = (series1, series2) => { return ( - series1._data.SeriesDate > series2._data.SeriesDate || - (series1._data.SeriesDate === series2._data.SeriesDate && - series1._data.SeriesTime > series2._data.SeriesTime) + series1.SeriesDate > series2.SeriesDate || + (series1.SeriesDate === series2.SeriesDate && + series1.SeriesTime > series2.SeriesTime) ); }; diff --git a/platform/core/src/classes/HotkeysManager.js b/platform/core/src/classes/HotkeysManager.js index f4369b2d77b..547d23d9f9b 100644 --- a/platform/core/src/classes/HotkeysManager.js +++ b/platform/core/src/classes/HotkeysManager.js @@ -67,12 +67,13 @@ export class HotkeysManager { UINotificationService, LoggerService, } = this._servicesManager.services; - const message = 'Erro while setting hotkeys'; + const message = 'Error while setting hotkeys'; LoggerService.error({ error, message }); UINotificationService.show({ title: 'Hotkeys Manager', message, type: 'error', + autoClose: false, }); } } diff --git a/platform/core/src/classes/StudyLoadingListener.js b/platform/core/src/classes/StudyLoadingListener.js index d00bdaadc8f..acbd4910362 100644 --- a/platform/core/src/classes/StudyLoadingListener.js +++ b/platform/core/src/classes/StudyLoadingListener.js @@ -469,8 +469,8 @@ class StudyLoadingListener { // TODO: Make this work for plugins if (!stack) { - console.warn('Skipping adding displaySet to StudyLoadingListener'); - console.warn(displaySet); + //console.warn('Skipping adding displaySet to StudyLoadingListener'); + //console.warn(displaySet); return; } diff --git a/platform/core/src/classes/metadata/StudyMetadata.js b/platform/core/src/classes/metadata/StudyMetadata.js index ad881b18ac7..578b7b40bfe 100644 --- a/platform/core/src/classes/metadata/StudyMetadata.js +++ b/platform/core/src/classes/metadata/StudyMetadata.js @@ -275,6 +275,8 @@ class StudyMetadata extends Metadata { const otherDisplaySets = allDisplaySets.filter( ds => + ds && + derivatedDisplaySet && ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID ); @@ -350,6 +352,13 @@ class StudyMetadata extends Metadata { referencedSeriesInstanceUID ); } else { + if ( + !displaySet.referencedDisplaySets || + displaySet.referencedDisplaySets.length === 0 + ) { + return false; + } + const filteredReferencedDisplaySets = displaySet.referencedDisplaySets.filter( referencedDisplaySet => referencedDisplaySet.SeriesInstanceUID === diff --git a/platform/core/src/services/MeasurementService/MeasurementService.js b/platform/core/src/services/MeasurementService/MeasurementService.js index 888a2501dac..639fc7e3751 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.js @@ -420,6 +420,10 @@ class MeasurementService { throw new Error('No source definition provided.'); } + if (definition == 'stack' || definition == 'stackPrefetch') { + return; + } + const sourceInfo = this._getSourceInfo(source); if (!this._sourceHasMappings(source)) { diff --git a/platform/core/src/studies/retrieveStudiesMetadata.js b/platform/core/src/studies/retrieveStudiesMetadata.js index e6ffd730932..e1cf6c3794c 100644 --- a/platform/core/src/studies/retrieveStudiesMetadata.js +++ b/platform/core/src/studies/retrieveStudiesMetadata.js @@ -7,7 +7,7 @@ import { retrieveStudyMetadata } from './retrieveStudyMetadata'; * This function calls retrieveStudyMetadata several times, asynchronously, * and waits for all of the results to be returned. * - * @param {Object} server Object with server configuration parameters + * @param {Array} servers array with server Objects with server configuration parameters * @param {Array} studyInstanceUIDs The UIDs of the Studies to be retrieved * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against @@ -16,7 +16,7 @@ import { retrieveStudyMetadata } from './retrieveStudyMetadata'; * @returns {Promise} that will be resolved with the metadata or rejected with the error */ export default function retrieveStudiesMetadata( - server, + servers, studyInstanceUIDs, filters, separateSeriesInstanceUIDFilters = false @@ -28,7 +28,7 @@ export default function retrieveStudiesMetadata( studyInstanceUIDs.forEach(function(StudyInstanceUID) { // Send the call and resolve or reject the related promise based on its outcome const promise = retrieveStudyMetadata( - server, + servers, StudyInstanceUID, filters, separateSeriesInstanceUIDFilters diff --git a/platform/core/src/studies/retrieveStudyMetadata.js b/platform/core/src/studies/retrieveStudyMetadata.js index ad931ef1239..5e6c7a078f0 100644 --- a/platform/core/src/studies/retrieveStudyMetadata.js +++ b/platform/core/src/studies/retrieveStudyMetadata.js @@ -7,7 +7,7 @@ const StudyMetaDataPromises = new Map(); /** * Retrieves study metadata * - * @param {Object} server Object with server configuration parameters + * @param {Array} servers array with server Objects with server configuration parameters * @param {string} StudyInstanceUID The UID of the Study to be retrieved * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against @@ -16,7 +16,7 @@ const StudyMetaDataPromises = new Map(); * @returns {Promise} that will be resolved with the metadata or rejected with the error */ export function retrieveStudyMetadata( - server, + servers, StudyInstanceUID, filters, separateSeriesInstanceUIDFilters = false @@ -25,8 +25,10 @@ export function retrieveStudyMetadata( // and further requests for that metadata will always fail. On failure, we probably need to remove the // corresponding promise from the "StudyMetaDataPromises" map... - if (!server) { - throw new Error(`${moduleName}: Required 'server' parameter not provided.`); + if (!servers) { + throw new Error( + `${moduleName}: Required 'servers' parameter not provided.` + ); } if (!StudyInstanceUID) { throw new Error( @@ -48,20 +50,12 @@ export function retrieveStudyMetadata( separateSeriesInstanceUIDFilters ) { promise = __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePromise( - server, + servers, StudyInstanceUID, filters ); } else { - promise = RetrieveMetadata(server, StudyInstanceUID, filters); - - /* - promise = new Promise((resolve, reject) => { - RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) { - resolve(data); - }, reject); - }); - */ + promise = RetrieveMetadata(servers, StudyInstanceUID, filters); } // Store the promise in cache @@ -72,18 +66,21 @@ export function retrieveStudyMetadata( /** * Splits up seriesInstanceUID filters to multiple calls for platforms - * @param {Object} server Object with server configuration parameters + * @param {Array} servers array with server Objects with server configuration parameters * @param {string} StudyInstanceUID The UID of the Study to be retrieved * @param {Object} filters - Object containing filters to be applied on retrieve metadata process */ function __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePromise( - server, + servers, StudyInstanceUID, filters ) { const { seriesInstanceUID } = filters; const seriesInstanceUIDs = seriesInstanceUID.split(','); + const googleServers = servers.filter(server => server.isGoogleStore === true); + const nonGoogleServers = servers.filter(server => !server.isGoogleStore); + return new Promise((resolve, reject) => { const promises = []; @@ -93,22 +90,19 @@ function __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePro }); promises.push( - RetrieveMetadata(server, StudyInstanceUID, seriesSpecificFilters) + RetrieveMetadata( + nonGoogleServers, + StudyInstanceUID, + seriesSpecificFilters + ) ); }); - Promise.all(promises).then(results => { - const data = results[0]; + promises.push(RetrieveMetadata(googleServers, StudyInstanceUID)); - let series = []; - - results.forEach(result => { - series = [...series, ...result.series]; - }); - - data.series = series; - - resolve(data); + Promise.all(promises).then(results => { + results = [].concat(...results); + resolve(results); }, reject); }); } diff --git a/platform/core/src/studies/retrieveStudyMetadata.test.js b/platform/core/src/studies/retrieveStudyMetadata.test.js index 4725ab562d4..1103a3bdf65 100644 --- a/platform/core/src/studies/retrieveStudyMetadata.test.js +++ b/platform/core/src/studies/retrieveStudyMetadata.test.js @@ -19,17 +19,4 @@ describe('retrieveStudyMetadata.js', () => { expect(callWithNoStudyInstanceUID).toThrow(Error); }); - - it('caches and returns the same promise for identical studyInstanceUIDs', () => { - const firstPromise = retrieveStudyMetadata( - fakeDicomWebServer, - 'fake-study-instance-uid' - ); - const secondPromise = retrieveStudyMetadata( - fakeDicomWebServer, - 'fake-study-instance-uid' - ); - - expect(firstPromise).toBe(secondPromise); - }); }); diff --git a/platform/core/src/studies/services/wado/getReferencedSeriesSequence.js b/platform/core/src/studies/services/wado/getReferencedSeriesSequence.js index ef32b16fde7..29dc446055b 100644 --- a/platform/core/src/studies/services/wado/getReferencedSeriesSequence.js +++ b/platform/core/src/studies/services/wado/getReferencedSeriesSequence.js @@ -21,16 +21,18 @@ const getReferencedSeriesSequence = instance => { const referencedInstanceSequenceRaw = referencedSeries['0008114A']; const referencedInstanceSequence = []; - referencedInstanceSequenceRaw.Value.forEach(referencedInstance => { - referencedInstanceSequence.push({ - referencedSOPClassUID: DICOMWeb.getString( - referencedInstance['00081150'] - ), - referencedSOPInstanceUID: DICOMWeb.getString( - referencedInstance['00081155'] - ), + if (referencedInstanceSequenceRaw) { + referencedInstanceSequenceRaw.Value.forEach(referencedInstance => { + referencedInstanceSequence.push({ + referencedSOPClassUID: DICOMWeb.getString( + referencedInstance['00081150'] + ), + referencedSOPInstanceUID: DICOMWeb.getString( + referencedInstance['00081155'] + ), + }); }); - }); + } referencedSeriesSequence.push({ referencedSeriesInstanceUID, diff --git a/platform/core/src/studies/services/wado/retrieveMetadata.js b/platform/core/src/studies/services/wado/retrieveMetadata.js index f3c544450ef..3a25e3e7d81 100644 --- a/platform/core/src/studies/services/wado/retrieveMetadata.js +++ b/platform/core/src/studies/services/wado/retrieveMetadata.js @@ -5,26 +5,33 @@ import RetrieveMetadataLoaderAsync from './retrieveMetadataLoaderAsync'; * Retrieve Study metadata from a DICOM server. If the server is configured to use lazy load, only the first series * will be loaded and the property "studyLoader" will be set to let consumer load remaining series as needed. * - * @param {Object} server Object with server configuration parameters + * @param {Array} servers array with server Objects with server configuration parameters * @param {string} StudyInstanceUID The Study Instance UID of the study which needs to be loaded * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against - * @returns {Object} A study descriptor object + * @returns {Array} A study descriptor object for each server */ -async function RetrieveMetadata(server, StudyInstanceUID, filters = {}) { - const RetrieveMetadataLoader = - server.enableStudyLazyLoad != false - ? RetrieveMetadataLoaderAsync - : RetrieveMetadataLoaderSync; +async function RetrieveMetadata(servers, StudyInstanceUID, filters = {}) { + return new Promise((resolve, reject) => { + const studyMetadataPromises = servers.map(server => { + const RetrieveMetadataLoader = + server.enableStudyLazyLoad != false + ? RetrieveMetadataLoaderAsync + : RetrieveMetadataLoaderSync; - const retrieveMetadataLoader = new RetrieveMetadataLoader( - server, - StudyInstanceUID, - filters - ); - const studyMetadata = retrieveMetadataLoader.execLoad(); + const retrieveMetadataLoader = new RetrieveMetadataLoader( + server, + StudyInstanceUID, + filters + ); + const studyMetadataPromise = retrieveMetadataLoader.execLoad(); + return studyMetadataPromise; + }); - return studyMetadata; + Promise.all(studyMetadataPromises).then(results => { + resolve(results); + }, reject); + }); } export default RetrieveMetadata; diff --git a/platform/core/src/studies/services/wado/studyInstanceHelpers.js b/platform/core/src/studies/services/wado/studyInstanceHelpers.js index 301342a7969..9b75392073b 100644 --- a/platform/core/src/studies/services/wado/studyInstanceHelpers.js +++ b/platform/core/src/studies/services/wado/studyInstanceHelpers.js @@ -102,6 +102,35 @@ async function makeSOPInstance(server, study, instance) { SOPInstanceUID, } = naturalizedInstance; + const validate = string => { + let rgx = /[^.0-9]+/g; + return string.match(rgx); + }; + + if (StudyInstanceUID === undefined || validate(StudyInstanceUID)) { + const error = + 'makeSOPInstance: StudyInstanceUID is not conforming with the UID (DICOM UI VR) character repertoire, skipping SOPInstance.'; + console.error(error); + + return; + } + + if (SeriesInstanceUID === undefined || validate(SeriesInstanceUID)) { + const error = + 'makeSOPInstance: SeriesInstanceUID is not conforming with the UID (DICOM UI VR) character repertoire, skipping SOPInstance.'; + console.error(error); + + return; + } + + if (SOPInstanceUID === undefined || validate(SOPInstanceUID)) { + const error = + 'makeSOPInstance: SOPInstanceUID is not conforming with the UID (DICOM UI VR) character repertoire, skipping SOPInstance.'; + console.error(error); + + return; + } + let series = study.seriesMap[SeriesInstanceUID]; if (!series) { diff --git a/platform/core/src/utils/StackManager.js b/platform/core/src/utils/StackManager.js index 49d8b00591c..4b56fe58f0e 100644 --- a/platform/core/src/utils/StackManager.js +++ b/platform/core/src/utils/StackManager.js @@ -25,7 +25,7 @@ function createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks) { const imageIds = []; let imageId; - displaySet.images.forEach((instance, imageIndex) => { + images.forEach((instance, imageIndex) => { const image = instance.getData(); const metaData = { instance: image, // in this context, instance will be the data of the InstanceMetadata object... @@ -35,7 +35,7 @@ function createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks) { imageIndex: imageIndex + 1, }; - const naturalizedInstance = instance.getData().metadata; + const naturalizedInstance = image.metadata; const NumberOfFrames = naturalizedInstance.NumberOfFrames; if (NumberOfFrames > 1) { diff --git a/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js b/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js index 631f462c82d..d7b030f418b 100644 --- a/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js +++ b/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js @@ -119,12 +119,20 @@ async function loadAndCacheDerivedDisplaySets( ) { if (recentDisplaySet.Modality === 'SEG' && logger) { const onDisplaySetLoadFailureHandler = error => { - logger.error({ error, message: error.message }); + const message = + error.message.includes('orthogonal') || + error.message.includes('oblique') + ? 'The segmentation has been detected as non coplanar,\ + If you really think it is coplanar,\ + please adjust the tolerance in the segmentation panel settings (at your own peril!)' + : error.message; + + logger.error({ error, message }); snackbar.show({ title: 'DICOM Segmentation Loader', - message: error.message, + message, type: 'error', - autoClose: true, + autoClose: false, }); }; diff --git a/platform/core/src/utils/urlUtil.js b/platform/core/src/utils/urlUtil.js index ddc91ac9b5b..6168f5f78da 100644 --- a/platform/core/src/utils/urlUtil.js +++ b/platform/core/src/utils/urlUtil.js @@ -40,8 +40,12 @@ const parse = toParse => { return {}; }; const parseParam = paramStr => { - const _paramDecoded = decode(paramStr); + let _paramDecoded = decode(paramStr); if (_paramDecoded && typeof _paramDecoded === 'string') { + const indexOfFirst = _paramDecoded.indexOf('!secondGoogleServer='); + if (indexOfFirst !== -1) { + _paramDecoded = _paramDecoded.substring(0, indexOfFirst); + } return _paramDecoded.split(PARAM_SEPARATOR); } }; diff --git a/platform/ui/src/components/measurementTable/MeasurementItem.css b/platform/ui/src/components/measurementTable/MeasurementItem.css new file mode 100644 index 00000000000..0e6ca523d45 --- /dev/null +++ b/platform/ui/src/components/measurementTable/MeasurementItem.css @@ -0,0 +1,26 @@ +.measurement-item { + display: flex; + justify-content: start; + margin: 0; +} + +.measurement-item .measurement-meta { + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: hidden; +} + +.measurement-item .measurement-meta-title { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + color: white; + font-size: 14px; + max-width: calc(100% - 30px); +} + +.measurement-item .measurement-meta-description { + font-size: 12px; + color: var(--text-secondary-color); +} diff --git a/platform/ui/src/components/measurementTable/MeasurementItem.js b/platform/ui/src/components/measurementTable/MeasurementItem.js new file mode 100644 index 00000000000..b7b503ed9bb --- /dev/null +++ b/platform/ui/src/components/measurementTable/MeasurementItem.js @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import './MeasurementItem.css'; + +const MeasurementItem = ({ onClick, title, description }) => { + return ( +
  • +
    +
    {title}
    +
    {description}
    +
    +
  • + ); +}; + +MeasurementItem.propTypes = { + onClick: PropTypes.func.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string, +}; + +MeasurementItem.defaultProps = { + description: '', +}; + +export default MeasurementItem; diff --git a/platform/ui/src/components/measurementTable/MeasurementSelect.js b/platform/ui/src/components/measurementTable/MeasurementSelect.js new file mode 100644 index 00000000000..6938b377120 --- /dev/null +++ b/platform/ui/src/components/measurementTable/MeasurementSelect.js @@ -0,0 +1,68 @@ +import React from 'react'; +import Select from 'react-select'; +import PropTypes from 'prop-types'; + +const MeasurementSelect = ({ value, formatOptionLabel, options, onChange }) => ( +