feat: Multiple fixes and implementation changes to react-cornerstone-viewport
* Update props for new react-cornerstone-viewport implementation * Create an preRegistration hook for the cornerstone extension to setup cornerstone tools (takes configuration) * Isolate measurements panel logic * reorder extension registration * remove unused setupTools * Restore CINE connection * fix stack prefetcher toggling * Cleanup OHIFCornerstoneViewport props * updated yarn lock * bust yarn.lock to get updated react-cornerstone-viewport * fix review comment; us isActive to better show it's influence on prefetch * refactor: remove pass through method * review performance optimization * review; comment out unused variable * shift tool under correct comment * Use alternative csTools config, if provided * Note regarding config options
This commit is contained in:
parent
5c5223fcf1
commit
1cc94f36a7
@ -48,6 +48,6 @@
|
|||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"react-cornerstone-viewport": "0.1.30"
|
"react-cornerstone-viewport": "2.x.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,36 +23,40 @@ const MEASUREMENT_ACTION_MAP = {
|
|||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
let dataFromStore;
|
let dataFromStore;
|
||||||
|
|
||||||
|
// TODO: This may not be updated anymore :thinking:
|
||||||
if (state.extensions && state.extensions.cornerstone) {
|
if (state.extensions && state.extensions.cornerstone) {
|
||||||
dataFromStore = state.extensions.cornerstone;
|
dataFromStore = state.extensions.cornerstone;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should be extension configuration
|
|
||||||
// ...dataFromStore -->
|
|
||||||
// availableTools,
|
|
||||||
// onNewImage,
|
|
||||||
// onRightClick,
|
|
||||||
// onTouchPress,
|
|
||||||
// onTouchStart,
|
|
||||||
// onMouseClick,
|
|
||||||
|
|
||||||
// If this is the active viewport, enable prefetching.
|
// If this is the active viewport, enable prefetching.
|
||||||
const { viewportIndex } = ownProps; //.viewportData;
|
const { viewportIndex } = ownProps; //.viewportData;
|
||||||
const isActive = viewportIndex === state.viewports.activeViewportIndex;
|
const isActive = viewportIndex === state.viewports.activeViewportIndex;
|
||||||
const viewportSpecificData =
|
const viewportSpecificData =
|
||||||
state.viewports.viewportSpecificData[viewportIndex] || {};
|
state.viewports.viewportSpecificData[viewportIndex] || {};
|
||||||
|
|
||||||
|
// CINE
|
||||||
|
let isPlaying = false;
|
||||||
|
let frameRate = 24;
|
||||||
|
|
||||||
|
if (viewportSpecificData && viewportSpecificData.cine) {
|
||||||
|
const cine = viewportSpecificData.cine;
|
||||||
|
|
||||||
|
isPlaying = cine.isPlaying === true;
|
||||||
|
frameRate = cine.cineFrameRate || frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
layout: state.viewports.layout,
|
// layout: state.viewports.layout,
|
||||||
isActive,
|
isActive,
|
||||||
// TODO: Need a cleaner and more versatile way.
|
// TODO: Need a cleaner and more versatile way.
|
||||||
// Currently justing using escape hatch + commands
|
// Currently justing using escape hatch + commands
|
||||||
// activeTool: activeButton && activeButton.command,
|
// activeTool: activeButton && activeButton.command,
|
||||||
...dataFromStore,
|
...dataFromStore,
|
||||||
enableStackPrefetch: isActive,
|
isStackPrefetchEnabled: isActive,
|
||||||
|
isPlaying,
|
||||||
|
frameRate,
|
||||||
//stack: viewportSpecificData.stack,
|
//stack: viewportSpecificData.stack,
|
||||||
cineToolData: viewportSpecificData.cine,
|
// viewport: viewportSpecificData.viewport,
|
||||||
viewport: viewportSpecificData.viewport,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import ConnectedCornerstoneViewport from "./ConnectedCornerstoneViewport";
|
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
|
||||||
import OHIF from "@ohif/core";
|
import OHIF from '@ohif/core';
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from 'prop-types';
|
||||||
import cornerstone from "cornerstone-core";
|
import cornerstone from 'cornerstone-core';
|
||||||
import handleSegmentationStorage from "./handleSegmentationStorage.js";
|
import handleSegmentationStorage from './handleSegmentationStorage.js';
|
||||||
|
|
||||||
const { StackManager } = OHIF.utils;
|
const { StackManager } = OHIF.utils;
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ cornerstone.metaData.addProvider(
|
|||||||
StackManager.setMetadataProvider(metadataProvider);
|
StackManager.setMetadataProvider(metadataProvider);
|
||||||
|
|
||||||
const SOP_CLASSES = {
|
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 = {};
|
const specialCaseHandlers = {};
|
||||||
@ -28,11 +28,11 @@ specialCaseHandlers[
|
|||||||
|
|
||||||
class OHIFCornerstoneViewport extends Component {
|
class OHIFCornerstoneViewport extends Component {
|
||||||
state = {
|
state = {
|
||||||
viewportData: null
|
viewportData: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
customProps: {}
|
customProps: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -40,17 +40,17 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
displaySet: PropTypes.object,
|
displaySet: PropTypes.object,
|
||||||
viewportIndex: PropTypes.number,
|
viewportIndex: PropTypes.number,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
customProps: PropTypes.object
|
customProps: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static id = "OHIFCornerstoneViewport";
|
static id = 'OHIFCornerstoneViewport';
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
console.log("OHIFCornerstoneViewport init()");
|
console.log('OHIFCornerstoneViewport init()');
|
||||||
}
|
}
|
||||||
|
|
||||||
static destroy() {
|
static destroy() {
|
||||||
console.log("OHIFCornerstoneViewport destroy()");
|
console.log('OHIFCornerstoneViewport destroy()');
|
||||||
StackManager.clearStacks();
|
StackManager.clearStacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,15 +72,15 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
frameIndex = 0
|
frameIndex = 0
|
||||||
) {
|
) {
|
||||||
if (!studies || !studies.length) {
|
if (!studies || !studies.length) {
|
||||||
throw new Error("Studies not provided.");
|
throw new Error('Studies not provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!studyInstanceUid) {
|
if (!studyInstanceUid) {
|
||||||
throw new Error("StudyInstanceUID not provided.");
|
throw new Error('StudyInstanceUID not provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!displaySetInstanceUid) {
|
if (!displaySetInstanceUid) {
|
||||||
throw new Error("StudyInstanceUID not provided.");
|
throw new Error('StudyInstanceUID not provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create shortcut to displaySet
|
// Create shortcut to displaySet
|
||||||
@ -89,7 +89,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!study) {
|
if (!study) {
|
||||||
throw new Error("Study not found.");
|
throw new Error('Study not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const displaySet = study.displaySets.find(set => {
|
const displaySet = study.displaySets.find(set => {
|
||||||
@ -97,7 +97,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!displaySet) {
|
if (!displaySet) {
|
||||||
throw new Error("Display Set not found.");
|
throw new Error('Display Set not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get stack from Stack Manager
|
// Get stack from Stack Manager
|
||||||
@ -110,7 +110,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
if (sopInstanceUid) {
|
if (sopInstanceUid) {
|
||||||
const index = stack.imageIds.findIndex(imageId => {
|
const index = stack.imageIds.findIndex(imageId => {
|
||||||
const sopCommonModule = cornerstone.metaData.get(
|
const sopCommonModule = cornerstone.metaData.get(
|
||||||
"sopCommonModule",
|
'sopCommonModule',
|
||||||
imageId
|
imageId
|
||||||
);
|
);
|
||||||
if (!sopCommonModule) {
|
if (!sopCommonModule) {
|
||||||
@ -124,7 +124,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
stack.currentImageIdIndex = index;
|
stack.currentImageIdIndex = index;
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
"SOPInstanceUID provided was not found in specified DisplaySet"
|
'SOPInstanceUID provided was not found in specified DisplaySet'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,22 +132,6 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getViewportData = (
|
|
||||||
studies,
|
|
||||||
studyInstanceUid,
|
|
||||||
displaySetInstanceUid,
|
|
||||||
sopInstanceUid,
|
|
||||||
frameIndex
|
|
||||||
) => {
|
|
||||||
return OHIFCornerstoneViewport.getCornerstoneStack(
|
|
||||||
studies,
|
|
||||||
studyInstanceUid,
|
|
||||||
displaySetInstanceUid,
|
|
||||||
sopInstanceUid,
|
|
||||||
frameIndex
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
getViewportData = async (
|
getViewportData = async (
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
@ -172,7 +156,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
const stack = OHIFCornerstoneViewport.getViewportData(
|
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUid,
|
||||||
@ -183,7 +167,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
viewportData = {
|
viewportData = {
|
||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUid,
|
||||||
stack
|
stack,
|
||||||
};
|
};
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -199,7 +183,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
displaySetInstanceUid,
|
displaySetInstanceUid,
|
||||||
sopClassUids,
|
sopClassUids,
|
||||||
sopInstanceUid,
|
sopInstanceUid,
|
||||||
frameIndex
|
frameIndex,
|
||||||
} = displaySet;
|
} = displaySet;
|
||||||
|
|
||||||
if (!studyInstanceUid || !displaySetInstanceUid) {
|
if (!studyInstanceUid || !displaySetInstanceUid) {
|
||||||
@ -208,7 +192,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
|
|
||||||
if (sopClassUids && sopClassUids.length > 1) {
|
if (sopClassUids && sopClassUids.length > 1) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"More than one SOPClassUid in the same series is not yet supported."
|
'More than one SOPClassUid in the same series is not yet supported.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +207,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
frameIndex
|
frameIndex
|
||||||
).then(viewportData => {
|
).then(viewportData => {
|
||||||
this.setState({
|
this.setState({
|
||||||
viewportData
|
viewportData,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -249,25 +233,42 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
render() {
|
render() {
|
||||||
let childrenWithProps = null;
|
let childrenWithProps = null;
|
||||||
|
|
||||||
|
if (!this.state.viewportData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { viewportIndex } = this.props;
|
||||||
|
const {
|
||||||
|
imageIds,
|
||||||
|
currentImageIdIndex,
|
||||||
|
// If this comes from the instance, would be a better default
|
||||||
|
// `FrameTime` in the instance
|
||||||
|
// frameRate = 0,
|
||||||
|
} = this.state.viewportData.stack;
|
||||||
|
|
||||||
// TODO: Does it make more sense to use Context?
|
// TODO: Does it make more sense to use Context?
|
||||||
if (this.props.children && this.props.children.length) {
|
if (this.props.children && this.props.children.length) {
|
||||||
childrenWithProps = this.props.children.map((child, index) => {
|
childrenWithProps = this.props.children.map((child, index) => {
|
||||||
return React.cloneElement(child, {
|
return React.cloneElement(child, {
|
||||||
viewportIndex: this.props.viewportIndex,
|
viewportIndex: this.props.viewportIndex,
|
||||||
key: index
|
key: index,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.state.viewportData && (
|
|
||||||
<ConnectedCornerstoneViewport
|
<ConnectedCornerstoneViewport
|
||||||
viewportData={this.state.viewportData}
|
viewportIndex={viewportIndex}
|
||||||
viewportIndex={this.props.viewportIndex}
|
imageIds={imageIds}
|
||||||
|
imageIdIndex={currentImageIdIndex}
|
||||||
|
// ~~ Connected (From REDUX)
|
||||||
|
// frameRate={frameRate}
|
||||||
|
// isPlaying={false}
|
||||||
|
// isStackPrefetchEnabled={true}
|
||||||
|
// onElementEnabled={() => {}}
|
||||||
|
// setViewportActive{() => {}}
|
||||||
{...this.props.customProps}
|
{...this.props.customProps}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{childrenWithProps}
|
{childrenWithProps}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
|
import init from './init.js';
|
||||||
import asyncComponent from './asyncComponent.js';
|
import asyncComponent from './asyncComponent.js';
|
||||||
import commandsModule from './commandsModule.js';
|
import commandsModule from './commandsModule.js';
|
||||||
import toolbarModule from './toolbarModule.js';
|
import toolbarModule from './toolbarModule.js';
|
||||||
|
|
||||||
const OHIFCornerstoneViewport = asyncComponent(() => import( /* webpackChunkName: "OHIFCornerstoneViewport" */ './OHIFCornerstoneViewport.js'));
|
const OHIFCornerstoneViewport = asyncComponent(() =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "OHIFCornerstoneViewport" */ './OHIFCornerstoneViewport.js'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -13,6 +18,15 @@ export default {
|
|||||||
*/
|
*/
|
||||||
id: 'cornerstone',
|
id: 'cornerstone',
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {object} [configuration={}]
|
||||||
|
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
||||||
|
*/
|
||||||
|
preRegistration(configuration = {}) {
|
||||||
|
init(configuration);
|
||||||
|
},
|
||||||
getViewportModule() {
|
getViewportModule() {
|
||||||
return OHIFCornerstoneViewport;
|
return OHIFCornerstoneViewport;
|
||||||
},
|
},
|
||||||
|
|||||||
87
extensions/cornerstone/src/init.js
Normal file
87
extensions/cornerstone/src/init.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import OHIF from '@ohif/core';
|
||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
import csTools from 'cornerstone-tools';
|
||||||
|
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {object} configuration
|
||||||
|
* @param {Object|Array} configuration.csToolsConfig
|
||||||
|
*/
|
||||||
|
export default function init(configuration = {}) {
|
||||||
|
const { csToolsConfig } = configuration;
|
||||||
|
const { StackManager } = OHIF.utils;
|
||||||
|
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||||
|
|
||||||
|
// ~~ Set our MetadataProvider
|
||||||
|
cornerstone.metaData.addProvider(
|
||||||
|
metadataProvider.provider.bind(metadataProvider)
|
||||||
|
);
|
||||||
|
|
||||||
|
StackManager.setMetadataProvider(metadataProvider);
|
||||||
|
|
||||||
|
// ~~
|
||||||
|
const defaultCsToolsConfig = csToolsConfig || {
|
||||||
|
globalToolSyncEnabled: true,
|
||||||
|
showSVGCursors: true,
|
||||||
|
autoResizeViewports: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
initCornerstoneTools(defaultCsToolsConfig);
|
||||||
|
|
||||||
|
// ~~ Toooools 🙌
|
||||||
|
const {
|
||||||
|
PanTool,
|
||||||
|
ZoomTool,
|
||||||
|
WwwcTool,
|
||||||
|
MagnifyTool,
|
||||||
|
StackScrollTool,
|
||||||
|
StackScrollMouseWheelTool,
|
||||||
|
// Touch
|
||||||
|
PanMultiTouchTool,
|
||||||
|
ZoomTouchPinchTool,
|
||||||
|
// Annotations
|
||||||
|
EraserTool,
|
||||||
|
ArrowAnnotateTool,
|
||||||
|
BidirectionalTool,
|
||||||
|
LengthTool,
|
||||||
|
AngleTool,
|
||||||
|
FreehandRoiTool,
|
||||||
|
EllipticalRoiTool,
|
||||||
|
DragProbeTool,
|
||||||
|
RectangleRoiTool,
|
||||||
|
// Segmentation
|
||||||
|
BrushTool,
|
||||||
|
} = csTools;
|
||||||
|
const tools = [
|
||||||
|
PanTool,
|
||||||
|
ZoomTool,
|
||||||
|
WwwcTool,
|
||||||
|
MagnifyTool,
|
||||||
|
StackScrollTool,
|
||||||
|
StackScrollMouseWheelTool,
|
||||||
|
// Touch
|
||||||
|
PanMultiTouchTool,
|
||||||
|
ZoomTouchPinchTool,
|
||||||
|
// Annotations
|
||||||
|
EraserTool,
|
||||||
|
ArrowAnnotateTool,
|
||||||
|
BidirectionalTool,
|
||||||
|
LengthTool,
|
||||||
|
AngleTool,
|
||||||
|
FreehandRoiTool,
|
||||||
|
EllipticalRoiTool,
|
||||||
|
DragProbeTool,
|
||||||
|
RectangleRoiTool,
|
||||||
|
// Segmentation
|
||||||
|
BrushTool,
|
||||||
|
];
|
||||||
|
|
||||||
|
tools.forEach(tool => csTools.addTool(tool));
|
||||||
|
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
||||||
|
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
||||||
|
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
|
||||||
|
csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required
|
||||||
|
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
|
||||||
|
csTools.setToolActive('ZoomTouchPinch', {});
|
||||||
|
}
|
||||||
@ -15,7 +15,6 @@ import {
|
|||||||
} from './utils/index.js';
|
} from './utils/index.js';
|
||||||
|
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
|
||||||
|
|
||||||
// ~~ EXTENSIONS
|
// ~~ EXTENSIONS
|
||||||
import { GenericViewerCommands, MeasurementsPanel } from './appExtensions';
|
import { GenericViewerCommands, MeasurementsPanel } from './appExtensions';
|
||||||
@ -27,7 +26,6 @@ import { Provider } from 'react-redux';
|
|||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
import { getActiveContexts } from './store/layout/selectors.js';
|
import { getActiveContexts } from './store/layout/selectors.js';
|
||||||
import i18n from '@ohif/i18n';
|
import i18n from '@ohif/i18n';
|
||||||
import setupTools from './setupTools.js';
|
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
|
||||||
// Contexts
|
// Contexts
|
||||||
@ -36,11 +34,6 @@ import UserManagerContext from './context/UserManagerContext';
|
|||||||
import AppContext from './context/AppContext';
|
import AppContext from './context/AppContext';
|
||||||
|
|
||||||
// ~~~~ APP SETUP
|
// ~~~~ APP SETUP
|
||||||
initCornerstoneTools({
|
|
||||||
globalToolSyncEnabled: true,
|
|
||||||
showSVGCursors: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const commandsManagerConfig = {
|
const commandsManagerConfig = {
|
||||||
getAppState: () => store.getState(),
|
getAppState: () => store.getState(),
|
||||||
getActiveContexts: () => getActiveContexts(store.getState()),
|
getActiveContexts: () => getActiveContexts(store.getState()),
|
||||||
@ -49,9 +42,6 @@ const commandsManagerConfig = {
|
|||||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||||
const hotkeysManager = new HotkeysManager(commandsManager);
|
const hotkeysManager = new HotkeysManager(commandsManager);
|
||||||
const extensionManager = new ExtensionManager({ commandsManager });
|
const extensionManager = new ExtensionManager({ commandsManager });
|
||||||
|
|
||||||
// CornerstoneTools and labeling/measurements?
|
|
||||||
setupTools(store);
|
|
||||||
// ~~~~ END APP SETUP
|
// ~~~~ END APP SETUP
|
||||||
|
|
||||||
// TODO[react] Use a provider when the whole tree is React
|
// TODO[react] Use a provider when the whole tree is React
|
||||||
@ -175,8 +165,9 @@ class App extends Component {
|
|||||||
function _initExtensions(extensions, hotkeys) {
|
function _initExtensions(extensions, hotkeys) {
|
||||||
const defaultExtensions = [
|
const defaultExtensions = [
|
||||||
GenericViewerCommands,
|
GenericViewerCommands,
|
||||||
MeasurementsPanel,
|
|
||||||
OHIFCornerstoneExtension,
|
OHIFCornerstoneExtension,
|
||||||
|
// WARNING: MUST BE REGISTERED _AFTER_ OHIFCORNERSTONEEXTENSION
|
||||||
|
MeasurementsPanel,
|
||||||
];
|
];
|
||||||
const mergedExtensions = defaultExtensions.concat(extensions);
|
const mergedExtensions = defaultExtensions.concat(extensions);
|
||||||
extensionManager.registerExtensions(mergedExtensions);
|
extensionManager.registerExtensions(mergedExtensions);
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import cornerstone from 'cornerstone-core';
|
|||||||
|
|
||||||
//
|
//
|
||||||
import jumpToRowItem from './jumpToRowItem.js';
|
import jumpToRowItem from './jumpToRowItem.js';
|
||||||
import getMeasurementLocationCallback from './../../lib/getMeasurementLocationCallback';
|
import getMeasurementLocationCallback from './getMeasurementLocationCallback';
|
||||||
|
|
||||||
const { setViewportSpecificData } = OHIF.redux.actions;
|
const { setViewportSpecificData } = OHIF.redux.actions;
|
||||||
const { MeasurementApi } = OHIF.measurements;
|
const { MeasurementApi } = OHIF.measurements;
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
const setLabellingFlowDataAction = labellingFlowData => ({
|
||||||
|
type: 'SET_LABELLING_FLOW_DATA',
|
||||||
|
labellingFlowData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetLabellingAndContextMenuAction = state => ({
|
||||||
|
type: 'RESET_LABELLING_AND_CONTEXT_MENU',
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setToolContextMenuDataAction = (viewportIndex, toolContextMenuData) => ({
|
||||||
|
type: 'SET_TOOL_CONTEXT_MENU_DATA',
|
||||||
|
viewportIndex,
|
||||||
|
toolContextMenuData,
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
resetLabellingAndContextMenuAction,
|
||||||
|
setLabellingFlowDataAction,
|
||||||
|
setToolContextMenuDataAction,
|
||||||
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData';
|
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData.js';
|
||||||
|
|
||||||
export default function getMeasurementLocationCallback(
|
export default function getMeasurementLocationCallback(
|
||||||
eventData,
|
eventData,
|
||||||
@ -1,7 +1,15 @@
|
|||||||
import ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
|
import ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
|
||||||
|
import init from './init.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
/**
|
||||||
|
* Only required property. Should be a unique value across all extensions.
|
||||||
|
*/
|
||||||
id: 'measurements-table',
|
id: 'measurements-table',
|
||||||
|
|
||||||
|
preRegistration(configuration = {}) {
|
||||||
|
init(configuration);
|
||||||
|
},
|
||||||
getPanelModule() {
|
getPanelModule() {
|
||||||
return {
|
return {
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
|
|||||||
188
platform/viewer/src/appExtensions/MeasurementsPanel/init.js
Normal file
188
platform/viewer/src/appExtensions/MeasurementsPanel/init.js
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import OHIF from '@ohif/core';
|
||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
import csTools from 'cornerstone-tools';
|
||||||
|
import {
|
||||||
|
getToolLabellingFlowCallback,
|
||||||
|
getOnRightClickCallback,
|
||||||
|
getOnTouchPressCallback,
|
||||||
|
getResetLabellingAndContextMenu,
|
||||||
|
} from './labelingFlowCallbacks.js';
|
||||||
|
import throttle from 'lodash.throttle';
|
||||||
|
|
||||||
|
// TODO: This only works because we have a hard dependency on this extension
|
||||||
|
// We need to decouple and make stuff like this possible w/o bundling this at
|
||||||
|
// build time
|
||||||
|
import store from './../../store';
|
||||||
|
|
||||||
|
const {
|
||||||
|
onAdded,
|
||||||
|
onRemoved,
|
||||||
|
onModified,
|
||||||
|
} = OHIF.measurements.MeasurementHandlers;
|
||||||
|
|
||||||
|
const MEASUREMENT_ACTION_MAP = {
|
||||||
|
added: onAdded,
|
||||||
|
removed: onRemoved,
|
||||||
|
modified: throttle(event => {
|
||||||
|
return onModified(event);
|
||||||
|
}, 300),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {*} configuration
|
||||||
|
*/
|
||||||
|
export default function init(configuration) {
|
||||||
|
// If these tools were already added by a different extension, we want to replace
|
||||||
|
// them with the same tools that have an alternative configuration. By passing in
|
||||||
|
// our custom `getMeasurementLocationCallback`, we can...
|
||||||
|
const toolLabellingFlowCallback = getToolLabellingFlowCallback(store);
|
||||||
|
|
||||||
|
// Removes all tools from all enabled elements w/ provided name
|
||||||
|
// Not commonly used API, so :eyes: for unknown side-effects
|
||||||
|
csTools.removeTool('Bidirectional');
|
||||||
|
csTools.removeTool('Length');
|
||||||
|
csTools.removeTool('Angle');
|
||||||
|
csTools.removeTool('FreehandRoi');
|
||||||
|
csTools.removeTool('EllipticalRoi');
|
||||||
|
csTools.removeTool('CircleRoi');
|
||||||
|
csTools.removeTool('RectangleRoi');
|
||||||
|
csTools.removeTool('ArrowAnnotate');
|
||||||
|
|
||||||
|
// Re-add each tool w/ our custom configuration
|
||||||
|
csTools.addTool(csTools.BidirectionalTool, {
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
csTools.addTool(csTools.LengthTool, {
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
csTools.addTool(csTools.AngleTool, {
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
csTools.addTool(csTools.FreehandRoiTool, {
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
csTools.addTool(csTools.EllipticalRoiTool, {
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
csTools.addTool(csTools.CircleRoiTool, {
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
csTools.addTool(csTools.RectangleRoiTool, {
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
csTools.addTool(csTools.ArrowAnnotateTool, {
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: MEASUREMENT_COMPLETED (not present in initial implementation)
|
||||||
|
const onMeasurementsChanged = (action, event) => {
|
||||||
|
return MEASUREMENT_ACTION_MAP[action](event);
|
||||||
|
};
|
||||||
|
const onMeasurementAdded = onMeasurementsChanged.bind(this, 'added');
|
||||||
|
const onMeasurementRemoved = onMeasurementsChanged.bind(this, 'removed');
|
||||||
|
const onMeasurementModified = onMeasurementsChanged.bind(this, 'modified');
|
||||||
|
const onLabelmapModified = onMeasurementsChanged.bind(
|
||||||
|
this,
|
||||||
|
'labelmapModified'
|
||||||
|
);
|
||||||
|
//
|
||||||
|
const onRightClick = getOnRightClickCallback(store);
|
||||||
|
const onTouchPress = getOnTouchPressCallback(store);
|
||||||
|
const onNewImage = getResetLabellingAndContextMenu(store);
|
||||||
|
const onMouseClick = getResetLabellingAndContextMenu(store);
|
||||||
|
const onTouchStart = getResetLabellingAndContextMenu(store);
|
||||||
|
|
||||||
|
// Because click gives us the native "mouse up", buttons will always be `0`
|
||||||
|
// Need to fallback to event.which;
|
||||||
|
const handleClick = cornerstoneMouseClickEvent => {
|
||||||
|
const mouseUpEvent = cornerstoneMouseClickEvent.detail.event;
|
||||||
|
const isRightClick = mouseUpEvent.which === 3;
|
||||||
|
|
||||||
|
if (isRightClick) {
|
||||||
|
onRightClick(cornerstoneMouseClickEvent);
|
||||||
|
} else {
|
||||||
|
onMouseClick(cornerstoneMouseClickEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function elementEnabledHandler(evt) {
|
||||||
|
const element = evt.detail.element;
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
csTools.EVENTS.MEASUREMENT_ADDED,
|
||||||
|
onMeasurementAdded
|
||||||
|
);
|
||||||
|
element.addEventListener(
|
||||||
|
csTools.EVENTS.MEASUREMENT_REMOVED,
|
||||||
|
onMeasurementRemoved
|
||||||
|
);
|
||||||
|
element.addEventListener(
|
||||||
|
csTools.EVENTS.MEASUREMENT_MODIFIED,
|
||||||
|
onMeasurementModified
|
||||||
|
);
|
||||||
|
element.addEventListener(
|
||||||
|
csTools.EVENTS.LABELMAP_MODIFIED,
|
||||||
|
onLabelmapModified
|
||||||
|
);
|
||||||
|
//
|
||||||
|
element.addEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress);
|
||||||
|
element.addEventListener(csTools.EVENTS.MOUSE_CLICK, handleClick);
|
||||||
|
element.addEventListener(csTools.EVENTS.TOUCH_START, onTouchStart);
|
||||||
|
// TODO: This makes scrolling painfully slow
|
||||||
|
// element.addEventListener(cornerstone.EVENTS.NEW_IMAGE, onNewImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementDisabledHandler(evt) {
|
||||||
|
const element = evt.detail.element;
|
||||||
|
|
||||||
|
element.removeEventListener(
|
||||||
|
csTools.EVENTS.MEASUREMENT_ADDED,
|
||||||
|
onMeasurementAdded
|
||||||
|
);
|
||||||
|
element.removeEventListener(
|
||||||
|
csTools.EVENTS.MEASUREMENT_REMOVED,
|
||||||
|
onMeasurementRemoved
|
||||||
|
);
|
||||||
|
element.removeEventListener(
|
||||||
|
csTools.EVENTS.MEASUREMENT_MODIFIED,
|
||||||
|
onMeasurementModified
|
||||||
|
);
|
||||||
|
element.removeEventListener(
|
||||||
|
csTools.EVENTS.LABELMAP_MODIFIED,
|
||||||
|
onLabelmapModified
|
||||||
|
);
|
||||||
|
//
|
||||||
|
element.removeEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress);
|
||||||
|
element.removeEventListener(csTools.EVENTS.MOUSE_CLICK, handleClick);
|
||||||
|
element.removeEventListener(csTools.EVENTS.TOUCH_START, onTouchStart);
|
||||||
|
// element.removeEventListener(cornerstone.EVENTS.NEW_IMAGE, onNewImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
cornerstone.events.addEventListener(
|
||||||
|
cornerstone.EVENTS.ELEMENT_ENABLED,
|
||||||
|
elementEnabledHandler
|
||||||
|
);
|
||||||
|
cornerstone.events.addEventListener(
|
||||||
|
cornerstone.EVENTS.ELEMENT_DISABLED,
|
||||||
|
elementDisabledHandler
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,144 @@
|
|||||||
|
import {
|
||||||
|
resetLabellingAndContextMenuAction,
|
||||||
|
setToolContextMenuDataAction,
|
||||||
|
setLabellingFlowDataAction,
|
||||||
|
} from './actions.js';
|
||||||
|
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData.js';
|
||||||
|
|
||||||
|
const VIEWPORT_INDEX = 0;
|
||||||
|
|
||||||
|
function getOnRightClickCallback(store) {
|
||||||
|
const setToolContextMenuData = (viewportIndex, toolContextMenuData) => {
|
||||||
|
store.dispatch(resetLabellingAndContextMenuAction());
|
||||||
|
store.dispatch(
|
||||||
|
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOnCloseCallback = viewportIndex => {
|
||||||
|
return function onClose() {
|
||||||
|
const toolContextMenuData = {
|
||||||
|
visible: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return function onRightClick(event) {
|
||||||
|
const eventData = event.detail;
|
||||||
|
const viewportIndex = VIEWPORT_INDEX; // parseInt(eventData.element.dataset.viewportIndex, 10);
|
||||||
|
|
||||||
|
const toolContextMenuData = {
|
||||||
|
eventData,
|
||||||
|
isTouchEvent: false,
|
||||||
|
onClose: getOnCloseCallback(viewportIndex),
|
||||||
|
};
|
||||||
|
|
||||||
|
// setToolContextMenuData(viewportIndex, toolContextMenuData);
|
||||||
|
setToolContextMenuData(0, toolContextMenuData);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOnTouchPressCallback(store) {
|
||||||
|
const setToolContextMenuData = (viewportIndex, toolContextMenuData) => {
|
||||||
|
store.dispatch(resetLabellingAndContextMenuAction());
|
||||||
|
store.dispatch(
|
||||||
|
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOnCloseCallback = viewportIndex => {
|
||||||
|
return function onClose() {
|
||||||
|
const toolContextMenuData = {
|
||||||
|
visible: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return function onTouchPress(event) {
|
||||||
|
const eventData = event.detail;
|
||||||
|
const viewportIndex = parseInt(eventData.element.dataset.viewportIndex, 10);
|
||||||
|
|
||||||
|
const toolContextMenuData = {
|
||||||
|
eventData,
|
||||||
|
isTouchEvent: true,
|
||||||
|
onClose: getOnCloseCallback(viewportIndex),
|
||||||
|
};
|
||||||
|
|
||||||
|
setToolContextMenuData(viewportIndex, toolContextMenuData);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResetLabellingAndContextMenu(store) {
|
||||||
|
return function resetLabellingAndContextMenu() {
|
||||||
|
store.dispatch(resetLabellingAndContextMenuAction());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} store
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function getToolLabellingFlowCallback(store) {
|
||||||
|
const setLabellingFlowData = labellingFlowData => {
|
||||||
|
store.dispatch(setLabellingFlowDataAction(labellingFlowData));
|
||||||
|
};
|
||||||
|
|
||||||
|
return function toolLabellingFlowCallback(
|
||||||
|
measurementData,
|
||||||
|
eventData,
|
||||||
|
doneCallback,
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
|
const updateLabelling = ({ location, response, description }) => {
|
||||||
|
// Update the measurement data with the labelling parameters
|
||||||
|
|
||||||
|
if (location) {
|
||||||
|
measurementData.location = location;
|
||||||
|
}
|
||||||
|
if (description) {
|
||||||
|
measurementData.description = description;
|
||||||
|
}
|
||||||
|
if (response) {
|
||||||
|
measurementData.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTableWithNewMeasurementData(measurementData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const labellingDoneCallback = () => {
|
||||||
|
setLabellingFlowData({ visible: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
const labellingFlowData = {
|
||||||
|
visible: true,
|
||||||
|
eventData,
|
||||||
|
measurementData,
|
||||||
|
skipAddLabelButton: options.skipAddLabelButton,
|
||||||
|
editLocation: options.editLocation,
|
||||||
|
editDescription: options.editDescription,
|
||||||
|
editResponse: options.editResponse,
|
||||||
|
editDescriptionOnDialog: options.editDescriptionOnDialog,
|
||||||
|
labellingDoneCallback,
|
||||||
|
updateLabelling,
|
||||||
|
};
|
||||||
|
|
||||||
|
setLabellingFlowData(labellingFlowData);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getToolLabellingFlowCallback,
|
||||||
|
getOnRightClickCallback,
|
||||||
|
getOnTouchPressCallback,
|
||||||
|
getResetLabellingAndContextMenu,
|
||||||
|
};
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import OHIF from "@ohif/core";
|
import OHIF from '@ohif/core';
|
||||||
import cornerstone from "cornerstone-core";
|
import cornerstone from 'cornerstone-core';
|
||||||
|
|
||||||
export default function updateTableWithNewMeasurementData({
|
export default function updateTableWithNewMeasurementData({
|
||||||
toolType,
|
toolType,
|
||||||
measurementNumber,
|
measurementNumber,
|
||||||
location,
|
location,
|
||||||
description
|
description,
|
||||||
}) {
|
}) {
|
||||||
// Update all measurements by measurement number
|
// Update all measurements by measurement number
|
||||||
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
|
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
|
||||||
@ -10,19 +10,9 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
const { viewportIndex } = ownProps;
|
const { viewportIndex } = ownProps;
|
||||||
const toolContextMenuData = state.ui.contextMenu[viewportIndex];
|
const toolContextMenuData = state.ui.contextMenu[viewportIndex];
|
||||||
let availableTools;
|
|
||||||
|
|
||||||
if (
|
|
||||||
state.extensions &&
|
|
||||||
state.extensions.cornerstone &&
|
|
||||||
state.extensions.cornerstone.availableTools
|
|
||||||
) {
|
|
||||||
availableTools = state.extensions.cornerstone.availableTools;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toolContextMenuData,
|
...toolContextMenuData,
|
||||||
availableTools,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
import getMeasurementLocationCallback from '../lib/getMeasurementLocationCallback';
|
// This whole component should live in the Measurements Extension :thinking:
|
||||||
|
import getMeasurementLocationCallback from '../appExtensions/MeasurementsPanel/getMeasurementLocationCallback';
|
||||||
|
|
||||||
import './ToolContextMenu.css';
|
import './ToolContextMenu.css';
|
||||||
|
|
||||||
@ -97,12 +98,11 @@ function getNearbyToolData(element, coords, toolTypes) {
|
|||||||
return pointNearTool ? nearbyTool : undefined;
|
return pointNearTool ? nearbyTool : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDropdownItems(eventData, isTouchEvent = false, availableTools) {
|
function getDropdownItems(eventData, isTouchEvent = false) {
|
||||||
const nearbyToolData = getNearbyToolData(
|
const nearbyToolData = getNearbyToolData(
|
||||||
eventData.element,
|
eventData.element,
|
||||||
eventData.currentPoints.canvas,
|
eventData.currentPoints.canvas,
|
||||||
toolTypes,
|
toolTypes
|
||||||
availableTools
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it
|
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it
|
||||||
@ -148,7 +148,6 @@ class ToolContextMenu extends Component {
|
|||||||
isTouchEvent: PropTypes.bool.isRequired,
|
isTouchEvent: PropTypes.bool.isRequired,
|
||||||
eventData: PropTypes.object,
|
eventData: PropTypes.object,
|
||||||
onClose: PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
availableTools: PropTypes.array,
|
|
||||||
visible: PropTypes.bool.isRequired,
|
visible: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,12 +167,8 @@ class ToolContextMenu extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isTouchEvent, eventData, availableTools } = this.props;
|
const { isTouchEvent, eventData } = this.props;
|
||||||
const dropdownItems = getDropdownItems(
|
const dropdownItems = getDropdownItems(eventData, isTouchEvent);
|
||||||
eventData,
|
|
||||||
isTouchEvent,
|
|
||||||
availableTools
|
|
||||||
);
|
|
||||||
|
|
||||||
// Skip if there is no dropdown item
|
// Skip if there is no dropdown item
|
||||||
if (!dropdownItems.length) {
|
if (!dropdownItems.length) {
|
||||||
|
|||||||
@ -1,246 +0,0 @@
|
|||||||
import OHIF from '@ohif/core';
|
|
||||||
import updateTableWithNewMeasurementData from './lib/updateTableWithNewMeasurementData';
|
|
||||||
|
|
||||||
function getToolLabellingFlowCallback(store) {
|
|
||||||
const setLabellingFlowDataAction = labellingFlowData => ({
|
|
||||||
type: 'SET_LABELLING_FLOW_DATA',
|
|
||||||
labellingFlowData,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setLabellingFlowData = labellingFlowData => {
|
|
||||||
store.dispatch(setLabellingFlowDataAction(labellingFlowData));
|
|
||||||
};
|
|
||||||
|
|
||||||
return function toolLabellingFlowCallback(
|
|
||||||
measurementData,
|
|
||||||
eventData,
|
|
||||||
doneCallback,
|
|
||||||
options = {}
|
|
||||||
) {
|
|
||||||
const updateLabelling = ({ location, response, description }) => {
|
|
||||||
// Update the measurement data with the labelling parameters
|
|
||||||
|
|
||||||
if (location) {
|
|
||||||
measurementData.location = location;
|
|
||||||
}
|
|
||||||
if (description) {
|
|
||||||
measurementData.description = description;
|
|
||||||
}
|
|
||||||
if (response) {
|
|
||||||
measurementData.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTableWithNewMeasurementData(measurementData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const labellingDoneCallback = () => {
|
|
||||||
setLabellingFlowData({ visible: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
const labellingFlowData = {
|
|
||||||
visible: true,
|
|
||||||
eventData,
|
|
||||||
measurementData,
|
|
||||||
skipAddLabelButton: options.skipAddLabelButton,
|
|
||||||
editLocation: options.editLocation,
|
|
||||||
editDescription: options.editDescription,
|
|
||||||
editResponse: options.editResponse,
|
|
||||||
editDescriptionOnDialog: options.editDescriptionOnDialog,
|
|
||||||
labellingDoneCallback,
|
|
||||||
updateLabelling,
|
|
||||||
};
|
|
||||||
|
|
||||||
setLabellingFlowData(labellingFlowData);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetLabellingAndContextMenuAction = state => ({
|
|
||||||
type: 'RESET_LABELLING_AND_CONTEXT_MENU',
|
|
||||||
state,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setToolContextMenuDataAction = (viewportIndex, toolContextMenuData) => ({
|
|
||||||
type: 'SET_TOOL_CONTEXT_MENU_DATA',
|
|
||||||
viewportIndex,
|
|
||||||
toolContextMenuData,
|
|
||||||
});
|
|
||||||
|
|
||||||
function getOnRightClickCallback(store) {
|
|
||||||
const setToolContextMenuData = (viewportIndex, toolContextMenuData) => {
|
|
||||||
store.dispatch(resetLabellingAndContextMenuAction());
|
|
||||||
store.dispatch(
|
|
||||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOnCloseCallback = viewportIndex => {
|
|
||||||
return function onClose() {
|
|
||||||
const toolContextMenuData = {
|
|
||||||
visible: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
store.dispatch(
|
|
||||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return function onRightClick(event) {
|
|
||||||
const eventData = event.detail;
|
|
||||||
const viewportIndex = parseInt(eventData.element.dataset.viewportIndex, 10);
|
|
||||||
|
|
||||||
const toolContextMenuData = {
|
|
||||||
eventData,
|
|
||||||
isTouchEvent: false,
|
|
||||||
onClose: getOnCloseCallback(viewportIndex),
|
|
||||||
};
|
|
||||||
|
|
||||||
setToolContextMenuData(viewportIndex, toolContextMenuData);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOnTouchPressCallback(store) {
|
|
||||||
const setToolContextMenuData = (viewportIndex, toolContextMenuData) => {
|
|
||||||
store.dispatch(resetLabellingAndContextMenuAction());
|
|
||||||
store.dispatch(
|
|
||||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOnCloseCallback = viewportIndex => {
|
|
||||||
return function onClose() {
|
|
||||||
const toolContextMenuData = {
|
|
||||||
visible: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
store.dispatch(
|
|
||||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return function onTouchPress(event) {
|
|
||||||
const eventData = event.detail;
|
|
||||||
const viewportIndex = parseInt(eventData.element.dataset.viewportIndex, 10);
|
|
||||||
|
|
||||||
const toolContextMenuData = {
|
|
||||||
eventData,
|
|
||||||
isTouchEvent: true,
|
|
||||||
onClose: getOnCloseCallback(viewportIndex),
|
|
||||||
};
|
|
||||||
|
|
||||||
setToolContextMenuData(viewportIndex, toolContextMenuData);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getResetLabellingAndContextMenu(store) {
|
|
||||||
return function resetLabellingAndContextMenu() {
|
|
||||||
store.dispatch(resetLabellingAndContextMenuAction());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function setupTools(store) {
|
|
||||||
const toolLabellingFlowCallback = getToolLabellingFlowCallback(store);
|
|
||||||
const availableTools = [
|
|
||||||
{ name: 'Pan', mouseButtonMasks: [1, 4] },
|
|
||||||
{ name: 'Zoom', mouseButtonMasks: [1, 2] },
|
|
||||||
{ name: 'Wwwc', mouseButtonMasks: [1] },
|
|
||||||
{ name: 'Magnify' },
|
|
||||||
{ name: 'WwwcRegion' },
|
|
||||||
{
|
|
||||||
name: 'Bidirectional',
|
|
||||||
props: {
|
|
||||||
configuration: {
|
|
||||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mouseButtonMasks: [1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Length',
|
|
||||||
props: {
|
|
||||||
configuration: {
|
|
||||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mouseButtonMasks: [1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Angle',
|
|
||||||
props: {
|
|
||||||
configuration: {
|
|
||||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mouseButtonMasks: [1],
|
|
||||||
},
|
|
||||||
{ name: 'StackScroll', mouseButtonMasks: [1] },
|
|
||||||
{ name: 'Brush', mouseButtonMasks: [1] },
|
|
||||||
{
|
|
||||||
name: 'FreehandRoi',
|
|
||||||
props: {
|
|
||||||
configuration: {
|
|
||||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mouseButtonMasks: [1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'EllipticalRoi',
|
|
||||||
props: {
|
|
||||||
configuration: {
|
|
||||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mouseButtonMasks: [1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CircleRoi',
|
|
||||||
props: {
|
|
||||||
configuration: {
|
|
||||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mouseButtonMasks: [1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'RectangleRoi',
|
|
||||||
props: {
|
|
||||||
configuration: {
|
|
||||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mouseButtonMasks: [1],
|
|
||||||
},
|
|
||||||
{ name: 'DragProbe' },
|
|
||||||
{ name: 'PanMultiTouch' },
|
|
||||||
{ name: 'ZoomTouchPinch' },
|
|
||||||
{ name: 'StackScrollMouseWheel' },
|
|
||||||
{ name: 'StackScrollMultiTouch' },
|
|
||||||
{ name: 'Eraser' },
|
|
||||||
{
|
|
||||||
name: 'ArrowAnnotate',
|
|
||||||
props: {
|
|
||||||
configuration: {
|
|
||||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const onRightClick = getOnRightClickCallback(store);
|
|
||||||
const onTouchPress = getOnTouchPressCallback(store);
|
|
||||||
const onNewImage = getResetLabellingAndContextMenu(store);
|
|
||||||
const onMouseClick = getResetLabellingAndContextMenu(store);
|
|
||||||
const onTouchStart = getResetLabellingAndContextMenu(store);
|
|
||||||
const toolAction = OHIF.redux.actions.setExtensionData('cornerstone', {
|
|
||||||
availableTools,
|
|
||||||
onNewImage,
|
|
||||||
onRightClick,
|
|
||||||
onTouchPress,
|
|
||||||
onTouchStart,
|
|
||||||
onMouseClick,
|
|
||||||
});
|
|
||||||
|
|
||||||
store.dispatch(toolAction);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user