Re-add extensions as top level dir
This commit is contained in:
parent
77ec71a12a
commit
780f84af89
14
extensions/ohif-cornerstone-extension/.babelrc
Normal file
14
extensions/ohif-cornerstone-extension/.babelrc
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
9
extensions/ohif-cornerstone-extension/.editorconfig
Normal file
9
extensions/ohif-cornerstone-extension/.editorconfig
Normal 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
|
||||||
16
extensions/ohif-cornerstone-extension/.eslintrc
Normal file
16
extensions/ohif-cornerstone-extension/.eslintrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended"
|
||||||
|
],
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
26
extensions/ohif-cornerstone-extension/.gitignore
vendored
Normal file
26
extensions/ohif-cornerstone-extension/.gitignore
vendored
Normal file
@ -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
|
||||||
3
extensions/ohif-cornerstone-extension/.prettierrc
Normal file
3
extensions/ohif-cornerstone-extension/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
4
extensions/ohif-cornerstone-extension/.travis.yml
Normal file
4
extensions/ohif-cornerstone-extension/.travis.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 9
|
||||||
|
- 8
|
||||||
21
extensions/ohif-cornerstone-extension/LICENSE
Normal file
21
extensions/ohif-cornerstone-extension/LICENSE
Normal 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.
|
||||||
97
extensions/ohif-cornerstone-extension/package.json
Normal file
97
extensions/ohif-cornerstone-extension/package.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
68
extensions/ohif-cornerstone-extension/rollup.config.js
Normal file
68
extensions/ohif-cornerstone-extension/rollup.config.js
Normal file
@ -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 || {}),
|
||||||
|
}
|
||||||
111
extensions/ohif-cornerstone-extension/src/ConnectedCineDialog.js
Normal file
111
extensions/ohif-cornerstone-extension/src/ConnectedCineDialog.js
Normal file
@ -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;
|
||||||
39
extensions/ohif-cornerstone-extension/src/ToolbarModule.js
Normal file
39
extensions/ohif-cornerstone-extension/src/ToolbarModule.js
Normal 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;
|
||||||
28
extensions/ohif-cornerstone-extension/src/config.js
Normal file
28
extensions/ohif-cornerstone-extension/src/config.js
Normal file
@ -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;
|
||||||
3
extensions/ohif-cornerstone-extension/src/index.js
Normal file
3
extensions/ohif-cornerstone-extension/src/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import OHIFCornerstoneExtension from './OHIFCornerstoneExtension.js';
|
||||||
|
|
||||||
|
export default OHIFCornerstoneExtension;
|
||||||
5639
extensions/ohif-cornerstone-extension/yarn.lock
Normal file
5639
extensions/ohif-cornerstone-extension/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
14
extensions/ohif-dicom-html-extension/.babelrc
Normal file
14
extensions/ohif-dicom-html-extension/.babelrc
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
9
extensions/ohif-dicom-html-extension/.editorconfig
Normal file
9
extensions/ohif-dicom-html-extension/.editorconfig
Normal 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
|
||||||
16
extensions/ohif-dicom-html-extension/.eslintrc
Normal file
16
extensions/ohif-dicom-html-extension/.eslintrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended"
|
||||||
|
],
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
23
extensions/ohif-dicom-html-extension/.gitignore
vendored
Normal file
23
extensions/ohif-dicom-html-extension/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
# 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
|
||||||
3
extensions/ohif-dicom-html-extension/.prettierrc
Normal file
3
extensions/ohif-dicom-html-extension/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
4
extensions/ohif-dicom-html-extension/.travis.yml
Normal file
4
extensions/ohif-dicom-html-extension/.travis.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 9
|
||||||
|
- 8
|
||||||
21
extensions/ohif-dicom-html-extension/LICENSE
Normal file
21
extensions/ohif-dicom-html-extension/LICENSE
Normal 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.
|
||||||
86
extensions/ohif-dicom-html-extension/package.json
Normal file
86
extensions/ohif-dicom-html-extension/package.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"name": "ohif-dicom-html-extension",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"description": "OHIF extension for rendering structured reports to HTML",
|
||||||
|
"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": {
|
||||||
|
"dcmjs": "^0.3.3",
|
||||||
|
"ohif-core": "^0.2.6",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react": "^15.0.0 || ^16.0.0",
|
||||||
|
"react-dom": "^15.0.0 || ^16.0.0"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"@svgr/rollup": "^4.1.0",
|
||||||
|
"babel-eslint": "^9.0.0",
|
||||||
|
"cross-env": "^5.2.0",
|
||||||
|
"eslint": "5.6.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",
|
||||||
|
"react-scripts": "^2.1.2",
|
||||||
|
"rollup": "^0.68.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": "^1.6.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
55
extensions/ohif-dicom-html-extension/rollup.config.js
Normal file
55
extensions/ohif-dicom-html-extension/rollup.config.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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 svgr from '@svgr/rollup'
|
||||||
|
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',
|
||||||
|
'prop-types': 'PropTypes',
|
||||||
|
'ohif-core': 'OHIF',
|
||||||
|
'dcmjs': 'dcmjs'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/index.js',
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
file: pkg.main,
|
||||||
|
format: 'umd',
|
||||||
|
name: 'ohif-dicom-pdf-extension',
|
||||||
|
sourcemap: true,
|
||||||
|
globals
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: pkg.module,
|
||||||
|
format: 'es',
|
||||||
|
sourcemap: true,
|
||||||
|
globals
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
builtins(),
|
||||||
|
external(),
|
||||||
|
postcss({
|
||||||
|
modules: false
|
||||||
|
}),
|
||||||
|
url(),
|
||||||
|
svgr(),
|
||||||
|
babel({
|
||||||
|
exclude: 'node_modules/**',
|
||||||
|
plugins: [ '@babel/external-helpers' ],
|
||||||
|
externalHelpers: true,
|
||||||
|
runtimeHelpers: true
|
||||||
|
}),
|
||||||
|
resolve(),
|
||||||
|
commonjs()
|
||||||
|
],
|
||||||
|
external: Object.keys(pkg.peerDependencies || {}),
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
:root {
|
||||||
|
--text-color-primary: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DicomHtmlViewport {
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
}
|
||||||
242
extensions/ohif-dicom-html-extension/src/DicomHtmlViewport.js
Normal file
242
extensions/ohif-dicom-html-extension/src/DicomHtmlViewport.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import * as dcmjs from 'dcmjs';
|
||||||
|
import TypedArrayProp from './TypedArrayProp';
|
||||||
|
import './DicomHtmlViewport.css';
|
||||||
|
|
||||||
|
function getRelationshipString(data) {
|
||||||
|
switch (data.RelationshipType) {
|
||||||
|
case 'HAS CONCEPT MOD':
|
||||||
|
return 'Concept modifier: ';
|
||||||
|
case 'HAS OBS CONTEXT':
|
||||||
|
return 'Observation context: ';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMeaningString = data => {
|
||||||
|
if (data.ConceptNameCodeSequence) {
|
||||||
|
const { CodeMeaning } = data.ConceptNameCodeSequence;
|
||||||
|
|
||||||
|
return `${CodeMeaning} = `;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
function getValueString(data) {
|
||||||
|
switch (data.ValueType) {
|
||||||
|
case 'CODE':
|
||||||
|
const {
|
||||||
|
CodeMeaning,
|
||||||
|
CodeValue,
|
||||||
|
CodingSchemeDesignator
|
||||||
|
} = data.ConceptNameCodeSequence;
|
||||||
|
|
||||||
|
return `${CodeMeaning} (${CodeValue}, ${CodingSchemeDesignator})`;
|
||||||
|
|
||||||
|
case 'PNAME':
|
||||||
|
return data.PersonName;
|
||||||
|
|
||||||
|
case 'TEXT':
|
||||||
|
return data.TextValue;
|
||||||
|
|
||||||
|
case 'UIDREF':
|
||||||
|
return data.UID;
|
||||||
|
|
||||||
|
case 'NUM':
|
||||||
|
const { MeasuredValueSequence } = data;
|
||||||
|
const numValue = MeasuredValueSequence.NumericValue;
|
||||||
|
const codeValue =
|
||||||
|
MeasuredValueSequence.MeasurementUnitsCodeSequence.CodeValue;
|
||||||
|
return `${numValue} ${codeValue}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function constructPlainValue(data) {
|
||||||
|
const value = getValueString(data);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
return getRelationshipString(data) + getMeaningString(data) + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function constructContentSequence(data, header) {
|
||||||
|
if (!data.ContentSequence) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = data.ContentSequence.map(item => parseContent(item)).filter(
|
||||||
|
item => item
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
items
|
||||||
|
};
|
||||||
|
|
||||||
|
if (header) {
|
||||||
|
result.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseContent(data) {
|
||||||
|
if (data.ValueType) {
|
||||||
|
if (data.ValueType === 'CONTAINER') {
|
||||||
|
const header = data.ConceptNameCodeSequence.CodeMeaning;
|
||||||
|
|
||||||
|
return constructContentSequence(data, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
return constructPlainValue(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.ContentSequence) {
|
||||||
|
return constructContentSequence(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { DicomMetaDictionary, DicomMessage } = dcmjs.data;
|
||||||
|
|
||||||
|
function getMainData(data) {
|
||||||
|
const root = [];
|
||||||
|
|
||||||
|
const patientValue = `${data.PatientName} (${data.PatientSex}, #${
|
||||||
|
data.PatientID
|
||||||
|
})`;
|
||||||
|
root.push(getMainDataItem('Patient', patientValue));
|
||||||
|
|
||||||
|
const studyValue = data.StudyDescription;
|
||||||
|
root.push(getMainDataItem('Study', studyValue));
|
||||||
|
|
||||||
|
const seriesValue = `${data.SeriesDescription} (#${data.SeriesNumber})`;
|
||||||
|
root.push(getMainDataItem('Series', seriesValue));
|
||||||
|
|
||||||
|
const manufacturerValue = `${data.Manufacturer} (${
|
||||||
|
data.ManufacturerModelName
|
||||||
|
}, #${data.DeviceSerialNumber})`;
|
||||||
|
|
||||||
|
root.push(getMainDataItem('Manufacturer', manufacturerValue));
|
||||||
|
|
||||||
|
const mainDataObjects = {
|
||||||
|
CompletionFlag: 'Completion flag',
|
||||||
|
VerificationFlag: 'Verification flag'
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(mainDataObjects).forEach(key => {
|
||||||
|
if (!data[key]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = getMainDataItem(mainDataObjects[key], data[key]);
|
||||||
|
|
||||||
|
root.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Format these dates
|
||||||
|
const contentDateTimeValue = `${data.ContentDate} ${data.ContentTime}`;
|
||||||
|
root.push(getMainDataItem('Content Date/Time', contentDateTimeValue));
|
||||||
|
|
||||||
|
root.push();
|
||||||
|
|
||||||
|
return <div>{root}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getContentSequence = (data, level = 1) => {
|
||||||
|
let header;
|
||||||
|
|
||||||
|
if (data.ConceptNameCodeSequence) {
|
||||||
|
const {
|
||||||
|
CodeMeaning,
|
||||||
|
CodeValue,
|
||||||
|
CodingSchemeDesignator
|
||||||
|
} = data.ConceptNameCodeSequence;
|
||||||
|
|
||||||
|
header = `${CodeMeaning} (${CodeValue} - ${CodingSchemeDesignator})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = [];
|
||||||
|
if (header) {
|
||||||
|
const HeaderDynamicLevel = `h${level}`;
|
||||||
|
|
||||||
|
root.push(<HeaderDynamicLevel key={header}>{header}</HeaderDynamicLevel>);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(data).forEach(key => {
|
||||||
|
const value = data[key];
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (value instanceof Object) {
|
||||||
|
content = getContentSequence(value, level + 1);
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<div key={key}>
|
||||||
|
{key} - {data[key]}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
root.push(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div>{root}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMainDataItem(key, value) {
|
||||||
|
return (
|
||||||
|
<div key={key}>
|
||||||
|
<b>{key}</b>: {value}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DicomHtmlViewport extends Component {
|
||||||
|
state = {
|
||||||
|
content: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
byteArray: TypedArrayProp.uint8
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const dataSet = this.setContentFromByteArray(this.props.byteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
setContentFromByteArray(byteArray) {
|
||||||
|
const arrayBuffer = byteArray.buffer;
|
||||||
|
const dicomData = DicomMessage.readFile(arrayBuffer);
|
||||||
|
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
||||||
|
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||||
|
|
||||||
|
const mainData = getMainData(dataset);
|
||||||
|
const contentSequence = getContentSequence(dataset);
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
{mainData}
|
||||||
|
{contentSequence}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={'DicomHtmlViewport'}>
|
||||||
|
{this.state.content}
|
||||||
|
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DicomHtmlViewport;
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import OHIFDicomHtmlViewport from './OHIFDicomHtmlViewport.js';
|
||||||
|
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
|
||||||
|
|
||||||
|
export default class OHIFDicomHtmlExtension {
|
||||||
|
/**
|
||||||
|
* Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?)
|
||||||
|
*/
|
||||||
|
getExtensionId() {
|
||||||
|
return 'html';
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewportModule() {
|
||||||
|
return OHIFDicomHtmlViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSopClassHandler() {
|
||||||
|
return OHIFDicomHtmlSopClassHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPanelModuleDefinition() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getToolbarModuleDefinition() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import OHIF from 'ohif-core';
|
||||||
|
|
||||||
|
const { plugins, utils } = OHIF;
|
||||||
|
const { PLUGIN_TYPES } = plugins;
|
||||||
|
|
||||||
|
// TODO: Should probably use dcmjs for this
|
||||||
|
const SOP_CLASS_UIDS = {
|
||||||
|
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
||||||
|
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
||||||
|
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
|
||||||
|
PROCEDURE_LOG_STORAGE: '1.2.840.10008.5.1.4.1.1.88.40',
|
||||||
|
MAMMOGRAPHY_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.50',
|
||||||
|
CHEST_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.65',
|
||||||
|
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67'
|
||||||
|
};
|
||||||
|
|
||||||
|
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 OHIFDicomHtmlSopClassHandler = {
|
||||||
|
id: 'OHIFDicomHtmlSopClassHandler',
|
||||||
|
type: PLUGIN_TYPES.SOP_CLASS_HANDLER,
|
||||||
|
sopClassUids,
|
||||||
|
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||||
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugin: 'html',
|
||||||
|
displaySetInstanceUid: utils.guid(),
|
||||||
|
wadoRoot: study.getData().wadoRoot,
|
||||||
|
wadoUri: instance.getData().wadouri,
|
||||||
|
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||||
|
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||||
|
studyInstanceUid: study.getStudyInstanceUID(),
|
||||||
|
authorizationHeaders
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OHIFDicomHtmlSopClassHandler;
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import DicomHtmlViewport from './DicomHtmlViewport';
|
||||||
|
|
||||||
|
class OHIFDicomHtmlViewport extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
studies: PropTypes.object,
|
||||||
|
displaySet: PropTypes.object,
|
||||||
|
viewportIndex: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
byteArray: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { displaySet } = this.props.viewportData;
|
||||||
|
const {
|
||||||
|
studyInstanceUid,
|
||||||
|
seriesInstanceUid,
|
||||||
|
sopInstanceUid,
|
||||||
|
wadoRoot,
|
||||||
|
wadoUri,
|
||||||
|
authorizationHeaders
|
||||||
|
} = displaySet;
|
||||||
|
|
||||||
|
this.retrieveDicomData(
|
||||||
|
studyInstanceUid,
|
||||||
|
seriesInstanceUid,
|
||||||
|
sopInstanceUid,
|
||||||
|
wadoRoot,
|
||||||
|
wadoUri,
|
||||||
|
authorizationHeaders
|
||||||
|
).then(
|
||||||
|
byteArray => {
|
||||||
|
this.setState({
|
||||||
|
byteArray
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.setState({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieveDicomData(
|
||||||
|
studyInstanceUid,
|
||||||
|
seriesInstanceUid,
|
||||||
|
sopInstanceUid,
|
||||||
|
wadoRoot,
|
||||||
|
wadoUri,
|
||||||
|
authorizationHeaders
|
||||||
|
) {
|
||||||
|
// TODO: Passing in a lot of data we aren't using
|
||||||
|
|
||||||
|
// 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: authorizationHeaders
|
||||||
|
})
|
||||||
|
.then(response => response.arrayBuffer())
|
||||||
|
.then(arraybuffer => {
|
||||||
|
return new Uint8Array(arraybuffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.state.byteArray && (
|
||||||
|
<DicomHtmlViewport byteArray={this.state.byteArray} />
|
||||||
|
)}
|
||||||
|
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OHIFDicomHtmlViewport;
|
||||||
205
extensions/ohif-dicom-html-extension/src/TypedArrayProp.js
Normal file
205
extensions/ohif-dicom-html-extension/src/TypedArrayProp.js
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// https://github.com/facebook/prop-types/issues/69
|
||||||
|
|
||||||
|
export const TypedArrayProp = {
|
||||||
|
any: (props, propName, componentName) => {
|
||||||
|
let obj = props[propName];
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
obj instanceof Float64Array ||
|
||||||
|
obj instanceof Float32Array ||
|
||||||
|
obj instanceof Int32Array ||
|
||||||
|
obj instanceof Int16Array ||
|
||||||
|
obj instanceof Int8Array ||
|
||||||
|
obj instanceof Uint32Array ||
|
||||||
|
obj instanceof Uint16Array ||
|
||||||
|
obj instanceof Uint8Array ||
|
||||||
|
obj instanceof Uint8ClampedArray
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a typed array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
float64: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Float64Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a Float64Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
float32: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Float32Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a Float32Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
float: (props, propName, componentName) => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
props[propName] instanceof Float64Array ||
|
||||||
|
props[propName] instanceof Float32Array
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a Float32Array or Float64Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
int32: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Int32Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected an Int32Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
int16: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Int16Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected an In16Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
int8: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Int8Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected an Int8Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
int: (props, propName, componentName) => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
props[propName] instanceof Int32Array ||
|
||||||
|
props[propName] instanceof Int16Array ||
|
||||||
|
props[propName] instanceof Int8Array
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected an Int32Array, In16Array, or Int8Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint32: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint32Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a Uint32Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint16: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint16Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a Uint16Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint8: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint8Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a Uint8Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint8clamped: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint8ClampedArray)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a Uint8ClampedArray.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint: (props, propName, componentName) => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
props[propName] instanceof Uint32Array ||
|
||||||
|
props[propName] instanceof Uint16Array ||
|
||||||
|
props[propName] instanceof Uint8Array ||
|
||||||
|
props[propName] instanceof Uint8ClampedArray
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' +
|
||||||
|
propName +
|
||||||
|
'` supplied to' +
|
||||||
|
' `' +
|
||||||
|
componentName +
|
||||||
|
'`. Expected a Uint32Array, Uint16Array, Uint8Array, or Uint8ClampedArray.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { TypedArrayProp as default };
|
||||||
3
extensions/ohif-dicom-html-extension/src/index.js
Normal file
3
extensions/ohif-dicom-html-extension/src/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import OHIFDicomHtmlExtension from './OHIFDicomHtmlExtension.js';
|
||||||
|
|
||||||
|
export default OHIFDicomHtmlExtension;
|
||||||
10892
extensions/ohif-dicom-html-extension/yarn.lock
Normal file
10892
extensions/ohif-dicom-html-extension/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
14
extensions/ohif-dicom-microscopy-extension/.babelrc
Normal file
14
extensions/ohif-dicom-microscopy-extension/.babelrc
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
9
extensions/ohif-dicom-microscopy-extension/.editorconfig
Normal file
9
extensions/ohif-dicom-microscopy-extension/.editorconfig
Normal 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
|
||||||
16
extensions/ohif-dicom-microscopy-extension/.eslintrc
Normal file
16
extensions/ohif-dicom-microscopy-extension/.eslintrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended"
|
||||||
|
],
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
23
extensions/ohif-dicom-microscopy-extension/.gitignore
vendored
Normal file
23
extensions/ohif-dicom-microscopy-extension/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
# 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
|
||||||
3
extensions/ohif-dicom-microscopy-extension/.prettierrc
Normal file
3
extensions/ohif-dicom-microscopy-extension/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
4
extensions/ohif-dicom-microscopy-extension/.travis.yml
Normal file
4
extensions/ohif-dicom-microscopy-extension/.travis.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 9
|
||||||
|
- 8
|
||||||
21
extensions/ohif-dicom-microscopy-extension/LICENSE
Normal file
21
extensions/ohif-dicom-microscopy-extension/LICENSE
Normal 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.
|
||||||
83
extensions/ohif-dicom-microscopy-extension/package.json
Normal file
83
extensions/ohif-dicom-microscopy-extension/package.json
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"name": "ohif-dicom-microscopy-extension",
|
||||||
|
"version": "0.0.5",
|
||||||
|
"description": "OHIF extension for Dicom Microscopy",
|
||||||
|
"author": "OHIF",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "OHIF/Viewers",
|
||||||
|
"main": "dist/index.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": {
|
||||||
|
"react": "^15.0.0 || ^16.0.0",
|
||||||
|
"react-dom": "^15.0.0 || ^16.0.0",
|
||||||
|
"ohif-core": "^0.2.6"
|
||||||
|
},
|
||||||
|
"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": "^9.0.0",
|
||||||
|
"cross-env": "^5.2.0",
|
||||||
|
"eslint": "5.6.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",
|
||||||
|
"rollup": "^0.68.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": "^1.6.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",
|
||||||
|
"dicom-microscopy-viewer": "^0.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
58
extensions/ohif-dicom-microscopy-extension/rollup.config.js
Normal file
58
extensions/ohif-dicom-microscopy-extension/rollup.config.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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',
|
||||||
|
'ohif-core': 'OHIF'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/index.js',
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
file: pkg.main,
|
||||||
|
format: 'umd',
|
||||||
|
name: 'ohif-dicom-microscopy-extension',
|
||||||
|
sourcemap: true,
|
||||||
|
globals
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: pkg.module,
|
||||||
|
format: 'es',
|
||||||
|
sourcemap: true,
|
||||||
|
globals
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
builtins(),
|
||||||
|
external(),
|
||||||
|
postcss({
|
||||||
|
modules: false
|
||||||
|
}),
|
||||||
|
url(),
|
||||||
|
babel({
|
||||||
|
exclude: 'node_modules/**',
|
||||||
|
plugins: [ '@babel/external-helpers' ],
|
||||||
|
externalHelpers: true,
|
||||||
|
runtimeHelpers: true
|
||||||
|
}),
|
||||||
|
resolve(),
|
||||||
|
commonjs({
|
||||||
|
include: 'node_modules/**',
|
||||||
|
namedExports: {
|
||||||
|
'node_modules/dicom-microscopy-viewer/build/dicom-microscopy-viewer.js': [
|
||||||
|
'api'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
external: Object.keys(pkg.peerDependencies || {}),
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import OHIF from 'ohif-core';
|
||||||
|
|
||||||
|
const { utils } = OHIF;
|
||||||
|
|
||||||
|
const SOP_CLASS_UIDS = {
|
||||||
|
VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.6'
|
||||||
|
};
|
||||||
|
|
||||||
|
const DicomMicroscopySopClassHandler = {
|
||||||
|
id: 'DicomMicroscopySopClassHandlerPlugin',
|
||||||
|
sopClassUids: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE],
|
||||||
|
getDisplaySetFromSeries(series, study, dicomWebClient) {
|
||||||
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
|
// Note: We are passing the dicomweb client into each viewport!
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugin: 'microscopy',
|
||||||
|
displaySetInstanceUid: utils.guid(),
|
||||||
|
dicomWebClient,
|
||||||
|
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||||
|
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||||
|
studyInstanceUid: study.getStudyInstanceUID()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DicomMicroscopySopClassHandler;
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { api } from 'dicom-microscopy-viewer';
|
||||||
|
|
||||||
|
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
|
||||||
|
|
||||||
|
class DicomMicroscopyViewport extends Component {
|
||||||
|
state = {
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.container = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
// install the microscopy renderer into the web page.
|
||||||
|
// you should only do this once.
|
||||||
|
installOpenLayersRenderer(container, displaySet) {
|
||||||
|
const dicomWebClient = displaySet.dicomWebClient;
|
||||||
|
|
||||||
|
const searchInstanceOptions = {
|
||||||
|
studyInstanceUID: displaySet.studyInstanceUid,
|
||||||
|
seriesInstanceUID: displaySet.seriesInstanceUid
|
||||||
|
};
|
||||||
|
|
||||||
|
dicomWebClient
|
||||||
|
.searchForInstances(searchInstanceOptions)
|
||||||
|
.then(instances => {
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < instances.length; i++) {
|
||||||
|
const sopInstanceUID = instances[i]['00080018']['Value'][0];
|
||||||
|
const retrieveInstanceOptions = {
|
||||||
|
studyInstanceUID: displaySet.studyInstanceUid,
|
||||||
|
seriesInstanceUID: displaySet.seriesInstanceUid,
|
||||||
|
sopInstanceUID
|
||||||
|
};
|
||||||
|
|
||||||
|
const promise = dicomWebClient
|
||||||
|
.retrieveInstanceMetadata(retrieveInstanceOptions)
|
||||||
|
.then(metadata => {
|
||||||
|
const imageType = metadata[0]['00080008']['Value'];
|
||||||
|
if (imageType[2] === 'VOLUME') {
|
||||||
|
return metadata[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
|
})
|
||||||
|
.then(metadata => {
|
||||||
|
metadata = metadata.filter(m => m);
|
||||||
|
|
||||||
|
const viewer = new microscopyViewer({
|
||||||
|
client: dicomWebClient,
|
||||||
|
metadata
|
||||||
|
});
|
||||||
|
|
||||||
|
viewer.render({ container });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { studies, displaySet } = this.props.viewportData;
|
||||||
|
|
||||||
|
this.installOpenLayersRenderer(this.container.current, displaySet);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const style = { width: '100%', height: '100%' };
|
||||||
|
return (
|
||||||
|
<div className={'DicomMicroscopyViewer'} style={style}>
|
||||||
|
{this.state.error ? (
|
||||||
|
<h2>{JSON.stringify(this.state.error)}</h2>
|
||||||
|
) : (
|
||||||
|
<div style={style} ref={this.container} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DicomMicroscopyViewport;
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import DicomMicroscopyViewport from './DicomMicroscopyViewport.js';
|
||||||
|
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
|
||||||
|
|
||||||
|
export default class OHIFDicomMicroscopyExtension {
|
||||||
|
/**
|
||||||
|
* Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?)
|
||||||
|
*/
|
||||||
|
getExtensionId() {
|
||||||
|
return 'microscopy';
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewportModule() {
|
||||||
|
return DicomMicroscopyViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSopClassHandler() {
|
||||||
|
return DicomMicroscopySopClassHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPanelModule() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getToolbarModule() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
extensions/ohif-dicom-microscopy-extension/src/index.js
Normal file
3
extensions/ohif-dicom-microscopy-extension/src/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import OHIFDicomMicroscopyExtension from './OHIFDicomMicroscopyExtension.js';
|
||||||
|
|
||||||
|
export default OHIFDicomMicroscopyExtension;
|
||||||
11025
extensions/ohif-dicom-microscopy-extension/yarn.lock
Normal file
11025
extensions/ohif-dicom-microscopy-extension/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
14
extensions/ohif-dicom-pdf-extension/.babelrc
Normal file
14
extensions/ohif-dicom-pdf-extension/.babelrc
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
9
extensions/ohif-dicom-pdf-extension/.editorconfig
Normal file
9
extensions/ohif-dicom-pdf-extension/.editorconfig
Normal 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
|
||||||
16
extensions/ohif-dicom-pdf-extension/.eslintrc
Normal file
16
extensions/ohif-dicom-pdf-extension/.eslintrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended"
|
||||||
|
],
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
23
extensions/ohif-dicom-pdf-extension/.gitignore
vendored
Normal file
23
extensions/ohif-dicom-pdf-extension/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
# 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
|
||||||
3
extensions/ohif-dicom-pdf-extension/.prettierrc
Normal file
3
extensions/ohif-dicom-pdf-extension/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
4
extensions/ohif-dicom-pdf-extension/.travis.yml
Normal file
4
extensions/ohif-dicom-pdf-extension/.travis.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 9
|
||||||
|
- 8
|
||||||
21
extensions/ohif-dicom-pdf-extension/LICENSE
Normal file
21
extensions/ohif-dicom-pdf-extension/LICENSE
Normal 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.
|
||||||
88
extensions/ohif-dicom-pdf-extension/package.json
Normal file
88
extensions/ohif-dicom-pdf-extension/package.json
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"name": "ohif-dicom-pdf-extension",
|
||||||
|
"version": "0.0.6",
|
||||||
|
"description": "OHIF extension for Dicom PDF",
|
||||||
|
"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": {
|
||||||
|
"dicom-parser": "^1.8.3",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"@svgr/rollup": "^4.1.0",
|
||||||
|
"babel-eslint": "^9.0.0",
|
||||||
|
"cross-env": "^5.2.0",
|
||||||
|
"eslint": "5.6.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",
|
||||||
|
"react-scripts": "^2.1.2",
|
||||||
|
"rollup": "^0.68.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": "^1.6.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.isequal": "^4.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
55
extensions/ohif-dicom-pdf-extension/rollup.config.js
Normal file
55
extensions/ohif-dicom-pdf-extension/rollup.config.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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 svgr from '@svgr/rollup'
|
||||||
|
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',
|
||||||
|
'prop-types': 'PropTypes',
|
||||||
|
'ohif-core': 'OHIF',
|
||||||
|
'dicom-parser': 'dicomParser'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/index.js',
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
file: pkg.main,
|
||||||
|
format: 'umd',
|
||||||
|
name: 'ohif-dicom-pdf-extension',
|
||||||
|
sourcemap: true,
|
||||||
|
globals
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: pkg.module,
|
||||||
|
format: 'es',
|
||||||
|
sourcemap: true,
|
||||||
|
globals
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
builtins(),
|
||||||
|
external(),
|
||||||
|
postcss({
|
||||||
|
modules: false
|
||||||
|
}),
|
||||||
|
url(),
|
||||||
|
svgr(),
|
||||||
|
babel({
|
||||||
|
exclude: 'node_modules/**',
|
||||||
|
plugins: [ '@babel/external-helpers' ],
|
||||||
|
externalHelpers: true,
|
||||||
|
runtimeHelpers: true
|
||||||
|
}),
|
||||||
|
resolve(),
|
||||||
|
commonjs()
|
||||||
|
],
|
||||||
|
external: Object.keys(pkg.peerDependencies || {}),
|
||||||
|
}
|
||||||
83
extensions/ohif-dicom-pdf-extension/src/DicomPDFViewport.js
Normal file
83
extensions/ohif-dicom-pdf-extension/src/DicomPDFViewport.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import dicomParser from 'dicom-parser';
|
||||||
|
import TypedArrayProp from './TypedArrayProp';
|
||||||
|
|
||||||
|
// TODO: Should probably use dcmjs for this
|
||||||
|
const SOP_CLASS_UIDS = {
|
||||||
|
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1'
|
||||||
|
};
|
||||||
|
|
||||||
|
class DicomPDFViewport extends Component {
|
||||||
|
state = {
|
||||||
|
fileURL: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
byteArray: TypedArrayProp.uint8
|
||||||
|
};
|
||||||
|
|
||||||
|
renderPDF = dataSet => {
|
||||||
|
const sopClassUid = dataSet.string('x00080016');
|
||||||
|
|
||||||
|
if (sopClassUid !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
|
||||||
|
throw new Error('This is not a DICOM-encapsulated PDF');
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileTag = dataSet.elements.x00420011;
|
||||||
|
const offset = fileTag.dataOffset;
|
||||||
|
const remainder = offset + fileTag.length;
|
||||||
|
const pdfByteArray = dataSet.byteArray.slice(offset, remainder);
|
||||||
|
const PDF = new Blob([pdfByteArray], { type: 'application/pdf' });
|
||||||
|
const fileURL = URL.createObjectURL(PDF);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
fileURL
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
parseByteArray = byteArray => {
|
||||||
|
const options = {
|
||||||
|
untilTag: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
let dataSet;
|
||||||
|
|
||||||
|
try {
|
||||||
|
dataSet = dicomParser.parseDicom(byteArray, options);
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const dataSet = this.parseByteArray(this.props.byteArray);
|
||||||
|
|
||||||
|
this.renderPDF(dataSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={'DicomPDFViewport'}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
>
|
||||||
|
{this.state.fileURL && (
|
||||||
|
<object
|
||||||
|
data={this.state.fileURL}
|
||||||
|
type="application/pdf"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DicomPDFViewport;
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
function OHIFComponentPlugin(props) {
|
||||||
|
return (<React.Fragment>{props.children}</React.Fragment>);
|
||||||
|
}
|
||||||
|
|
||||||
|
OHIFComponentPlugin.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
init: PropTypes.func.isRequired,
|
||||||
|
destroy: PropTypes.func.isRequired,
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OHIFComponentPlugin;
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import OHIFDicomPDFViewport from './OHIFDicomPDFViewport.js';
|
||||||
|
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
|
||||||
|
|
||||||
|
export default class OHIFDicomPDFExtension {
|
||||||
|
/**
|
||||||
|
* Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?)
|
||||||
|
*/
|
||||||
|
getExtensionId() {
|
||||||
|
return 'pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewportModule() {
|
||||||
|
return OHIFDicomPDFViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSopClassHandler() {
|
||||||
|
return OHIFDicomPDFSopClassHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPanelModule() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getToolbarModule() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import OHIF from "ohif-core";
|
||||||
|
|
||||||
|
const { plugins, utils } = OHIF;
|
||||||
|
const { PLUGIN_TYPES } = plugins;
|
||||||
|
|
||||||
|
// TODO: Should probably use dcmjs for this
|
||||||
|
const SOP_CLASS_UIDS = {
|
||||||
|
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1'
|
||||||
|
};
|
||||||
|
|
||||||
|
const OHIFDicomPDFSopClassHandler = {
|
||||||
|
id: 'OHIFDicomPDFSopClassHandlerPlugin',
|
||||||
|
type: PLUGIN_TYPES.SOP_CLASS_HANDLER,
|
||||||
|
sopClassUids: [
|
||||||
|
SOP_CLASS_UIDS.ENCAPSULATED_PDF
|
||||||
|
],
|
||||||
|
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||||
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugin: 'pdf',
|
||||||
|
displaySetInstanceUid: utils.guid(),
|
||||||
|
wadoRoot: study.getData().wadoRoot,
|
||||||
|
wadoUri: instance.getData().wadouri,
|
||||||
|
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||||
|
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||||
|
studyInstanceUid: study.getStudyInstanceUID(),
|
||||||
|
authorizationHeaders: authorizationHeaders
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OHIFDicomPDFSopClassHandler;
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import OHIF from 'ohif-core';
|
||||||
|
import OHIFComponentPlugin from './OHIFComponentPlugin.js';
|
||||||
|
import DicomPDFViewport from './DicomPDFViewport';
|
||||||
|
|
||||||
|
const { DICOMWeb } = OHIF;
|
||||||
|
|
||||||
|
class OHIFDicomPDFViewport extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
studies: PropTypes.object,
|
||||||
|
displaySet: PropTypes.object,
|
||||||
|
viewportIndex: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
byteArray: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
static id = 'DicomPDFViewportPDF';
|
||||||
|
|
||||||
|
static init() {
|
||||||
|
console.log('DicomPDFViewport init()');
|
||||||
|
}
|
||||||
|
|
||||||
|
static destroy() {
|
||||||
|
console.log('DicomPDFViewport destroy()');
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { displaySet } = this.props.viewportData;
|
||||||
|
const { studyInstanceUid, seriesInstanceUid, sopInstanceUid, wadoRoot, wadoUri, authorizationHeaders} = displaySet;
|
||||||
|
|
||||||
|
this.retrieveDicomData(studyInstanceUid, seriesInstanceUid, sopInstanceUid, wadoRoot, wadoUri, authorizationHeaders).then(byteArray => {
|
||||||
|
this.setState({
|
||||||
|
byteArray
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
this.setState({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieveDicomData(studyInstanceUid, seriesInstanceUid, sopInstanceUid, wadoRoot, wadoUri, authorizationHeaders) {
|
||||||
|
// TODO: Passing in a lot of data we aren't using
|
||||||
|
|
||||||
|
// 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: authorizationHeaders
|
||||||
|
}).then(response => response.arrayBuffer()).then(arraybuffer => {
|
||||||
|
return new Uint8Array(arraybuffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { id, init, destroy } = OHIFDicomPDFViewport;
|
||||||
|
const pluginProps = { id, init, destroy };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OHIFComponentPlugin {...pluginProps}>
|
||||||
|
{this.state.byteArray && (
|
||||||
|
<DicomPDFViewport byteArray={this.state.byteArray} />
|
||||||
|
)}
|
||||||
|
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>}
|
||||||
|
</OHIFComponentPlugin>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OHIFDicomPDFViewport;
|
||||||
137
extensions/ohif-dicom-pdf-extension/src/TypedArrayProp.js
Normal file
137
extensions/ohif-dicom-pdf-extension/src/TypedArrayProp.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// https://github.com/facebook/prop-types/issues/69
|
||||||
|
|
||||||
|
export const TypedArrayProp = {
|
||||||
|
any: (props, propName, componentName) => {
|
||||||
|
let obj = props[propName];
|
||||||
|
if (!(obj instanceof Float64Array ||
|
||||||
|
obj instanceof Float32Array ||
|
||||||
|
obj instanceof Int32Array ||
|
||||||
|
obj instanceof Int16Array ||
|
||||||
|
obj instanceof Int8Array ||
|
||||||
|
obj instanceof Uint32Array ||
|
||||||
|
obj instanceof Uint16Array ||
|
||||||
|
obj instanceof Uint8Array ||
|
||||||
|
obj instanceof Uint8ClampedArray)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a typed array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
float64: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Float64Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a Float64Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
float32: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Float32Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a Float32Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
float: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Float64Array ||
|
||||||
|
props[propName] instanceof Float32Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a Float32Array or Float64Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
int32: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Int32Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected an Int32Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
int16: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Int16Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected an In16Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
int8: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Int8Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected an Int8Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
int: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Int32Array ||
|
||||||
|
props[propName] instanceof Int16Array ||
|
||||||
|
props[propName] instanceof Int8Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected an Int32Array, In16Array, or Int8Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint32: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint32Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a Uint32Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint16: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint16Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a Uint16Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint8: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint8Array)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a Uint8Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint8clamped: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint8ClampedArray)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a Uint8ClampedArray.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uint: (props, propName, componentName) => {
|
||||||
|
if (!(props[propName] instanceof Uint32Array ||
|
||||||
|
props[propName] instanceof Uint16Array ||
|
||||||
|
props[propName] instanceof Uint8Array ||
|
||||||
|
props[propName] instanceof Uint8ClampedArray)) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid prop `' + propName + '` supplied to' +
|
||||||
|
' `' + componentName + '`. Expected a Uint32Array, Uint16Array, Uint8Array, or Uint8ClampedArray.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { TypedArrayProp as default };
|
||||||
3
extensions/ohif-dicom-pdf-extension/src/index.js
Normal file
3
extensions/ohif-dicom-pdf-extension/src/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import OHIFDicomPDFExtension from './OHIFDicomPDFExtension.js';
|
||||||
|
|
||||||
|
export default OHIFDicomPDFExtension;
|
||||||
10901
extensions/ohif-dicom-pdf-extension/yarn.lock
Normal file
10901
extensions/ohif-dicom-pdf-extension/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
14
extensions/ohif-vtk-extension/.babelrc
Normal file
14
extensions/ohif-vtk-extension/.babelrc
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
9
extensions/ohif-vtk-extension/.editorconfig
Normal file
9
extensions/ohif-vtk-extension/.editorconfig
Normal 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
|
||||||
16
extensions/ohif-vtk-extension/.eslintrc
Normal file
16
extensions/ohif-vtk-extension/.eslintrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended"
|
||||||
|
],
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
26
extensions/ohif-vtk-extension/.gitignore
vendored
Normal file
26
extensions/ohif-vtk-extension/.gitignore
vendored
Normal file
@ -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
|
||||||
3
extensions/ohif-vtk-extension/.prettierrc
Normal file
3
extensions/ohif-vtk-extension/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
4
extensions/ohif-vtk-extension/.travis.yml
Normal file
4
extensions/ohif-vtk-extension/.travis.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 9
|
||||||
|
- 8
|
||||||
21
extensions/ohif-vtk-extension/LICENSE
Normal file
21
extensions/ohif-vtk-extension/LICENSE
Normal 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.
|
||||||
93
extensions/ohif-vtk-extension/package.json
Normal file
93
extensions/ohif-vtk-extension/package.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"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-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.11",
|
||||||
|
"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": "^0.0.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
76
extensions/ohif-vtk-extension/rollup.config.js
Normal file
76
extensions/ohif-vtk-extension/rollup.config.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
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: {
|
||||||
|
'node_modules/react-vtkjs-viewport/dist/index.js': [
|
||||||
|
'getImageData',
|
||||||
|
'loadImageData',
|
||||||
|
'VTKViewport',
|
||||||
|
'VTKMPRViewport',
|
||||||
|
],
|
||||||
|
'.yalc/react-vtkjs-viewport/dist/index.js': [
|
||||||
|
'getImageData',
|
||||||
|
'loadImageData',
|
||||||
|
'VTKViewport',
|
||||||
|
'VTKMPRViewport',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
external: Object.keys(pkg.peerDependencies || {}),
|
||||||
|
}
|
||||||
36
extensions/ohif-vtk-extension/src/ConnectedToolbarSection.js
Normal file
36
extensions/ohif-vtk-extension/src/ConnectedToolbarSection.js
Normal 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 Icons = 'icons.svg';
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const activeButton = state.tools.buttons.find(tool => tool.active === true);
|
||||||
|
|
||||||
|
return {
|
||||||
|
buttons: [ {
|
||||||
|
command: 'Rotate',
|
||||||
|
type: 'tool',
|
||||||
|
text: 'Rotate',
|
||||||
|
svgUrl: `${Icons}#3d-rotate`,
|
||||||
|
active: true
|
||||||
|
}],
|
||||||
|
activeCommand: 'Rotate'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
setToolActive: tool => {
|
||||||
|
//dispatch(setToolActive(tool.command))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConnectedToolbarSection = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ToolbarSection);
|
||||||
|
|
||||||
|
export default ConnectedToolbarSection;
|
||||||
61
extensions/ohif-vtk-extension/src/ConnectedVTKViewport.js
Normal file
61
extensions/ohif-vtk-extension/src/ConnectedVTKViewport.js
Normal 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;
|
||||||
17
extensions/ohif-vtk-extension/src/LoadingIndicator.css
Normal file
17
extensions/ohif-vtk-extension/src/LoadingIndicator.css
Normal 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;
|
||||||
|
}
|
||||||
48
extensions/ohif-vtk-extension/src/LoadingIndicator.js
Normal file
48
extensions/ohif-vtk-extension/src/LoadingIndicator.js
Normal 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;
|
||||||
54
extensions/ohif-vtk-extension/src/OHIFVTKExtension.js
Normal file
54
extensions/ohif-vtk-extension/src/OHIFVTKExtension.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
263
extensions/ohif-vtk-extension/src/OHIFVTKViewport.js
Normal file
263
extensions/ohif-vtk-extension/src/OHIFVTKViewport.js
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
let imageDataObject;
|
||||||
|
let labelmapDataObject;
|
||||||
|
let doneLoadingCallback;
|
||||||
|
let callbacks;
|
||||||
|
|
||||||
|
switch (sopClassUid) {
|
||||||
|
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||||
|
throw new Error("Not yet implemented");
|
||||||
|
|
||||||
|
const data = handleSegmentationStorage(stack.imageIds, displaySetInstanceUid, cornerstone);
|
||||||
|
imageDataObject = data.referenceDataObject;
|
||||||
|
labelmapDataObject = data.labelmapDataObject;
|
||||||
|
|
||||||
|
doneLoadingCallback = () => {
|
||||||
|
resolve({
|
||||||
|
data: imageDataObject.vtkImageData,
|
||||||
|
labelmap: labelmapDataObject
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
callbacks = [
|
||||||
|
doneLoadingCallback
|
||||||
|
];
|
||||||
|
|
||||||
|
loadImageData(imageDataObject, callbacks, cornerstone);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid, cornerstone);
|
||||||
|
doneLoadingCallback = () => {
|
||||||
|
resolve({
|
||||||
|
data: imageDataObject.vtkImageData,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
15
extensions/ohif-vtk-extension/src/ToolbarModule.js
Normal file
15
extensions/ohif-vtk-extension/src/ToolbarModule.js
Normal 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;
|
||||||
121
extensions/ohif-vtk-extension/src/handleSegmentationStorage.js
Normal file
121
extensions/ohif-vtk-extension/src/handleSegmentationStorage.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import OHIF from 'ohif-core';
|
||||||
|
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;
|
||||||
3
extensions/ohif-vtk-extension/src/index.js
Normal file
3
extensions/ohif-vtk-extension/src/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import OHIFVTKExtension from './OHIFVTKExtension.js';
|
||||||
|
|
||||||
|
export default OHIFVTKExtension;
|
||||||
8951
extensions/ohif-vtk-extension/yarn.lock
Normal file
8951
extensions/ohif-vtk-extension/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user