feat(extensions): Toolbar module depends on plugin in active viewport

This commit is contained in:
Erik Ziegler 2019-02-24 15:26:50 +01:00
parent 2fdc1687d0
commit f111411046
47 changed files with 10240 additions and 72 deletions

5
.gitmodules vendored
View File

@ -6,4 +6,7 @@
url = https://github.com/OHIF/react-viewerbase.git
[submodule "Packages-react/react-cornerstone-viewport"]
path = Packages-react/react-cornerstone-viewport
url = https://github.com/cornerstonejs/react-cornerstone-viewport.git
url = https://github.com/cornerstonejs/react-cornerstone-viewport.git
[submodule "Packages-react/react-vtkjs-viewport"]
path = Packages-react/react-vtkjs-viewport
url = https://github.com/OHIF/react-vtkjs-viewport.git

View File

@ -22,4 +22,5 @@ yarn-debug.log*
yarn-error.log*
.idea
yalc.lock
yalc.lock
.yalc

View File

@ -1,6 +1,6 @@
{
"name": "ohif-cornerstone-extension",
"version": "0.0.17",
"version": "0.0.20",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@ -26,12 +26,13 @@
"dcmjs": "^0.3.3",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"ohif-core": "^0.3.0",
"ohif-core": "^0.3.3",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0",
"react-redux": "^6.0.0",
"react-resize-detector": "^3.4.0",
"react-viewerbase": "^0.2.10",
"redux": "^4.0.1"
},
"devDependencies": {
@ -51,6 +52,7 @@
"gh-pages": "^2.0.1",
"husky": "^1.3.1",
"lint-staged": "^8.1.0",
"lodash.clonedeep": "^4.5.0",
"prettier": "^1.15.3",
"react": "^16.6.3",
"react-dom": "^16.6.3",
@ -90,6 +92,6 @@
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.6",
"lodash.throttle": "^4.1.1",
"react-cornerstone-viewport": "^0.1.16"
"react-cornerstone-viewport": "^0.1.20"
}
}

View File

@ -0,0 +1,29 @@
import { connect } from 'react-redux';
import { ToolbarSection } from 'react-viewerbase';
import OHIF from 'ohif-core'
const { setToolActive } = OHIF.redux.actions;
const mapStateToProps = state => {
const activeButton = state.tools.buttons.find(tool => tool.active === true);
return {
buttons: state.tools.buttons,
activeCommand: activeButton && activeButton.command
};
};
const mapDispatchToProps = dispatch => {
return {
setToolActive: tool => {
dispatch(setToolActive(tool.command))
}
};
};
const ConnectedToolbarSection = connect(
mapStateToProps,
mapDispatchToProps
)(ToolbarSection);
export default ConnectedToolbarSection;

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import OHIFCornerstoneViewport from './OHIFCornerstoneViewport.js';
import ToolbarModule from './ToolbarModule.js';
/**
* Pass in 'children' to a React component. The purpose of this is to
@ -48,6 +49,6 @@ export default class OHIFCornerstoneExtension {
}
getToolbarModule() {
return null;
return ToolbarModule;
}
}

View File

@ -0,0 +1,39 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ConnectedToolbarSection from './ConnectedToolbarSection';
import { ToolbarButton } from 'react-viewerbase';
import ConnectedCineDialog from './ConnectedCineDialog';
class ToolbarModule extends Component {
state = {
cineDialogOpen: false
}
onClickCineToolbarButton = () => {
this.setState({
cineDialogOpen: !this.state.cineDialogOpen
});
};
render() {
const cineDialogContainerStyle = {
display: this.state.cineDialogOpen ? 'inline-block' : 'none'
};
return (<div className="ToolbarModule">
<ConnectedToolbarSection />
<ToolbarButton
active={this.state.cineDialogOpen}
onClick={this.onClickCineToolbarButton}
text={'CINE'}
iconClasses={'fab fa-youtube'}
/>
<div className="CineDialogContainer" style={cineDialogContainerStyle}>
<ConnectedCineDialog />
</div>
</div>
);
}
}
export default ToolbarModule;

View File

@ -3248,6 +3248,11 @@ lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@ -4413,10 +4418,10 @@ randomfill@^1.0.3:
randombytes "^2.0.5"
safe-buffer "^5.1.0"
react-cornerstone-viewport@^0.1.16:
version "0.1.16"
resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-0.1.16.tgz#b1db13a3160ee10ffa9bfd91a6a0247f008170e2"
integrity sha512-hMEWoA3oDuurJ0AWZDH/hw1jPD7yY3EbaguE0CFusXdHsclCa7GjZXZb9EPqiAw8bF8vBIty7s96dwkqSFSLdw==
react-cornerstone-viewport@^0.1.20:
version "0.1.20"
resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-0.1.20.tgz#90e369a48e3f47dd5da69d42f0c8ee7e87e81543"
integrity sha512-SbaSjCAVFpQoUUXZ6d5F5QwKrP66zRJ4mN7JXPGyiL6+/m0jGkaBVci6pftkth66Lu5HRlCZUS8nh0plONPUtA==
dependencies:
lodash.debounce "^4.0.8"
moment "^2.23.0"

View File

@ -1,6 +1,6 @@
{
"name": "ohif-dicom-pdf-extension",
"version": "0.0.5",
"version": "0.0.6",
"description": "OHIF extension for Dicom PDF",
"author": "OHIF",
"license": "MIT",
@ -20,7 +20,7 @@
},
"peerDependencies": {
"dicom-parser": "^1.8.3",
"ohif-core": "^0.2.6",
"ohif-core": "^0.3.4",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"

View File

@ -17,11 +17,11 @@ export default class OHIFDicomPDFExtension {
return OHIFDicomPDFSopClassHandler;
}
getPanelModuleDefinition() {
getPanelModule() {
return null;
}
getToolbarModuleDefinition() {
getToolbarModule() {
return null;
}
}

View File

@ -3173,11 +3173,6 @@ detect-port-alt@1.1.6:
address "^1.0.1"
debug "^2.6.0"
dicom-parser@^1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/dicom-parser/-/dicom-parser-1.8.3.tgz#6e4b862898112304db30143147562011c1ce6a4d"
integrity sha512-CMeUr+jea7Ml70N+/Z5Pd2MYtvLp6IU+TnvdLe6VRVKzZuTeYLYyuAQa9R+sFK4v4N39hig+hKHN+Wfi9sQ6GA==
diff@^3.2.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"

View File

@ -0,0 +1,14 @@
{
"presets": [
["@babel/preset-env", {
"targets": {
"ie": "11"
}
}],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime"
]
}

View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -0,0 +1,16 @@
{
"extends": [
"react-app",
"eslint:recommended",
"plugin:react/recommended"
],
"parser": "babel-eslint",
"env": {
"jest": true
},
"settings": {
"react": {
"version": "detect",
},
},
}

View File

@ -0,0 +1,25 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# builds
build
dist
.rpt2_cache
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
yalc.lock

View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@ -0,0 +1,4 @@
language: node_js
node_js:
- 9
- 8

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,31 @@
# react-vtkjs-viewport
> VTK.js image viewport component for React
[![NPM](https://img.shields.io/npm/v/react-vtkjs-viewport.svg)](https://www.npmjs.com/package/react-vtkjs-viewport)
## Install
```bash
npm install --save react-vtkjs-viewport
```
## Usage
```jsx
import React, { Component } from 'react'
import CornerstoneViewport from 'react-cornerstone-viewport'
class Example extends Component {
render () {
return (
<CornerstoneViewport />
)
}
}
```
## License
MIT © [OHIF](https://github.com/OHIF)

View File

@ -0,0 +1,55 @@
{
"name": "react-vtkjs-viewport",
"version": "0.0.6-76b678ec",
"description": "VTK.js image viewport component for React",
"author": "OHIF Contributors",
"license": "MIT",
"repository": "OHIF/react-vtkjs-viewport",
"main": "dist/index.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"build": "webpack --progress --colors --mode development",
"build:release": "webpack --progress --colors --mode production",
"start": "webpack --watch --progress --colors --mode development",
"prepare": "npm run build:release",
"predeploy": "cd example && npm install && npm run build:release",
"prepublishOnly": "npm run build:release",
"deploy": "gh-pages -d example/build",
"generateStaticSite": "./generateStaticSite.sh"
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,jsx,json,css}": [
"prettier --single-quote --write",
"git add"
]
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"files": [
"dist"
],
"dependencies": {
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8",
"vtk.js": "^8.3.11"
}
}

View File

@ -0,0 +1 @@
76b678ec6a9c889ca68231a62fd71346

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,95 @@
{
"name": "ohif-vtk-extension",
"version": "0.0.1",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "dist/index.es.js",
"jsnext:main": "dist/index.es.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build",
"start": "rollup -c -w",
"lint": "eslint -c .eslintrc --fix src && prettier --single-quote --write src/**/*.{js,jsx,json,css}"
},
"peerDependencies": {
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "^3.1.0",
"cornerstone-wado-image-loader": "^2.2.3",
"dcmjs": "^0.3.6",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"ohif-core": "^0.3.3",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0",
"react-redux": "^6.0.0",
"react-resize-detector": "^3.4.0",
"redux": "^4.0.1"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^10.0.1",
"cross-env": "^5.2.0",
"eslint": "5.13.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-react": "^7.11.1",
"gh-pages": "^2.0.1",
"husky": "^1.3.1",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-viewerbase": "^0.2.10",
"rollup": "^1.1.2",
"rollup-plugin-babel": "^4.2.0",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-peer-deps-external": "^2.2.0",
"rollup-plugin-postcss": "^2.0.3",
"rollup-plugin-url": "^2.1.0",
"stylelint": "^9.9.0",
"stylelint-config-recommended": "^2.1.0",
"stylus": "^0.54.5"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,jsx,json,css}": [
"prettier --single-quote --write",
"git add"
]
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"files": [
"dist"
],
"dependencies": {
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.6",
"react-vtkjs-viewport": "file:.yalc/react-vtkjs-viewport"
}
}

View File

@ -0,0 +1,70 @@
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import external from 'rollup-plugin-peer-deps-external'
import postcss from 'rollup-plugin-postcss'
import resolve from 'rollup-plugin-node-resolve'
import url from 'rollup-plugin-url'
import pkg from './package.json'
// Deal with https://github.com/rollup/rollup-plugin-commonjs/issues/297
import builtins from 'rollup-plugin-node-builtins';
const globals = {
'react': 'React',
'react-dom': 'ReactDOM',
'react-redux': 'ReactRedux',
'react-resize-detector': 'ReactResizeDetector',
'prop-types': 'PropTypes',
'cornerstone-core': 'cornerstone',
'cornerstone-wado-image-loader': 'cornerstoneWADOImageLoader',
'cornerstone-math': 'cornerstoneMath',
'cornerstone-tools': 'cornerstoneTools',
'dcmjs': 'dcmjs',
'dicom-parser': 'dicomParser',
'ohif-core': 'OHIF',
'hammerjs': 'Hammer'
};
export default {
input: 'src/index.js',
output: [
{
file: pkg.main,
format: 'umd',
name: 'ohif-vtk-extension',
sourcemap: true,
globals
},
{
file: pkg.module,
format: 'es',
sourcemap: true,
globals
}
],
plugins: [
builtins(),
external(),
postcss({
modules: false
}),
url(),
babel({
exclude: 'node_modules/**',
externalHelpers: true,
runtimeHelpers: true
}),
resolve(),
commonjs({
include: ['node_modules/**', '.yalc/**'],
namedExports: {
'.yalc/react-vtkjs-viewport/dist/index.js': [
'getImageData',
'loadImageData',
'VTKViewport',
'VTKMPRViewport',
]
}
})
],
external: Object.keys(pkg.peerDependencies || {}),
}

View File

@ -0,0 +1,36 @@
import { connect } from 'react-redux';
import { ToolbarSection } from 'react-viewerbase';
import OHIF from 'ohif-core'
const { setToolActive } = OHIF.redux.actions;
const mapStateToProps = state => {
const activeButton = state.tools.buttons.find(tool => tool.active === true);
return {
buttons: [ {
command: 'Rotate',
type: 'tool',
text: 'Rotate',
iconClasses: 'fa fa-rotate',
//svgUrl: `${Icons}#icon-tools-stack-scroll`,
active: true
}],
activeCommand: 'Rotate'
};
};
const mapDispatchToProps = dispatch => {
return {
setToolActive: tool => {
//dispatch(setToolActive(tool.command))
}
};
};
const ConnectedToolbarSection = connect(
mapStateToProps,
mapDispatchToProps
)(ToolbarSection);
export default ConnectedToolbarSection;

View File

@ -0,0 +1,61 @@
import { connect } from 'react-redux';
import { VTKMPRViewport } from 'react-vtkjs-viewport';
import OHIF from 'ohif-core';
const {
setViewportActive,
setViewportSpecificData,
clearViewportSpecificData
} = OHIF.redux.actions;
const mapStateToProps = (state, ownProps) => {
const activeButton = state.tools.buttons.find(tool => tool.active === true);
let dataFromStore;
if (state.extensions && state.extensions.vtk) {
dataFromStore = state.extensions.vtk;
}
// If this is the active viewport, enable prefetching.
const { viewportIndex } = ownProps; //.viewportData;
const isActive = viewportIndex === state.viewports.activeViewportIndex;
const viewportSpecificData =
state.viewports.viewportSpecificData[viewportIndex] || {};
const viewportLayout = state.viewports.layout.viewports[viewportIndex];
const pluginDetails = viewportLayout.vtk || {};
return {
layout: state.viewports.layout,
isActive,
...pluginDetails,
activeTool: activeButton && activeButton.command,
...dataFromStore,
enableStackPrefetch: isActive,
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
const { viewportIndex } = ownProps;
return {
setViewportActive: () => {
dispatch(setViewportActive(viewportIndex));
},
setViewportSpecificData: data => {
dispatch(setViewportSpecificData(viewportIndex, data));
},
clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData(viewportIndex));
}
};
};
const ConnectedVTKViewport = connect(
mapStateToProps,
//mapDispatchToProps
)(VTKMPRViewport);
export default ConnectedVTKViewport;

View File

@ -0,0 +1,17 @@
.imageViewerLoadingIndicator {
color: #91B9CD;
}
.loadingIndicator {
background-color: rgba(0, 0, 0, 0.75);
font-size: 18px;
height: 100%;
overflow: hidden;
position: absolute;
top: 0;
width: 100%;
z-index: 1;
/* Necessary for click-through to cornerstone element below */
pointer-events: none;
}

View File

@ -0,0 +1,48 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import './LoadingIndicator.css';
class LoadingIndicator extends PureComponent {
static propTypes = {
percentComplete: PropTypes.number.isRequired,
error: PropTypes.object
};
static defaultProps = {
percentComplete: 0,
error: null
};
render() {
let percComplete;
if (this.props.percentComplete && this.props.percentComplete !== 100) {
percComplete = `${this.props.percentComplete}%`;
}
return (
<React.Fragment>
{this.props.error ? (
<div className="imageViewerErrorLoadingIndicator loadingIndicator">
<div className="indicatorContents">
<h4>Error Loading Image</h4>
<p className="description">An error has occurred.</p>
<p className="details">{this.props.error.message}</p>
</div>
</div>
) : (
<div className="imageViewerLoadingIndicator loadingIndicator">
<div className="indicatorContents">
<p>
Loading... <i className="fa fa-spin fa-circle-o-notch fa-fw" />{' '}
{percComplete}
</p>
</div>
</div>
)}
</React.Fragment>
);
}
}
export default LoadingIndicator;

View File

@ -0,0 +1,54 @@
import React, { Component } from 'react';
import OHIFVTKViewport from './OHIFVTKViewport.js';
import ToolbarModule from './ToolbarModule.js';
/**
* Pass in 'children' to a React component. The purpose of this is to
* allow end users of this extension to pass in components which will
* be rendered on top of the base components.
*
* @param WrappedComponent
* @param children
* @return {function(*): *}
*/
function withChildren(WrappedComponent, children) {
return function(props) {
return <WrappedComponent children={children} { ...props } />;
};
}
// Note: If you are authoring extensions which use stateful libraries (e.g. cornerstone-core, react-redux) as peerDependencies and are also duplicated at the application level, try using 'yalc' to link it to the application, rather than yarn link. This can help fix 'module not found' issues.
// https://github.com/whitecolor/yalc
export default class OHIFVTKExtension {
constructor(children) {
this.children = children;
}
/**
* Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?)
*/
getExtensionId() {
return 'vtk';
}
getViewportModule() {
if (this.children && this.children.viewport) {
return withChildren(OHIFVTKViewport, this.children.viewport);
}
return OHIFVTKViewport;
}
getSopClassHandler() {
return null;
}
getPanelModule() {
return null;
}
getToolbarModule() {
return ToolbarModule;
}
}

View File

@ -0,0 +1,258 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import ConnectedVTKViewport from './ConnectedVTKViewport';
import cornerstone from 'cornerstone-core';
import handleSegmentationStorage from './handleSegmentationStorage.js';
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
import LoadingIndicator from './LoadingIndicator.js';
const { StackManager } = OHIF.utils;
// Metadata configuration
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
cornerstone.metaData.addProvider(
metadataProvider.provider.bind(metadataProvider)
);
StackManager.setMetadataProvider(metadataProvider);
const SOP_CLASSES = {
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4'
};
const specialCaseHandlers = {};
specialCaseHandlers[
SOP_CLASSES.SEGMENTATION_STORAGE
] = handleSegmentationStorage;
class OHIFVTKViewport extends Component {
state = {
viewportData: null
};
static propTypes = {
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportIndex: PropTypes.number,
children: PropTypes.node
};
static id = 'OHIFCornerstoneViewport';
static init() {
console.log('OHIFCornerstoneViewport init()');
}
static destroy() {
console.log('OHIFCornerstoneViewport destroy()');
StackManager.clearStacks();
}
static getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
) {
// Create shortcut to displaySet
const study = studies.find(
study => study.studyInstanceUid === studyInstanceUid
);
const displaySet = study.displaySets.find(set => {
return set.displaySetInstanceUid === displaySetInstanceUid;
});
// Get stack from Stack Manager
const storedStack = StackManager.findOrCreateStack(study, displaySet);
// Clone the stack here so we don't mutate it
const stack = Object.assign({}, storedStack);
if (frameIndex !== undefined) {
stack.currentImageIdIndex = frameIndex;
} else if (sopInstanceUid) {
const index = stack.imageIds.findIndex(imageId => {
const sopCommonModule = cornerstone.metaData.get(
'sopCommonModule',
imageId
);
if (!sopCommonModule) {
return;
}
return sopCommonModule.sopInstanceUID === sopInstanceUid;
});
if (index > -1) {
stack.currentImageIdIndex = index;
}
} else {
stack.currentImageIdIndex = 0;
}
return stack;
}
static getViewportData = (
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
) => {
return OHIFVTKViewport.getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
);
};
getViewportData = (
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
) => {
return new Promise((resolve, reject) => {
const stack = OHIFVTKViewport.getViewportData(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
);
switch (sopClassUid) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
throw new Error("Not yet implemented");
const data = handleSegmentationStorage(stack.imageIds, displaySetInstanceUid, cornerstone);
const imageDataObject = data.referenceDataObject;
const labelmapDataObject = data.labelmapDataObject;
const doneLoadingCallback = () => {
resolve({
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject
});
};
const callbacks = [
doneLoadingCallback
];
loadImageData(imageDataObject, callbacks, cornerstone);
break;
default:
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid, cornerstone);
const doneLoadingCallback = () => {
resolve({
data: imageDataObject.vtkImageData,
});
};
const callbacks = [
doneLoadingCallback
];
loadImageData(imageDataObject, callbacks, cornerstone);
break;
}
});
};
setStateFromProps() {
const { studies, displaySet } = this.props.viewportData;
const {
studyInstanceUid,
displaySetInstanceUid,
sopClassUids,
sopInstanceUid,
frameIndex
} = displaySet;
if (sopClassUids.length > 1) {
console.warn(
'More than one SOPClassUid in the same series is not yet supported.'
);
}
const sopClassUid = sopClassUids[0];
this.getViewportData(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
).then(({ data, labelmap })=> {
this.setState({
viewportData: data,
labelmap
});
});
}
componentDidMount() {
this.setStateFromProps();
}
componentDidUpdate(prevProps) {
const { studies, displaySet } = this.props.viewportData;
const prevDisplaySet = prevProps.viewportData.displaySet;
if (
displaySet.displaySetInstanceUid !==
prevDisplaySet.displaySetInstanceUid ||
displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
this.setStateFromProps();
}
}
render() {
let childrenWithProps = null;
// TODO: Does it make more sense to use Context?
if (this.props.children && this.props.children.length) {
childrenWithProps = this.props.children.map((child, index) => {
return React.cloneElement(child, {
viewportIndex: this.props.viewportIndex,
key: index
});
});
}
const style = { width: '100%', height: '100%', position: 'relative' };
return (
<>
{this.state.viewportData ? (
<ConnectedVTKViewport
data={this.state.viewportData}
labelmap={this.state.labelmap}
viewportIndex={this.props.viewportIndex}
/>
) : <div style={style}>
<LoadingIndicator/>
</div>}
{childrenWithProps}
</>
);
}
}
export default OHIFVTKViewport;

View File

@ -0,0 +1,15 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ConnectedToolbarSection from './ConnectedToolbarSection';
class ToolbarModule extends Component {
render() {
return (
<div className="ToolbarModule">
<ConnectedToolbarSection />
</div>
);
}
}
export default ToolbarModule;

View File

@ -0,0 +1,123 @@
import OHIF from 'ohif-core';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import * as dcmjs from 'dcmjs';
const { StackManager } = OHIF.utils;
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
const study = studies.find(
study => study.studyInstanceUid === studyInstanceUid
);
const displaySet = study.displaySets.find(set => {
return set.displaySetInstanceUid === displaySetInstanceUid;
});
return displaySet;
}
function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) {
const study = studies.find(
study => study.studyInstanceUid === studyInstanceUid
);
return study.displaySets.filter(set => {
return set.seriesInstanceUid === seriesInstanceUid;
});
}
function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) {
const study = studies.find(
study => study.studyInstanceUid === studyInstanceUid
);
// Create shortcut to displaySet
const displaySet = getDisplaySet(
studies,
studyInstanceUid,
displaySetInstanceUid
);
// Get stack from Stack Manager
const stack = StackManager.findOrCreateStack(study, displaySet);
// Clone the stack here so we don't mutate it later
const stackClone = Object.assign({}, stack);
stackClone.currentImageIdIndex = 0;
return stackClone;
}
function retrieveDicomData(wadoUri) {
// TODO: Authorization header depends on the server. If we ever have multiple servers
// we will need to figure out how / when to pass this information in.
return fetch(wadoUri, {
headers: OHIF.DICOMWeb.getAuthorizationHeader()
}).then(response => response.arrayBuffer());
}
async function handleSegmentationStorage(
studies,
studyInstanceUid,
displaySetInstanceUid
) {
const study = studies.find(
study => study.studyInstanceUid === studyInstanceUid
);
const displaySet = getDisplaySet(
studies,
studyInstanceUid,
displaySetInstanceUid
);
const segWadoUri = displaySet.images[0].getData().wadouri;
const arrayBuffer = await retrieveDicomData(segWadoUri);
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
const segments = dcmjs.adapters.VTKjs.Segmentation.generateSegments(dataset);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
const displaySets = getDisplaySetsBySeries(
studies,
studyInstanceUid,
seriesInstanceUid
);
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same seriesInstanceUid. This is not supported yet...'
);
}
const referenceDisplaySet = displaySets[0];
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
if (!results) {
throw new Error('Fractional segmentations are not supported');
}
const cachedStack = StackManager.findOrCreateStack(
study,
referenceDisplaySet
);
const stack = Object.assign({}, cachedStack);
stack.currentImageIdIndex = 0;
return {
referenceDataObject,
labelmapDataObject
}
return {
studyInstanceUid,
displaySetInstanceUid,
stack
};
}
export default handleSegmentationStorage;

View File

@ -0,0 +1,3 @@
import OHIFVTKExtension from './OHIFVTKExtension.js';
export default OHIFVTKExtension;

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit 10701c5425c58ccfca7d8758a1698f438bc197e9
Subproject commit a728166da104670b9d62ba4cd8ec4eef645ff29e

View File

@ -4,4 +4,7 @@ node_modules
yarn.lock
yarn-error.log
src/sha.js
src/version.js
src/version.js
.yalc
yalc.lock

View File

@ -106,11 +106,11 @@
"hammerjs": "^2.0.8",
"lodash.isequal": "^4.5.0",
"moment": "^2.24.0",
"ohif-core": "^0.3.3",
"ohif-cornerstone-extension": "0.0.17",
"ohif-core": "^0.3.4",
"ohif-cornerstone-extension": "^0.0.20",
"ohif-dicom-html-extension": "^0.0.2",
"ohif-dicom-microscopy-extension": "^0.0.5",
"ohif-dicom-pdf-extension": "^0.0.5",
"ohif-dicom-pdf-extension": "^0.0.6",
"oidc-client": "^1.6.1",
"prop-types": "^15.6.2",
"react-bootstrap-modal": "^4.2.0",

View File

@ -9,6 +9,7 @@ import ui from './redux/ui.js';
import OHIFStandaloneViewer from './OHIFStandaloneViewer';
import WhiteLabellingContext from './WhiteLabellingContext';
import OHIFCornerstoneExtension from 'ohif-cornerstone-extension';
//import OHIFVTKExtension from 'ohif-vtk-extension';
import OHIFDicomPDFExtension from 'ohif-dicom-pdf-extension';
import OHIFDicomHtmlExtension from 'ohif-dicom-html-extension';
import OHIFDicomMicroscopyExtension from 'ohif-dicom-microscopy-extension';
@ -146,6 +147,7 @@ store.dispatch(toolAction);
/** TODO: extensions should be passed in as prop as soon as we have the extensions as separate packages and then registered by ExtensionsManager */
const extensions = [
new OHIFCornerstoneExtension(),
//new OHIFVTKExtension(),
new OHIFDicomPDFExtension(),
new OHIFDicomHtmlExtension(),
new OHIFDicomMicroscopyExtension()

View File

@ -0,0 +1,130 @@
import { connect } from 'react-redux';
import PluginSwitch from './PluginSwitch.js';
import OHIF from 'ohif-core'
const { setLayout } = OHIF.redux.actions;
const mapStateToProps = state => {
const { activeViewportIndex, layout } = state.viewports;
return {
activeViewportIndex,
layout
};
};
const mapDispatchToProps = dispatch => {
return {
setLayout: (data) => {
dispatch(setLayout(data))
}
};
};
function setSingleLayoutData(originalArray, viewportIndex, data) {
const viewports = originalArray.slice();
const layoutData = Object.assign({}, viewports[viewportIndex], data);
viewports[viewportIndex] = layoutData;
return viewports;
}
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
const { activeViewportIndex, layout } = propsFromState;
const { setLayout } = propsFromDispatch;
// TODO: Do not display certain options if the current display set
// cannot be displayed using these view types
const options = [
{
title: 'Original Acquisition',
onClick: (click) => {
console.warn('Original Acquisition');
const layoutData = setSingleLayoutData(layout.viewports, activeViewportIndex, { plugin: 'cornerstone'});
setLayout({ viewports: layoutData });
}
},
{
title: 'Axial',
onClick: (click) => {
console.warn('Axial');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [0, 0, 1]
}
};
const layoutData = setSingleLayoutData(layout.viewports, activeViewportIndex, data);
setLayout({ viewports: layoutData });
}
},
{
title: 'Sagittal',
onClick: (click) => {
console.warn('Sagittal');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [1, 0, 0]
}
};
const layoutData = setSingleLayoutData(layout.viewports, activeViewportIndex, data);
setLayout({ viewports: layoutData });
}
},
{
title: 'Coronal',
onClick: (click) => {
console.warn('Coronal');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [0, 1, 0]
}
};
const layoutData = setSingleLayoutData(layout.viewports, activeViewportIndex, data);
setLayout({ viewports: layoutData });
}
},
{
title: '3D Perspective',
onClick: (click) => {
console.warn('3D Perspective');
const data = {
plugin: 'vtk',
vtk: {
mode: '3d',
}
};
const layoutData = setSingleLayoutData(layout.viewports, activeViewportIndex, data);
setLayout({ viewports: layoutData });
}
}
];
return {
options,
};
};
const ConnectedPluginSwitch = connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(PluginSwitch);
export default ConnectedPluginSwitch;

View File

@ -2,8 +2,17 @@ import { connect } from 'react-redux';
import ToolbarRow from './ToolbarRow';
import { setLeftSidebarOpen, setRightSidebarOpen } from '../redux/actions.js';
const defaultPlugin = 'cornerstone';
const mapStateToProps = state => {
const { layout, viewportSpecificData, activeViewportIndex } = state.viewports;
const pluginInLayout = layout.viewports[activeViewportIndex] && layout.viewports[activeViewportIndex].plugin;
const pluginInViewportData = viewportSpecificData[activeViewportIndex] && viewportSpecificData[activeViewportIndex].plugin;
const pluginInActiveViewport = pluginInLayout || pluginInViewportData || defaultPlugin;
// const extensionData = state.extensions[pluginInActiveViewport];
return {
pluginId: pluginInActiveViewport,
leftSidebarOpen: state.ui.leftSidebarOpen,
rightSidebarOpen: state.ui.rightSidebarOpen
};

View File

@ -0,0 +1,22 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Dropdown } from 'react-viewerbase';
class PluginSwitch extends Component {
static propTypes = {
options: PropTypes.array,
};
static defaultProps = {
};
render() {
return (
<div className="PluginSwitch">
{this.props.options && <Dropdown title={"View"} list={this.props.options}/>}
</div>
);
}
}
export default PluginSwitch;

View File

@ -1,23 +1,20 @@
import React, { Component } from 'react';
import ConnectedToolbarSection from './ConnectedToolbarSection';
import ConnectedLayoutButton from './ConnectedLayoutButton';
import ConnectedCineDialog from './ConnectedCineDialog.js';
import PropTypes from 'prop-types';
import { ToolbarButton, RoundedButtonGroup } from 'react-viewerbase';
import OHIF from 'ohif-core';
import { RoundedButtonGroup } from 'react-viewerbase';
import ConnectedLayoutButton from './ConnectedLayoutButton';
///import ConnectedPluginSwitch from './ConnectedPluginSwitch.js';
import './ToolbarRow.css';
const Icons = 'icons.svg';
class ToolbarRow extends Component {
state = {
cineDialogOpen: false
};
static propTypes = {
leftSidebarOpen: PropTypes.bool.isRequired,
rightSidebarOpen: PropTypes.bool.isRequired,
setLeftSidebarOpen: PropTypes.func,
setRightSidebarOpen: PropTypes.func
setRightSidebarOpen: PropTypes.func,
pluginId: PropTypes.string
};
static defaultProps = {
@ -33,12 +30,6 @@ class ToolbarRow extends Component {
this.props.setRightSidebarOpen(!!value);
};
onClickCineToolbarButton = () => {
this.setState({
cineDialogOpen: !this.state.cineDialogOpen
});
};
render() {
const leftSidebarToggle = [
{
@ -68,9 +59,23 @@ class ToolbarRow extends Component {
? rightSidebarToggle[0].value
: null;
const cineDialogContainerStyle = {
display: this.state.cineDialogOpen ? 'inline-block' : 'none'
};
const currentPluginId = this.props.pluginId;
const { PLUGIN_TYPES, availablePlugins } = OHIF.plugins;
const plugin = availablePlugins.find(entry => {
return (
entry.type === PLUGIN_TYPES.TOOLBAR &&
entry.id === currentPluginId
);
});
let pluginComp;
if (plugin) {
const PluginComponent = plugin.component;
pluginComp = <PluginComponent/>
}
return (
<div className="ToolbarRow">
@ -81,17 +86,9 @@ class ToolbarRow extends Component {
onValueChanged={this.onLeftSidebarValueChanged}
/>
</div>
<ConnectedToolbarSection />
{ pluginComp }
<ConnectedLayoutButton />
<ToolbarButton
active={this.state.cineDialogOpen}
onClick={this.onClickCineToolbarButton}
text={'CINE'}
iconClasses={'fab fa-youtube'}
/>
<div className="CineDialogContainer" style={cineDialogContainerStyle}>
<ConnectedCineDialog />
</div>
{/*<ConnectedPluginSwitch/>*/}
<div className="pull-right m-t-1 rm-x-1" style={{ marginLeft: 'auto' }}>
<RoundedButtonGroup
options={rightSidebarToggle}

View File

@ -1 +1 @@
export default '6f8b9ae5932e23a1b2031ad173d9a87121e9eae8';
export default '416ecb659362a0ebf5449109186c55511421cc33';

View File

@ -7811,10 +7811,10 @@ octal@^1.0.0:
resolved "https://registry.yarnpkg.com/octal/-/octal-1.0.0.tgz#63e7162a68efbeb9e213588d58e989d1e5c4530b"
integrity sha1-Y+cWKmjvvrniE1iNWOmJ0eXEUws=
ohif-core@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.3.3.tgz#6067b31dfdd7c91541aa4d5cf1ad617038147640"
integrity sha512-uE1EzMrSE+HZIvCBysEvnIwZfb3M5HTLnDGh9NEurBmTOYjD0NIHVoMimjNbxINoNQUBUpOUdJbXAuUk2YOuZg==
ohif-core@^0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.3.4.tgz#0dbed1f7edbd1cd9a826157fba6937171633fb7c"
integrity sha512-qK0fnQ6Yh7cYcQQN6D3csYGtRxVNxJOyT6yxmgQIdAEoK87oPmmApZL8jxMySPrGgF5RqPFqplPTrxrKh82tOw==
dependencies:
"@babel/runtime" "^7.2.0"
dicomweb-client "^0.4.2"
@ -7825,15 +7825,15 @@ ohif-core@^0.3.3:
lodash.merge "^4.6.1"
validate.js "^0.12.0"
ohif-cornerstone-extension@0.0.17:
version "0.0.17"
resolved "https://registry.yarnpkg.com/ohif-cornerstone-extension/-/ohif-cornerstone-extension-0.0.17.tgz#58c182d3227d328c5a17966b5e7c40b513920704"
integrity sha512-tT6rbirsCyMDgYPzJXjNhk7zT9KN/mJaKPfp3TVcNIgN1EAARQROU/5shaLUa6DA8M3QDQyKVIVKu+39i6ijVg==
ohif-cornerstone-extension@^0.0.20:
version "0.0.20"
resolved "https://registry.yarnpkg.com/ohif-cornerstone-extension/-/ohif-cornerstone-extension-0.0.20.tgz#831da61160338ba30b09d6454876d3d7b318445b"
integrity sha512-m/qmBFcV3vJ6+ma0UHXHqMN0Nz1Zw9I47T6zgnvz7ZwxJ3h8aQfUjVPGtRcuB2snna4c4NmEHaxoEGRR0bFdIg==
dependencies:
"@babel/runtime" "^7.2.0"
classnames "^2.2.6"
lodash.throttle "^4.1.1"
react-cornerstone-viewport "^0.1.16"
react-cornerstone-viewport "^0.1.20"
ohif-dicom-html-extension@^0.0.2:
version "0.0.2"
@ -7851,10 +7851,10 @@ ohif-dicom-microscopy-extension@^0.0.5:
classnames "^2.2.6"
dicom-microscopy-viewer "^0.2.0"
ohif-dicom-pdf-extension@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/ohif-dicom-pdf-extension/-/ohif-dicom-pdf-extension-0.0.5.tgz#8d2b453aa9d1df736f5c54818f7ee682f60f4c32"
integrity sha512-YlMgh25hJ+p9e/Uzgg0Wx3deutUKaPd1t/PtL8gIHD+3/k3hgmTsz8nYsQ7xKGSXGpl56d/r9PrRpKF9n8nLSw==
ohif-dicom-pdf-extension@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/ohif-dicom-pdf-extension/-/ohif-dicom-pdf-extension-0.0.6.tgz#5c1c1bf955d160dda01c36c4ed899d8e65384d63"
integrity sha512-MRBDnC78itsos3c8QMKjgaFp8tmXyGIq5qVLREr//osqCYirhWQbCFJv73eYR5+bRCdJfUMMpvnDSChFIhcT6A==
dependencies:
"@babel/runtime" "^7.2.0"
classnames "^2.2.6"
@ -9440,10 +9440,10 @@ react-bootstrap-modal@^4.2.0:
react-overlays "^0.8.0"
react-transition-group "^2.0.0"
react-cornerstone-viewport@^0.1.16:
version "0.1.16"
resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-0.1.16.tgz#b1db13a3160ee10ffa9bfd91a6a0247f008170e2"
integrity sha512-hMEWoA3oDuurJ0AWZDH/hw1jPD7yY3EbaguE0CFusXdHsclCa7GjZXZb9EPqiAw8bF8vBIty7s96dwkqSFSLdw==
react-cornerstone-viewport@^0.1.20:
version "0.1.20"
resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-0.1.20.tgz#90e369a48e3f47dd5da69d42f0c8ee7e87e81543"
integrity sha512-SbaSjCAVFpQoUUXZ6d5F5QwKrP66zRJ4mN7JXPGyiL6+/m0jGkaBVci6pftkth66Lu5HRlCZUS8nh0plONPUtA==
dependencies:
lodash.debounce "^4.0.8"
moment "^2.23.0"

@ -1 +1 @@
Subproject commit 3773fd4899eb8faf24a128f6a4a43f6e6e5762ee
Subproject commit 05f58a4be2d7f90671d71a2c5cf96a2f08bb219d

@ -1 +1 @@
Subproject commit 6294f3c78f085b7501136964e194cc82e995b15d
Subproject commit 3fe82ecec15dc3e1843a00b0d88da8f3f7e161c4

@ -0,0 +1 @@
Subproject commit 8d1a0b11e5e565f779f12082d24ca8f9daa0b051