Merge pull request #549 from dannyrb/feature/extensions-panels-and-docs

Feature/extensions panels and docs
This commit is contained in:
Danny Brown 2019-06-21 09:13:42 -04:00 committed by GitHub
commit f7ba7621ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1190 additions and 1064 deletions

View File

@ -8,57 +8,121 @@ toolbar, or as complex as a new viewport capable of rendering volumes in 3D.
- [Overview](#overview)
- [Modules](#modules)
- [Viewport](#viewport)
- [Toolbar](#toolbar)
- [SOP Class Handler](#sopclasshandler)
- [Panel](#panel)
- [Commands](#commands)
- [Hotkeys](#hotkeys)
- [Toolbar](#toolbar)
- [Panel](#panel)
- [Viewport](#viewport)
- [SOP Class Handler](#sopclasshandler)
## Overview
At a glance, an extension is a class or object that has a `getExtensionId()`
method, and one or more "module" methods. You can find an abbreviated extension
below, or
[view the source](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneExtension.js#L32-L65)
of our `cornerstone` viewport extension.
At a glance, an extension is a javascript object that has an `id` property, and
one or more "module" methods. You can find an abbreviated extension below, or
[view the source][example-ext-src] of our example extension.
```js
class myCustomExtension {
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'example-extension',
/** Required */
getExtensionId: () => 'my-extension-id';
/**
* Registers one or more named commands scoped to a context. Commands are
* the primary means for...
*/
getCommandsModule() {
return {
defaultContext: 'VIEWER'
actions: { ... },
definitions: { ... }
}
},
/** React component that receives props from ConnectLayoutManager
* If more than one viewport module is registered, SopClassHandler
* is used to help determine which component is used */
getViewportModule: () => reactViewportComponent;
/**
* Allows you to provide toolbar definitions that will be merged with any
* existing application toolbar configuration. Used to determine which
* buttons should be visible when, their order, what happens when they're
* clicked, etc.
*/
getToolbarModule() {
return {
definitions: [ ... ],
defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE'
}
}
/** React component that adds buttons/behavior to the viewer Toolbar */
getToolbarModule: () => reactToolbarComponent;
/**
* Not yet implemented
*/
getPanelModule: () => null,
/**
* Registers a ReactComponent that should be used to render data in a
* Viewport. The first registered viewport is our "default viewport". If
* more than one viewport is registered, we use `SopClassHandlers` to
* determine which viewport should be used.
*/
getViewportModule: () => reactViewportComponent,
/** Provides a whitelist of SOPClassUIDs the viewport is capable of rendering.
* Can modify default behavior for methods like `getDisplaySetFromSeries` */
getSopClassHandler: () => {
id: 'some-other-unique-id',
type: PLUGIN_TYPES.SOP_CLASS_HANDLER,
sopClassUids: ['string'],
getDisplaySetFromSeries: (series, study, dicomWebClient, authorizationHeaders) => ...
};
// Not yet used
getPanelModule: () => null;
sopClassUids: [ ... ],
getDisplaySetFromSeries: (series, study, dicomWebClient, authorizationHeaders) => { ... }
},
}
```
### Modules
There are a few different kinds of modules. Each kind of module allows us to
extend the viewer in a different way, and provides a consistent API for us to do
so. You can find a full list of the
[different types of modules `in ohif-core`](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6).
Information on each type of module, it's API, and how we determine when/where it
should be used is included below:
There are a few different module types. Each module type allows us to extend the
viewer in a different way, and provides a consistent API for us to do so. You
can find a full list of the different types of modules
[`in ohif-core`][module-types]. Information on each type of module, it's API,
and how we determine when/where it should be used is included below.
> NOTE: Modifying the extensions/modules registered to the OHIF Viewer currently
> requires us to import and pass extensions to the ExtensionManager in
> `src/App.js`, then rebuild the application. Long-term, we intend to make it
> possible to accomplish this without a build step.
#### Commands
The Commands Module allows us to register one or more commands scoped to
specific contexts. Commands can be run by [hotkeys][#], [toolbar buttons][#],
and any registered custom react component (like a [viewport][#] or [panel][#]).
Here is a simple example commands module:
```js
{
getCommandsModule() {
return {
actions: {
speak: ({ viewports, words }) => {
console.log(viewports, words);
},
},
definitions: {
rotateViewportCW: {
commandFn: actions.rotateViewport,
storeContexts: ['viewports'],
options: { rotation: 90 }
},
rotateViewportCCW: {
commandFn: actions.rotateViewport,
storeContexts: ['viewports'],
options: { rotation: -90 },
context: 'ACTIVE_VIEWER::CORNERSTONE'
},
},
defaultContext: 'VIEWER'
}
}
}
```
#### Viewport
@ -114,10 +178,6 @@ For a complete example implementation,
> The panel module is not yet in use.
#### Commands
...
#### Hotkeys
...
@ -159,3 +219,12 @@ top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions)
directory.
{% include "./_maintained-extensions-table.md" %}
<!--
Links
-->
<!-- prettier-ignore-start -->
[example-ext-src]: https://github.com/OHIF/Viewers/blob/master/extensions/_ohif-example-extension/src/index.js)
[module-types]: https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6
<!-- prettier-ignore-end -->

View File

@ -0,0 +1,69 @@
/**
*
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'example-extension',
getViewportModule() {},
getSopClassHandlerModule() {
return sopClassHandlerModule;
},
getPanelModule() {},
getToolbarModule() {},
getCommandsModule(/* store */) {
return commandsModule;
},
};
/**
*
*/
const commandsModule = {
actions: {
// Store Contexts + Options
exampleAction: ({ viewports, param1 }) => {
console.log(`There are ${viewports.length} viewports`);
console.log(`param1's value is: ${param1}`);
},
},
definitions: {
exampleActionDef: {
commandFn: this.actions.exampleAction,
storeContexts: ['viewports'],
options: { param1: 'hello world' },
},
},
};
/**
*
*/
const sopClassHandlerModule = {
id: 'OHIFDicomHtmlSopClassHandler',
sopClassUids: Object.values({
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',
}),
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
return {
plugin: 'html',
displaySetInstanceUid: 0, //utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
sopInstanceUid: instance.getSOPInstanceUID(),
seriesInstanceUid: series.getSeriesInstanceUID(),
studyInstanceUid: study.getStudyInstanceUID(),
authorizationHeaders,
};
},
};

View File

@ -1,3 +1,9 @@
{
"singleQuote": true
}
"trailingComma": "es5",
"printWidth": 80,
"proseWrap": "always",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"endOfLine": "lf"
}

View File

@ -1,54 +1,89 @@
# @ohif/extension-cornerstone
## Commands
![npm (scoped)](https://img.shields.io/npm/v/@ohif/extension-cornerstone.svg?style=flat-square)
This extensions includes the following `Commands` and `Command Definitions`. These can be registered with `@ohif/core`'s `CommandManager`. After registering the commands, they can be bound to `hotkeys` using the `HotkeysManager` and listed in the `UserPreferences` modal.
This extension adds support for viewing and manipulating 2D medical images via a
viewport. The underlying implementation wraps the
`cornerstonejs/react-cornerstone-viewport`, and provides basic commands and
toolbar buttons for common actions.
You can read more about [`Commands`](), [`Hotkeys`](), and the [`UserPreferences` Modal]() in their respective locations in the OHIF Viewer's documentation.
<!-- TODO: Simple image or GIF? -->
| Command Name | Description | Store Contexts |
| ---------------------------- | ----------- | -------------- |
| `rotateViewportCW` | | viewports |
| `rotateViewportCCW` | | viewports |
| `invertViewport` | | viewports |
| `flipViewportVertical` | | viewports |
| `flipViewportHorizontal` | | viewports |
| `scaleUpViewport` | | viewports |
| `scaleDownViewport` | | viewports |
| `fitViewportToWindow` | | viewports |
| `resetViewport` | | viewports |
| clearAnnotations | TODO | |
| next/previous Image | TODO | |
| first/last Image | TODO | |
| `nextViewportDisplaySet` | | none |
| `previousViewportDisplaySet` | | none |
#### Index
### TODO:
Extension Id: `cornerstone`
_SET TOOL_
- [Commands Module](#commands-module)
- [Toolbar Module](#toolbar-module)
- [Viewport Module](#viewport-module)
- [] Default Tool
- [] Set Zoom Tool
- [] Set WWWC Tool
- [] Set Pan Tool
- [] Set Angle Measurement Tool
- [] Set Stack Scroll Tool
- [] Set Magnify Tool
- [] Set Length Tool
- [] Set Annotate Tool
- [] Set Pixel Probe Tool
- [] Set Elliptical ROI Tool
- [] Set Rectangle ROI Tool
## Commands Module
_OTHER_
This extensions includes the following `Commands` and `Command Definitions`.
These can be registered with `@ohif/core`'s `CommandManager`. After registering
the commands, they can be bound to `hotkeys` using the `HotkeysManager` and
listed in the `UserPreferences` modal.
- Show/Hide CINE
- W/L Presets
- W/L Presets config
You can read more about [`Commands`][docs-commands], [`Hotkeys`][docs-hotkeys],
and the [`UserPreferences` Modal][docs-userprefs] in their respective locations
in the OHIF Viewer's documentation.
| Command Name | Description | Store Contexts |
| ---------------------------- | --------------------------------------- | -------------- |
| `rotateViewportCW` | | viewports |
| `rotateViewportCCW` | | viewports |
| `invertViewport` | | viewports |
| `flipViewportVertical` | | viewports |
| `flipViewportHorizontal` | | viewports |
| `scaleUpViewport` | | viewports |
| `scaleDownViewport` | | viewports |
| `fitViewportToWindow` | | viewports |
| `resetViewport` | | viewports |
| clearAnnotations | TODO | |
| next/previous Image | TODO | |
| first/last Image | TODO | |
| `nextViewportDisplaySet` | | |
| `previousViewportDisplaySet` | | |
| `setToolActive` | Activates tool for primary button/touch | |
## Toolbar Module
Our toolbar module contains definitions for:
- `StackScroll`
- `Zoom`
- `Wwwc`
- `Pan`
- `Length`
- `Angle`
- `Reset`
- `Cine`
All use the `ACTIVE_VIEWPORT::CORNERSTONE` context.
## Viewport Module
Our Viewport wraps [cornerstonejs/react-cornerstone-viewport][react-viewport]
and is connected the redux store. This module is the most prone to change as we
hammer out our Viewport interface.
## Resources
### Repositories
- [cornerstonejs/react-cornerstone-viewport][react-viewport]
- [cornerstonejs/cornerstoneTools][cornerstone-tools]
- [cornerstonejs/cornerstone][cornerstone]
<!--
Links
-->
<!-- prettier-ignore-start -->
[docs-commands]: https://www.com
[docs-hotkeys]: https://www.com
[docs-userprefs]: htt
[react-viewport]: https://github.com/cornerstonejs/react-cornerstone-viewport
[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools
[cornerstone]: https://github.com/cornerstonejs/cornerstone
<!-- prettier-ignore-end -->

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "0.0.36",
"version": "0.0.37",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@ -26,7 +26,7 @@
"dcmjs": "^0.3.8",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"ohif-core": "^0.3.5",
"ohif-core": "^0.6.0",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0",

View File

@ -1,16 +1,11 @@
import { connect } from 'react-redux';
import CornerstoneViewport from 'react-cornerstone-viewport';
import OHIF from 'ohif-core';
import { connect } from 'react-redux';
import throttle from 'lodash.throttle';
const {
setViewportActive,
setViewportSpecificData,
clearViewportSpecificData
} = OHIF.redux.actions;
const { setViewportActive, setViewportSpecificData } = 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) {
@ -26,12 +21,14 @@ const mapStateToProps = (state, ownProps) => {
return {
layout: state.viewports.layout,
isActive,
activeTool: activeButton && activeButton.command,
// TODO: Need a cleaner and more versatile way.
// Currently justing using escape hatch + commands
// activeTool: activeButton && activeButton.command,
...dataFromStore,
enableStackPrefetch: isActive,
//stack: viewportSpecificData.stack,
cineToolData: viewportSpecificData.cine,
viewport: viewportSpecificData.viewport
viewport: viewportSpecificData.viewport,
};
};
@ -57,7 +54,9 @@ const mapDispatchToProps = (dispatch, ownProps) => {
const enabledElement = event.detail.element;
dispatch(
setViewportSpecificData(viewportIndex, {
dom: enabledElement
// TODO: Hack to make sure our plugin info is available from the outset
plugin: 'cornerstone',
dom: enabledElement,
})
);
},
@ -66,18 +65,18 @@ const mapDispatchToProps = (dispatch, ownProps) => {
const {
onAdded,
onRemoved,
onModified
onModified,
} = OHIF.measurements.MeasurementHandlers;
const actions = {
added: onAdded,
removed: onRemoved,
modified: throttle(event => {
return onModified(event);
}, 300)
}, 300),
};
return actions[action](event);
}
},
};
};

View File

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

View File

@ -1,65 +0,0 @@
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;
}
}

View File

@ -1,9 +1,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import cornerstone from 'cornerstone-core';
import './config';
import React, { Component } from 'react';
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from 'ohif-core';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import handleSegmentationStorage from './handleSegmentationStorage.js';
const { StackManager } = OHIF.utils;
@ -76,11 +78,11 @@ class OHIFCornerstoneViewport extends Component {
}
if (!studyInstanceUid) {
throw new Error('StudyInstanceUID not provided.')
throw new Error('StudyInstanceUID not provided.');
}
if (!displaySetInstanceUid) {
throw new Error('StudyInstanceUID not provided.')
throw new Error('StudyInstanceUID not provided.');
}
// Create shortcut to displaySet
@ -123,7 +125,9 @@ class OHIFCornerstoneViewport extends Component {
if (index > -1) {
stack.currentImageIdIndex = index;
} else {
console.warn('SOPInstanceUID provided was not found in specified DisplaySet');
console.warn(
'SOPInstanceUID provided was not found in specified DisplaySet'
);
}
}

View File

@ -1,43 +0,0 @@
import React, { Component } from 'react';
import ConnectedCineDialog from './ConnectedCineDialog';
import ConnectedToolbarSection from './ConnectedToolbarSection';
import { ToolbarButton } from 'react-viewerbase';
class ToolbarModule extends Component {
state = {
cineDialogOpen: false
};
onClickCineToolbarButton = () => {
this.setState({
cineDialogOpen: !this.state.cineDialogOpen
});
};
render() {
const cineDialogContainerStyle = {
display: this.state.cineDialogOpen ? 'block' : 'none',
position: 'absolute',
top: '82px',
zIndex: 999
};
return (
<div className="ToolbarModule">
<ConnectedToolbarSection />
<ToolbarButton
active={this.state.cineDialogOpen}
onClick={this.onClickCineToolbarButton}
text="CINE"
icon="youtube"
/>
<div className="CineDialogContainer" style={cineDialogContainerStyle}>
<ConnectedCineDialog />
</div>
</div>
);
}
}
export default ToolbarModule;

View File

@ -1,8 +1,5 @@
import cornerstone from 'cornerstone-core';
import { redux } from 'ohif-core';
import store from './../store/';
const { setToolActive } = redux.actions;
import cornerstoneTools from 'cornerstone-tools';
const actions = {
rotateViewport: ({ viewports, rotation }) => {
@ -80,12 +77,13 @@ const actions = {
cornerstone.setViewport(enabledElement, viewport);
}
},
// This has a weird hard dependency on the tools that are available as toolbar
// buttons. You can see this in `ohif-core/src/redux/reducers/tools.js`
// the `toolName` needs to equal the button's `command` property.
// NOTE: It would be nice if `hotkeys` could set this, instead of creating a command per tool
setCornerstoneToolActive: ({ toolName }) => {
store.dispatch(setToolActive(toolName));
// TODO: this is receiving `evt` from `ToolbarRow`. We could use it to have
// better mouseButtonMask sets.
setToolActive: ({ toolName }) => {
if (!toolName) {
console.warn('No toolname provided to setToolActive command');
}
cornerstoneTools.setToolActive(toolName, { mouseButtonMask: 1 });
},
updateViewportDisplaySet: ({ direction }) => {
// TODO
@ -130,13 +128,11 @@ const definitions = {
options: {},
},
scaleUpViewport: {
keys: '',
commandFn: actions.scaleViewport,
storeContexts: ['viewports'],
options: { direction: 1 },
},
scaleDownViewport: {
keys: '',
commandFn: actions.scaleViewport,
storeContexts: ['viewports'],
options: { direction: -1 },
@ -166,10 +162,10 @@ const definitions = {
options: { direction: -1 },
},
// TOOLS
setZoomTool: {
commandFn: actions.setCornerstoneToolActive,
setToolActive: {
commandFn: actions.setToolActive,
storeContexts: [],
options: { toolName: 'Zoom' },
options: {},
},
};
@ -182,4 +178,8 @@ function _getActiveViewportEnabledElement(viewports, activeIndex) {
return activeViewport.dom;
}
export default definitions;
export default {
actions,
definitions,
defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE',
};

View File

@ -1,7 +1,7 @@
import Hammer from 'hammerjs';
import cornerstone from 'cornerstone-core';
import cornerstoneMath from 'cornerstone-math';
import cornerstoneTools from 'cornerstone-tools';
import Hammer from 'hammerjs';
// For debugging
window.cornerstoneTools = cornerstoneTools;
@ -17,12 +17,9 @@ 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
// Tool styles/colors
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;

View File

@ -1,7 +1,8 @@
import * as dcmjs from 'dcmjs';
import OHIF from 'ohif-core';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import * as dcmjs from 'dcmjs';
const { StackManager } = OHIF.utils;
@ -27,28 +28,6 @@ function getDisplaySetsBySeries(studies, studyInstanceUid, 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,
@ -77,7 +56,7 @@ 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()
headers: OHIF.DICOMWeb.getAuthorizationHeader(),
}).then(response => response.arrayBuffer());
}
@ -144,7 +123,7 @@ async function handleSegmentationStorage(
return {
studyInstanceUid,
displaySetInstanceUid,
stack
stack,
};
}

View File

@ -1,3 +1,23 @@
import OHIFCornerstoneExtension from './OHIFCornerstoneExtension.js';
import OHIFCornerstoneViewport from './OHIFCornerstoneViewport.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
export default OHIFCornerstoneExtension;
/**
*
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'cornerstone',
getViewportModule() {
return OHIFCornerstoneViewport;
},
getToolbarModule() {
return toolbarModule;
},
getCommandsModule() {
return commandsModule;
},
};

View File

@ -0,0 +1,106 @@
// TODO: A way to add Icons that don't already exist?
// - Register them and add
// - Include SVG Source/Inline?
// - By URL, or own component?
// TODO: `ohif-core` toolbar builder?
// What KINDS of toolbar buttons do we have...
// - One's that dispatch commands
// - One's that set tool's active
// - More custom, like CINE
// - Built in for one's like this, or custom components?
// Visible?
// Disabled?
// Based on contexts or misc. criteria?
// -- ACTIVE_ROUTE::VIEWER
// -- ACTIVE_VIEWPORT::CORNERSTONE
// setToolActive commands should receive the button event that triggered
// so we can do the "bind to this butyon" magic
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
SET_TOOL_ACTIVE: 'setToolActive',
BUILT_IN: 'builtIn',
};
const definitions = [
{
id: 'StackScroll',
label: 'Stack Scroll',
icon: 'bars',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'StackScroll' },
},
{
id: 'Zoom',
label: 'Zoom',
icon: 'search-plus',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Zoom' },
},
{
id: 'Wwwc',
label: 'Levels',
icon: 'level',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Wwwc' },
},
{
id: 'Pan',
label: 'Pan',
icon: 'arrows',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Pan' },
},
{
id: 'Length',
label: 'Length',
icon: 'measure-temp',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Length' },
},
{
id: 'Angle',
label: 'Angle',
icon: 'angle-left',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Angle' },
},
{
id: 'Reset',
label: 'Reset',
icon: 'reset',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'resetViewport',
},
{
id: 'Cine',
label: 'CINE',
icon: 'youtube',
//
type: TOOLBAR_BUTTON_TYPES.BUILT_IN,
options: {
behavior: 'CINE',
},
},
];
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE',
};

View File

@ -1,3 +1,9 @@
{
"singleQuote": true
}
"trailingComma": "es5",
"printWidth": 80,
"proseWrap": "always",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"endOfLine": "lf"
}

View File

@ -1,6 +1,6 @@
{
"name": "ohif-dicom-html-extension",
"version": "0.0.2",
"name": "@ohif/extension-dicom-html",
"version": "0.0.3",
"description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF",
"license": "MIT",
@ -20,11 +20,14 @@
},
"peerDependencies": {
"dcmjs": "^0.3.3",
"ohif-core": "^0.2.6",
"ohif-core": "^0.6.0",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
},
"dependencies": {
"@babel/runtime": "^7.2.0"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-external-helpers": "^7.2.0",
@ -80,7 +83,7 @@
"files": [
"dist"
],
"dependencies": {
"@babel/runtime": "^7.2.0"
"publishConfig": {
"access": "public"
}
}

View File

@ -1,27 +0,0 @@
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;
}
}

View File

@ -1,7 +1,4 @@
import OHIF from 'ohif-core';
const { plugins, utils } = OHIF;
const { PLUGIN_TYPES } = plugins;
import { MODULE_TYPES, utils } from 'ohif-core';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
@ -20,7 +17,7 @@ const sopClassUids = Object.values(SOP_CLASS_UIDS);
// same SOP Class
const OHIFDicomHtmlSopClassHandler = {
id: 'OHIFDicomHtmlSopClassHandler',
type: PLUGIN_TYPES.SOP_CLASS_HANDLER,
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUids,
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();

View File

@ -1,3 +1,16 @@
import OHIFDicomHtmlExtension from './OHIFDicomHtmlExtension.js';
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
import OHIFDicomHtmlViewport from './OHIFDicomHtmlViewport.js';
export default OHIFDicomHtmlExtension;
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'html',
getViewportModule() {
return OHIFDicomHtmlViewport;
},
getSopClassHandlerModule() {
return OHIFDicomHtmlSopClassHandler;
}
};

View File

@ -1,3 +1,9 @@
{
"singleQuote": true
}
"trailingComma": "es5",
"printWidth": 80,
"proseWrap": "always",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"endOfLine": "lf"
}

View File

@ -0,0 +1,34 @@
# @ohif/extension-dicom-microscopy
![npm (scoped)](https://img.shields.io/npm/v/@ohif/extension-dicom-microscopy.svg?style=flat-square)
<!-- TODO: Simple image or GIF? -->
#### Index
Extension Id: `microscopy`
- [SopClassHandler Module](#sopclasshandler-module)
- [Viewport Module](#viewport-module)
## SopClassHandler Module
..
## Viewport Module
...
## Resources
### Repositories
...
<!--
Links
-->
<!-- prettier-ignore-start -->
<!-- prettier-ignore-end -->

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-microscopy",
"version": "0.0.6",
"version": "0.0.7",
"description": "OHIF extension for Dicom Microscopy",
"author": "OHIF",
"license": "MIT",
@ -21,7 +21,7 @@
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0",
"ohif-core": "^0.4.0"
"ohif-core": "^0.6.0"
},
"devDependencies": {
"@babel/core": "^7.2.2",

View File

@ -3,7 +3,7 @@ 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'
VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.6',
};
const DicomMicroscopySopClassHandler = {
@ -20,9 +20,9 @@ const DicomMicroscopySopClassHandler = {
dicomWebClient,
sopInstanceUid: instance.getSOPInstanceUID(),
seriesInstanceUid: series.getSeriesInstanceUID(),
studyInstanceUid: study.getStudyInstanceUID()
studyInstanceUid: study.getStudyInstanceUID(),
};
}
},
};
export default DicomMicroscopySopClassHandler;

View File

@ -1,11 +1,12 @@
import React, { Component } from 'react';
import { api } from 'dicom-microscopy-viewer';
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
class DicomMicroscopyViewport extends Component {
state = {
error: null
error: null,
};
constructor(props) {
@ -21,7 +22,7 @@ class DicomMicroscopyViewport extends Component {
const searchInstanceOptions = {
studyInstanceUID: displaySet.studyInstanceUid,
seriesInstanceUID: displaySet.seriesInstanceUid
seriesInstanceUID: displaySet.seriesInstanceUid,
};
dicomWebClient
@ -33,7 +34,7 @@ class DicomMicroscopyViewport extends Component {
const retrieveInstanceOptions = {
studyInstanceUID: displaySet.studyInstanceUid,
seriesInstanceUID: displaySet.seriesInstanceUid,
sopInstanceUID
sopInstanceUID,
};
const promise = dicomWebClient
@ -53,7 +54,7 @@ class DicomMicroscopyViewport extends Component {
const viewer = new microscopyViewer({
client: dicomWebClient,
metadata
metadata,
});
viewer.render({ container });

View File

@ -1,27 +0,0 @@
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;
}
}

View File

@ -1,3 +1,16 @@
import OHIFDicomMicroscopyExtension from './OHIFDicomMicroscopyExtension.js';
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
import DicomMicroscopyViewport from './DicomMicroscopyViewport.js';
export default OHIFDicomMicroscopyExtension;
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'microscopy',
getViewportModule() {
return DicomMicroscopyViewport;
},
getSopClassHandlerModule() {
return DicomMicroscopySopClassHandler;
},
};

View File

@ -1,3 +1,9 @@
{
"singleQuote": true
}
"trailingComma": "es5",
"printWidth": 80,
"proseWrap": "always",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"endOfLine": "lf"
}

View File

@ -1,6 +1,6 @@
{
"name": "ohif-dicom-pdf-extension",
"version": "0.0.6",
"name": "@ohif/extension-dicom-pdf",
"version": "0.0.7",
"description": "OHIF extension for Dicom PDF",
"author": "OHIF",
"license": "MIT",
@ -20,11 +20,16 @@
},
"peerDependencies": {
"dicom-parser": "^1.8.3",
"ohif-core": "^0.3.4",
"ohif-core": "^0.6.0",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
},
"dependencies": {
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.6",
"lodash.isequal": "^4.5.0"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-external-helpers": "^7.2.0",
@ -80,9 +85,7 @@
"files": [
"dist"
],
"dependencies": {
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.6",
"lodash.isequal": "^4.5.0"
"publishConfig": {
"access": "public"
}
}

View File

@ -1,27 +0,0 @@
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;
}
}

View File

@ -1,7 +1,4 @@
import OHIF from "ohif-core";
const { plugins, utils } = OHIF;
const { PLUGIN_TYPES } = plugins;
import { MODULE_TYPES, utils } from 'ohif-core';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
@ -10,10 +7,8 @@ const SOP_CLASS_UIDS = {
const OHIFDicomPDFSopClassHandler = {
id: 'OHIFDicomPDFSopClassHandlerPlugin',
type: PLUGIN_TYPES.SOP_CLASS_HANDLER,
sopClassUids: [
SOP_CLASS_UIDS.ENCAPSULATED_PDF
],
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUids: [SOP_CLASS_UIDS.ENCAPSULATED_PDF],
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
@ -28,6 +23,6 @@ const OHIFDicomPDFSopClassHandler = {
authorizationHeaders: authorizationHeaders
};
}
}
};
export default OHIFDicomPDFSopClassHandler;

View File

@ -1,3 +1,16 @@
import OHIFDicomPDFExtension from './OHIFDicomPDFExtension.js';
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
import OHIFDicomPDFViewport from './OHIFDicomPDFViewport.js';
export default OHIFDicomPDFExtension;
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'pdf',
getViewportModule() {
return OHIFDicomPDFViewport;
},
getSopClassHandlerModule() {
return OHIFDicomPDFSopClassHandler;
}
};

View File

@ -1,3 +1,9 @@
{
"singleQuote": true
}
"trailingComma": "es5",
"printWidth": 80,
"proseWrap": "always",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"endOfLine": "lf"
}

View File

@ -1 +1,55 @@
# @ohif/extension-vtk
![npm (scoped)](https://img.shields.io/npm/v/@ohif/extension-vtk.svg?style=flat-square)
<!-- TODO: Simple image or GIF? -->
#### Index
Extension Id: `vtk`
- [Commands Module](#commands-module)
- [Toolbar Module](#toolbar-module)
- [Viewport Module](#viewport-module)
## Commands Module
| Command Name | Description | Store Contexts |
| ---------------------- | ----------- | -------------- |
| `axial` | | viewports |
| `coronal` | | viewports |
| `sagittal` | | viewports |
| `enableRotateTool` | | viewports |
| `enableCrosshairsTool` | | viewports |
| `enableLevelTool` | | viewports |
| `mpr2d` | | viewports |
## Toolbar Module
Our toolbar module contains definitions for:
- `Crosshairs`
- `WWWC`
- `Rotate`
All use the `ACTIVE_VIEWPORT::VTK` context.
## Viewport Module
Our Viewport wraps [OHIF/react-vtkjs-viewport][react-viewport] and is connected
the redux store. This module is the most prone to change as we hammer out our
Viewport interface.
## Resources
### Repositories
- [OHIF/react-vtkjs-viewport][react-viewport]
<!--
Links
-->
<!-- prettier-ignore-start -->
[react-viewport]: https://github.com/OHIF/react-vtkjs-viewport
<!-- prettier-ignore-end -->

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "0.0.6",
"version": "0.0.7",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@ -27,7 +27,7 @@
"dicom-parser": "^1.8.3",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"ohif-core": "^0.5.9",
"ohif-core": "^0.6.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
@ -37,6 +37,12 @@
"react-viewerbase": "^0.8.1",
"redux": "^4.0.1"
},
"dependencies": {
"@babel/runtime": "^7.4.5",
"@ohif/i18n": "0.0.4",
"react-vtkjs-viewport": "0.0.9",
"vtk.js": "^8.9.1"
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/plugin-external-helpers": "^7.2.0",
@ -100,11 +106,5 @@
],
"publishConfig": {
"access": "public"
},
"dependencies": {
"@babel/runtime": "^7.4.5",
"@ohif/i18n": "0.0.4",
"react-vtkjs-viewport": "0.0.9",
"vtk.js": "^8.9.1"
}
}

View File

@ -1,53 +0,0 @@
import { connect } from 'react-redux';
import { ToolbarSection } from 'react-viewerbase';
import OHIF from 'ohif-core';
const { setToolActive } = OHIF.redux.actions;
const mapStateToProps = state => {
return {
buttons: [
{
command: 'Crosshairs',
type: 'tool',
text: 'Crosshairs',
icon: 'crosshairs',
active: true,
onClick: () => {
// TODO: Make these use setToolActive instead
window.commandsManager.runCommand('enableCrosshairsTool', {}, 'vtk');
}
},
{
command: 'WWWC',
type: 'tool',
text: 'WWWC',
icon: 'level',
active: true,
onClick: () => {
// TODO: Make these use setToolActive instead
window.commandsManager.runCommand('enableLevelTool', {}, 'vtk');
}
},
{
command: 'Rotate',
type: 'tool',
text: 'Rotate',
icon: '3d-rotate',
active: false,
onClick: () => {
// TODO: Make these use setToolActive instead
window.commandsManager.runCommand('enableRotateTool', {}, 'vtk');
}
},
],
activeCommand: 'Crosshairs'
};
};
const ConnectedToolbarSection = connect(
mapStateToProps,
null
)(ToolbarSection);
export default ConnectedToolbarSection;

View File

@ -1,15 +1,10 @@
import { connect } from 'react-redux';
import { View2D } from 'react-vtkjs-viewport';
import OHIF from 'ohif-core';
import { View2D } from 'react-vtkjs-viewport';
import { connect } from 'react-redux';
const {
setViewportActive,
setViewportSpecificData,
clearViewportSpecificData
} = OHIF.redux.actions;
const { setViewportActive, setViewportSpecificData } = 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) {
@ -19,9 +14,6 @@ const mapStateToProps = (state, ownProps) => {
// If this is the active viewport, enable prefetching.
const { viewportIndex } = ownProps;
const isActive = viewportIndex === state.viewports.activeViewportIndex;
const viewportSpecificData =
state.viewports.viewportSpecificData[viewportIndex] || {};
const viewportLayout = state.viewports.layout.viewports[viewportIndex];
const pluginDetails = viewportLayout.vtk || {};
@ -29,9 +21,10 @@ const mapStateToProps = (state, ownProps) => {
layout: state.viewports.layout,
isActive,
...pluginDetails,
activeTool: activeButton && activeButton.command,
// Hopefully this doesn't break anything under the hood for this one
// activeTool: activeButton && activeButton.command,
...dataFromStore,
enableStackPrefetch: isActive
enableStackPrefetch: isActive,
};
};
@ -45,13 +38,12 @@ const mapDispatchToProps = (dispatch, ownProps) => {
setViewportSpecificData: data => {
dispatch(setViewportSpecificData(viewportIndex, data));
}
},
};
};
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
const { afterCreation } = propsFromState;
const { setViewportSpecificData } = propsFromDispatch;
const props = {
...propsFromState,
@ -74,7 +66,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
if (afterCreation && typeof afterCreation === 'function') {
afterCreation(api);
}
}
},
};
return props;
};

View File

@ -1,5 +1,5 @@
.imageViewerLoadingIndicator {
color: #91B9CD;
color: #91b9cd;
}
.loadingIndicator {

View File

@ -1,17 +1,18 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import './LoadingIndicator.css';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
class LoadingIndicator extends PureComponent {
static propTypes = {
percentComplete: PropTypes.number.isRequired,
error: PropTypes.object
error: PropTypes.object,
};
static defaultProps = {
percentComplete: 0,
error: null
error: null,
};
render() {

View File

@ -1,75 +0,0 @@
import React, { Component } from 'react';
import OHIFVTKViewport from './OHIFVTKViewport.js';
import ToolbarModule from './ToolbarModule.js';
import { definitions } from './commands';
/**
* 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, commandsManager }) {
this.children = children;
_registerCommands(commandsManager, definitions, this.getExtensionId());
}
/**
* 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;
}
}
/**
* Register all Viewer commands
*
* @private
*/
function _registerCommands(commandsManager, definitions, commandContext) {
commandsManager.createContext(commandContext);
Object.keys(definitions).forEach(commandName => {
const commandDefinition = definitions[commandName];
commandsManager.registerCommand(
commandContext,
commandName,
commandDefinition
);
});
}

View File

@ -1,17 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import cornerstone from 'cornerstone-core';
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
import ConnectedVTKViewport from './ConnectedVTKViewport';
import LoadingIndicator from './LoadingIndicator.js';
import OHIF from 'ohif-core';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import handleSegmentationStorage from './handleSegmentationStorage.js';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
import ConnectedVTKViewport from './ConnectedVTKViewport';
import handleSegmentationStorage from './handleSegmentationStorage.js';
import LoadingIndicator from './LoadingIndicator.js';
const { StackManager } = OHIF.utils;
@ -25,7 +24,7 @@ cornerstone.metaData.addProvider(
StackManager.setMetadataProvider(metadataProvider);
const SOP_CLASSES = {
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4'
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
};
const specialCaseHandlers = {};
@ -51,7 +50,7 @@ function createLabelMapImageData(backgroundImageData) {
const values = new Uint8Array(backgroundImageData.getNumberOfPoints());
const dataArray = vtkDataArray.newInstance({
numberOfComponents: 1, // labelmap with single component
values
values,
});
labelMapData.getPointData().setScalars(dataArray);
@ -62,14 +61,14 @@ class OHIFVTKViewport extends Component {
state = {
volumes: null,
paintFilterLabelMapImageData: null,
paintFilterBackgroundImageData: null
paintFilterBackgroundImageData: null,
};
static propTypes = {
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportIndex: PropTypes.number,
children: PropTypes.node
children: PropTypes.node,
};
static id = 'OHIFVTKViewport';
@ -165,7 +164,7 @@ class OHIFVTKViewport extends Component {
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject
labelmap: labelmapDataObject,
};
});
default:
@ -173,7 +172,7 @@ class OHIFVTKViewport extends Component {
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData
data: imageDataObject.vtkImageData,
};
});
}
@ -202,7 +201,7 @@ class OHIFVTKViewport extends Component {
displaySetInstanceUid,
sopClassUids,
sopInstanceUid,
frameIndex
frameIndex,
} = displaySet;
if (sopClassUids.length > 1) {
@ -231,7 +230,7 @@ class OHIFVTKViewport extends Component {
this.setState({
volumes: [volumeActor],
paintFilterBackgroundImageData: data,
paintFilterLabelMapImageData: labelmap
paintFilterLabelMapImageData: labelmap,
});
}
@ -261,7 +260,7 @@ class OHIFVTKViewport extends Component {
childrenWithProps = this.props.children.map((child, index) => {
return React.cloneElement(child, {
viewportIndex: this.props.viewportIndex,
key: index
key: index,
});
});
}

View File

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

View File

@ -1,25 +1,22 @@
import {
vtkInteractorStyleMPRWindowLevel,
vtkInteractorStyleMPRSlice,
vtkInteractorStyleMPRCrosshairs,
vtkInteractorStyleMPRSlice,
vtkInteractorStyleMPRWindowLevel,
vtkSVGCrosshairsWidget,
vtkSVGWidgetManager
vtkSVGWidgetManager,
} from 'react-vtkjs-viewport';
import setViewportToVTK from './utils/setViewportToVTK.js';
import setMPRLayout from './utils/setMPRLayout.js';
import setViewportToVTK from './utils/setViewportToVTK.js';
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
// TODO: Should be another way to get this
const commandsManager = window.commandsManager;
// TODO: Put this somewhere else
let apis = {};
function getCrosshairCallbackForIndex(index) {
return ({worldPos}) => {
return ({ worldPos }) => {
// Set camera focal point to world coordinate for linked views
apis.forEach((api, viewportIndex) => {
if (viewportIndex !== index) {
@ -52,7 +49,10 @@ function getCrosshairCallbackForIndex(index) {
const displayPosition = wPos.getComputedDisplayValue(renderer);
const { svgWidgetManager } = api;
api.svgWidgets.crosshairsWidget.setPoint(displayPosition[0], displayPosition[1]);
api.svgWidgets.crosshairsWidget.setPoint(
displayPosition[0],
displayPosition[1]
);
svgWidgetManager.render();
});
};
@ -230,7 +230,7 @@ const actions = {
api.svgWidgetManager = svgWidgetManager;
api.svgWidgets = {
crosshairsWidget
crosshairsWidget,
};
switch (index) {
@ -255,45 +255,49 @@ const actions = {
renderWindow.render();
});
}
},
};
const definitions = {
axial: {
commandFn: actions.axial,
storeContexts: ['viewports'],
options: {}
options: {},
},
coronal: {
commandFn: actions.coronal,
storeContexts: ['viewports'],
options: {}
options: {},
},
sagittal: {
commandFn: actions.sagittal,
storeContexts: ['viewports'],
options: {}
options: {},
},
enableRotateTool: {
commandFn: actions.enableRotateTool,
storeContexts: ['viewports'],
options: {}
options: {},
},
enableCrosshairsTool: {
commandFn: actions.enableCrosshairsTool,
storeContexts: ['viewports'],
options: {}
options: {},
},
enableLevelTool: {
commandFn: actions.enableLevelTool,
storeContexts: ['viewports'],
options: {}
options: {},
},
mpr2d: {
commandFn: actions.mpr2d,
storeContexts: ['viewports'],
options: {}
}
options: {},
context: 'VIEWER',
},
};
export { definitions };
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::VTK',
};

View File

@ -1,5 +1,6 @@
import OHIF from 'ohif-core';
import * as dcmjs from 'dcmjs';
import OHIF from 'ohif-core';
import { api } from 'dicomweb-client';
const { StackManager } = OHIF.utils;
@ -56,14 +57,14 @@ function retrieveDicomData(
) {
const config = {
url: wadoRoot,
headers: DICOMWeb.getAuthorizationHeader()
headers: DICOMWeb.getAuthorizationHeader(),
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID
sopInstanceUID,
};
return dicomWeb.retrieveInstance(options);
@ -136,13 +137,13 @@ async function handleSegmentationStorage(
return {
referenceDataObject,
labelmapDataObject
labelmapDataObject,
};
return {
studyInstanceUid,
displaySetInstanceUid,
stack
stack,
};
}

View File

@ -1,7 +1,24 @@
import OHIFVTKViewport from './OHIFVTKViewport.js';
import commandsModule from './commandsModule.js';
// This feels weird
import loadLocales from './loadLocales';
import toolbarModule from './toolbarModule.js';
import OHIFVTKExtension from './OHIFVTKExtension.js';
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'vtk',
getViewportModule() {
return OHIFVTKViewport;
},
getToolbarModule() {
return toolbarModule;
},
getCommandsModule() {
return commandsModule;
},
};
loadLocales();
export default OHIFVTKExtension;

View File

@ -0,0 +1,39 @@
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
SET_TOOL_ACTIVE: 'setToolActive',
};
const definitions = [
{
id: 'Crosshairs',
label: 'Crosshairs',
icon: 'crosshairs',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'enableCrosshairsTool',
commandOptions: {},
},
{
id: 'WWWC',
label: 'WWWC',
icon: 'level',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'enableLevelTool',
commandOptions: {},
},
{
id: 'Rotate',
label: 'Rotate',
icon: '3d-rotate',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'enableRotateTool',
commandOptions: {},
},
];
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::VTK',
};

View File

@ -11,14 +11,14 @@ export default function setMPRLayout(displaySet) {
for (let i = 0; i < numViewports; i++) {
viewports.push({
height: `${100 / rows}%`,
width: `${100 / columns}%`
width: `${100 / columns}%`,
});
viewportSpecificData[i] = displaySet;
viewportSpecificData[i].plugin = 'vtk';
}
const layout = {
viewports
viewports,
};
const viewportIndices = [0, 1, 2];
@ -33,7 +33,7 @@ export default function setMPRLayout(displaySet) {
}*/
const data = {
plugin: 'vtk',
// plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
@ -42,8 +42,8 @@ export default function setMPRLayout(displaySet) {
if (apis.every(a => !!a)) {
resolve(apis);
}
}
}
},
},
};
updatedViewports = setSingleLayoutData(

View File

@ -14,13 +14,13 @@ export default function setViewportToVTK(
}*/
const data = {
plugin: 'vtk',
// plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
resolve(api);
}
}
},
},
};
const updatedViewports = setSingleLayoutData(

View File

@ -67,9 +67,11 @@
},
"dependencies": {
"@babel/runtime": "^7.4.5",
"@ohif/extension-cornerstone": "0.0.36",
"@ohif/extension-dicom-microscopy": "0.0.6",
"@ohif/extension-vtk": "0.0.6",
"@ohif/extension-cornerstone": "0.0.37",
"@ohif/extension-dicom-html": "0.0.3",
"@ohif/extension-dicom-microscopy": "0.0.7",
"@ohif/extension-dicom-pdf": "0.0.7",
"@ohif/extension-vtk": "0.0.7",
"@ohif/i18n": "0.0.4",
"classnames": "^2.2.6",
"cornerstone-core": "^2.2.8",
@ -84,9 +86,7 @@
"i18next-browser-languagedetector": "^3.0.1",
"lodash.isequal": "4.5.0",
"moment": "^2.24.0",
"ohif-core": "0.5.9",
"ohif-dicom-html-extension": "^0.0.2",
"ohif-dicom-pdf-extension": "^0.0.6",
"ohif-core": "0.6.0",
"oidc-client": "1.7.x",
"prop-types": "^15.7.2",
"react-i18next": "^10.11.0",
@ -94,9 +94,12 @@
"react-resize-detector": "^4.2.0",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"react-viewerbase": "0.9.0",
"react-viewerbase": "0.10.0",
"redux": "^4.0.1",
"redux-oidc": "3.1.x"
"redux-logger": "^3.0.6",
"redux-oidc": "3.1.x",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.4.5",

View File

@ -2,23 +2,21 @@ import './config';
import {
CommandsManager,
ExtensionManager,
HotkeysManager,
extensions,
redux,
utils,
} from 'ohif-core';
import React, { Component } from 'react';
import {
getDefaultToolbarButtons,
getUserManagerForOpenIdConnectClient,
initWebWorkers,
} from './utils/index.js';
import ConnectedToolContextMenu from './connectedComponents/ConnectedToolContextMenu';
import { I18nextProvider } from 'react-i18next';
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
import OHIFDicomHtmlExtension from 'ohif-dicom-html-extension';
import OHIFDicomHtmlExtension from '@ohif/extension-dicom-html';
import OHIFDicomMicroscopyExtension from '@ohif/extension-dicom-microscopy';
import OHIFDicomPDFExtension from 'ohif-dicom-pdf-extension';
import OHIFDicomPDFExtension from '@ohif/extension-dicom-pdf';
import OHIFStandaloneViewer from './OHIFStandaloneViewer';
import OHIFVTKExtension from '@ohif/extension-vtk';
import { OidcProvider } from 'redux-oidc';
@ -27,22 +25,20 @@ import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import WhiteLabellingContext from './WhiteLabellingContext';
import appCommands from './appCommands';
import setupTools from './setupTools';
import { getActiveContexts } from './store/layout/selectors.js';
import i18n from '@ohif/i18n';
import { I18nextProvider } from 'react-i18next';
import setupTools from './setupTools';
import store from './store';
// ~~~~ APP SETUP
const commandsManagerConfig = {
getAppState: () => store.getState(),
getActiveContexts: () => store.getState().ui.activeContexts,
getActiveContexts: () => getActiveContexts(store.getState()),
};
const commandsManager = new CommandsManager(commandsManagerConfig);
const hotkeysManager = new HotkeysManager(commandsManager);
// TODO: @dannyrb will fix this
window.commandsManager = commandsManager;
const extensionManager = new ExtensionManager({ commandsManager });
// TODO: Should be done in extensions w/ commandsModule
// ~~ ADD COMMANDS
@ -50,32 +46,21 @@ appCommands.init(commandsManager);
if (window.config.hotkeys) {
hotkeysManager.setHotkeys(window.config.hotkeys, true);
}
// Force active contexts for now. These should be set in Viewer/ActiveViewer
store.dispatch({
type: 'ADD_ACTIVE_CONTEXT',
item: 'VIEWER',
});
store.dispatch({
type: 'ADD_ACTIVE_CONTEXT',
item: 'VIEWER::CORNERSTONE',
});
// ~~~~ END APP SETUP
setupTools(store);
const children = {
viewport: [<ConnectedToolContextMenu key="tool-context" />],
};
// const children = {
// viewport: [<ConnectedToolContextMenu key="tool-context" />],
// };
/** TODO: extensions should be passed in as prop as soon as we have the extensions as separate packages and then registered by ExtensionsManager */
extensions.ExtensionManager.registerExtensions(store, [
new OHIFCornerstoneExtension({ children }),
new OHIFVTKExtension({ commandsManager }),
new OHIFDicomPDFExtension(),
new OHIFDicomHtmlExtension(),
new OHIFDicomMicroscopyExtension(),
extensionManager.registerExtensions([
OHIFCornerstoneExtension,
OHIFVTKExtension,
OHIFDicomPDFExtension,
OHIFDicomHtmlExtension,
OHIFDicomMicroscopyExtension,
]);
// TODO[react] Use a provider when the whole tree is React
@ -104,12 +89,6 @@ class App extends Component {
constructor(props) {
super(props);
//
const defaultButtons = getDefaultToolbarButtons(this.props.routerBasename);
const buttonsAction = redux.actions.setAvailableButtons(defaultButtons);
store.dispatch(buttonsAction);
if (this.props.oidc.length) {
const firstOpenIdClient = this.props.oidc[0];
@ -162,4 +141,5 @@ class App extends Component {
export default App;
export { commandsManager, hotkeysManager };
// Make our managers accessible
export { commandsManager, extensionManager, hotkeysManager };

View File

@ -1,9 +1,7 @@
import cornerstoneCommandDefinitions from './cornerstone.js';
import viewerCommandDefinitions from './viewer.js';
const CONTEXTS = {
viewer: 'VIEWER',
cornerstone: 'VIEWER::CORNERSTONE',
};
/**
@ -12,7 +10,6 @@ const CONTEXTS = {
*/
function init(commandsManager) {
_registerViewerCommands(commandsManager);
_registerCornerstoneCommands(commandsManager);
}
/**
@ -35,26 +32,6 @@ function _registerViewerCommands(commandsManager) {
});
}
/**
* Register all Cornerstone commands
*
* @private
*/
function _registerCornerstoneCommands(commandsManager) {
const commandContext = CONTEXTS.cornerstone;
commandsManager.createContext(commandContext);
Object.keys(cornerstoneCommandDefinitions).forEach(commandName => {
const commandDefinition = cornerstoneCommandDefinitions[commandName];
commandsManager.registerCommand(
commandContext,
commandName,
commandDefinition
);
});
}
export default {
init,
};

View File

@ -1,15 +1,15 @@
import './Header.css';
import './Header.css';
import { Link, withRouter } from 'react-router-dom';
import React, { Component } from 'react';
import { Dropdown } from 'react-viewerbase';
import { withTranslation } from 'react-i18next';
import './Header.css';
import OHIFLogo from '../OHIFLogo/OHIFLogo.js';
import PropTypes from 'prop-types';
// import { UserPreferencesModal } from 'react-viewerbase';
import { hotkeysManager } from './../../App.js';
import { withTranslation } from 'react-i18next';
class Header extends Component {
static propTypes = {
@ -25,8 +25,8 @@ class Header extends Component {
};
// onSave: data => {
// const contextName = window.store.getState().commandContext.context;
// const preferences = cloneDeep(window.store.getState().preferences);
// const contextName = store.getState().commandContext.context;
// const preferences = cloneDeep(store.getState().preferences);
// preferences[contextName] = data;
// dispatch(setUserPreferences(preferences));
// dispatch(setUserPreferencesModalOpen(false));

View File

@ -1,7 +1,7 @@
import dicomParser from 'dicom-parser';
import OHIF from 'ohif-core';
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import OHIF from 'ohif-core';
import dicomParser from 'dicom-parser';
import version from './version.js';
window.info = {
@ -10,8 +10,10 @@ window.info = {
};
// For debugging
//if (process.env.node_env === 'development') {
window.cornerstone = cornerstone;
window.cornerstoneWADOImageLoader = cornerstoneWADOImageLoader;
//}
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;

View File

@ -20,14 +20,14 @@ const mapStateToProps = state => {
const cineData = cine || {
isPlaying: false,
cineFrameRate: 24
cineFrameRate: 24,
};
// New props we're creating?
return {
activeEnabledElement: dom,
activeViewportCineData: cineData,
activeViewportIndex: state.viewports.activeViewportIndex
activeViewportIndex: state.viewports.activeViewportIndex,
};
};
@ -35,7 +35,7 @@ const mapDispatchToProps = dispatch => {
return {
dispatchSetViewportSpecificData: (viewportIndex, data) => {
dispatch(setViewportSpecificData(viewportIndex, data));
}
},
};
};
@ -43,7 +43,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
const {
activeEnabledElement,
activeViewportCineData,
activeViewportIndex
activeViewportIndex,
} = propsFromState;
return {
@ -54,7 +54,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
cine.isPlaying = !cine.isPlaying;
propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, {
cine
cine,
});
},
onFrameRateChanged: frameRate => {
@ -62,7 +62,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
cine.cineFrameRate = frameRate;
propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, {
cine
cine,
});
},
onClickNextButton: () => {
@ -89,7 +89,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
if (!stackData || !stackData.data || !stackData.data.length) return;
const lastIndex = stackData.data[0].imageIds.length - 1;
scrollToIndex(activeEnabledElement, lastIndex);
}
},
};
};

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { LayoutButton } from 'react-viewerbase';
import OHIF from 'ohif-core';
import { connect } from 'react-redux';
const { setLayout } = OHIF.redux.actions;
@ -22,7 +22,6 @@ const mapDispatchToProps = dispatch => {
viewports.push({
height: `${100 / rows}%`,
width: `${100 / columns}%`,
plugin: 'cornerstone', // Temporary because right now switching back from VTK breaks things
});
}
const layout = {

View File

@ -1,32 +1,29 @@
import { connect } from 'react-redux';
import { LayoutManager } from 'react-viewerbase';
import OHIF from 'ohif-core';
import { MODULE_TYPES } from 'ohif-core';
import { connect } from 'react-redux';
import { extensionManager } from './../App.js';
const mapStateToProps = state => {
const viewportPluginIds = state.plugins.availablePlugins
.filter(plugin => plugin.type === OHIF.plugins.PLUGIN_TYPES.VIEWPORT)
.map(plugin => plugin.id);
const availableViewportModules = {};
const viewportModules = extensionManager.modules[MODULE_TYPES.VIEWPORT];
const availablePlugins = {};
viewportPluginIds.forEach(id => {
const plugin = OHIF.plugins.availablePlugins.find(
plugin => plugin.id === id
);
if (plugin) {
availablePlugins[id] = plugin.component;
}
viewportModules.forEach(moduleDefinition => {
availableViewportModules[moduleDefinition.extensionId] =
moduleDefinition.module;
});
// TODO Use something like state.plugins.defaultPlugin[OHIF.plugins.PLUGIN_TYPES.VIEWPORT]
// TODO: Use something like state.plugins.defaultPlugin[MODULE_TYPES.VIEWPORT]
let defaultPlugin;
if (viewportPluginIds && viewportPluginIds.length) {
defaultPlugin = viewportPluginIds[0];
if (viewportModules.length) {
defaultPlugin = viewportModules[0].extensionId;
}
return {
layout: state.viewports.layout,
activeViewportIndex: state.viewports.activeViewportIndex,
availablePlugins,
// TODO: rename `availableViewportModules`
availablePlugins: availableViewportModules,
// TODO: rename `defaultViewportModule`
defaultPlugin,
};
};

View File

@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import PluginSwitch from './PluginSwitch.js';
import OHIF from 'ohif-core';
import PluginSwitch from './PluginSwitch.js';
import { commandsManager } from './../App.js';
import { connect } from 'react-redux';
const { setLayout } = OHIF.redux.actions;
@ -60,7 +61,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
icon: 'cube',
active: false,
onClick: () => {
window.commandsManager.runCommand('axial', {}, 'vtk');
commandsManager.runCommand('axial');
},
},
{
@ -68,7 +69,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
icon: 'cube',
active: false,
onClick: () => {
window.commandsManager.runCommand('sagittal', {}, 'vtk');
commandsManager.runCommand('sagittal');
},
},
{
@ -76,15 +77,14 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
icon: 'cube',
active: false,
onClick: () => {
window.commandsManager.runCommand('coronal', {}, 'vtk');
commandsManager.runCommand('coronal');
},
},*/
{
text: '2D MPR',
label: '2D MPR',
icon: 'cube',
active: false,
onClick: () => {
window.commandsManager.runCommand('mpr2d', {}, 'vtk');
commandsManager.runCommand('mpr2d');
},
},
];

View File

@ -5,23 +5,11 @@ import {
import ToolbarRow from './ToolbarRow';
import { connect } from 'react-redux';
const defaultPlugin = 'cornerstone';
import { getActiveContexts } from './../store/layout/selectors.js';
const mapStateToProps = state => {
const { layout, viewportSpecificData, activeViewportIndex } = state.viewports;
const pluginInLayout =
layout.viewports[activeViewportIndex] &&
layout.viewports[activeViewportIndex].plugin;
const pluginInViewportData =
viewportSpecificData[activeViewportIndex] &&
viewportSpecificData[activeViewportIndex].plugin;
const pluginInActiveViewport =
pluginInLayout || pluginInViewportData || defaultPlugin;
// const extensionData = state.extensions[pluginInActiveViewport];
return {
pluginId: pluginInActiveViewport,
activeContexts: getActiveContexts(state),
leftSidebarOpen: state.ui.leftSidebarOpen,
rightSidebarOpen: state.ui.rightSidebarOpen,
};

View File

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

View File

@ -5,8 +5,6 @@ import { connect } from 'react-redux';
const {
setViewportSpecificData,
clearViewportSpecificData,
// setToolActive,
// setActiveViewportSpecificData,
} = OHIF.redux.actions;
const mapStateToProps = state => {
@ -28,12 +26,6 @@ const mapDispatchToProps = dispatch => {
clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData());
},
// setToolActive: tool => {
// dispatch(setToolActive(tool));
// },
// setActiveViewportSpecificData: viewport => {
// dispatch(setActiveViewportSpecificData(viewport));
// },
};
};

View File

@ -1,12 +1,14 @@
import './ToolbarRow.css';
import React, { Component } from 'react';
import { RoundedButtonGroup, ToolbarButton } from 'react-viewerbase';
import { commandsManager, extensionManager } from './../App.js';
import ConnectedCineDialog from './ConnectedCineDialog';
import ConnectedLayoutButton from './ConnectedLayoutButton';
import ConnectedPluginSwitch from './ConnectedPluginSwitch.js';
import OHIF from 'ohif-core';
import { MODULE_TYPES } from 'ohif-core';
import PropTypes from 'prop-types';
import { RoundedButtonGroup } from 'react-viewerbase';
class ToolbarRow extends Component {
static propTypes = {
@ -14,7 +16,7 @@ class ToolbarRow extends Component {
rightSidebarOpen: PropTypes.bool.isRequired,
setLeftSidebarOpen: PropTypes.func,
setRightSidebarOpen: PropTypes.func,
pluginId: PropTypes.string,
activeContexts: PropTypes.arrayOf(PropTypes.string).isRequired,
};
static defaultProps = {
@ -22,6 +24,38 @@ class ToolbarRow extends Component {
rightSidebarOpen: false,
};
constructor(props) {
super(props);
const toolbarButtonDefinitions = _getVisibleToolbarButtons.call(this);
// TODO:
// If it's a tool that can be active... Mark it as active?
// - Tools that are on/off?
// - Tools that can be bound to multiple buttons?
// Normal ToolbarButtons...
// Just how high do we need to hoist this state?
// Why ToolbarRow instead of just Toolbar? Do we have any others?
this.state = {
toolbarButtons: toolbarButtonDefinitions,
activeButtons: [],
isCineDialogOpen: false,
};
this._handleBuiltIn = _handleBuiltIn.bind(this);
}
componentDidUpdate(prevProps) {
const activeContextsChanged =
prevProps.activeContexts !== this.props.activeContexts;
if (activeContextsChanged) {
this.setState({
toolbarButtons: _getVisibleToolbarButtons.call(this),
});
}
}
onLeftSidebarValueChanged = value => {
this.props.setLeftSidebarOpen(!!value);
};
@ -55,44 +89,111 @@ class ToolbarRow extends Component {
? rightSidebarToggle[0].value
: null;
const currentPluginId = this.props.pluginId;
const buttonComponents = _getButtonComponents.call(
this,
this.state.toolbarButtons,
this.state.activeButtons
);
const { PLUGIN_TYPES, availablePlugins } = OHIF.plugins;
const plugin = availablePlugins.find(entry => {
return (
entry.type === PLUGIN_TYPES.TOOLBAR && entry.id === currentPluginId
);
});
let pluginComp;
if (plugin) {
const PluginComponent = plugin.component;
pluginComp = <PluginComponent />;
}
const cineDialogContainerStyle = {
display: this.state.isCineDialogOpen ? 'block' : 'none',
position: 'absolute',
top: '82px',
zIndex: 999,
};
return (
<div className="ToolbarRow">
<div className="pull-left m-t-1 p-y-1" style={{ padding: '10px' }}>
<RoundedButtonGroup
options={leftSidebarToggle}
value={leftSidebarValue}
onValueChanged={this.onLeftSidebarValueChanged}
/>
<>
<div className="ToolbarRow">
<div className="pull-left m-t-1 p-y-1" style={{ padding: '10px' }}>
<RoundedButtonGroup
options={leftSidebarToggle}
value={leftSidebarValue}
onValueChanged={this.onLeftSidebarValueChanged}
/>
</div>
{buttonComponents}
<ConnectedLayoutButton />
<ConnectedPluginSwitch />
<div
className="pull-right m-t-1 rm-x-1"
style={{ marginLeft: 'auto' }}
>
<RoundedButtonGroup
options={rightSidebarToggle}
value={rightSidebarValue}
onValueChanged={this.onRightSidebarValueChanged}
/>
</div>
</div>
{pluginComp}
<ConnectedLayoutButton />
<ConnectedPluginSwitch />
<div className="pull-right m-t-1 rm-x-1" style={{ marginLeft: 'auto' }}>
<RoundedButtonGroup
options={rightSidebarToggle}
value={rightSidebarValue}
onValueChanged={this.onRightSidebarValueChanged}
/>
<div className="CineDialogContainer" style={cineDialogContainerStyle}>
<ConnectedCineDialog />
</div>
</div>
</>
);
}
}
/**
* Determine which extension buttons should be showing, if they're
* active, and what their onClick behavior should be.
*/
function _getButtonComponents(toolbarButtons, activeButtons) {
return toolbarButtons.map((button, index) => {
// TODO: If `button.buttons`, use `ExpandedToolMenu`
// I don't believe any extensions currently leverage this
return (
<ToolbarButton
key={button.id}
label={button.label}
icon={button.icon}
onClick={(evt, props) => {
if (button.commandName) {
const options = Object.assign({ evt }, button.commandOptions);
commandsManager.runCommand(button.commandName, options);
}
// TODO: Use Types ENUM
// TODO: We can update this to be a `getter` on the extension to query
// For the active tools after we apply our updates?
if (button.type === 'setToolActive') {
this.setState({
activeButtons: [button.id],
});
} else if (button.type === 'builtIn') {
this._handleBuiltIn(button.options);
}
}}
isActive={activeButtons.includes(button.id)}
/>
);
});
}
function _getVisibleToolbarButtons() {
const toolbarModules = extensionManager.modules[MODULE_TYPES.TOOLBAR];
const toolbarButtonDefinitions = [];
toolbarModules.forEach(extension => {
const { definitions, defaultContext } = extension.module;
definitions.forEach(definition => {
const context = definition.context || defaultContext;
if (this.props.activeContexts.includes(context)) {
toolbarButtonDefinitions.push(definition);
}
});
});
return toolbarButtonDefinitions;
}
function _handleBuiltIn({ behavior } = {}) {
if (behavior === 'CINE') {
this.setState({
isCineDialogOpen: !this.state.isCineDialogOpen,
});
}
}
export default ToolbarRow;

View File

@ -2,8 +2,7 @@ import './ViewerMain.css';
import { Component } from 'react';
import ConnectedLayoutManager from './ConnectedLayoutManager.js';
// import { OHIF } from 'ohif-core';
//
import ConnectedToolContextMenu from './ConnectedToolContextMenu.js';
import PropTypes from 'prop-types';
import React from 'react';
@ -20,15 +19,6 @@ class ViewerMain extends Component {
constructor(props) {
super(props);
// Initialize hotkeys
// new OHIF.HotkeysUtil('viewer', {
// setViewportSpecificData: props.setViewportSpecificData,
// clearViewportSpecificData: props.clearViewportSpecificData,
// setToolActive: props.setToolActive,
// setActiveViewportSpecificData: props.setActiveViewportSpecificData,
// });
// hotkeys.init();
this.state = {
displaySets: [],
};
@ -139,7 +129,10 @@ class ViewerMain extends Component {
studies={this.props.studies}
viewportData={this.getViewportData()}
setViewportData={this.setViewportData}
/>
>
{/* Children to add to each viewport that support children */}
<ConnectedToolContextMenu />
</ConnectedLayoutManager>
</div>
);
}

View File

@ -1,9 +1,13 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import ConnectedViewer from './ConnectedViewer.js';
import { metadata, studies, utils } from 'ohif-core';
const { createDisplaySets } = OHIF.utils;
import ConnectedViewer from './ConnectedViewer.js';
import PropTypes from 'prop-types';
import { extensionManager } from './../App.js';
const { OHIFStudyMetadata } = metadata;
const { retrieveStudiesMetadata } = studies;
const { studyMetadataManager, updateMetaDataManager } = utils;
class ViewerRetrieveStudyData extends Component {
static propTypes = {
@ -17,32 +21,55 @@ class ViewerRetrieveStudyData extends Component {
error: null,
};
componentDidMount() {
async componentDidMount() {
// TODO: Avoid using timepoints here
//const params = { studyInstanceUids, seriesInstanceUids, timepointId, timepointsFilter={} };
const { studyInstanceUids, seriesInstanceUids, server } = this.props;
const promise = OHIF.studies.retrieveStudiesMetadata(
server,
studyInstanceUids,
seriesInstanceUids
);
// Render the viewer when the data is ready
promise
.then(studies => {
const updatedStudies = createDisplaySets(studies);
try {
const studies = await retrieveStudiesMetadata(
server,
studyInstanceUids,
seriesInstanceUids
);
this.setState({
studies: updatedStudies,
});
})
.catch(error => {
this.setState({
error: true,
});
// Render the viewer when the data is ready
// TODO: CLEAR THIS SOMEWHERE ELSE
studyMetadataManager.purge();
throw new Error(error);
// Map studies to new format, update metadata manager?
const updatedStudies = studies.map(study => {
const studyMetadata = new OHIFStudyMetadata(
study,
study.studyInstanceUid
);
const sopClassHandlerModules =
extensionManager.modules['sopClassHandlerModule'];
study.displaySets =
study.displaySets ||
studyMetadata.createDisplaySets(sopClassHandlerModules);
studyMetadata.setDisplaySets(study.displaySets);
// Updates WADO-RS metaDataManager
updateMetaDataManager(study);
studyMetadataManager.add(studyMetadata);
return study;
});
this.setState({
studies: updatedStudies,
});
} catch (err) {
this.setState({
error: true,
});
// TODO: Handle gracefully instead of throwing?
throw new Error(err);
}
}
render() {

View File

@ -1,4 +1,7 @@
import { OHIF } from 'ohif-core';
import { measurements, utils } from 'ohif-core';
const { MeasurementApi } = measurements;
const { studyMetadataManager } = utils;
// TODO: Move this function to OHIF itself so we can use it on the OHIF measurment table (when it is finished)
@ -47,9 +50,7 @@ export default function jumpToRowItem(
let measurement = dataAtThisTimepoint;
const { tool } = OHIF.measurements.MeasurementApi.getToolConfiguration(
toolType
);
const { tool } = MeasurementApi.getToolConfiguration(toolType);
if (options.childToolKey) {
measurement = dataAtThisTimepoint[options.childToolKey];
} else if (Array.isArray(tool.childTools)) {
@ -79,7 +80,7 @@ export default function jumpToRowItem(
return;
}
const study = OHIF.utils.studyMetadataManager.get(data.studyInstanceUid);
const study = studyMetadataManager.get(data.studyInstanceUid);
if (!study) {
throw new Error('Study not found.');
}

View File

@ -1,10 +1,14 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import qs from 'querystring';
import Viewer from '../connectedComponents/Viewer';
import OHIF from 'ohif-core';
import { log, metadata, studies, utils } from 'ohif-core';
const { createDisplaySets } = OHIF.utils;
import PropTypes from 'prop-types';
import Viewer from '../connectedComponents/Viewer';
import { extensionManager } from './../App.js';
import qs from 'querystring';
const { OHIFStudyMetadata } = metadata;
const { retrieveStudiesMetadata } = studies;
const { studyMetadataManager, updateMetaDataManager } = utils;
class StandaloneRouting extends Component {
state = {
@ -31,7 +35,7 @@ class StandaloneRouting extends Component {
// Add event listeners for request failure
oReq.addEventListener('error', error => {
OHIF.log.warn('An error occurred while retrieving the JSON data');
log.warn('An error occurred while retrieving the JSON data');
reject(error);
});
@ -41,11 +45,11 @@ class StandaloneRouting extends Component {
// Parse the response content
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText
if (!oReq.responseText) {
OHIF.log.warn('Response was undefined');
log.warn('Response was undefined');
reject(new Error('Response was undefined'));
}
OHIF.log.info(JSON.stringify(oReq.responseText, null, 2));
log.info(JSON.stringify(oReq.responseText, null, 2));
const data = JSON.parse(oReq.responseText);
if (data.servers && query.studyInstanceUids) {
@ -55,20 +59,18 @@ class StandaloneRouting extends Component {
const studyInstanceUids = query.studyInstanceUids.split(';');
const seriesInstanceUids = [];
OHIF.studies
.retrieveStudiesMetadata(
server,
studyInstanceUids,
seriesInstanceUids
)
.then(
studies => {
resolve(studies);
},
error => {
reject(error);
}
);
retrieveStudiesMetadata(
server,
studyInstanceUids,
seriesInstanceUids
).then(
studies => {
resolve(studies);
},
error => {
reject(error);
}
);
} else {
resolve(data.studies);
}
@ -77,7 +79,7 @@ class StandaloneRouting extends Component {
// Open the Request to the server for the JSON data
// In this case we have a server-side route called /api/
// which responds to GET requests with the study data
OHIF.log.info(`Sending Request to: ${url}`);
log.info(`Sending Request to: ${url}`);
oReq.open('GET', url);
oReq.setRequestHeader('Accept', 'application/json');
@ -86,18 +88,39 @@ class StandaloneRouting extends Component {
});
}
componentDidMount() {
const query = qs.parse(this.props.location.search);
StandaloneRouting.parseQueryAndFetchStudies(query).then(
studies => {
const updatedStudies = createDisplaySets(studies);
async componentDidMount() {
try {
const query = qs.parse(this.props.location.search);
const studies = await StandaloneRouting.parseQueryAndFetchStudies(query);
this.setState({ studies: updatedStudies });
},
error => {
this.setState({ error });
}
);
studyMetadataManager.purge();
// Map studies to new format, update metadata manager?
const updatedStudies = studies.map(study => {
const studyMetadata = new OHIFStudyMetadata(
study,
study.studyInstanceUid
);
const sopClassHandlerModules =
extensionManager.modules['sopClassHandlerModule'];
study.displaySets =
study.displaySets ||
studyMetadata.createDisplaySets(sopClassHandlerModules);
studyMetadata.setDisplaySets(study.displaySets);
// Updates WADO-RS metaDataManager
updateMetaDataManager(study);
studyMetadataManager.add(studyMetadata);
return study;
});
this.setState({ studies: updatedStudies });
} catch (error) {
this.setState({ error });
}
}
render() {

View File

@ -1,18 +1,28 @@
import { combineReducers, createStore } from 'redux';
import { applyMiddleware, combineReducers, createStore } from 'redux';
// import { createLogger } from 'redux-logger';
import layoutReducers from './layout/reducers.js';
import { reducer as oidcReducer } from 'redux-oidc';
import { redux } from 'ohif-core';
import thunkMiddleware from 'redux-thunk';
// Combine our ohif-core, ui, and oidc reducers
// Set init data, using values found in localStorage
const { reducers, localStorage } = redux;
// const loggerMiddleware = createLogger();
reducers.ui = layoutReducers;
reducers.oidc = oidcReducer;
const combined = combineReducers(reducers);
const store = createStore(combined, localStorage.loadState());
const rootReducer = combineReducers(reducers);
const store = createStore(
rootReducer,
localStorage.loadState(), // preloadedState
applyMiddleware(
thunkMiddleware // Lets us dispatch() functions
// loggerMiddleware // neat middleware that logs actions
)
);
// When the store's preferences change,
// Update our cached preferences in localStorage

View File

@ -1,18 +1,3 @@
export const addActiveContext = state => ({
type: 'ADD_ACTIVE_CONTEXT',
state,
});
export const removeActiveContext = state => ({
type: 'REMOVE_ACTIVE_CONTEXT',
state,
});
export const clearActiveContexts = state => ({
type: 'CLEAR_ACTIVE_CONTEXTS',
state,
});
export const setLeftSidebarOpen = state => ({
type: 'SET_LEFT_SIDEBAR_OPEN',
state,
@ -24,10 +9,6 @@ export const setRightSidebarOpen = state => ({
});
const actions = {
addActiveContext,
removeActiveContext,
clearActiveContexts,
//
setLeftSidebarOpen,
setRightSidebarOpen,
};

View File

@ -3,27 +3,10 @@ const defaultState = {
rightSidebarOpen: false,
labelling: {},
contextMenu: {},
activeContexts: [],
};
const ui = (state = defaultState, action) => {
switch (action.type) {
// ~ ACTIVE CONTEXTS
// https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#inserting-and-removing-items-in-arrays
case 'ADD_ACTIVE_CONTEXT': {
const shallowCopy = Object.assign({}, state);
shallowCopy.activeContexts = [...shallowCopy.activeContexts, action.item];
return shallowCopy;
}
case 'REMOVE_ACTIVE_CONTEXT': {
const shallowCopy = Object.assign({}, state);
shallowCopy.activeContexts = shallowCopy.activeContexts.filter(
item => item !== action.item
);
return shallowCopy;
}
case 'CLEAR_ACTIVE_CONTEXTS':
return Object.assign({}, state, { activeContexts: [] });
// ~ SIDEBAR
case 'SET_LEFT_SIDEBAR_OPEN':
return Object.assign({}, state, { leftSidebarOpen: action.state });

View File

@ -0,0 +1,28 @@
import { createSelector } from 'reselect';
const getActiveViewportIndex = state => state.viewports.activeViewportIndex;
const getLayoutViewports = state => state.viewports.layout.viewports;
const getViewportSpecificData = state => state.viewports.viewportSpecificData;
/**
* Think of this as a computed getter for our store. It lets us watch parts of
* our redux state, and only update/recalculate when those values change.
*/
export const getActiveContexts = createSelector(
[getActiveViewportIndex, getLayoutViewports, getViewportSpecificData],
(activeViewportIndex, layoutViewports, viewportSpecificData) => {
const activeContexts = ['VIEWER'];
const activeLayoutViewport = layoutViewports[activeViewportIndex] || {};
const activeViewportSpecificData =
viewportSpecificData[activeViewportIndex] || {};
const activeViewportPluginName =
activeLayoutViewport.plugin || activeViewportSpecificData.plugin;
if (activeViewportPluginName) {
const activeViewportExtension = `ACTIVE_VIEWPORT::${activeViewportPluginName.toUpperCase()}`;
activeContexts.push(activeViewportExtension);
}
return activeContexts;
}
);

View File

@ -1,60 +0,0 @@
export default function() {
return [
{
command: 'StackScroll',
type: 'tool',
text: 'Stack Scroll',
icon: 'bars',
active: false,
},
{
command: 'Zoom',
type: 'tool',
text: 'Zoom',
icon: 'search-plus',
active: false,
},
{
command: 'Wwwc',
type: 'tool',
text: 'Levels',
icon: 'level',
active: true,
},
{
command: 'Pan',
type: 'tool',
text: 'Pan',
icon: 'arrows',
active: false,
},
{
command: 'Length',
type: 'tool',
text: 'Length',
icon: 'measure-temp',
active: false,
},
/*{
command: 'Annotate',
type: 'tool',
text: 'Annotate',
icon: `icon-tools-measure-non-target`,
active: false
},*/
{
command: 'Angle',
type: 'tool',
text: 'Angle',
icon: 'angle-left',
active: false,
},
{
command: 'reset',
type: 'command',
text: 'Reset',
icon: 'reset',
active: false,
},
];
}

View File

@ -1,9 +0,0 @@
import getDefaultToolbarButtons from './getDefaultToolbarButtons.js';
describe('getDefaultToolbarButtons.js', () => {
it('returns a non-empty array', () => {
const buttons = getDefaultToolbarButtons();
expect(buttons.length).toBeGreaterThan(0);
});
});

View File

@ -1,9 +1,4 @@
import getDefaultToolbarButtons from './getDefaultToolbarButtons.js';
import getUserManagerForOpenIdConnectClient from './getUserManagerForOpenIdConnectClient.js';
import initWebWorkers from './initWebWorkers.js';
export {
getDefaultToolbarButtons,
getUserManagerForOpenIdConnectClient,
initWebWorkers,
};
export { getUserManagerForOpenIdConnectClient, initWebWorkers };

View File

@ -5,11 +5,7 @@ describe('utils', () => {
const utilExports = Object.keys(utils).sort();
expect(utilExports).toEqual(
[
'getDefaultToolbarButtons',
'getUserManagerForOpenIdConnectClient',
'initWebWorkers',
].sort()
['getUserManagerForOpenIdConnectClient', 'initWebWorkers'].sort()
);
});
});

View File

@ -1435,29 +1435,45 @@
universal-user-agent "^2.0.0"
url-template "^2.0.8"
"@ohif/extension-cornerstone@0.0.36":
version "0.0.36"
resolved "https://registry.yarnpkg.com/@ohif/extension-cornerstone/-/extension-cornerstone-0.0.36.tgz#a09a01d152bb9f850698b537b02355456425f8d1"
integrity sha512-FrR9L74vqjNsW2wOSHeDXQo/twJAf/kM0awCn2/zGXN4tNxEaZRq56gfij86CH0OBGl1Wdzb8Zl6xe0JCtsNrA==
"@ohif/extension-cornerstone@0.0.37":
version "0.0.37"
resolved "https://registry.yarnpkg.com/@ohif/extension-cornerstone/-/extension-cornerstone-0.0.37.tgz#d9d31409ffbc773dde17767daa02b87f461cf11a"
integrity sha512-090WPGXOYIxZjQe0mIM7SipgR3ggUZpbtWpwg7huWC0PQObIM3Juy4L8mtss23OKaKsmrmVxUaFc5NRQhs+Z+w==
dependencies:
"@babel/runtime" "^7.2.0"
classnames "^2.2.6"
lodash.throttle "^4.1.1"
react-cornerstone-viewport "0.1.30"
"@ohif/extension-dicom-microscopy@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@ohif/extension-dicom-microscopy/-/extension-dicom-microscopy-0.0.6.tgz#18e0c128689695a7af505bc3dd7ede97df7f8be2"
integrity sha512-5On3GdZgeyMse2CKI0sbxJJ269IqlFXZ0Wi+8+D9lsp0Wdt2AOx15ZmmFA9MJ6GGluMHNrrTY2gZBwLyLUZyRw==
"@ohif/extension-dicom-html@0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@ohif/extension-dicom-html/-/extension-dicom-html-0.0.3.tgz#1d085f603379599cf07801ec003e3d5050e2d659"
integrity sha512-S+Mg9OFHNvllDDza55y4/NmBV1bFJ+NLbpcnsw45QyfgT3KAg8v/8ht1QYM9IcBGqhxMTJs8OqBuJI9sAiTWlw==
dependencies:
"@babel/runtime" "^7.2.0"
"@ohif/extension-dicom-microscopy@0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@ohif/extension-dicom-microscopy/-/extension-dicom-microscopy-0.0.7.tgz#2ac78c2eef8915e43a81e82ff895c251ced0b441"
integrity sha512-QXf7+YE297owIWBMGJJsJwbMk6bfmmu0fBJQhYFwXQYBOToSiabdr21PBxNADf88ZC1kiBcULOcTBRypJtd31g==
dependencies:
"@babel/runtime" "^7.2.0"
classnames "^2.2.6"
dicom-microscopy-viewer "^0.4.3"
"@ohif/extension-vtk@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@ohif/extension-vtk/-/extension-vtk-0.0.6.tgz#a4100963d87f268190ecac7784ff131bd4350b20"
integrity sha512-IwXIpAQEOSRbPNXW0B6XfBMrp3k3LvdD/uyrOeEjI35hVjABf/9aGwqMk9wiw672AN26w/eZ04uXeKjRBsLzsQ==
"@ohif/extension-dicom-pdf@0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@ohif/extension-dicom-pdf/-/extension-dicom-pdf-0.0.7.tgz#ba80a77235d43876e25485e1a964f36ebf104b94"
integrity sha512-uOevtxH6Vm2p+2xgo+ER3+oV1zfnUqHYoeqrICaKP3PKXGcVanWFE8W/RWiNZ+PS2/LNpSjJyLfuSxwZ3IBUVw==
dependencies:
"@babel/runtime" "^7.2.0"
classnames "^2.2.6"
lodash.isequal "^4.5.0"
"@ohif/extension-vtk@0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@ohif/extension-vtk/-/extension-vtk-0.0.7.tgz#5bad8e35cae65957b467a12cc17e027ce80ad40a"
integrity sha512-yEdtnk7ImIoM3YrDkXXFo9ClJAXbd3rc9si3GlrOY6ImeWtwTf5LvcHG3QCe1iIfEJ2Q8wkGfERddbSw3joIYQ==
dependencies:
"@babel/runtime" "^7.4.5"
"@ohif/i18n" "0.0.4"
@ -4503,6 +4519,11 @@ dedent@0.7.0, dedent@^0.7.0:
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
deep-diff@^0.3.5:
version "0.3.8"
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84"
integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=
deep-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@ -10266,10 +10287,10 @@ octokit-pagination-methods@^1.1.0:
resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
ohif-core@0.5.9:
version "0.5.9"
resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.5.9.tgz#337835c82571bdc4935210273a6018c3af872adf"
integrity sha512-TvA/THiTpBnerRbv4FJlfwSMSyb4Tkn40ud14FP6JxLUQgCtLsTsaFhXIFBwZvBR/qVJwIlDluMxj/vqobCwkQ==
ohif-core@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.6.0.tgz#9fea32d8d10da84b9030d2e4f76b30e431e14ac2"
integrity sha512-2Z/HTRZ21i7w056MifH1k0y4brhrZSUsmeqbMcPLSh1cztRGnLQBamBZ08SzWvktcNxLDuwJI9zNDkJ8Gw4Idw==
dependencies:
"@babel/runtime" "^7.2.0"
ajv "^6.10.0"
@ -10280,22 +10301,6 @@ ohif-core@0.5.9:
mousetrap "^1.6.3"
validate.js "^0.12.0"
ohif-dicom-html-extension@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/ohif-dicom-html-extension/-/ohif-dicom-html-extension-0.0.2.tgz#5dd80836ee8f12f6ddf6de2b88c72af96d8c347b"
integrity sha512-7/YkaKrgnydiypFl39mvWO5vxCoGecudkdyCFedgDnG10UvkDF4nBcgNvoBupwBtGKYhXTfqye6fPWxQO0NWyw==
dependencies:
"@babel/runtime" "^7.2.0"
ohif-dicom-pdf-extension@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/ohif-dicom-pdf-extension/-/ohif-dicom-pdf-extension-0.0.6.tgz#5c1c1bf955d160dda01c36c4ed899d8e65384d63"
integrity sha512-MRBDnC78itsos3c8QMKjgaFp8tmXyGIq5qVLREr//osqCYirhWQbCFJv73eYR5+bRCdJfUMMpvnDSChFIhcT6A==
dependencies:
"@babel/runtime" "^7.2.0"
classnames "^2.2.6"
lodash.isequal "^4.5.0"
oidc-client@1.7.x:
version "1.7.1"
resolved "https://registry.yarnpkg.com/oidc-client/-/oidc-client-1.7.1.tgz#8b9d8d50fd7f878968b1cda17712c1747eef9a54"
@ -12408,10 +12413,10 @@ react-transition-group@^2.0.0, react-transition-group@^2.2.0:
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"
react-viewerbase@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.9.0.tgz#947ab61bf32e1d58836a637403d128247e175d2e"
integrity sha512-VeOGzno5fg9A0Ttx+O3CQ8KXPr1XbdEOVbA+JiLwcuycX3CmDsqLzODsjeXb8npfXDbQA+n/ZPCyxsB7JDEBlA==
react-viewerbase@0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.10.0.tgz#4ac07649325759f8a58fc3be50dea4d1db8f13f8"
integrity sha512-pUt+HUGoEkDnVhxcg+gsoRzegEPVpsMPH8cnJINRoaATqbxc0jbt1N+3fTNTbPMMKZYMxC99L5bQ6OQ1QF+3qw==
dependencies:
"@babel/runtime" "7.2.0"
"@ohif/i18n" "^0.0.4"
@ -12710,6 +12715,13 @@ redeyed@~2.1.0:
dependencies:
esprima "~4.0.0"
redux-logger@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8=
dependencies:
deep-diff "^0.3.5"
redux-oidc@3.1.x:
version "3.1.2"
resolved "https://registry.yarnpkg.com/redux-oidc/-/redux-oidc-3.1.2.tgz#31a771b0e05a65879626262e1e63ba70a0936ffd"
@ -12717,6 +12729,11 @@ redux-oidc@3.1.x:
optionalDependencies:
immutable ">=3.6.0"
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5"
@ -13028,6 +13045,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
reselect@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
reserved-words@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"