Re-add extensions as top level dir
This commit is contained in:
1 parent
77ec71a12a
commit
780f84af89
86 files changed
+51115
No files matched your search
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"ie": "11"
|
||||
}
|
||||
}],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": [
|
||||
"react-app",
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
"jest": true
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
# 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
|
||||
.yalc
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 9
|
||||
- 8
|
||||
@@ -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.
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"name": "ohif-cornerstone-extension",
|
||||
"version": "0.0.28",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"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.8",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"ohif-core": "^0.3.5",
|
||||
"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": {
|
||||
"@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",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"prettier": "^1.15.3",
|
||||
"react": "^16.6.3",
|
||||
"react-dom": "^16.6.3",
|
||||
"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",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-cornerstone-viewport": "^0.1.25"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
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',
|
||||
'react-viewerbase': 'reactViewerbase',
|
||||
'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-cornerstone-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/**',
|
||||
namedExports: {
|
||||
'node_modules/react-is/index.js': [
|
||||
'isValidElementType'
|
||||
]
|
||||
}
|
||||
})
|
||||
],
|
||||
external: Object.keys(pkg.peerDependencies || {}),
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { CineDialog } from 'react-viewerbase';
|
||||
import OHIF from 'ohif-core';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
const { setViewportSpecificData } = OHIF.redux.actions;
|
||||
|
||||
// TODO: I'm guessing this function will be used in other connect locations
|
||||
// so we might want to put it somewhere shared
|
||||
function getActiveViewportSpecificData(state) {
|
||||
const { viewportSpecificData, activeViewportIndex } = state.viewports;
|
||||
return viewportSpecificData[activeViewportIndex];
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
// TODO:
|
||||
// - Test if including CineDialog in the toolbarRow will prevent it
|
||||
// from hovering over the rest of the UI when visible.
|
||||
//
|
||||
// - Create custom ToolbarButton which just shows Play state
|
||||
// - Connect this ToolbarButton to Redux
|
||||
const activeViewportSpecificData = getActiveViewportSpecificData(state);
|
||||
|
||||
let stack = {
|
||||
imageIds: [],
|
||||
currentImageIdIndex: 0
|
||||
};
|
||||
if (activeViewportSpecificData && activeViewportSpecificData.stack) {
|
||||
stack = activeViewportSpecificData.stack
|
||||
}
|
||||
|
||||
let cine = {
|
||||
isPlaying: false,
|
||||
cineFrameRate: 24
|
||||
};
|
||||
if (activeViewportSpecificData && activeViewportSpecificData.cine) {
|
||||
cine = activeViewportSpecificData.cine
|
||||
}
|
||||
|
||||
|
||||
// TODO: activeViewportStackData won't currently change anything on
|
||||
// CornerstoneViewport. The updates are too frequent and it's killing
|
||||
// performance. Need to revisit how we can do this.
|
||||
return {
|
||||
activeViewportStackData: stack,
|
||||
activeViewportCineData: cine,
|
||||
activeViewportIndex: state.viewports.activeViewportIndex
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
dispatchSetViewportSpecificData: (viewportIndex, data) => {
|
||||
dispatch(setViewportSpecificData(viewportIndex, data));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
||||
const { activeViewportStackData, activeViewportCineData, activeViewportIndex } = propsFromState;
|
||||
|
||||
return {
|
||||
cineFrameRate: activeViewportCineData.cineFrameRate,
|
||||
isPlaying: activeViewportCineData.isPlaying,
|
||||
onPlayPauseChanged: isPlaying => {
|
||||
const cine = cloneDeep(activeViewportCineData);
|
||||
cine.isPlaying = !cine.isPlaying;
|
||||
|
||||
propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, { cine });
|
||||
},
|
||||
onFrameRateChanged: frameRate => {
|
||||
const cine = cloneDeep(activeViewportCineData);
|
||||
cine.cineFrameRate = frameRate;
|
||||
|
||||
propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, { cine });
|
||||
},
|
||||
onClickNextButton: () => {
|
||||
const stack = cloneDeep(activeViewportStackData);
|
||||
const largestPossibleIndex = stack.imageIds.length - 1;
|
||||
stack.currentImageIdIndex = Math.min(stack.currentImageIdIndex + 1, largestPossibleIndex)
|
||||
|
||||
propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, { stack });
|
||||
},
|
||||
onClickBackButton: () => {
|
||||
const stack = cloneDeep(activeViewportStackData);
|
||||
stack.currentImageIdIndex = Math.max(stack.currentImageIdIndex - 1, 0);
|
||||
|
||||
propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, { stack });
|
||||
},
|
||||
onClickSkipToStart: () => {
|
||||
const stack = cloneDeep(activeViewportStackData);
|
||||
stack.currentImageIdIndex = 0;
|
||||
|
||||
propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, { stack });
|
||||
},
|
||||
onClickSkipToEnd: () => {
|
||||
const stack = cloneDeep(activeViewportStackData);
|
||||
stack.currentImageIdIndex = stack.imageIds.length;
|
||||
|
||||
propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, { stack });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedCineDialog = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mergeProps
|
||||
)(CineDialog);
|
||||
|
||||
export default ConnectedCineDialog;
|
||||
@@ -0,0 +1,77 @@
|
||||
import { connect } from 'react-redux';
|
||||
import CornerstoneViewport from 'react-cornerstone-viewport';
|
||||
import OHIF from 'ohif-core';
|
||||
import throttle from 'lodash.throttle';
|
||||
|
||||
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.cornerstone) {
|
||||
dataFromStore = state.extensions.cornerstone;
|
||||
}
|
||||
|
||||
// If this is the active viewport, enable prefetching.
|
||||
const { viewportIndex } = ownProps; //.viewportData;
|
||||
const isActive = viewportIndex === state.viewports.activeViewportIndex;
|
||||
const viewportSpecificData =
|
||||
state.viewports.viewportSpecificData[viewportIndex] || {};
|
||||
|
||||
return {
|
||||
layout: state.viewports.layout,
|
||||
isActive,
|
||||
activeTool: activeButton && activeButton.command,
|
||||
...dataFromStore,
|
||||
enableStackPrefetch: isActive,
|
||||
//stack: viewportSpecificData.stack,
|
||||
cineToolData: viewportSpecificData.cine
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const { viewportIndex } = ownProps;
|
||||
|
||||
return {
|
||||
setViewportActive: () => {
|
||||
dispatch(setViewportActive(viewportIndex));
|
||||
},
|
||||
|
||||
setViewportSpecificData: data => {
|
||||
dispatch(setViewportSpecificData(viewportIndex, data));
|
||||
},
|
||||
|
||||
clearViewportSpecificData: () => {
|
||||
dispatch(clearViewportSpecificData(viewportIndex));
|
||||
},
|
||||
|
||||
onMeasurementsChanged: (event, action) => {
|
||||
const {
|
||||
onAdded,
|
||||
onRemoved,
|
||||
onModified
|
||||
} = OHIF.measurements.MeasurementHandlers;
|
||||
const actions = {
|
||||
added: onAdded,
|
||||
removed: onRemoved,
|
||||
modified: throttle(event => {
|
||||
return onModified(event);
|
||||
}, 300)
|
||||
};
|
||||
|
||||
return actions[action](event);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedCornerstoneViewport = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CornerstoneViewport);
|
||||
|
||||
export default ConnectedCornerstoneViewport;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,65 @@
|
||||
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
|
||||
* 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 componentWithProps(WrappedComponent, children, customProps) {
|
||||
return function(props) {
|
||||
const extraProps = {
|
||||
customProps
|
||||
};
|
||||
if (children.viewport) {
|
||||
extraProps.children = children.viewport;
|
||||
}
|
||||
|
||||
const mergedProps = Object.assign({}, props, extraProps);
|
||||
|
||||
return <WrappedComponent {...mergedProps} />;
|
||||
};
|
||||
}
|
||||
|
||||
// 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 OHIFCornerstoneExtension {
|
||||
constructor(props) {
|
||||
const { children = {}, customProps = {} } = props;
|
||||
this.children = children;
|
||||
this.customProps = customProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?)
|
||||
*/
|
||||
getExtensionId() {
|
||||
return 'cornerstone';
|
||||
}
|
||||
|
||||
getViewportModule() {
|
||||
return componentWithProps(
|
||||
OHIFCornerstoneViewport,
|
||||
this.children,
|
||||
this.customProps
|
||||
);
|
||||
}
|
||||
|
||||
getSopClassHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getPanelModule() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getToolbarModule() {
|
||||
return ToolbarModule;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import OHIF from 'ohif-core';
|
||||
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import './config';
|
||||
import handleSegmentationStorage from './handleSegmentationStorage.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 OHIFCornerstoneViewport extends Component {
|
||||
state = {
|
||||
viewportData: null
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
customProps: {}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
studies: PropTypes.object,
|
||||
displaySet: PropTypes.object,
|
||||
viewportIndex: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
customProps: PropTypes.object
|
||||
};
|
||||
|
||||
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 OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
);
|
||||
};
|
||||
|
||||
getViewportData = async (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
) => {
|
||||
let viewportData;
|
||||
|
||||
switch (sopClassUid) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
const specialCaseHandler =
|
||||
specialCaseHandlers[SOP_CLASSES.SEGMENTATION_STORAGE];
|
||||
|
||||
viewportData = await specialCaseHandler(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const stack = OHIFCornerstoneViewport.getViewportData(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
viewportData = {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
stack
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return viewportData;
|
||||
};
|
||||
|
||||
setStateFromProps() {
|
||||
const { studies, displaySet } = this.props.viewportData;
|
||||
const {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUids,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
} = displaySet;
|
||||
|
||||
if (sopClassUids && sopClassUids.length > 1) {
|
||||
console.warn(
|
||||
'More than one SOPClassUid in the same series is not yet supported.'
|
||||
);
|
||||
}
|
||||
|
||||
const sopClassUid = sopClassUids && sopClassUids[0];
|
||||
|
||||
this.getViewportData(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
).then(viewportData => {
|
||||
this.setState({
|
||||
viewportData
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.state.viewportData && (
|
||||
<ConnectedCornerstoneViewport
|
||||
viewportData={this.state.viewportData}
|
||||
viewportIndex={this.props.viewportIndex}
|
||||
{...this.props.customProps}
|
||||
/>
|
||||
)}
|
||||
{childrenWithProps}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OHIFCornerstoneViewport;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,28 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneMath from 'cornerstone-math';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import Hammer from 'hammerjs';
|
||||
|
||||
// For debugging
|
||||
window.cornerstoneTools = cornerstoneTools;
|
||||
|
||||
cornerstoneTools.external.cornerstone = cornerstone;
|
||||
cornerstoneTools.external.Hammer = Hammer;
|
||||
cornerstoneTools.external.cornerstoneMath = cornerstoneMath;
|
||||
cornerstoneTools.init();
|
||||
|
||||
// Set the tool font and font size
|
||||
// context.font = "[style] [variant] [weight] [size]/[line height] [font family]";
|
||||
const fontFamily =
|
||||
'Roboto, OpenSans, HelveticaNeue-Light, Helvetica Neue Light, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif';
|
||||
cornerstoneTools.textStyle.setFont(`16px ${fontFamily}`);
|
||||
|
||||
// Set the tool width
|
||||
cornerstoneTools.toolStyle.setToolWidth(2);
|
||||
// Set color for inactive tools
|
||||
cornerstoneTools.toolColors.setToolColor('rgb(255, 255, 0)');
|
||||
|
||||
// Set color for active tools
|
||||
cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)');
|
||||
|
||||
cornerstoneTools.store.state.touchProximity = 40;
|
||||
@@ -0,0 +1,151 @@
|
||||
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 parseSeg(arrayBuffer, imageIds) {
|
||||
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
||||
imageIds,
|
||||
arrayBuffer,
|
||||
cornerstone.metaData
|
||||
);
|
||||
}
|
||||
|
||||
function addSegMetadataToCornerstoneToolState(
|
||||
segMetadata,
|
||||
toolState,
|
||||
displaySetInstanceUid
|
||||
) {
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||
toolState
|
||||
);
|
||||
|
||||
const brushModule = cornerstoneTools.store.modules.brush;
|
||||
|
||||
for (let i = 0; i < segMetadata.length; i++) {
|
||||
brushModule.setters.metadata(displaySetInstanceUid, i, segMetadata[i]);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
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());
|
||||
const results = parseSeg(arrayBuffer, imageIds);
|
||||
|
||||
if (!results) {
|
||||
throw new Error('Fractional segmentations are not supported');
|
||||
}
|
||||
|
||||
const { segMetadata, toolState } = results;
|
||||
|
||||
segMetadata.seriesInstanceUid = seriesInstanceUid;
|
||||
|
||||
addSegMetadataToCornerstoneToolState(
|
||||
segMetadata,
|
||||
toolState,
|
||||
displaySetInstanceUid
|
||||
);
|
||||
|
||||
const cachedStack = StackManager.findOrCreateStack(
|
||||
study,
|
||||
referenceDisplaySet
|
||||
);
|
||||
const stack = Object.assign({}, cachedStack);
|
||||
stack.currentImageIdIndex = 0;
|
||||
|
||||
return {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
stack
|
||||
};
|
||||
}
|
||||
|
||||
export default handleSegmentationStorage;
|
||||
@@ -0,0 +1,3 @@
|
||||
import OHIFCornerstoneExtension from './OHIFCornerstoneExtension.js';
|
||||
|
||||
export default OHIFCornerstoneExtension;
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user