Re-add extensions as top level dir

This commit is contained in:
dannyrb committed 2019-04-16 12:00:06 -04:00
1 parent 77ec71a12a
commit 780f84af89
86 files changed
+51115

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
{
"presets": [
["@babel/preset-env", {
"targets": {
"ie": "11"
}
}],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime"
]
}
@@ -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
View File
@@ -0,0 +1,16 @@
{
"extends": [
"react-app",
"eslint:recommended",
"plugin:react/recommended"
],
"parser": "babel-eslint",
"env": {
"jest": true
},
"settings": {
"react": {
"version": "detect",
},
},
}
+26
View 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
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 9
- 8
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -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"
}
}
@@ -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 || {}),
}
@@ -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;
@@ -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;
@@ -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;
}
@@ -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;
@@ -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;
}
}
@@ -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;
@@ -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;
@@ -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;
@@ -0,0 +1,3 @@
import OHIFVTKExtension from './OHIFVTKExtension.js';
export default OHIFVTKExtension;
File diff suppressed because it is too large. Load diff