Compare commits

...
Author SHA1 Message Date
ohif-bot eab115e70c chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.8.0
 - @ohif/extension-dicom-microscopy@0.51.0
 - @ohif/extension-dicom-rt@0.2.0
 - @ohif/extension-dicom-segmentation@0.3.0
 - @ohif/extension-vtk@1.6.0
 - @ohif/ui@1.5.0
 - @ohif/viewer@4.2.0
2020-06-04 09:54:24 +00:00
c02b232b0c feat: 🎸 1729 - error boundary wrapper (#1764)
* Add error boundaries

* Fix PWA e2e.

* feat: ErrorBoundaryDialog

* replace component to use ErrorBoundaryDialog

* add proptypes

* fix context

* remove ErrorBoundary from extensions

Co-authored-by: igoroctaviano <igoroctaviano@gmail.com>
Co-authored-by: James A. Petts <jamesapetts@gmail.com>
2020-06-04 11:52:30 +02:00
ohif-bot f01640d009 chore(release): publish [skip ci]
- @ohif/viewer@4.1.0
2020-05-15 18:59:23 +00:00
Steve Pieperanddannyrb 63fd65690c feat: expose some app internals as window.app (#1735)
* feat: expose some app internals as window.app

This can help developers explore and access some
internal functionaltiy for debugging in the console.

For example, this command can download the currently
viewed study:

ohif.app.commandsManager.runCommand("downloadAndZip", {listOfUIDs: [window.location.href.split("/").pop()]})

TODO: collect this example and other handy functions on a wiki page

Co-authored-by: dannyrb <danny.ri.brown@gmail.com>

* fix: typo in window.ohif.app assignment

Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
2020-05-15 14:55:54 -04:00
ohif-bot 944afe4a3e chore(release): publish [skip ci]
- @ohif/viewer@4.0.0
2020-05-14 14:24:17 +00:00
Igor OctavianoandJames Petts b1678ce639 fix: 🐛 Fix race condition when loading derived display sets (#1718)
When we have multiple SEGs/RTSTRUCTs we intend to load the most recent
by default.

BREAKING CHANGE: 🧨 However we start to load once the first set of metadata arrives. We need
to wait until all series metadata is fetched.

✅ Closes: #1715

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-05-14 16:20:23 +02:00
ohif-bot 39937cca08 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.1.4
 - @ohif/extension-vtk@1.5.6
 - @ohif/core@2.9.6
 - @ohif/viewer@3.11.11
2020-05-14 11:53:42 +00:00
Igor OctavianoandJames Petts f7b8b6a41c fix: 🐛 Load default display set when no time metadata (#1684)
* fix: 🐛 Load default display set when no time metadata

Load default displayset (first item in the array) when no series date or
series time availalbe

✅ Closes: #1683

* Add batch update

* Fix tests.

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-05-14 13:51:57 +02:00
ohif-bot 79fe442293 chore(release): publish [skip ci]
- @ohif/viewer@3.11.10
2020-05-13 14:03:09 +00:00
Danny Brown 875238e5b0 chore: add fuzzymatching to default server configs
Add fuzzymatching to default server configs
2020-05-13 10:00:55 -04:00
Erik Ziegler d2d3ca2828 Add fuzzymatching to default server configs 2020-05-13 15:58:29 +02:00
ohif-bot cebea832a3 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.7.3
 - @ohif/extension-dicom-rt@0.1.3
 - @ohif/extension-dicom-segmentation@0.2.6
 - @ohif/extension-vtk@1.5.5
 - @ohif/core@2.9.5
 - @ohif/viewer@3.11.9
2020-05-12 14:49:58 +00:00
James Petts c4f84b1174 fix: 🐛 Fix seg color load (#1724) 2020-05-12 16:47:35 +02:00
ohif-bot f9fe4232e7 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.2.5
 - @ohif/extension-vtk@1.5.4
 - @ohif/core@2.9.4
 - @ohif/viewer@3.11.8
2020-05-06 17:20:22 +00:00
Igor Octaviano 42c22df1b6 Segmentation UI for VTKjs (#1685)
* Add single viewport configuration

* Multiple viewport configuration

* Improve performance by using independent set methods

* Add jump to slice command

* Add context configuration

* Cache panel visibility

* Fix sync between vtk and cornerstone

* Remove apis index

* Add approach

* Add loading to update volumes

* Fix broken configuration

* Bump vtk version

* Use loading label

* Update cy tests after vtk loading label changed

* Remove loading for segs
2020-05-06 19:17:44 +02:00
ohif-bot 19a8e71317 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.2.4
 - @ohif/viewer@3.11.7
2020-05-04 14:46:58 +00:00
James Petts fa95bae6ef Support initialization of segment color from RecommendedDisplayCIELabValue (#1709)
* fix: 🐛 Proper error handling for derived display sets

* Revert idc.js change

* Load correct Segment colors.
2020-05-04 16:43:09 +02:00
ohif-bot 8a20a0526e chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.7.2
 - @ohif/extension-dicom-html@1.2.2
 - @ohif/extension-dicom-rt@0.1.2
 - @ohif/extension-dicom-segmentation@0.2.3
 - @ohif/extension-vtk@1.5.3
 - @ohif/core@2.9.3
 - @ohif/ui@1.4.4
 - @ohif/viewer@3.11.6
2020-05-04 14:15:22 +00:00
James Petts 5b20d8f323 fix: 🐛 Proper error handling for derived display sets (#1708)
* fix: 🐛 Proper error handling for derived display sets

* Revert idc.js change
2020-05-04 16:10:39 +02:00
ohif-bot 9ea37a2201 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.7.1
 - @ohif/viewer@3.11.5
2020-05-04 12:37:30 +00:00
Igor OctavianoandJames Petts 2a66e175df Update viewport state after moving the stack (#1645)
* Update viewport state after moving the stack

* Only trigger new imagecallback if index bigger than 0

* Fix cornerstone viewport version

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-05-04 14:30:51 +02:00
65 changed files with 1479 additions and 436 deletions

No files matched your search

+41
View File
@@ -3,6 +3,47 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.8.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.3...@ohif/extension-cornerstone@2.8.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [2.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.2...@ohif/extension-cornerstone@2.7.3) (2020-05-12)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [2.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.1...@ohif/extension-cornerstone@2.7.2) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [2.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.0...@ohif/extension-cornerstone@2.7.1) (2020-05-04)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.1...@ohif/extension-cornerstone@2.7.0) (2020-04-24) # [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.1...@ohif/extension-cornerstone@2.7.0) (2020-04-24)
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "2.7.0", "version": "2.8.0",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -34,9 +34,9 @@
"@ohif/ui": "^0.50.0", "@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.12.5", "cornerstone-tools": "4.15.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "^0.12.2", "dcmjs": "^0.12.3",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
@@ -51,6 +51,6 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"react-cornerstone-viewport": "^2.3.6" "react-cornerstone-viewport": "2.3.8"
} }
} }
@@ -4,6 +4,7 @@ import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import debounce from 'lodash.debounce';
const { StackManager } = OHIF.utils; const { StackManager } = OHIF.utils;
@@ -179,7 +180,7 @@ class OHIFCornerstoneViewport extends Component {
if ( if (
displaySet.displaySetInstanceUID !== displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID || prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex displaySet.frameIndex !== prevDisplaySet.frameIndex
) { ) {
@@ -215,12 +216,26 @@ class OHIFCornerstoneViewport extends Component {
}); });
} }
const debouncedNewImageHandler = debounce(({ currentImageIdIndex, sopInstanceUid }) => {
const { displaySet } = this.props.viewportData;
const { StudyInstanceUID } = displaySet;
if (currentImageIdIndex > 0) {
this.props.onNewImage({
StudyInstanceUID,
SOPInstanceUID: sopInstanceUid,
frameIndex: currentImageIdIndex,
activeViewportIndex: viewportIndex,
});
}
}, 700);
return ( return (
<> <>
<ConnectedCornerstoneViewport <ConnectedCornerstoneViewport
viewportIndex={viewportIndex} viewportIndex={viewportIndex}
imageIds={imageIds} imageIds={imageIds}
imageIdIndex={currentImageIdIndex} imageIdIndex={currentImageIdIndex}
onNewImage={debouncedNewImageHandler}
// ~~ Connected (From REDUX) // ~~ Connected (From REDUX)
// frameRate={frameRate} // frameRate={frameRate}
// isPlaying={false} // isPlaying={false}
+11 -2
View File
@@ -34,8 +34,17 @@ export default {
preRegistration({ servicesManager, configuration = {} }) { preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration }); init({ servicesManager, configuration });
}, },
getViewportModule() { getViewportModule({ commandsManager }) {
return OHIFCornerstoneViewport; const ExtendedOHIFCornerstoneViewport = props => {
const onNewImageHandler = jumpData => {
commandsManager.runCommand('jumpToImage', jumpData);
};
return (
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
);
};
return ExtendedOHIFCornerstoneViewport;
}, },
getToolbarModule() { getToolbarModule() {
return toolbarModule; return toolbarModule;
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.1...@ohif/extension-dicom-html@1.2.2) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.0...@ohif/extension-dicom-html@1.2.1) (2020-04-28) ## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.0...@ohif/extension-dicom-html@1.2.1) (2020-04-28)
**Note:** Version bump only for package @ohif/extension-dicom-html **Note:** Version bump only for package @ohif/extension-dicom-html
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-html", "name": "@ohif/extension-dicom-html",
"version": "1.2.1", "version": "1.2.2",
"description": "OHIF extension for rendering structured reports to HTML", "description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -29,7 +29,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"dcmjs": "^0.12.2", "dcmjs": "^0.12.3",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.9...@ohif/extension-dicom-microscopy@0.51.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.8...@ohif/extension-dicom-microscopy@0.50.9) (2020-04-02) ## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.8...@ohif/extension-dicom-microscopy@0.50.9) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy **Note:** Version bump only for package @ohif/extension-dicom-microscopy
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-microscopy", "name": "@ohif/extension-dicom-microscopy",
"version": "0.50.9", "version": "0.51.0",
"description": "OHIF extension for Dicom Microscopy", "description": "OHIF extension for Dicom Microscopy",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -92,8 +92,8 @@ class DicomMicroscopyViewport extends Component {
{this.state.error ? ( {this.state.error ? (
<h2>{JSON.stringify(this.state.error)}</h2> <h2>{JSON.stringify(this.state.error)}</h2>
) : ( ) : (
<div style={style} ref={this.container} /> <div style={style} ref={this.container} />
)} )}
</div> </div>
); );
} }
+44
View File
@@ -3,6 +3,50 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.4...@ohif/extension-dicom-rt@0.2.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.3...@ohif/extension-dicom-rt@0.1.4) (2020-05-14)
### Bug Fixes
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
## [0.1.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.2...@ohif/extension-dicom-rt@0.1.3) (2020-05-12)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.1...@ohif/extension-dicom-rt@0.1.2) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.0...@ohif/extension-dicom-rt@0.1.1) (2020-04-28) ## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.0...@ohif/extension-dicom-rt@0.1.1) (2020-04-28)
**Note:** Version bump only for package @ohif/extension-dicom-rt **Note:** Version bump only for package @ohif/extension-dicom-rt
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-rt", "name": "@ohif/extension-dicom-rt",
"version": "0.1.1", "version": "0.2.0",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.", "description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -30,8 +30,8 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.0.9", "cornerstone-tools": "4.15.1",
"dcmjs": "^0.12.2", "dcmjs": "^0.12.3",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
@@ -31,7 +31,18 @@ const refreshViewport = () => {
* @param {number} props.isOpen - isOpen * @param {number} props.isOpen - isOpen
* @returns component * @returns component
*/ */
const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }) => { const RTPanel = ({
studies,
viewports,
activeIndex,
isOpen,
onContourItemClick,
activeContexts = [],
contexts = {}
}) => {
const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
const [selectedContour, setSelectedContour] = useState(); const [selectedContour, setSelectedContour] = useState();
const DEFAULT_SET_INDEX = 0; const DEFAULT_SET_INDEX = 0;
const DEFAULT_STATE = { const DEFAULT_STATE = {
@@ -119,33 +130,35 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
onClick={() => { onClick={() => {
setSelectedContour(isSameContour ? null : ROINumber); setSelectedContour(isSameContour ? null : ROINumber);
const enabledElements = cornerstone.getEnabledElements(); if (isCornerstone()) {
const element = enabledElements[activeIndex].element; const enabledElements = cornerstone.getEnabledElements();
const toolState = cornerstoneTools.getToolState(element, 'stack'); const element = enabledElements[activeIndex].element;
const toolState = cornerstoneTools.getToolState(element, 'stack');
if (!toolState) { if (!toolState) {
return; return;
}
const imageIds = toolState.data[0].imageIds;
const module = cornerstoneTools.getModule('rtstruct');
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
state.selectedSet.SeriesInstanceUID,
ROINumber,
imageIds
);
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get('SOPInstanceUID', imageId);
const StudyInstanceUID = cornerstone.metaData.get('StudyInstanceUID', imageId);
onContourItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex
});
} }
const imageIds = toolState.data[0].imageIds;
const module = cornerstoneTools.getModule('rtstruct');
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
state.selectedSet.SeriesInstanceUID,
ROINumber,
imageIds
);
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get('SOPInstanceUID', imageId);
const StudyInstanceUID = cornerstone.metaData.get('StudyInstanceUID', imageId);
onContourItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex
});
}} }}
label={`${ROIName} ${interpretedType}`} label={`${ROIName} ${interpretedType}`}
index={ROINumber} index={ROINumber}
@@ -190,7 +203,7 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
/> />
</div> </div>
{!state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />} {!state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />}
{state.referencedDisplaysets.map(displaySet => { {state.sets && state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet; const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
@@ -205,9 +218,14 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
visible={isLoaded && loadedSet.visible} visible={isLoaded && loadedSet.visible}
hideVisibleButton={!isLoaded} hideVisibleButton={!isLoaded}
expanded={isLoaded && loadedSet.SeriesInstanceUID === state.selectedSet.SeriesInstanceUID} expanded={isLoaded && loadedSet.SeriesInstanceUID === state.selectedSet.SeriesInstanceUID}
onVisibilityChange={() => { onVisibilityChange={newVisibility => {
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
module.setters.toggleStructureSet(SeriesInstanceUID); loadedSet.ROIContours.forEach(({ ROINumber }) => {
module.setters.toggleROIContour(loadedSet.SeriesInstanceUID, ROINumber);
});
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
setState(state => ({ ...state, sets }));
refreshViewport();
}} }}
onExpandChange={async () => { onExpandChange={async () => {
if (!isLoaded) { if (!isLoaded) {
@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { TableListItem, Icon } from '@ohif/ui'; import { TableListItem, Icon } from '@ohif/ui';
@@ -28,6 +28,11 @@ const StructureSetItem = ({
selected = false, selected = false,
}) => { }) => {
const [isVisible, setIsVisible] = useState(visible); const [isVisible, setIsVisible] = useState(visible);
useEffect(() => {
setIsVisible(visible);
}, [visible])
return ( return (
<div className={`dcmrt-structure-set-item ${selected && 'selected'}`}> <div className={`dcmrt-structure-set-item ${selected && 'selected'}`}>
<TableListItem <TableListItem
+9 -2
View File
@@ -20,14 +20,21 @@ export default {
preRegistration({ servicesManager, configuration = {} }) { preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration }); init({ servicesManager, configuration });
}, },
getPanelModule({ commandsManager }) { getPanelModule({ commandsManager, api }) {
const ExtendedRTPanel = props => { const ExtendedRTPanel = props => {
const { activeContexts } = api.hooks.useAppContext();
const contourItemClickHandler = contourData => { const contourItemClickHandler = contourData => {
commandsManager.runCommand('jumpToImage', contourData); commandsManager.runCommand('jumpToImage', contourData);
}; };
return ( return (
<RTPanel {...props} onContourItemClick={contourItemClickHandler} /> <RTPanel
{...props}
onContourItemClick={contourItemClickHandler}
activeContexts={activeContexts}
contexts={api.contexts}
/>
); );
}; };
@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.6...@ohif/extension-dicom-segmentation@0.3.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.5...@ohif/extension-dicom-segmentation@0.2.6) (2020-05-12)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [0.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.4...@ohif/extension-dicom-segmentation@0.2.5) (2020-05-06)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.3...@ohif/extension-dicom-segmentation@0.2.4) (2020-05-04)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.2...@ohif/extension-dicom-segmentation@0.2.3) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.1...@ohif/extension-dicom-segmentation@0.2.2) (2020-04-28) ## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.1...@ohif/extension-dicom-segmentation@0.2.2) (2020-04-28)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation **Note:** Version bump only for package @ohif/extension-dicom-segmentation
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-segmentation", "name": "@ohif/extension-dicom-segmentation",
"version": "0.2.2", "version": "0.3.0",
"description": "OHIF extension for viewing segmentations in the 2D MPR view", "description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -30,7 +30,7 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.12.5", "cornerstone-tools": "4.15.1",
"dcmjs": "^0.12.2", "dcmjs": "^0.12.2",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
@@ -38,6 +38,7 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"gl-matrix": "^3.3.0",
"react-select": "^3.0.8" "react-select": "^3.0.8"
} }
} }
@@ -1,71 +0,0 @@
import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
};
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
// TODO: Handle the case where there is more than one SOP Class Handler for the
// same SOP Class.
const OHIFDicomSegSopClassHandler = {
id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function (
series,
study,
dicomWebClient,
authorizationHeaders
) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDate,
SeriesTime,
SeriesDescription,
FrameOfReferenceUID,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
} = metadata;
const segDisplaySet = {
Modality: 'SEG',
displaySetInstanceUID: utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
FrameOfReferenceUID,
authorizationHeaders,
metadata,
isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded.
isLoaded: false,
SeriesDate,
SeriesTime,
SeriesDescription,
};
segDisplaySet.load = function (referencedDisplaySet, studies) {
return loadSegmentation(
segDisplaySet,
referencedDisplaySet,
studies
).catch(error => {
segDisplaySet.isLoaded = false;
throw new Error(error);
});
};
return segDisplaySet;
},
};
export default OHIFDicomSegSopClassHandler;
@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { TableListItem, Icon } from '@ohif/ui'; import { TableListItem, Icon } from '@ohif/ui';
@@ -19,6 +19,11 @@ ColoredCircle.propTypes = {
const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true, onVisibilityChange }) => { const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true, onVisibilityChange }) => {
const [isVisible, setIsVisible] = useState(visible); const [isVisible, setIsVisible] = useState(visible);
useEffect(() => {
setIsVisible(visible);
}, [visible]);
return ( return (
<div className="dcmseg-segment-item"> <div className="dcmseg-segment-item">
<TableListItem <TableListItem
@@ -32,7 +32,11 @@ const refreshViewport = () => {
* @param {Array} props.viewports - Viewports data (viewportSpecificData) * @param {Array} props.viewports - Viewports data (viewportSpecificData)
* @param {number} props.activeIndex - Active viewport index * @param {number} props.activeIndex - Active viewport index
* @param {boolean} props.isOpen - Boolean that indicates if the panel is expanded * @param {boolean} props.isOpen - Boolean that indicates if the panel is expanded
* @param {Function} props.onSegItemClick - Segment click handler * @param {Function} props.onSegmentItemClick - Segment click handler
* @param {Function} props.onSegmentVisibilityChange - Segment visibiliy change handler
* @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
* @returns component * @returns component
*/ */
const SegmentationPanel = ({ const SegmentationPanel = ({
@@ -40,24 +44,40 @@ const SegmentationPanel = ({
viewports, viewports,
activeIndex, activeIndex,
isOpen, isOpen,
onSegItemClick, onSegmentItemClick,
onSegmentVisibilityChange,
onConfigurationChange,
onDisplaySetLoadFailure,
onSelectedSegmentationChange,
activeContexts = [],
contexts = {},
}) => { }) => {
const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
/* /*
* TODO: wrap get/set interactions with the cornerstoneTools * TODO: wrap get/set interactions with the cornerstoneTools
* store with context to make these kind of things less blurry. * store with context to make these kind of things less blurry.
*/ */
const { configuration } = cornerstoneTools.getModule('segmentation'); const { configuration } = cornerstoneTools.getModule('segmentation');
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10; const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
/*
* TODO: We shouldn't hardcode brushColor color, in the future
* the SEG may set the colorLUT to whatever it wants.
*/
const [state, setState] = useState({ const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS, brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: brushColor:
'rgba(221, 85, 85, 1)' /* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */, 'rgba(221, 85, 85, 1)',
selectedSegment: null, selectedSegment: null,
selectedSegmentation: null, selectedSegmentation: null,
showSegSettings: false, showSegmentationSettings: false,
brushStackState: null, brushStackState: null,
labelmapList: [], labelmapList: [],
segmentList: [], segmentList: [],
cachedSegmentsProperties: [],
isLoading: false
}); });
useEffect(() => { useEffect(() => {
@@ -74,6 +94,25 @@ const SegmentationPanel = ({
updateState('brushStackState', module.state.series[firstImageId]); updateState('brushStackState', module.state.series[firstImageId]);
}; };
/*
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
* or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
* before the segments are fully loaded, the panel can subscribe to this custom event
* and update itself with the new segments.
*
* This limitation is due to the fact that the cs segmentation module is an object (which will be
* updated after the segments are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
document.addEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations);
/* /*
* These are specific to each element; * These are specific to each element;
* Need to iterate cornerstone-tools tracked enabled elements? * Need to iterate cornerstone-tools tracked enabled elements?
@@ -87,6 +126,7 @@ const SegmentationPanel = ({
); );
return () => { return () => {
document.removeEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations);
cornerstoneTools.store.state.enabledElements.forEach(enabledElement => cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
enabledElement.removeEventListener( enabledElement.removeEventListener(
'cornerstonetoolslabelmapmodified', 'cornerstonetoolslabelmapmodified',
@@ -94,9 +134,9 @@ const SegmentationPanel = ({
) )
); );
}; };
}); }, [activeIndex, viewports]);
useEffect(() => { const refreshSegmentations = useCallback(() => {
const module = cornerstoneTools.getModule('segmentation'); const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex]; const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get( const studyMetadata = studyMetadataManager.get(
@@ -106,7 +146,6 @@ const SegmentationPanel = ({
activeViewport.displaySetInstanceUID activeViewport.displaySetInstanceUID
); );
const brushStackState = module.state.series[firstImageId]; const brushStackState = module.state.series[firstImageId];
if (brushStackState) { if (brushStackState) {
const labelmap3D = const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex]; brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
@@ -115,7 +154,11 @@ const SegmentationPanel = ({
firstImageId, firstImageId,
activeViewport activeViewport
); );
const segmentList = getSegmentList(labelmap3D, firstImageId, brushStackState); const segmentList = getSegmentList(
labelmap3D,
firstImageId,
brushStackState
);
setState(state => ({ setState(state => ({
...state, ...state,
brushStackState, brushStackState,
@@ -130,13 +173,21 @@ const SegmentationPanel = ({
segmentList: [], segmentList: [],
})); }));
} }
}, [studies, viewports, activeIndex, getLabelmapList, getSegmentList, state.selectedSegmentation]); }, [
viewports,
activeIndex,
state.isLoading
]);
useEffect(() => {
refreshSegmentations();
}, [viewports, activeIndex, state.selectedSegmentation, activeContexts, state.isLoading]);
/* Handle open/closed panel behaviour */ /* Handle open/closed panel behaviour */
useEffect(() => { useEffect(() => {
setState(state => ({ setState(state => ({
...state, ...state,
showSegSettings: state.showSegSettings && !isOpen, showSegmentationSettings: state.showSegmentationSettings && !isOpen,
})); }));
}, [isOpen]); }, [isOpen]);
@@ -170,7 +221,9 @@ const SegmentationPanel = ({
studies, studies,
displaySet, displaySet,
firstImageId, firstImageId,
brushStackState.activeLabelmapIndex brushStackState.activeLabelmapIndex,
() => onSelectedSegmentationChange(),
onDisplaySetLoadFailure
); );
updateState('selectedSegmentation', activatedLabelmapIndex); updateState('selectedSegmentation', activatedLabelmapIndex);
}, },
@@ -249,65 +302,104 @@ const SegmentationPanel = ({
: prev; : prev;
}); });
const enabledElements = cornerstone.getEnabledElements(); if (isCornerstone()) {
const element = enabledElements[activeIndex].element; const enabledElements = cornerstone.getEnabledElements();
const toolState = cornerstoneTools.getToolState(element, 'stack'); const element = enabledElements[activeIndex].element;
const toolState = cornerstoneTools.getToolState(element, 'stack');
if (!toolState) { if (!toolState) {
return; return;
}
const imageIds = toolState.data[0].imageIds;
const imageId = imageIds[closest];
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
onSegmentItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex,
});
} }
const imageIds = toolState.data[0].imageIds; if (isVTK()) {
const imageId = imageIds[closest]; const activeViewport = viewports[activeIndex];
const frameIndex = imageIds.indexOf(imageId); const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const allDisplaySets = studyMetadata.getDisplaySets();
const currentDisplaySet = allDisplaySets.find(
displaySet =>
displaySet.displaySetInstanceUID ===
activeViewport.displaySetInstanceUID
);
const SOPInstanceUID = cornerstone.metaData.get( const frame = labelmap3D.labelmaps2D[closest];
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
onSegItemClick({ onSegmentItemClick({
StudyInstanceUID, studies,
SOPInstanceUID, StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
frameIndex, displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
activeViewportIndex: activeIndex, SOPClassUID: viewports[activeIndex].sopClassUIDs[0],
}); SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
segmentNumber,
frameIndex: closest,
frame,
});
}
}; };
const enabledElements = cornerstone.getEnabledElements(); const isSegmentVisible = () => {
const enabledElementViewport = enabledElements[activeIndex]; return !labelmap3D.segmentsHidden[segmentIndex];
};
let isVisible = true; const toggleSegmentVisibility = () => {
if (enabledElementViewport) { const segmentsHidden = labelmap3D.segmentsHidden;
const element = enabledElementViewport.element; segmentsHidden[segmentIndex] = !segmentsHidden[segmentIndex];
const module = cornerstoneTools.getModule('segmentation'); return !segmentsHidden[segmentIndex];
isVisible = module.getters.isSegmentVisible( };
element,
segmentNumber, const cachedSegmentProperties = state.cachedSegmentsProperties[segmentNumber];
brushStackState.activeLabelmapIndex let visible = isSegmentVisible();
); if (cachedSegmentProperties && cachedSegmentProperties.visible !== visible) {
toggleSegmentVisibility();
} }
segmentList.push( segmentList.push(
<SegmentItem <SegmentItem
key={segmentNumber} key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`} itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={setCurrentSelectedSegment} onClick={() => setCurrentSelectedSegment()}
label={segmentLabel} label={segmentLabel}
index={segmentNumber} index={segmentNumber}
color={color} color={color}
visible={isVisible} visible={visible}
onVisibilityChange={() => { onVisibilityChange={newVisibility => {
const element = enabledElements[activeIndex].element; if (isCornerstone()) {
module.setters.toggleSegmentVisibility( const enabledElements = cornerstone.getEnabledElements();
element, const element = enabledElements[activeIndex].element;
segmentNumber, module.setters.toggleSegmentVisibility(
brushStackState.activeLabelmapIndex element,
); segmentNumber,
brushStackState.activeLabelmapIndex
);
}
if (isVTK()) {
onSegmentVisibilityChange(segmentNumber, newVisibility);
}
updateCachedSegmentsProperties(segmentNumber, { visible: newVisibility });
refreshViewport(); refreshViewport();
}} }}
/> />
@@ -323,9 +415,25 @@ const SegmentationPanel = ({
* Show default name * Show default name
*/ */
}, },
[activeIndex, onSegItemClick, state.selectedSegment] [activeIndex, onSegmentItemClick, state.selectedSegment, state.isLoading]
); );
const updateCachedSegmentsProperties = (segmentNumber, properties) => {
const segmentsProperties = state.cachedSegmentsProperties;
const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
segmentsProperties[segmentNumber] =
segmentProperties ?
{ ...segmentProperties, ...properties } :
properties;
updateState('cachedSegmentsProperties', segmentsProperties);
};
useEffect(() => {
updateState('cachedSegmentsProperties', []);
}, [activeContexts]);
const updateState = (field, value) => { const updateState = (field, value) => {
setState(state => ({ ...state, [field]: value })); setState(state => ({ ...state, [field]: value }));
}; };
@@ -374,7 +482,6 @@ const SegmentationPanel = ({
}; };
const updateConfiguration = newConfiguration => { const updateConfiguration = newConfiguration => {
/* Supported configuration */
configuration.renderFill = newConfiguration.renderFill; configuration.renderFill = newConfiguration.renderFill;
configuration.renderOutline = newConfiguration.renderOutline; configuration.renderOutline = newConfiguration.renderOutline;
configuration.shouldRenderInactiveLabelmaps = configuration.shouldRenderInactiveLabelmaps =
@@ -384,14 +491,17 @@ const SegmentationPanel = ({
configuration.outlineWidth = newConfiguration.outlineWidth; configuration.outlineWidth = newConfiguration.outlineWidth;
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive; configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive; configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
onConfigurationChange(newConfiguration);
refreshViewport(); refreshViewport();
}; };
if (state.showSegSettings) { const disabledConfigurationFields = ['outlineAlpha', 'shouldRenderInactiveLabelmaps'];
if (state.showSegmentationSettings) {
return ( return (
<SegmentationSettings <SegmentationSettings
disabledFields={isVTK() ? disabledConfigurationFields : []}
configuration={configuration} configuration={configuration}
onBack={() => updateState('showSegSettings', false)} onBack={() => updateState('showSegmentationSettings', false)}
onChange={updateConfiguration} onChange={updateConfiguration}
/> />
); );
@@ -403,7 +513,7 @@ const SegmentationPanel = ({
name="cog" name="cog"
width="25px" width="25px"
height="25px" height="25px"
onClick={() => updateState('showSegSettings', true)} onClick={() => updateState('showSegmentationSettings', true)}
/> />
{false && ( {false && (
<form className="selector-form"> <form className="selector-form">
@@ -424,7 +534,9 @@ const SegmentationPanel = ({
<h3>Segmentations</h3> <h3>Segmentations</h3>
<div className="segmentations"> <div className="segmentations">
<SegmentationSelect <SegmentationSelect
value={state.labelmapList.find(i => i.value === state.selectedSegmentation)} value={state.labelmapList.find(
i => i.value === state.selectedSegmentation
)}
formatOptionLabel={SegmentationItem} formatOptionLabel={SegmentationItem}
options={state.labelmapList} options={state.labelmapList}
/> />
@@ -509,7 +621,9 @@ const _setActiveLabelmap = async (
studies, studies,
displaySet, displaySet,
firstImageId, firstImageId,
activeLabelmapIndex activeLabelmapIndex,
callback = () => { },
onDisplaySetLoadFailure
) => { ) => {
if (displaySet.labelmapIndex === activeLabelmapIndex) { if (displaySet.labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`); log.warn(`${activeLabelmapIndex} is already the active labelmap`);
@@ -519,7 +633,17 @@ const _setActiveLabelmap = async (
if (!displaySet.isLoaded) { if (!displaySet.isLoaded) {
// What props does this expect `viewportSpecificData` to have? // What props does this expect `viewportSpecificData` to have?
// TODO: Should this return the `labelmapIndex`? // TODO: Should this return the `labelmapIndex`?
await displaySet.load(viewportSpecificData, studies);
const loadPromise = displaySet.load(viewportSpecificData, studies);
loadPromise.catch(error => {
onDisplaySetLoadFailure(error);
// Return old index.
return activeLabelmapIndex;
});
await loadPromise;
} }
const { state } = cornerstoneTools.getModule('segmentation'); const { state } = cornerstoneTools.getModule('segmentation');
@@ -528,6 +652,8 @@ const _setActiveLabelmap = async (
refreshViewport(); refreshViewport();
callback();
return displaySet.labelmapIndex; return displaySet.labelmapIndex;
}; };
@@ -4,7 +4,7 @@ import { Range } from '@ohif/ui';
import './SegmentationSettings.css'; import './SegmentationSettings.css';
const SegmentationSettings = ({ configuration, onBack, onChange }) => { const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields = [] }) => {
const [state, setState] = useState({ const [state, setState] = useState({
renderFill: configuration.renderFill, renderFill: configuration.renderFill,
renderOutline: configuration.renderOutline, renderOutline: configuration.renderOutline,
@@ -70,28 +70,32 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
/> />
{state.renderOutline && ( {state.renderOutline && (
<> <>
<CustomRange {!disabledFields.includes('outlineAlpha') && (
value={state.outlineAlpha * 100} <CustomRange
label="Opacity" value={state.outlineAlpha * 100}
showPercentage label="Opacity"
step={1} showPercentage
min={0} step={1}
max={100} min={0}
onChange={event => save('outlineAlpha', toFloat(event.target.value))} max={100}
/> onChange={event => save('outlineAlpha', toFloat(event.target.value))}
<CustomRange />
value={state.outlineWidth} )}
label="Width" {!disabledFields.includes('outlineWidth') && (
showValue <CustomRange
step={1} value={state.outlineWidth}
min={0} label="Width"
max={5} showValue
onChange={event => save('outlineWidth', parseInt(event.target.value))} step={1}
/> min={0}
max={5}
onChange={event => save('outlineWidth', parseInt(event.target.value))}
/>
)}
</> </>
)} )}
</div> </div>
{(state.renderFill || state.renderOutline) && ( {(state.renderFill || state.renderOutline) && !disabledFields.includes('shouldRenderInactiveLabelmaps') && (
<div <div
className="settings-group" className="settings-group"
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }} style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
@@ -103,7 +107,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
/> />
{state.shouldRenderInactiveLabelmaps && ( {state.shouldRenderInactiveLabelmaps && (
<> <>
{state.renderFill && ( {state.renderFill && !disabledFields.includes('fillAlphaInactive') && (
<CustomRange <CustomRange
label="Fill Opacity" label="Fill Opacity"
showPercentage showPercentage
@@ -114,7 +118,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))} onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
/> />
)} )}
{state.renderOutline && ( {state.renderOutline && !disabledFields.includes('outlineAlphaInactive') && (
<CustomRange <CustomRange
label="Outline Opacity" label="Outline Opacity"
showPercentage showPercentage
@@ -0,0 +1,64 @@
import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
};
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
export default function getSopClassHandlerModule({ servicesManager }) {
// TODO: Handle the case where there is more than one SOP Class Handler for the
// same SOP Class.
return {
id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function(
series,
study,
dicomWebClient,
authorizationHeaders
) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDate,
SeriesTime,
SeriesDescription,
FrameOfReferenceUID,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
} = metadata;
const segDisplaySet = {
Modality: 'SEG',
displaySetInstanceUID: utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
FrameOfReferenceUID,
authorizationHeaders,
metadata,
isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded.
isLoaded: false,
SeriesDate,
SeriesTime,
SeriesDescription,
};
segDisplaySet.load = function(referencedDisplaySet, studies) {
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies);
};
return segDisplaySet;
},
};
}
+49 -8
View File
@@ -2,7 +2,7 @@ import React from 'react';
import init from './init.js'; import init from './init.js';
import toolbarModule from './toolbarModule.js'; import toolbarModule from './toolbarModule.js';
import sopClassHandlerModule from './OHIFDicomSegSopClassHandler.js'; import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js'; import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
export default { export default {
@@ -23,14 +23,57 @@ export default {
getToolbarModule({ servicesManager }) { getToolbarModule({ servicesManager }) {
return toolbarModule; return toolbarModule;
}, },
getPanelModule({ commandsManager }) { getPanelModule({ commandsManager, api, servicesManager }) {
const { UINotificationService } = servicesManager;
const ExtendedSegmentationPanel = props => { const ExtendedSegmentationPanel = props => {
const segItemClickHandler = segData => { const { activeContexts } = api.hooks.useAppContext();
commandsManager.runCommand('jumpToImage', segData);
const onDisplaySetLoadFailureHandler = error => {
UINotificationService.show({
title: 'DICOM Segmentation Loader',
message: error.message,
type: 'error',
autoClose: false,
});
};
const segmentItemClickHandler = data => {
commandsManager.runCommand('jumpToImage', data);
commandsManager.runCommand('jumpToSlice', data);
};
const onSegmentVisibilityChangeHandler = (segmentNumber, visible) => {
commandsManager.runCommand('setSegmentConfiguration', {
segmentNumber,
visible,
});
};
const onConfigurationChangeHandler = configuration => {
commandsManager.runCommand('setSegmentationConfiguration', {
globalOpacity: configuration.fillAlpha,
outlineThickness: configuration.outlineWidth,
renderOutline: configuration.renderOutline,
visible: configuration.renderFill,
});
};
const onSelectedSegmentationChangeHandler = () => {
commandsManager.runCommand('requestNewSegmentation');
}; };
return ( return (
<SegmentationPanel {...props} onSegItemClick={segItemClickHandler} /> <SegmentationPanel
{...props}
activeContexts={activeContexts}
contexts={api.contexts}
onSegmentItemClick={segmentItemClickHandler}
onSegmentVisibilityChange={onSegmentVisibilityChangeHandler}
onConfigurationChange={onConfigurationChangeHandler}
onSelectedSegmentationChange={onSelectedSegmentationChangeHandler}
onDisplaySetLoadFailure={onDisplaySetLoadFailureHandler}
/>
); );
}; };
@@ -72,7 +115,5 @@ export default {
defaultContext: ['VIEWER'], defaultContext: ['VIEWER'],
}; };
}, },
getSopClassHandlerModule({ servicesManager }) { getSopClassHandlerModule,
return sopClassHandlerModule;
},
}; };
@@ -34,28 +34,58 @@ export default async function loadSegmentation(
referencedDisplaySet.SeriesInstanceUID referencedDisplaySet.SeriesInstanceUID
); );
const results = _parseSeg(segArrayBuffer, imageIds); return new Promise((resolve, reject) => {
let results;
if (!results) { try {
throw new Error('Fractional segmentations are not yet supported'); results = _parseSeg(segArrayBuffer, imageIds);
} } catch (error) {
segDisplaySet.isLoaded = false;
reject(error);
}
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results; const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
const { setters } = cornerstoneTools.getModule('segmentation'); const { setters } = cornerstoneTools.getModule('segmentation');
// TODO: Could define a color LUT based on colors in the SEG. // TODO: Could define a color LUT based on colors in the SEG.
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]); const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
setters.labelmap3DByFirstImageId( setters.labelmap3DByFirstImageId(
imageIds[0], imageIds[0],
labelmapBuffer, labelmapBuffer,
labelmapIndex, labelmapIndex,
segMetadata, segMetadata,
imageIds.length, imageIds.length,
segmentsOnFrame segmentsOnFrame,
); colorLUTIndex
);
segDisplaySet.labelmapIndex = labelmapIndex; segDisplaySet.labelmapIndex = labelmapIndex;
/*
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
* or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
* before the segments are fully loaded, the panel can subscribe to this custom event
* and update itself with the new segments.
*
* This limitation is due to the fact that the cs segmentation module is an object (which will be
* updated after the segments are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event);
resolve(labelmapIndex);
});
} }
function _getNextLabelmapIndex(firstImageId) { function _getNextLabelmapIndex(firstImageId) {
@@ -79,6 +109,69 @@ function _getNextLabelmapIndex(firstImageId) {
return labelmapIndex; return labelmapIndex;
} }
function _makeColorLUTAndGetIndex(segMetadata) {
const { setters, state } = cornerstoneTools.getModule('segmentation');
const { colorLutTables } = state;
const colorLUTIndex = _getNextColorLUTIndex();
const { data } = segMetadata;
if (
!data.some(
segment =>
segment &&
(segment.ROIDisplayColor || segment.RecommendedDisplayCIELabValue)
)
) {
// Use default cornerstoneTools colorLUT.
return 0;
}
const colorLUT = [];
for (let i = 0; i < data.length; i++) {
const segment = data[i];
if (!segment) {
continue;
}
const { ROIDisplayColor, RecommendedDisplayCIELabValue } = segment;
if (RecommendedDisplayCIELabValue) {
const rgb = dcmjs.data.Colors.dicomlab2RGB(
RecommendedDisplayCIELabValue
).map(x => Math.round(x * 255));
colorLUT[i] = [...rgb, 255];
} else if (ROIDisplayColor) {
colorLUT[i] = [...ROIDisplayColor, 255];
} else {
colorLUT[i] = [...colorLutTables[0][i]];
}
}
colorLUT.shift();
setters.colorLUT(colorLUTIndex, colorLUT);
return colorLUTIndex;
}
function _getNextColorLUTIndex() {
const { state } = cornerstoneTools.getModule('segmentation');
const { colorLutTables } = state;
let colorLUTIndex = colorLutTables.length;
for (let i = 0; i < colorLutTables.length; i++) {
if (!colorLutTables[i]) {
colorLUTIndex = i;
break;
}
}
return colorLUTIndex;
}
function _parseSeg(arrayBuffer, imageIds) { function _parseSeg(arrayBuffer, imageIds) {
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState( return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
imageIds, imageIds,
+46
View File
@@ -3,6 +3,52 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.6...@ohif/extension-vtk@1.6.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [1.5.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.5...@ohif/extension-vtk@1.5.6) (2020-05-14)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.5.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.4...@ohif/extension-vtk@1.5.5) (2020-05-12)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [1.5.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.3...@ohif/extension-vtk@1.5.4) (2020-05-06)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.5.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.2...@ohif/extension-vtk@1.5.3) (2020-05-04)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.1...@ohif/extension-vtk@1.5.2) (2020-05-04) ## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.1...@ohif/extension-vtk@1.5.2) (2020-05-04)
**Note:** Version bump only for package @ohif/extension-vtk **Note:** Version bump only for package @ohif/extension-vtk
+6 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.5.2", "version": "1.6.0",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -33,7 +33,7 @@
"@ohif/i18n": "^0.50.0", "@ohif/i18n": "^0.50.0",
"@ohif/ui": "^0.50.0", "@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.12.5", "cornerstone-tools": "4.15.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "^0.12.2", "dcmjs": "^0.12.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
@@ -50,12 +50,12 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"react-vtkjs-viewport": "^0.8.3" "react-vtkjs-viewport": "^0.9.0"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.9.2", "@ohif/core": "^2.9.6",
"@ohif/ui": "^1.4.3", "@ohif/ui": "^1.5.0",
"cornerstone-tools": "4.12.5", "cornerstone-tools": "4.15.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"gh-pages": "^2.0.1", "gh-pages": "^2.0.1",
+9 -9
View File
@@ -32,16 +32,16 @@ class LoadingIndicator extends PureComponent {
</div> </div>
</div> </div>
) : ( ) : (
<div className="imageViewerLoadingIndicator loadingIndicator"> <div className="imageViewerLoadingIndicator loadingIndicator">
<div className="indicatorContents"> <div className="indicatorContents">
<p> <p>
{this.props.t('Reformatting')}... {this.props.t('Loading...')}
<i className="fa fa-spin fa-circle-o-notch fa-fw" /> <i className="fa fa-spin fa-circle-o-notch fa-fw" />
{percComplete} {percComplete}
</p> </p>
</div>
</div> </div>
</div> )}
)}
</React.Fragment> </React.Fragment>
); );
} }
+16 -12
View File
@@ -49,7 +49,7 @@ class OHIFVTKViewport extends Component {
state = { state = {
volumes: null, volumes: null,
paintFilterLabelMapImageData: null, paintFilterLabelMapImageData: null,
paintFilterBackgroundImageData: null, paintFilterBackgroundImageData: null
}; };
static propTypes = { static propTypes = {
@@ -69,7 +69,7 @@ class OHIFVTKViewport extends Component {
}; };
static defaultProps = { static defaultProps = {
onScroll: () => {}, onScroll: () => { },
}; };
static id = 'OHIFVTKViewport'; static id = 'OHIFVTKViewport';
@@ -156,6 +156,10 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState; const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex]; const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(isHidden => {
return { visible: !isHidden };
});
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`; const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
if (labelmapCache[vtkLabelmapID]) { if (labelmapCache[vtkLabelmapID]) {
@@ -339,13 +343,13 @@ class OHIFVTKViewport extends Component {
this.setStateFromProps(); this.setStateFromProps();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps, prevState) {
const { displaySet } = this.props.viewportData; const { displaySet } = this.props.viewportData;
const prevDisplaySet = prevProps.viewportData.displaySet; const prevDisplaySet = prevProps.viewportData.displaySet;
if ( if (
displaySet.displaySetInstanceUID !== displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID || prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex displaySet.frameIndex !== prevDisplaySet.frameIndex
) { ) {
@@ -405,10 +409,6 @@ class OHIFVTKViewport extends Component {
const style = { width: '100%', height: '100%', position: 'relative' }; const style = { width: '100%', height: '100%', position: 'relative' };
const visible = configuration.renderFill || configuration.renderOutline;
const opacity = configuration.fillAlpha;
const outlineThickness = configuration.outlineThickness;
return ( return (
<> <>
<div style={style}> <div style={style}>
@@ -428,10 +428,14 @@ class OHIFVTKViewport extends Component {
dataDetails={this.state.dataDetails} dataDetails={this.state.dataDetails}
labelmapRenderingOptions={{ labelmapRenderingOptions={{
colorLUT: this.state.labelmapColorLUT, colorLUT: this.state.labelmapColorLUT,
globalOpacity: opacity, globalOpacity: configuration.fillAlpha,
visible, visible: configuration.renderFill,
outlineThickness, outlineThickness: configuration.outlineWidth,
renderOutline: true, renderOutline: configuration.renderOutline,
segmentsDefaultProperties: this.segmentsDefaultProperties,
onNewSegmentationRequested: () => {
this.setStateFromProps();
}
}} }}
onScroll={this.props.onScroll} onScroll={this.props.onScroll}
/> />
+151 -2
View File
@@ -5,10 +5,13 @@ import {
vtkInteractorStyleMPRRotate, vtkInteractorStyleMPRRotate,
vtkSVGCrosshairsWidget, vtkSVGCrosshairsWidget,
} from 'react-vtkjs-viewport'; } from 'react-vtkjs-viewport';
import { getImageData } from 'react-vtkjs-viewport';
import { vec3 } from 'gl-matrix';
import setMPRLayout from './utils/setMPRLayout.js'; import setMPRLayout from './utils/setMPRLayout.js';
import setViewportToVTK from './utils/setViewportToVTK.js'; import setViewportToVTK from './utils/setViewportToVTK.js';
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js'; import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
import OHIFVTKViewport from './OHIFVTKViewport';
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
const { BlendMode } = Constants; const { BlendMode } = Constants;
@@ -34,7 +37,6 @@ const commandsModule = ({ commandsManager }) => {
} }
const displaySet = viewportSpecificData[activeViewportIndex]; const displaySet = viewportSpecificData[activeViewportIndex];
let api; let api;
if (!api) { if (!api) {
try { try {
@@ -99,6 +101,20 @@ const commandsModule = ({ commandsManager }) => {
}); });
} }
const _convertModelToWorldSpace = (position, vtkImageData) => {
const indexToWorld = vtkImageData.getIndexToWorld();
const pos = vec3.create();
position[0] += 0.5; /* Move to the centre of the voxel. */
position[1] += 0.5; /* Move to the centre of the voxel. */
position[2] += 0.5; /* Move to the centre of the voxel. */
vec3.set(pos, position[0], position[1], position[2]);
vec3.transformMat4(pos, pos, indexToWorld);
return pos;
};
const actions = { const actions = {
getVtkApis: ({ index }) => { getVtkApis: ({ index }) => {
return apis[index]; return apis[index];
@@ -124,6 +140,119 @@ const commandsModule = ({ commandsManager }) => {
_setView(api, [0, 1, 0], [0, 0, 1]); _setView(api, [0, 1, 0], [0, 0, 1]);
}, },
requestNewSegmentation: async ({ viewports }) => {
const allViewports = Object.values(viewports.viewportSpecificData);
const promises = allViewports.map(async (viewport, viewportIndex) => {
let api = apis[viewportIndex];
if (!api) {
api = await _getActiveViewportVTKApi(viewports);
apis[viewportIndex] = api;
}
api.requestNewSegmentation();
api.updateImage();
});
await Promise.all(promises);
},
jumpToSlice: async ({
viewports,
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
segmentNumber,
frameIndex,
frame,
done = () => { }
}) => {
let api = apis[viewports.activeViewportIndex];
if (!api) {
api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
}
const stack = OHIFVTKViewport.getCornerstoneStack(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
frameIndex,
);
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
let pixelIndex = 0;
let x = 0;
let y = 0;
let count = 0;
const rows = imageDataObject.dimensions[1];
const cols = imageDataObject.dimensions[0];
for (let j = 0; j < rows; j++) {
for (let i = 0; i < cols; i++) {
// [i, j] =
const pixel = frame.pixelData[pixelIndex];
if (pixel === segmentNumber) {
x += i;
y += j;
count++;
}
pixelIndex++;
}
}
x /= count;
y /= count;
const position = [x, y, frameIndex];
const worldPos = _convertModelToWorldSpace(position, imageDataObject.vtkImageData);
api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
done();
},
setSegmentationConfiguration: async ({
viewports,
globalOpacity,
visible,
renderOutline,
outlineThickness,
}) => {
const allViewports = Object.values(viewports.viewportSpecificData);
const promises = allViewports.map(async (viewport, viewportIndex) => {
let api = apis[viewportIndex];
if (!api) {
api = await _getActiveViewportVTKApi(viewports);
apis[viewportIndex] = api;
}
api.setGlobalOpacity(globalOpacity);
api.setVisibility(visible);
api.setOutlineThickness(outlineThickness);
api.setOutlineRendering(renderOutline);
api.updateImage();
});
await Promise.all(promises);
},
setSegmentConfiguration: async ({ viewports, visible, segmentNumber }) => {
const allViewports = Object.values(viewports.viewportSpecificData);
const promises = allViewports.map(async (viewport, viewportIndex) => {
let api = apis[viewportIndex];
if (!api) {
api = await _getActiveViewportVTKApi(viewports);
apis[viewportIndex] = api;
}
api.setSegmentVisibility(segmentNumber, visible);
api.updateImage();
});
await Promise.all(promises);
},
enableRotateTool: () => { enableRotateTool: () => {
apis.forEach(api => { apis.forEach(api => {
const istyle = vtkInteractorStyleMPRRotate.newInstance(); const istyle = vtkInteractorStyleMPRRotate.newInstance();
@@ -280,6 +409,26 @@ const commandsModule = ({ commandsManager }) => {
window.vtkActions = actions; window.vtkActions = actions;
const definitions = { const definitions = {
requestNewSegmentation: {
commandFn: actions.requestNewSegmentation,
storeContexts: ['viewports'],
options: {},
},
jumpToSlice: {
commandFn: actions.jumpToSlice,
storeContexts: ['viewports'],
options: {},
},
setSegmentationConfiguration: {
commandFn: actions.setSegmentationConfiguration,
storeContexts: ['viewports'],
options: {},
},
setSegmentConfiguration: {
commandFn: actions.setSegmentConfiguration,
storeContexts: ['viewports'],
options: {},
},
axial: { axial: {
commandFn: actions.axial, commandFn: actions.axial,
storeContexts: ['viewports'], storeContexts: ['viewports'],
+3 -1
View File
@@ -1,3 +1,4 @@
import React from 'react';
import asyncComponent from './asyncComponent.js'; import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js'; import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js'; import toolbarModule from './toolbarModule.js';
@@ -16,7 +17,8 @@ const vtkExtension = {
id: 'vtk', id: 'vtk',
getViewportModule({ commandsManager }) { getViewportModule({ commandsManager }) {
return withCommandsManager(OHIFVTKViewport, commandsManager); const ExtendedVTKViewport = props => <OHIFVTKViewport {...props} />;
return withCommandsManager(ExtendedVTKViewport, commandsManager);
}, },
getToolbarModule() { getToolbarModule() {
return toolbarModule; return toolbarModule;
+41
View File
@@ -3,6 +3,47 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.5...@ohif/core@2.9.6) (2020-05-14)
### Bug Fixes
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
## [2.9.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.4...@ohif/core@2.9.5) (2020-05-12)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [2.9.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.3...@ohif/core@2.9.4) (2020-05-06)
**Note:** Version bump only for package @ohif/core
## [2.9.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.2...@ohif/core@2.9.3) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [2.9.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.1...@ohif/core@2.9.2) (2020-05-04) ## [2.9.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.1...@ohif/core@2.9.2) (2020-05-04)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.9.2", "version": "2.9.6",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
@@ -32,7 +32,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.12.5", "cornerstone-tools": "4.15.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3" "dicom-parser": "^1.8.3"
}, },
@@ -79,7 +79,7 @@ export class StudyMetadata extends Metadata {
Object.defineProperty(this, 'studyInstanceUID', { Object.defineProperty(this, 'studyInstanceUID', {
configurable: false, configurable: false,
enumerable: false, enumerable: false,
get: function() { get: function () {
return this.getStudyInstanceUID(); return this.getStudyInstanceUID();
}, },
}); });
@@ -2,7 +2,7 @@ import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js'; import log from './../log.js';
export default class ExtensionManager { export default class ExtensionManager {
constructor({ commandsManager, servicesManager, appConfig = {} }) { constructor({ commandsManager, servicesManager, api, appConfig = {} }) {
this.modules = {}; this.modules = {};
this.registeredExtensionIds = []; this.registeredExtensionIds = [];
this.moduleTypeNames = Object.values(MODULE_TYPES); this.moduleTypeNames = Object.values(MODULE_TYPES);
@@ -10,6 +10,7 @@ export default class ExtensionManager {
this._commandsManager = commandsManager; this._commandsManager = commandsManager;
this._servicesManager = servicesManager; this._servicesManager = servicesManager;
this._appConfig = appConfig; this._appConfig = appConfig;
this._api = api;
this.moduleTypeNames.forEach(moduleType => { this.moduleTypeNames.forEach(moduleType => {
this.modules[moduleType] = []; this.modules[moduleType] = [];
@@ -119,6 +120,7 @@ export default class ExtensionManager {
commandsManager: this._commandsManager, commandsManager: this._commandsManager,
appConfig: this._appConfig, appConfig: this._appConfig,
configuration, configuration,
api: this._api
}); });
if (!extensionModule) { if (!extensionModule) {
@@ -50,10 +50,12 @@ import studyMetadataManager from './studyMetadataManager';
const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => { const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet; const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
const promises = [];
const studyMetadata = studyMetadataManager.get(StudyInstanceUID); const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
if (!studyMetadata) { if (!studyMetadata) {
return; return promises;
} }
const derivedDisplaySets = studyMetadata.getDerivedDatasets({ const derivedDisplaySets = studyMetadata.getDerivedDatasets({
@@ -61,7 +63,7 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
}); });
if (!derivedDisplaySets.length) { if (!derivedDisplaySets.length) {
return; return promises;
} }
// Filter by type // Filter by type
@@ -88,7 +90,7 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
// find most recent and load it. // find most recent and load it.
let recentDateTime = 0; let recentDateTime = 0;
let recentDisplaySet; let recentDisplaySet = displaySets[0];
displaySets.forEach(displaySet => { displaySets.forEach(displaySet => {
const dateTime = Number( const dateTime = Number(
@@ -100,8 +102,10 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
} }
}); });
recentDisplaySet.load(referencedDisplaySet, studies); promises.push(recentDisplaySet.load(referencedDisplaySet, studies));
}); });
return promises;
}; };
export default loadAndCacheDerivedDisplaySets; export default loadAndCacheDerivedDisplaySets;
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.4...@ohif/ui@1.5.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [1.4.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.3...@ohif/ui@1.4.4) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [1.4.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.2...@ohif/ui@1.4.3) (2020-04-09) ## [1.4.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.2...@ohif/ui@1.4.3) (2020-04-09)
**Note:** Version bump only for package @ohif/ui **Note:** Version bump only for package @ohif/ui
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.4.3", "version": "1.5.0",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -53,6 +53,7 @@
"react-dnd-html5-backend": "^9.4.0", "react-dnd-html5-backend": "^9.4.0",
"react-dnd-touch-backend": "^9.4.0", "react-dnd-touch-backend": "^9.4.0",
"react-draggable": "^4.1.0", "react-draggable": "^4.1.0",
"react-error-boundary": "^2.2.1",
"react-i18next": "^10.11.0", "react-i18next": "^10.11.0",
"react-modal": "^3.11.1", "react-modal": "^3.11.1",
"react-with-direction": "1.3.0" "react-with-direction": "1.3.0"
@@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ErrorBoundary } from 'react-error-boundary';
import './ErrorFallback.css';
const ErrorFallback = ({ error, componentStack, resetErrorBoundary }) => {
return (
<div className="ErrorFallback" role="alert">
<p>Something went wrong.</p>
<pre>{error.message}</pre>
<pre>{componentStack}</pre>
</div>
);
};
const OHIFErrorBoundary = ({
context = 'OHIF',
onReset = () => {},
onError = () => {},
fallbackComponent,
children,
}) => {
const onErrorHandler = (error, componentStack) => {
console.error(`${context} Error Boundary`, error, componentStack);
onError(error, componentStack);
};
const onResetHandler = () => {
onReset();
};
return (
<ErrorBoundary
FallbackComponent={fallbackComponent || ErrorFallback}
onReset={onResetHandler}
onError={onErrorHandler}
>
{children}
</ErrorBoundary>
);
};
OHIFErrorBoundary.propTypes = {
context: PropTypes.string,
onReset: PropTypes.func,
onError: PropTypes.func,
children: PropTypes.node.isRequired,
fallbackComponent: PropTypes.element,
};
export default OHIFErrorBoundary;
@@ -0,0 +1,4 @@
.ErrorFallback {
padding: 10px;
color: var(--active-color);
}
@@ -0,0 +1 @@
export { default as ErrorBoundary } from './ErrorBoundary';
+2 -1
View File
@@ -7,7 +7,6 @@ import { AboutContent } from './content/aboutContent/AboutContent';
import { TabComponents, TabFooter } from './tabComponents'; import { TabComponents, TabFooter } from './tabComponents';
import { HotkeyField } from './customForm'; import { HotkeyField } from './customForm';
import { LanguageSwitcher } from './languageSwitcher'; import { LanguageSwitcher } from './languageSwitcher';
import { Checkbox } from './checkbox'; import { Checkbox } from './checkbox';
import { CineDialog } from './cineDialog'; import { CineDialog } from './cineDialog';
import { ViewportDownloadForm } from './content/viewportDownloadForm'; import { ViewportDownloadForm } from './content/viewportDownloadForm';
@@ -25,8 +24,10 @@ import {
} from './studyList'; } from './studyList';
import { ToolbarSection } from './toolbarSection'; import { ToolbarSection } from './toolbarSection';
import { Tooltip } from './tooltip'; import { Tooltip } from './tooltip';
import { ErrorBoundary } from './errorBoundary';
export { export {
ErrorBoundary,
ContextMenu, ContextMenu,
Checkbox, Checkbox,
CineDialog, CineDialog,
@@ -127,7 +127,7 @@
.sb-message { .sb-message {
font-size: 14px; font-size: 14px;
word-break: break-all; word-break: normal;
} }
.sb-item { .sb-item {
+2
View File
@@ -29,6 +29,7 @@ import {
Tooltip, Tooltip,
AboutContent, AboutContent,
OHIFModal, OHIFModal,
ErrorBoundary
} from './components'; } from './components';
import { useDebounce, useMedia } from './hooks'; import { useDebounce, useMedia } from './hooks';
@@ -123,6 +124,7 @@ export {
DialogProvider, DialogProvider,
withDialog, withDialog,
useDialog, useDialog,
ErrorBoundary,
// Hooks // Hooks
useDebounce, useDebounce,
useMedia, useMedia,
+104
View File
@@ -3,6 +3,110 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [4.2.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.1.0...@ohif/viewer@4.2.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
# [4.1.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.0.0...@ohif/viewer@4.1.0) (2020-05-15)
### Features
* expose some app internals as window.app ([#1735](https://github.com/OHIF/Viewers/issues/1735)) ([63fd656](https://github.com/OHIF/Viewers/commit/63fd65690cba450721870a6222e0fb3ad71bb291))
# [4.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.11...@ohif/viewer@4.0.0) (2020-05-14)
### Bug Fixes
* 🐛 Fix race condition when loading derived display sets ([#1718](https://github.com/OHIF/Viewers/issues/1718)) ([b1678ce](https://github.com/OHIF/Viewers/commit/b1678ce6399dde37a9878f45ccc7c63286d93fab)), closes [#1715](https://github.com/OHIF/Viewers/issues/1715)
### BREAKING CHANGES
* 🧨 However we start to load once the first set of metadata arrives. We need
to wait until all series metadata is fetched.
## [3.11.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.10...@ohif/viewer@3.11.11) (2020-05-14)
### Bug Fixes
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
## [3.11.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.9...@ohif/viewer@3.11.10) (2020-05-13)
**Note:** Version bump only for package @ohif/viewer
## [3.11.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.8...@ohif/viewer@3.11.9) (2020-05-12)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [3.11.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.7...@ohif/viewer@3.11.8) (2020-05-06)
**Note:** Version bump only for package @ohif/viewer
## [3.11.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.6...@ohif/viewer@3.11.7) (2020-05-04)
**Note:** Version bump only for package @ohif/viewer
## [3.11.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.5...@ohif/viewer@3.11.6) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [3.11.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.4...@ohif/viewer@3.11.5) (2020-05-04)
**Note:** Version bump only for package @ohif/viewer
## [3.11.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.3...@ohif/viewer@3.11.4) (2020-05-04) ## [3.11.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.3...@ohif/viewer@3.11.4) (2020-05-04)
**Note:** Version bump only for package @ohif/viewer **Note:** Version bump only for package @ohif/viewer
@@ -22,7 +22,7 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2); expect($list.length).to.be.eq(3);
expect($list).to.contain('Juno'); expect($list).to.contain('Juno');
}); });
}); });
@@ -127,7 +127,7 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2); expect($list.length).to.be.eq(3);
expect($list).to.contain('Juno'); expect($list).to.contain('Juno');
}); });
}); });
@@ -137,7 +137,7 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2); expect($list.length).to.be.eq(6);
expect($list).to.contain('ProstateX-0000'); expect($list).to.contain('ProstateX-0000');
}); });
}); });
@@ -1,7 +1,7 @@
describe('OHIF Microscopy Extension', () => { describe('OHIF Microscopy Extension', () => {
before(() => { before(() => {
cy.openStudyModality('SM'); cy.openStudyModality('SM');
cy.expectMinimumThumbnails(6); cy.expectMinimumThumbnails(2);
}); });
it('checks if series thumbnails are being displayed', () => { it('checks if series thumbnails are being displayed', () => {
@@ -21,8 +21,8 @@ describe('OHIF VTK Extension', () => {
//Select 2D MPR button //Select 2D MPR button
cy.get('[data-cy="2d mpr"]').click(); cy.get('[data-cy="2d mpr"]').click();
//Wait Reformatting Images //Wait waitVTKLoading Images
cy.waitVTKReformatting(); cy.waitVTKLoading();
}); });
beforeEach(() => { beforeEach(() => {
@@ -18,8 +18,8 @@ describe('Visual Regression - OHIF VTK Extension', () => {
//Select 2D MPR button //Select 2D MPR button
cy.get('[data-cy="2d mpr"]').click(); cy.get('[data-cy="2d mpr"]').click();
//Wait Reformatting Images //Wait waitVTKLoading Images
cy.waitVTKReformatting(); cy.waitVTKwaitVTKLoading();
}); });
beforeEach(() => { beforeEach(() => {
+5 -5
View File
@@ -107,15 +107,15 @@ Cypress.Commands.add('waitStudyList', () => {
}); });
}); });
Cypress.Commands.add('waitVTKReformatting', () => { Cypress.Commands.add('waitVTKLoading', () => {
// Wait for start reformatting // Wait for start loading
cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => { cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => {
expect($grid).to.contain.text('Reform'); expect($grid).to.contain.text('Loading');
}); });
// Wait for finish reformatting // Wait for finish loading
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => { cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
expect($grid).not.to.contain.text('Reform'); expect($grid).not.to.contain.text('Loading');
}); });
}); });
+10 -10
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "3.11.4", "version": "4.2.0",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -48,24 +48,24 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.9.2", "@ohif/core": "^2.9.6",
"@ohif/extension-cornerstone": "^2.7.0", "@ohif/extension-cornerstone": "^2.8.0",
"@ohif/extension-dicom-html": "^1.2.1", "@ohif/extension-dicom-html": "^1.2.2",
"@ohif/extension-dicom-microscopy": "^0.50.9", "@ohif/extension-dicom-microscopy": "^0.51.0",
"@ohif/extension-dicom-p10-downloader": "^0.1.0", "@ohif/extension-dicom-p10-downloader": "^0.1.0",
"@ohif/extension-dicom-pdf": "^1.0.3", "@ohif/extension-dicom-pdf": "^1.0.3",
"@ohif/extension-dicom-rt": "^0.1.1", "@ohif/extension-dicom-rt": "^0.2.0",
"@ohif/extension-dicom-segmentation": "^0.2.2", "@ohif/extension-dicom-segmentation": "^0.3.0",
"@ohif/extension-lesion-tracker": "^0.2.0", "@ohif/extension-lesion-tracker": "^0.2.0",
"@ohif/extension-vtk": "^1.5.2", "@ohif/extension-vtk": "^1.6.0",
"@ohif/i18n": "^0.52.8", "@ohif/i18n": "^0.52.8",
"@ohif/ui": "^1.4.3", "@ohif/ui": "^1.5.0",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.12.5", "cornerstone-tools": "4.15.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "^0.12.2", "dcmjs": "^0.12.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
+1
View File
@@ -15,6 +15,7 @@ window.config = {
imageRendering: 'wadors', imageRendering: 'wadors',
thumbnailRendering: 'wadors', thumbnailRendering: 'wadors',
enableStudyLazyLoad: true, enableStudyLazyLoad: true,
supportsFuzzyMatching: true,
}, },
], ],
}, },
+1
View File
@@ -12,6 +12,7 @@ window.config = {
imageRendering: 'wadors', imageRendering: 'wadors',
thumbnailRendering: 'wadors', thumbnailRendering: 'wadors',
enableStudyLazyLoad: true, enableStudyLazyLoad: true,
supportsFuzzyMatching: true,
}, },
], ],
}, },
@@ -11,6 +11,7 @@ window.config = {
qidoSupportsIncludeField: true, qidoSupportsIncludeField: true,
imageRendering: 'wadors', imageRendering: 'wadors',
thumbnailRendering: 'wadors', thumbnailRendering: 'wadors',
supportsFuzzyMatching: true,
}, },
], ],
}, },
+59 -40
View File
@@ -13,6 +13,7 @@ import {
ModalProvider, ModalProvider,
DialogProvider, DialogProvider,
OHIFModal, OHIFModal,
ErrorBoundary
} from '@ohif/ui'; } from '@ohif/ui';
import { import {
@@ -53,7 +54,7 @@ import store from './store';
/** Contexts */ /** Contexts */
import WhiteLabelingContext from './context/WhiteLabelingContext'; import WhiteLabelingContext from './context/WhiteLabelingContext';
import UserManagerContext from './context/UserManagerContext'; import UserManagerContext from './context/UserManagerContext';
import AppContext from './context/AppContext'; import { AppProvider, useAppContext, CONTEXTS } from './context/AppContext';
/** ~~~~~~~~~~~~~ Application Setup */ /** ~~~~~~~~~~~~~ Application Setup */
const commandsManagerConfig = { const commandsManagerConfig = {
@@ -71,6 +72,14 @@ let extensionManager;
// TODO[react] Use a provider when the whole tree is React // TODO[react] Use a provider when the whole tree is React
window.store = store; window.store = store;
window.ohif = window.ohif || {};
window.ohif.app = {
commandsManager,
hotkeysManager,
servicesManager,
extensionManager,
};
class App extends Component { class App extends Component {
static propTypes = { static propTypes = {
config: PropTypes.oneOfType([ config: PropTypes.oneOfType([
@@ -159,53 +168,57 @@ class App extends Component {
if (this._userManager) { if (this._userManager) {
return ( return (
<AppContext.Provider value={{ appConfig: this._appConfig }}> <ErrorBoundary context='App'>
<Provider store={store}> <Provider store={store}>
<I18nextProvider i18n={i18n}> <AppProvider config={this._appConfig}>
<OidcProvider store={store} userManager={this._userManager}> <I18nextProvider i18n={i18n}>
<UserManagerContext.Provider value={this._userManager}> <OidcProvider store={store} userManager={this._userManager}>
<Router basename={routerBasename}> <UserManagerContext.Provider value={this._userManager}>
<WhiteLabelingContext.Provider value={whiteLabeling}> <Router basename={routerBasename}>
<SnackbarProvider service={UINotificationService}> <WhiteLabelingContext.Provider value={whiteLabeling}>
<DialogProvider service={UIDialogService}> <SnackbarProvider service={UINotificationService}>
<ModalProvider <DialogProvider service={UIDialogService}>
modal={OHIFModal} <ModalProvider
service={UIModalService} modal={OHIFModal}
> service={UIModalService}
<OHIFStandaloneViewer >
userManager={this._userManager} <OHIFStandaloneViewer
/> userManager={this._userManager}
</ModalProvider> />
</DialogProvider> </ModalProvider>
</SnackbarProvider> </DialogProvider>
</WhiteLabelingContext.Provider> </SnackbarProvider>
</Router> </WhiteLabelingContext.Provider>
</UserManagerContext.Provider> </Router>
</OidcProvider> </UserManagerContext.Provider>
</I18nextProvider> </OidcProvider>
</I18nextProvider>
</AppProvider>
</Provider> </Provider>
</AppContext.Provider> </ErrorBoundary>
); );
} }
return ( return (
<AppContext.Provider value={{ appConfig: this._appConfig }}> <ErrorBoundary context='App'>
<Provider store={store}> <Provider store={store}>
<I18nextProvider i18n={i18n}> <AppProvider config={this._appConfig}>
<Router basename={routerBasename}> <I18nextProvider i18n={i18n}>
<WhiteLabelingContext.Provider value={whiteLabeling}> <Router basename={routerBasename}>
<SnackbarProvider service={UINotificationService}> <WhiteLabelingContext.Provider value={whiteLabeling}>
<DialogProvider service={UIDialogService}> <SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal} service={UIModalService}> <DialogProvider service={UIDialogService}>
<OHIFStandaloneViewer /> <ModalProvider modal={OHIFModal} service={UIModalService}>
</ModalProvider> <OHIFStandaloneViewer />
</DialogProvider> </ModalProvider>
</SnackbarProvider> </DialogProvider>
</WhiteLabelingContext.Provider> </SnackbarProvider>
</Router> </WhiteLabelingContext.Provider>
</I18nextProvider> </Router>
</I18nextProvider>
</AppProvider>
</Provider> </Provider>
</AppContext.Provider> </ErrorBoundary>
); );
} }
@@ -255,6 +268,12 @@ function _initExtensions(extensions, cornerstoneExtensionConfig, appConfig) {
commandsManager, commandsManager,
servicesManager, servicesManager,
appConfig, appConfig,
api: {
contexts: CONTEXTS,
hooks: {
useAppContext
}
}
}); });
const requiredExtensions = [ const requiredExtensions = [
+5 -3
View File
@@ -5,7 +5,7 @@ import { Route, Switch } from 'react-router-dom';
import { NProgress } from '@tanem/react-nprogress'; import { NProgress } from '@tanem/react-nprogress';
import { CSSTransition } from 'react-transition-group'; import { CSSTransition } from 'react-transition-group';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ViewerbaseDragDropContext } from '@ohif/ui'; import { ViewerbaseDragDropContext, ErrorBoundary } from '@ohif/ui';
import { SignoutCallbackComponent } from 'redux-oidc'; import { SignoutCallbackComponent } from 'redux-oidc';
import asyncComponent from './components/AsyncComponent.js'; import asyncComponent from './components/AsyncComponent.js';
import * as RoutesUtil from './routes/routesUtil'; import * as RoutesUtil from './routes/routesUtil';
@@ -191,8 +191,10 @@ class OHIFStandaloneViewer extends Component {
{match === null ? ( {match === null ? (
<></> <></>
) : ( ) : (
<Component match={match} location={this.props.location} /> <ErrorBoundary context={match.url}>
)} <Component match={match} location={this.props.location} />
</ErrorBoundary>
)}
</CSSTransition> </CSSTransition>
)} )}
</Route> </Route>
@@ -0,0 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ErrorBoundary } from '@ohif/ui';
import { servicesManager } from './../../App';
const { UIModalService } = servicesManager.services;
const ErrorBoundaryDialog = ({ context, children }) => {
const handleOnError = (error, componentStack) => {
const ErrorDialog = () => (
<div className="ErrorFallback" role="alert">
<div>
<h3>
{context}: <span>{error.message}</span>
</h3>
</div>
<pre>{componentStack}</pre>
</div>
);
UIModalService.show({
content: ErrorDialog,
title: `${context}: ${error.message}`,
});
};
const fallbackComponent = () => (
<div className="ErrorFallback" role="alert">
<p>
Error rendering {context}. <br /> Check the browser console for more
details.
</p>
</div>
);
return (
<ErrorBoundary
fallbackComponent={fallbackComponent}
context={context}
onError={handleOnError}
>
{children}
</ErrorBoundary>
);
};
ErrorBoundaryDialog.propTypes = {
context: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
};
export default ErrorBoundaryDialog;
@@ -0,0 +1,3 @@
import ErrorBoundaryDialog from './ErrorBoundaryDialog';
export default ErrorBoundaryDialog;
@@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { utils } from '@ohif/core'; import { utils } from '@ohif/core';
import { useSnackbarContext } from '@ohif/ui';
// //
import ViewportPane from './ViewportPane.js'; import ViewportPane from './ViewportPane.js';
import DefaultViewport from './DefaultViewport.js'; import DefaultViewport from './DefaultViewport.js';
@@ -11,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils; const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function (props) { const ViewportGrid = function(props) {
const { const {
activeViewportIndex, activeViewportIndex,
availablePlugins, availablePlugins,
@@ -23,6 +24,7 @@ const ViewportGrid = function (props) {
studies, studies,
viewportData, viewportData,
children, children,
isStudyLoaded,
} = props; } = props;
const rowSize = 100 / numRows; const rowSize = 100 / numRows;
@@ -33,11 +35,26 @@ const ViewportGrid = function (props) {
return null; return null;
} }
const snackbar = useSnackbarContext();
useEffect(() => { useEffect(() => {
viewportData.forEach(displaySet => { if (isStudyLoaded) {
loadAndCacheDerivedDisplaySets(displaySet, studies); viewportData.forEach(displaySet => {
}); const promises = loadAndCacheDerivedDisplaySets(displaySet, studies);
}, [studies, viewportData]);
promises.forEach(promise => {
promise.catch(error => {
snackbar.show({
title: 'Error loading derived display set:',
message: error.message,
type: 'error',
autoClose: false,
});
});
});
});
}
}, [studies, viewportData, isStudyLoaded, snackbar]);
const getViewportPanes = () => const getViewportPanes = () =>
layout.viewports.map((layout, viewportIndex) => { layout.viewports.map((layout, viewportIndex) => {
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import './ViewportPane.css'; import './ViewportPane.css';
const ViewportPane = function(props) { const ViewportPane = function (props) {
const { children, onDrop, viewportIndex, className: propClassName } = props; const { children, onDrop, viewportIndex, className: propClassName } = props;
const [{ hovered, highlighted }, drop] = useDrop({ const [{ hovered, highlighted }, drop] = useDrop({
accept: 'thumbnail', accept: 'thumbnail',
@@ -1,15 +0,0 @@
// TODO: REPLACE THIS WITH A CONTEXT PROVIDER
// EVERYTHING IN `VIEWER.JS` COULD USE THIS FOR APPROPRIATE CONTEXT
import ToolbarRow from './ToolbarRow';
import { connect } from 'react-redux';
import { getActiveContexts } from './../store/layout/selectors.js';
const mapStateToProps = state => {
return {
activeContexts: getActiveContexts(state),
};
};
const ConnectedToolbarRow = connect(mapStateToProps)(ToolbarRow);
export default ConnectedToolbarRow;
@@ -16,6 +16,7 @@ import { commandsManager, extensionManager } from './../App.js';
import ConnectedCineDialog from './ConnectedCineDialog'; import ConnectedCineDialog from './ConnectedCineDialog';
import ConnectedLayoutButton from './ConnectedLayoutButton'; import ConnectedLayoutButton from './ConnectedLayoutButton';
import { withAppContext } from '../context/AppContext';
class ToolbarRow extends Component { class ToolbarRow extends Component {
// TODO: Simplify these? isOpen can be computed if we say "any" value for selected, // TODO: Simplify these? isOpen can be computed if we say "any" value for selected,
@@ -381,5 +382,5 @@ function _handleBuiltIn(button) {
} }
export default withTranslation(['Common', 'ViewportDownloadForm'])( export default withTranslation(['Common', 'ViewportDownloadForm'])(
withModal(withDialog(ToolbarRow)) withModal(withDialog(withAppContext(ToolbarRow)))
); );
@@ -2,15 +2,15 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { MODULE_TYPES } from '@ohif/core'; import OHIF, { MODULE_TYPES, DICOMSR } from '@ohif/core';
import OHIF, { DICOMSR } from '@ohif/core';
import { withDialog } from '@ohif/ui'; import { withDialog } from '@ohif/ui';
import moment from 'moment'; import moment from 'moment';
import ConnectedHeader from './ConnectedHeader.js'; import ConnectedHeader from './ConnectedHeader.js';
import ConnectedToolbarRow from './ConnectedToolbarRow.js'; import ToolbarRow from './ToolbarRow.js';
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js'; import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
import ConnectedViewerMain from './ConnectedViewerMain.js'; import ConnectedViewerMain from './ConnectedViewerMain.js';
import SidePanel from './../components/SidePanel.js'; import SidePanel from './../components/SidePanel.js';
import ErrorBoundaryDialog from './../components/ErrorBoundaryDialog';
import { extensionManager } from './../App.js'; import { extensionManager } from './../App.js';
// Contexts // Contexts
@@ -26,6 +26,7 @@ class Viewer extends Component {
PropTypes.shape({ PropTypes.shape({
StudyInstanceUID: PropTypes.string.isRequired, StudyInstanceUID: PropTypes.string.isRequired,
StudyDate: PropTypes.string, StudyDate: PropTypes.string,
PatientID: PropTypes.string,
displaySets: PropTypes.arrayOf( displaySets: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
displaySetInstanceUID: PropTypes.string.isRequired, displaySetInstanceUID: PropTypes.string.isRequired,
@@ -256,42 +257,44 @@ class Viewer extends Component {
</WhiteLabelingContext.Consumer> </WhiteLabelingContext.Consumer>
{/* TOOLBAR */} {/* TOOLBAR */}
<ConnectedToolbarRow <ErrorBoundaryDialog context="ToolbarRow">
isLeftSidePanelOpen={this.state.isLeftSidePanelOpen} <ToolbarRow
isRightSidePanelOpen={this.state.isRightSidePanelOpen} isLeftSidePanelOpen={this.state.isLeftSidePanelOpen}
selectedLeftSidePanel={ isRightSidePanelOpen={this.state.isRightSidePanelOpen}
this.state.isLeftSidePanelOpen selectedLeftSidePanel={
? this.state.selectedLeftSidePanel this.state.isLeftSidePanelOpen
: '' ? this.state.selectedLeftSidePanel
} : ''
selectedRightSidePanel={
this.state.isRightSidePanelOpen
? this.state.selectedRightSidePanel
: ''
}
handleSidePanelChange={(side, selectedPanel) => {
const sideClicked = side && side[0].toUpperCase() + side.slice(1);
const openKey = `is${sideClicked}SidePanelOpen`;
const selectedKey = `selected${sideClicked}SidePanel`;
const updatedState = Object.assign({}, this.state);
const isOpen = updatedState[openKey];
const prevSelectedPanel = updatedState[selectedKey];
// RoundedButtonGroup returns `null` if selected button is clicked
const isSameSelectedPanel =
prevSelectedPanel === selectedPanel || selectedPanel === null;
updatedState[selectedKey] = selectedPanel || prevSelectedPanel;
const isClosedOrShouldClose = !isOpen || isSameSelectedPanel;
if (isClosedOrShouldClose) {
updatedState[openKey] = !updatedState[openKey];
} }
selectedRightSidePanel={
this.state.isRightSidePanelOpen
? this.state.selectedRightSidePanel
: ''
}
handleSidePanelChange={(side, selectedPanel) => {
const sideClicked = side && side[0].toUpperCase() + side.slice(1);
const openKey = `is${sideClicked}SidePanelOpen`;
const selectedKey = `selected${sideClicked}SidePanel`;
const updatedState = Object.assign({}, this.state);
this.setState(updatedState); const isOpen = updatedState[openKey];
}} const prevSelectedPanel = updatedState[selectedKey];
studies={this.props.studies} // RoundedButtonGroup returns `null` if selected button is clicked
/> const isSameSelectedPanel =
prevSelectedPanel === selectedPanel || selectedPanel === null;
updatedState[selectedKey] = selectedPanel || prevSelectedPanel;
const isClosedOrShouldClose = !isOpen || isSameSelectedPanel;
if (isClosedOrShouldClose) {
updatedState[openKey] = !updatedState[openKey];
}
this.setState(updatedState);
}}
studies={this.props.studies}
/>
</ErrorBoundaryDialog>
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/} {/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
{/*<StudyPrefetcher studies={this.props.studies} />*/} {/*<StudyPrefetcher studies={this.props.studies} />*/}
@@ -299,37 +302,46 @@ class Viewer extends Component {
{/* VIEWPORTS + SIDEPANELS */} {/* VIEWPORTS + SIDEPANELS */}
<div className="FlexboxLayout"> <div className="FlexboxLayout">
{/* LEFT */} {/* LEFT */}
<SidePanel from="left" isOpen={this.state.isLeftSidePanelOpen}> <ErrorBoundaryDialog context="LeftSidePanel">
{VisiblePanelLeft ? ( <SidePanel from="left" isOpen={this.state.isLeftSidePanelOpen}>
<VisiblePanelLeft {VisiblePanelLeft ? (
viewports={this.props.viewports} <VisiblePanelLeft
studies={this.props.studies} viewports={this.props.viewports}
activeIndex={this.props.activeViewportIndex} studies={this.props.studies}
/> activeIndex={this.props.activeViewportIndex}
) : ( />
<ConnectedStudyBrowser ) : (
studies={this.state.thumbnails} <ConnectedStudyBrowser
studyMetadata={this.props.studies} studies={this.state.thumbnails}
/> studyMetadata={this.props.studies}
)} />
</SidePanel> )}
</SidePanel>
</ErrorBoundaryDialog>
{/* MAIN */} {/* MAIN */}
<div className={classNames('main-content')}> <div className={classNames('main-content')}>
<ConnectedViewerMain studies={this.props.studies} /> <ErrorBoundaryDialog context="ViewerMain">
<ConnectedViewerMain
studies={this.props.studies}
isStudyLoaded={this.props.isStudyLoaded}
/>
</ErrorBoundaryDialog>
</div> </div>
{/* RIGHT */} {/* RIGHT */}
<SidePanel from="right" isOpen={this.state.isRightSidePanelOpen}> <ErrorBoundaryDialog context="RightSidePanel">
{VisiblePanelRight && ( <SidePanel from="right" isOpen={this.state.isRightSidePanelOpen}>
<VisiblePanelRight {VisiblePanelRight && (
isOpen={this.state.isRightSidePanelOpen} <VisiblePanelRight
viewports={this.props.viewports} isOpen={this.state.isRightSidePanelOpen}
studies={this.props.studies} viewports={this.props.viewports}
activeIndex={this.props.activeViewportIndex} studies={this.props.studies}
/> activeIndex={this.props.activeViewportIndex}
)} />
</SidePanel> )}
</SidePanel>
</ErrorBoundaryDialog>
</div> </div>
</> </>
); );
@@ -150,6 +150,7 @@ class ViewerMain extends Component {
<div className="ViewerMain"> <div className="ViewerMain">
{this.state.displaySets.length && ( {this.state.displaySets.length && (
<ConnectedViewportGrid <ConnectedViewportGrid
isStudyLoaded={this.props.isStudyLoaded}
studies={this.props.studies} studies={this.props.studies}
viewportData={viewportData} viewportData={viewportData}
setViewportData={this.setViewportData} setViewportData={this.setViewportData}
@@ -105,7 +105,7 @@ const _showUserMessage = (queryParamApplied, message, dialog = {}) => {
return; return;
} }
const { show: showUserMessage = () => {} } = dialog; const { show: showUserMessage = () => { } } = dialog;
showUserMessage({ showUserMessage({
message, message,
}); });
@@ -269,7 +269,8 @@ function ViewerRetrieveStudyData({
const promises = Array(concurrentRequestsAllowed) const promises = Array(concurrentRequestsAllowed)
.fill(null) .fill(null)
.map(loadNextSeries); .map(loadNextSeries);
await Promise.all(promises);
return await Promise.all(promises);
}; };
const loadStudies = async () => { const loadStudies = async () => {
+29 -1
View File
@@ -1,5 +1,33 @@
import React from 'react'; import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import { getActiveContexts } from '../store/layout/selectors.js';
let AppContext = React.createContext({}); let AppContext = React.createContext({});
export const CONTEXTS = {
CORNERSTONE: 'ACTIVE_VIEWPORT::CORNERSTONE',
VTK: 'ACTIVE_VIEWPORT::VTK'
};
export const useAppContext = () => useContext(AppContext);
export const AppProvider = ({ children, config }) => {
const activeContexts = useSelector(state => getActiveContexts(state));
return (
<AppContext.Provider value={{ appConfig: config, activeContexts }}>
{children}
</AppContext.Provider>
);
};
export const withAppContext = Component => {
return function WrappedComponent(props) {
const { appConfig, activeContexts } = useAppContext();
return (
<Component {...props} appConfig={appConfig} activeContexts={activeContexts} />
);
};
};
export default AppContext; export default AppContext;
+25 -13
View File
@@ -1247,7 +1247,7 @@
pirates "^4.0.0" pirates "^4.0.0"
source-map-support "^0.5.9" source-map-support "^0.5.9"
"@babel/runtime@7.1.2", "@babel/runtime@7.5.5", "@babel/runtime@7.6.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": "@babel/runtime@7.1.2", "@babel/runtime@7.5.5", "@babel/runtime@7.6.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.6":
version "7.5.5" version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
@@ -5945,10 +5945,10 @@ cornerstone-math@^0.1.8:
resolved "https://registry.yarnpkg.com/cornerstone-math/-/cornerstone-math-0.1.8.tgz#68ab1f9e4fdcd7c5cb23a0d2eb4263f9f894f1c5" resolved "https://registry.yarnpkg.com/cornerstone-math/-/cornerstone-math-0.1.8.tgz#68ab1f9e4fdcd7c5cb23a0d2eb4263f9f894f1c5"
integrity sha512-x7NEQHBtVG7j1yeyj/aRoKTpXv1Vh2/H9zNLMyqYJDtJkNng8C4Q8M3CgZ1qer0Yr7eVq2x+Ynmj6kfOm5jXKw== integrity sha512-x7NEQHBtVG7j1yeyj/aRoKTpXv1Vh2/H9zNLMyqYJDtJkNng8C4Q8M3CgZ1qer0Yr7eVq2x+Ynmj6kfOm5jXKw==
cornerstone-tools@4.12.5: cornerstone-tools@4.15.1:
version "4.12.5" version "4.15.1"
resolved "https://registry.yarnpkg.com/cornerstone-tools/-/cornerstone-tools-4.12.5.tgz#9a4a35d9851d6c81f165977aa6ede8cf228d4c80" resolved "https://registry.yarnpkg.com/cornerstone-tools/-/cornerstone-tools-4.15.1.tgz#f0b1026da9c7758defc088bb2f1e78426a23d91e"
integrity sha512-tNcIQ9E2+GbfZDbbPnWmocYPi0OALlYLKQbXa3gc7rdnF8K9DsptnpGzMtiKfoWUjYfsUWrytvI/RgusXRWCTg== integrity sha512-fJuTUJW/NDSD520jPB++tX//Kq80jPDngChHZrhx2xjj/9dCnexD9kmTX30Z0WUq0a2JEHZ6FlcOX38kSmw1hQ==
dependencies: dependencies:
"@babel/runtime" "7.1.2" "@babel/runtime" "7.1.2"
cornerstone-math "0.1.7" cornerstone-math "0.1.7"
@@ -9110,6 +9110,11 @@ gl-matrix@^3.1.0:
resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.1.0.tgz#f5b2de17d8fed95a79e5025b10cded0ab9ccbed0" resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.1.0.tgz#f5b2de17d8fed95a79e5025b10cded0ab9ccbed0"
integrity sha512-526NA+3EA+ztAQi0IZpSWiM0fyQXIp7IbRvfJ4wS/TjjQD0uv0fVybXwwqqSOlq33UckivI0yMDlVtboWm3k7A== integrity sha512-526NA+3EA+ztAQi0IZpSWiM0fyQXIp7IbRvfJ4wS/TjjQD0uv0fVybXwwqqSOlq33UckivI0yMDlVtboWm3k7A==
gl-matrix@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b"
integrity sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA==
gl-preserve-state@^1.0.0: gl-preserve-state@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/gl-preserve-state/-/gl-preserve-state-1.0.0.tgz#4ef710d62873f1470ed015c6546c37dacddd4198" resolved "https://registry.yarnpkg.com/gl-preserve-state/-/gl-preserve-state-1.0.0.tgz#4ef710d62873f1470ed015c6546c37dacddd4198"
@@ -15598,10 +15603,10 @@ react-codemirror2@^6.0.0:
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-6.0.0.tgz#180065df57a64026026cde569a9708fdf7656525" resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-6.0.0.tgz#180065df57a64026026cde569a9708fdf7656525"
integrity sha512-D7y9qZ05FbUh9blqECaJMdDwKluQiO3A9xB+fssd5jKM7YAXucRuEOlX32mJQumUvHUkHRHqXIPBjm6g0FW0Ag== integrity sha512-D7y9qZ05FbUh9blqECaJMdDwKluQiO3A9xB+fssd5jKM7YAXucRuEOlX32mJQumUvHUkHRHqXIPBjm6g0FW0Ag==
react-cornerstone-viewport@^2.3.6: react-cornerstone-viewport@2.3.8:
version "2.3.6" version "2.3.8"
resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-2.3.6.tgz#1c76bd37c95284254767296952845bacd3b810a4" resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-2.3.8.tgz#7af8360f29bca986ae4e36b4e503269b88ddc52f"
integrity sha512-MBs1Mgl0g0YswkhgoaYlaPoPDVs/HZQ3Z89thHxVytvJsgnQvhc3YrR48iRaUfQj0eYoazh7AL/lLCfd8veFKA== integrity sha512-aiG2uVNrDY6SQx4t/HBxIA3zsMsCwT+6TpcXK9qSSoXhs+X6OTmYEKncWUqL0jtxU1yfh6JTUz8ARTg03gtF+A==
dependencies: dependencies:
classnames "^2.2.6" classnames "^2.2.6"
date-fns "^2.2.1" date-fns "^2.2.1"
@@ -15791,6 +15796,13 @@ react-dropzone@^10.1.7:
file-selector "^0.1.11" file-selector "^0.1.11"
prop-types "^15.7.2" prop-types "^15.7.2"
react-error-boundary@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-2.2.1.tgz#32ed74386a90482060cc2fea948bb7135465c4cb"
integrity sha512-8SZMkJRFUb0JuluHKwuUtkh5vvVWBg3O/bJIWgNMhMJv529aG//TmcphR3cSMhCWjz1vFZDb4taJd6pmwP5mEQ==
dependencies:
"@babel/runtime" "^7.9.6"
react-error-overlay@^4.0.1: react-error-overlay@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89"
@@ -16024,10 +16036,10 @@ react-transition-group@^4.1.1:
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.6.2" prop-types "^15.6.2"
react-vtkjs-viewport@^0.8.3: react-vtkjs-viewport@^0.9.0:
version "0.8.4" version "0.9.0"
resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.8.4.tgz#6bd9837d7762b845e77f053fb8fbf4469428d594" resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.9.0.tgz#5a7890643e511946f960db6b05142318be2dfe80"
integrity sha512-b90ENJzmStiahxVox+7mMwZij3NVUu//jE07PV2Qtp6E7q/eLXn6ZUwcEt9w5YHN36xLZVcx9b53ztLcXVgh/Q== integrity sha512-kjpNr1n+eW0nU736HH07IsSYQXphxfn/RBvKtpBer9tNsl+FyLH8h+uY8KWdv8kVBHkEoaH6/srxB0JBa8l+jA==
dependencies: dependencies:
date-fns "^2.2.1" date-fns "^2.2.1"
gl-matrix "^3.1.0" gl-matrix "^3.1.0"