Merge remote-tracking branch 'upstream/master' into vtkjsViewportPerformance
This commit is contained in:
commit
a9d28d85da
@ -142,8 +142,8 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: true
|
||||
# - setup_remote_docker:
|
||||
# docker_layer_caching: true
|
||||
- run:
|
||||
name: Build and push Docker image
|
||||
command: |
|
||||
@ -198,8 +198,8 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: true
|
||||
# - setup_remote_docker:
|
||||
# docker_layer_caching: true
|
||||
- run:
|
||||
name: Deploy latest version to viewer.ohif.org
|
||||
command: |
|
||||
|
||||
@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.3.0...@ohif/extension-cornerstone@1.3.1) (2019-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 set tools bidirectional, eraser and annotate command ([#1020](https://github.com/OHIF/Viewers/issues/1020)) ([a28984e](https://github.com/OHIF/Viewers/commit/a28984e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.5...@ohif/extension-cornerstone@1.3.0) (2019-10-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Multiple fixes and implementation changes to react-cornerstone-viewport ([1cc94f3](https://github.com/OHIF/Viewers/commit/1cc94f3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.4...@ohif/extension-cornerstone@1.2.5) (2019-09-27)
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "1.2.5",
|
||||
"version": "1.3.1",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@ -48,6 +48,6 @@
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"classnames": "^2.2.6",
|
||||
"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) => {
|
||||
let dataFromStore;
|
||||
|
||||
// TODO: This may not be updated anymore :thinking:
|
||||
if (state.extensions && 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.
|
||||
const { viewportIndex } = ownProps; //.viewportData;
|
||||
const isActive = viewportIndex === state.viewports.activeViewportIndex;
|
||||
const viewportSpecificData =
|
||||
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 {
|
||||
layout: state.viewports.layout,
|
||||
// layout: state.viewports.layout,
|
||||
isActive,
|
||||
// TODO: Need a cleaner and more versatile way.
|
||||
// Currently justing using escape hatch + commands
|
||||
// activeTool: activeButton && activeButton.command,
|
||||
...dataFromStore,
|
||||
enableStackPrefetch: isActive,
|
||||
isStackPrefetchEnabled: isActive,
|
||||
isPlaying,
|
||||
frameRate,
|
||||
//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 OHIF from "@ohif/core";
|
||||
import PropTypes from "prop-types";
|
||||
import cornerstone from "cornerstone-core";
|
||||
import handleSegmentationStorage from "./handleSegmentationStorage.js";
|
||||
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;
|
||||
|
||||
@ -18,7 +18,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 = {};
|
||||
@ -28,11 +28,11 @@ specialCaseHandlers[
|
||||
|
||||
class OHIFCornerstoneViewport extends Component {
|
||||
state = {
|
||||
viewportData: null
|
||||
viewportData: null,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
customProps: {}
|
||||
customProps: {},
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
@ -40,17 +40,17 @@ class OHIFCornerstoneViewport extends Component {
|
||||
displaySet: PropTypes.object,
|
||||
viewportIndex: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
customProps: PropTypes.object
|
||||
customProps: PropTypes.object,
|
||||
};
|
||||
|
||||
static id = "OHIFCornerstoneViewport";
|
||||
static id = 'OHIFCornerstoneViewport';
|
||||
|
||||
static init() {
|
||||
console.log("OHIFCornerstoneViewport init()");
|
||||
console.log('OHIFCornerstoneViewport init()');
|
||||
}
|
||||
|
||||
static destroy() {
|
||||
console.log("OHIFCornerstoneViewport destroy()");
|
||||
console.log('OHIFCornerstoneViewport destroy()');
|
||||
StackManager.clearStacks();
|
||||
}
|
||||
|
||||
@ -72,15 +72,15 @@ class OHIFCornerstoneViewport extends Component {
|
||||
frameIndex = 0
|
||||
) {
|
||||
if (!studies || !studies.length) {
|
||||
throw new Error("Studies not provided.");
|
||||
throw new Error('Studies not provided.');
|
||||
}
|
||||
|
||||
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
|
||||
@ -89,7 +89,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
);
|
||||
|
||||
if (!study) {
|
||||
throw new Error("Study not found.");
|
||||
throw new Error('Study not found.');
|
||||
}
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
@ -97,7 +97,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
});
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error("Display Set not found.");
|
||||
throw new Error('Display Set not found.');
|
||||
}
|
||||
|
||||
// Get stack from Stack Manager
|
||||
@ -110,7 +110,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
if (sopInstanceUid) {
|
||||
const index = stack.imageIds.findIndex(imageId => {
|
||||
const sopCommonModule = cornerstone.metaData.get(
|
||||
"sopCommonModule",
|
||||
'sopCommonModule',
|
||||
imageId
|
||||
);
|
||||
if (!sopCommonModule) {
|
||||
@ -124,7 +124,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
stack.currentImageIdIndex = index;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
static getViewportData = (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
) => {
|
||||
return OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
);
|
||||
};
|
||||
|
||||
getViewportData = async (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
@ -172,7 +156,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const stack = OHIFCornerstoneViewport.getViewportData(
|
||||
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
@ -183,7 +167,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
viewportData = {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
stack
|
||||
stack,
|
||||
};
|
||||
|
||||
break;
|
||||
@ -199,7 +183,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
displaySetInstanceUid,
|
||||
sopClassUids,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
frameIndex,
|
||||
} = displaySet;
|
||||
|
||||
if (!studyInstanceUid || !displaySetInstanceUid) {
|
||||
@ -208,7 +192,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
|
||||
if (sopClassUids && sopClassUids.length > 1) {
|
||||
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
|
||||
).then(viewportData => {
|
||||
this.setState({
|
||||
viewportData
|
||||
viewportData,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -249,25 +233,42 @@ class OHIFCornerstoneViewport extends Component {
|
||||
render() {
|
||||
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?
|
||||
if (this.props.children && this.props.children.length) {
|
||||
childrenWithProps = this.props.children.map((child, index) => {
|
||||
return React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index
|
||||
key: index,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.state.viewportData && (
|
||||
<ConnectedCornerstoneViewport
|
||||
viewportData={this.state.viewportData}
|
||||
viewportIndex={this.props.viewportIndex}
|
||||
{...this.props.customProps}
|
||||
/>
|
||||
)}
|
||||
<ConnectedCornerstoneViewport
|
||||
viewportIndex={viewportIndex}
|
||||
imageIds={imageIds}
|
||||
imageIdIndex={currentImageIdIndex}
|
||||
// ~~ Connected (From REDUX)
|
||||
// frameRate={frameRate}
|
||||
// isPlaying={false}
|
||||
// isStackPrefetchEnabled={true}
|
||||
// onElementEnabled={() => {}}
|
||||
// setViewportActive{() => {}}
|
||||
{...this.props.customProps}
|
||||
/>
|
||||
{childrenWithProps}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
import init from './init.js';
|
||||
import asyncComponent from './asyncComponent.js';
|
||||
import commandsModule from './commandsModule.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',
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {object} [configuration={}]
|
||||
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
||||
*/
|
||||
preRegistration(configuration = {}) {
|
||||
init(configuration);
|
||||
},
|
||||
getViewportModule() {
|
||||
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', {});
|
||||
}
|
||||
@ -74,7 +74,7 @@ const definitions = [
|
||||
label: 'Annotate',
|
||||
icon: 'measure-non-target',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'ArrowAnnotate' },
|
||||
},
|
||||
@ -200,7 +200,7 @@ const definitions = [
|
||||
label: 'Bidirectional',
|
||||
icon: 'measure-target',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Bidirectional' },
|
||||
},
|
||||
@ -209,7 +209,7 @@ const definitions = [
|
||||
label: 'Eraser',
|
||||
icon: 'eraser',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Eraser' },
|
||||
},
|
||||
|
||||
@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.3...@ohif/extension-vtk@0.52.4) (2019-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.2...@ohif/extension-vtk@0.52.3) (2019-10-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.1...@ohif/extension-vtk@0.52.2) (2019-10-09)
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "0.52.2",
|
||||
"version": "0.52.4",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@ -52,7 +52,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^1.2.0",
|
||||
"@ohif/ui": "^0.53.1",
|
||||
"@ohif/ui": "^0.53.3",
|
||||
"cornerstone-tools": "^4.0.9",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
|
||||
@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.2...@ohif/ui@0.53.3) (2019-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🎸 switch ohif logo from text + font to SVG ([#1021](https://github.com/OHIF/Viewers/issues/1021)) ([e7de8be](https://github.com/OHIF/Viewers/commit/e7de8be))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.1...@ohif/ui@0.53.2) (2019-10-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.0...@ohif/ui@0.53.1) (2019-10-04)
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "0.53.1",
|
||||
"version": "0.53.3",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@ -33,8 +33,8 @@ as easy as possible. You will, however, need to include either the default font
|
||||
or the font you set for your theme:
|
||||
|
||||
```js
|
||||
// Google Fonts, Sanchez & Roboto
|
||||
'https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap';
|
||||
// Google Fonts, Roboto
|
||||
'https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap';
|
||||
```
|
||||
|
||||
`@ohif/ui` also looks for theme CSS variabled defined on `:root`. You can find a
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ lang }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
<title>{{ title }}</title>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="/public/docz.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap">
|
||||
<title>{{ title }}</title>
|
||||
|
||||
<style>
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="/public/docz.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap">
|
||||
|
||||
<style>
|
||||
/* "Tide" theme */
|
||||
/* TODO:
|
||||
- Convert the other themes to CSS Variables
|
||||
@ -40,7 +41,7 @@
|
||||
--table-text-primary-color: #ffffff;
|
||||
--table-text-secondary-color: #91b9cd;
|
||||
|
||||
--large-numbers-color:#6FBDE2;
|
||||
--large-numbers-color: #6FBDE2;
|
||||
|
||||
--state-error: #FFCCCC;
|
||||
--state-error-border: #FFCCCC;
|
||||
@ -70,11 +71,13 @@
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif !important;
|
||||
}
|
||||
</style>
|
||||
{{ head }}
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" />
|
||||
{{ footer }}
|
||||
</body>
|
||||
</style>
|
||||
{{ head }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root" />
|
||||
{{ footer }}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -35,7 +35,7 @@ class StudyBrowser extends Component {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="ThumbnailEntryContainer">
|
||||
<div className="ThumbnailEntryContainer" data-cy="thumbnail-list">
|
||||
<ThumbnailEntry
|
||||
key={thumb.displaySetInstanceUid}
|
||||
{...study}
|
||||
|
||||
@ -45,7 +45,7 @@ class ThumbnailEntryDragSource extends Component {
|
||||
const dropEffect = 'copy';
|
||||
|
||||
return connectDragSource(
|
||||
<div className="ThumbnailEntryContainer">
|
||||
<div className="ThumbnailEntryContainer" data-cy="thumbnail-list">
|
||||
<DragPreview {...this.props} />
|
||||
<ThumbnailEntry {...this.props} />
|
||||
</div>,
|
||||
|
||||
@ -53,6 +53,7 @@ import measureTargetUn from './icons/measure-target-un.svg';
|
||||
import measureTemp from './icons/measure-temp.svg';
|
||||
import objectGroup from './icons/object-group.svg';
|
||||
import ohifLogo from './icons/ohif-logo.svg';
|
||||
import ohifTextLogo from './icons/ohif-text-logo.svg';
|
||||
import oval from './icons/oval.svg';
|
||||
import palette from './icons/palette.svg';
|
||||
import play from './icons/play.svg';
|
||||
@ -120,6 +121,7 @@ const ICONS = {
|
||||
'inline-edit': inlineEdit,
|
||||
list,
|
||||
'ohif-logo': ohifLogo,
|
||||
'ohif-text-logo': ohifTextLogo,
|
||||
lock,
|
||||
play,
|
||||
database,
|
||||
|
||||
3
platform/ui/src/elements/Icon/icons/ohif-text-logo.svg
Normal file
3
platform/ui/src/elements/Icon/icons/ohif-text-logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 25 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -25,11 +25,13 @@ const PUBLIC_URL = process.env.PUBLIC_URL || '/';
|
||||
const APP_CONFIG = process.env.APP_CONFIG || 'config/default.js';
|
||||
const PROXY_TARGET = process.env.PROXY_TARGET;
|
||||
const PROXY_DOMAIN = process.env.PROXY_DOMAIN;
|
||||
const SKIP_MINIMIZE = process.env.SKIP_MINIMIZE;
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const baseConfig = webpackBase(env, argv, { SRC_DIR, DIST_DIR });
|
||||
const isProdBuild = process.env.NODE_ENV === 'production';
|
||||
const hasProxy = PROXY_TARGET && PROXY_DOMAIN;
|
||||
const skipMinimize = SKIP_MINIMIZE === 'true';
|
||||
|
||||
const mergedConfig = merge(baseConfig, {
|
||||
devtool: isProdBuild ? 'source-map' : 'cheap-module-eval-source-map',
|
||||
@ -50,7 +52,7 @@ module.exports = (env, argv) => {
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: isProdBuild,
|
||||
minimize: isProdBuild && !skipMinimize,
|
||||
sideEffects: true,
|
||||
},
|
||||
module: {
|
||||
@ -85,8 +87,8 @@ module.exports = (env, argv) => {
|
||||
]),
|
||||
// https://github.com/faceyspacey/extract-css-chunks-webpack-plugin#webpack-4-standalone-installation
|
||||
new ExtractCssChunksPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css',
|
||||
filename: isProdBuild ? '[name].[hash].css' : '[name].css',
|
||||
chunkFilename: isProdBuild ? '[id].[hash].css' : '[id].css',
|
||||
ignoreOrder: false, // Enable to remove warnings about conflicting order
|
||||
}),
|
||||
// Generate "index.html" w/ correct includes/imports
|
||||
@ -129,14 +131,14 @@ module.exports = (env, argv) => {
|
||||
|
||||
if (!isProdBuild) {
|
||||
mergedConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
|
||||
//
|
||||
} else {
|
||||
mergedConfig.optimization.minimizer = [
|
||||
new TerserJSPlugin({
|
||||
sourceMap: true,
|
||||
parallel: true,
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
// No bueno
|
||||
// new OptimizeCSSAssetsPlugin({}),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,39 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.1...@ohif/viewer@1.5.2) (2019-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🎸 switch ohif logo from text + font to SVG ([#1021](https://github.com/OHIF/Viewers/issues/1021)) ([e7de8be](https://github.com/OHIF/Viewers/commit/e7de8be))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.0...@ohif/viewer@1.5.1) (2019-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 set current viewport as active when switching layouts ([#1018](https://github.com/OHIF/Viewers/issues/1018)) ([2a74355](https://github.com/OHIF/Viewers/commit/2a74355))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.4.5...@ohif/viewer@1.5.0) (2019-10-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Multiple fixes and implementation changes to react-cornerstone-viewport ([1cc94f3](https://github.com/OHIF/Viewers/commit/1cc94f3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.4.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.4.4...@ohif/viewer@1.4.5) (2019-10-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
@ -1,47 +1,48 @@
|
||||
|
||||
describe('OHIF Study Viewer Page', () => {
|
||||
|
||||
before(() => {
|
||||
cy.openStudy("MISTER^MR");
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', ()=> {
|
||||
cy.get('.ThumbnailEntryContainer')
|
||||
cy.openStudy('MISTER^MR');
|
||||
cy.waitDicomImage();
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', () => {
|
||||
cy.get('[data-cy="thumbnail-list"]')
|
||||
.its('length')
|
||||
.should('be.gt', 1);
|
||||
});
|
||||
|
||||
it('drags and drop a series thumbnail into viewport', () => {
|
||||
cy.get('.ThumbnailEntryContainer:nth-child(2)') //element to be dragged
|
||||
.drag('.cornerstone-canvas'); //dropzone element
|
||||
|
||||
const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-left.overlay-element > div';
|
||||
const expectedText = 'Ser: 2Img: 1 1/13512 x 512Loc: -17.60 mm Thick: 3.00 mm';
|
||||
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Series left panel can be hidden/displayed', ()=> {
|
||||
const seriesButton = '.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton';
|
||||
const leftPanel = 'section.sidepanel.from-left';
|
||||
|
||||
cy.get(seriesButton).click();
|
||||
cy.get(leftPanel).should('not.be.enabled')
|
||||
|
||||
cy.get(seriesButton).click();
|
||||
cy.get(leftPanel).should('be.visible');
|
||||
});
|
||||
|
||||
it('checks if Measurements right panel can be hidden/displayed', ()=> {
|
||||
const measurementsButton = '.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton';
|
||||
const rightPanel = 'section.sidepanel.from-right';
|
||||
|
||||
cy.get(measurementsButton).click();
|
||||
cy.get(rightPanel).should('be.visible');
|
||||
|
||||
cy.get(measurementsButton).click();
|
||||
cy.get(rightPanel).should('not.be.enabled');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('drags and drop a series thumbnail into viewport', () => {
|
||||
cy.get('[data-cy="thumbnail-list"]:nth-child(2)') //element to be dragged
|
||||
.drag('.cornerstone-canvas'); //dropzone element
|
||||
|
||||
const overlaySeriesInformation =
|
||||
'div.ViewportOverlay > div.bottom-left.overlay-element > div';
|
||||
const expectedText =
|
||||
'Ser: 2Img: 1 1/13512 x 512Loc: -17.60 mm Thick: 3.00 mm';
|
||||
|
||||
cy.get(overlaySeriesInformation).should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Series left panel can be hidden/displayed', () => {
|
||||
const seriesButton =
|
||||
'.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton';
|
||||
const leftPanel = 'section.sidepanel.from-left';
|
||||
|
||||
cy.get(seriesButton).click();
|
||||
cy.get(leftPanel).should('not.be.enabled');
|
||||
|
||||
cy.get(seriesButton).click();
|
||||
cy.get(leftPanel).should('be.visible');
|
||||
});
|
||||
|
||||
it('checks if Measurements right panel can be hidden/displayed', () => {
|
||||
const measurementsButton =
|
||||
'.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton';
|
||||
const rightPanel = 'section.sidepanel.from-right';
|
||||
|
||||
cy.get(measurementsButton).click();
|
||||
cy.get(rightPanel).should('be.visible');
|
||||
|
||||
cy.get(measurementsButton).click();
|
||||
cy.get(rightPanel).should('not.be.enabled');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { DragSimulator } from "../helpers/DragSimulator.js";
|
||||
import { doesNotReject } from "assert";
|
||||
import { disconnect } from "cluster";
|
||||
import { DragSimulator } from '../helpers/DragSimulator.js';
|
||||
import { doesNotReject } from 'assert';
|
||||
import { disconnect } from 'cluster';
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
@ -28,21 +28,19 @@ import { disconnect } from "cluster";
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add('openStudy', (patientName) => {
|
||||
cy.visit('/');
|
||||
cy.get('#patientName')
|
||||
.type(patientName);
|
||||
Cypress.Commands.add('openStudy', patientName => {
|
||||
cy.visit('/');
|
||||
cy.get('#patientName').type(patientName);
|
||||
|
||||
cy.get('.studylistStudy > .patientName').as('patientResult')
|
||||
.then({ timeout: 5000 }, ($patientResult) => {
|
||||
cy.contains(patientName)
|
||||
.click();
|
||||
})
|
||||
}
|
||||
);
|
||||
cy.get('.studylistStudy > .patientName')
|
||||
.as('patientResult')
|
||||
.then({ timeout: 5000 }, $patientResult => {
|
||||
cy.contains(patientName).click();
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('drag', {prevSubject: 'element',},
|
||||
(...args) => DragSimulator.simulate(...args)
|
||||
Cypress.Commands.add('drag', { prevSubject: 'element' }, (...args) =>
|
||||
DragSimulator.simulate(...args)
|
||||
);
|
||||
|
||||
/**
|
||||
@ -53,20 +51,18 @@ Cypress.Commands.add('drag', {prevSubject: 'element',},
|
||||
* @param {number[]} firstClick - Click position [x, y]
|
||||
* @param {number[]} secondClick - Click position [x, y]
|
||||
*/
|
||||
Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
|
||||
|
||||
cy.get(viewport).then(($viewport) => {
|
||||
const [ x1, y1 ] = firstClick;
|
||||
const [ x2, y2 ] = secondClick;
|
||||
Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
|
||||
cy.get(viewport).then($viewport => {
|
||||
const [x1, y1] = firstClick;
|
||||
const [x2, y2] = secondClick;
|
||||
|
||||
cy.wrap($viewport)
|
||||
.click(x1, y1, {force: true})
|
||||
.trigger('mousemove', {clientX:x2, clientY:y2})
|
||||
.click(x2, y2, {force: true})
|
||||
.click(x1, y1, { force: true })
|
||||
.trigger('mousemove', { clientX: x2, clientY: y2 })
|
||||
.click(x2, y2, { force: true });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Command to perform three clicks into three different positions. Each position must be [x, y].
|
||||
* The positions are considering the element as reference, therefore, top-left of the element will be (0, 0).
|
||||
@ -76,62 +72,66 @@ Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
|
||||
* @param {number[]} secondClick - Click position [x, y]
|
||||
* @param {number[]} thirdClick - Click position [x, y]
|
||||
*/
|
||||
Cypress.Commands.add('addAngle', (viewport, firstClick, secondClick, thirdClick) => {
|
||||
|
||||
cy.get(viewport).then(($viewport) => {
|
||||
const [ x1, y1 ] = firstClick;
|
||||
const [ x2, y2 ] = secondClick;
|
||||
const [ x3, y3 ] = thirdClick;
|
||||
|
||||
cy.wrap($viewport)
|
||||
.click(x1, y1, {force: true})
|
||||
.trigger('mousemove', {clientX:x2, clientY:y2})
|
||||
.click(x2, y2, {force: true})
|
||||
.trigger('mousemove', {clientX:x3, clientY:y3})
|
||||
.click(x3, y3, {force: true})
|
||||
});
|
||||
});
|
||||
Cypress.Commands.add(
|
||||
'addAngle',
|
||||
(viewport, firstClick, secondClick, thirdClick) => {
|
||||
cy.get(viewport).then($viewport => {
|
||||
const [x1, y1] = firstClick;
|
||||
const [x2, y2] = secondClick;
|
||||
const [x3, y3] = thirdClick;
|
||||
|
||||
cy.wrap($viewport)
|
||||
.click(x1, y1, { force: true })
|
||||
.trigger('mousemove', { clientX: x2, clientY: y2 })
|
||||
.click(x2, y2, { force: true })
|
||||
.trigger('mousemove', { clientX: x3, clientY: y3 })
|
||||
.click(x3, y3, { force: true });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
//Command to wait DICOM image to load into the viewport
|
||||
Cypress.Commands.add('waitDicomImage', (timeout = 10000) => {
|
||||
Cypress.Commands.add('waitDicomImage', (timeout = 20000) => {
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then($cornerstone => {
|
||||
.then({ timeout }, $cornerstone => {
|
||||
return new Cypress.Promise(resolve => {
|
||||
const onEvent = (renderedEvt) => {
|
||||
const element = renderedEvt.detail.element;
|
||||
const onEvent = renderedEvt => {
|
||||
const element = renderedEvt.detail.element;
|
||||
|
||||
element.removeEventListener('cornerstoneimagerendered', onEvent);
|
||||
$cornerstone.events.removeEventListener('cornerstoneimagerendered', onEvent);
|
||||
resolve();
|
||||
}
|
||||
const onEnabled = (enabledEvt) => {
|
||||
const element = enabledEvt.detail.element;
|
||||
|
||||
element.addEventListener('cornerstoneimagerendered', onEvent);
|
||||
}
|
||||
$cornerstone.events.addEventListener('cornerstoneelementenabled', onEnabled);
|
||||
element.removeEventListener('cornerstoneimagerendered', onEvent);
|
||||
$cornerstone.events.removeEventListener(
|
||||
'cornerstoneimagerendered',
|
||||
onEvent
|
||||
);
|
||||
resolve();
|
||||
};
|
||||
const onEnabled = enabledEvt => {
|
||||
const element = enabledEvt.detail.element;
|
||||
|
||||
element.addEventListener('cornerstoneimagerendered', onEvent);
|
||||
};
|
||||
$cornerstone.events.addEventListener(
|
||||
'cornerstoneelementenabled',
|
||||
onEnabled
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
//Command to reset the viewport changes throught the cornerstone method
|
||||
Cypress.Commands.add('resetViewport', () => {
|
||||
cy.get('@resetBtn').click()
|
||||
cy.get('@resetBtn').click();
|
||||
});
|
||||
|
||||
|
||||
Cypress.Commands.add('imageZoomIn', () => {
|
||||
cy.get('@zoomBtn').click();
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('imageContrast', () => {
|
||||
@ -139,7 +139,7 @@ Cypress.Commands.add('imageContrast', () => {
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'top', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
});
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'top', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "1.4.5",
|
||||
"version": "1.5.2",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@ -20,8 +20,8 @@
|
||||
"build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js",
|
||||
"build:viewer": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:e2e": "cross-env NODE_ENV=development node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js SKIP_MINIMIZE=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html SKIP_MINIMIZE=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:package": "yarn run build:package",
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
@ -51,9 +51,9 @@
|
||||
"@ohif/extension-dicom-html": "^1.0.0",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||
"@ohif/extension-vtk": "^0.52.2",
|
||||
"@ohif/extension-vtk": "^0.52.4",
|
||||
"@ohif/i18n": "^0.50.5",
|
||||
"@ohif/ui": "^0.53.1",
|
||||
"@ohif/ui": "^0.53.3",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
|
||||
@ -1,41 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json" />
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="<%= PUBLIC_URL %>app-config.js"
|
||||
></script>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<title>OHIF Viewer</title>
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json" />
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script
|
||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>OHIF Viewer</title>
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
<!-- WEB FONTS -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
|
||||
<script>
|
||||
window.config.extensions = [SomeExtension];
|
||||
</script> -->
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,67 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="Open Health Imaging Foundation DICOM Viewer"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<meta name="description" content="Open Health Imaging Foundation DICOM Viewer" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Sanchez"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script
|
||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
|
||||
<title>OHIF Standalone Viewer</title>
|
||||
</head>
|
||||
<title>OHIF Standalone Viewer</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="/index.umd.js" crossorigin></script>
|
||||
<script>
|
||||
var containerId = 'root';
|
||||
var componentRenderedOrUpdatedCallback = function() {
|
||||
console.log('OHIF Viewer rendered/updated');
|
||||
};
|
||||
window.OHIFViewer.installViewer(
|
||||
{
|
||||
// routerBasename: '/',
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
},
|
||||
],
|
||||
},
|
||||
<script src="/index.umd.js" crossorigin></script>
|
||||
<script>
|
||||
var containerId = 'root';
|
||||
var componentRenderedOrUpdatedCallback = function () {
|
||||
console.log('OHIF Viewer rendered/updated');
|
||||
};
|
||||
window.OHIFViewer.installViewer(
|
||||
{
|
||||
// routerBasename: '/',
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
},
|
||||
],
|
||||
},
|
||||
containerId,
|
||||
componentRenderedOrUpdatedCallback
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
},
|
||||
containerId,
|
||||
componentRenderedOrUpdatedCallback
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -15,7 +15,6 @@ import {
|
||||
} from './utils/index.js';
|
||||
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||
|
||||
// ~~ EXTENSIONS
|
||||
import { GenericViewerCommands, MeasurementsPanel } from './appExtensions';
|
||||
@ -27,7 +26,6 @@ import { Provider } from 'react-redux';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { getActiveContexts } from './store/layout/selectors.js';
|
||||
import i18n from '@ohif/i18n';
|
||||
import setupTools from './setupTools.js';
|
||||
import store from './store';
|
||||
|
||||
// Contexts
|
||||
@ -36,11 +34,6 @@ import UserManagerContext from './context/UserManagerContext';
|
||||
import AppContext from './context/AppContext';
|
||||
|
||||
// ~~~~ APP SETUP
|
||||
initCornerstoneTools({
|
||||
globalToolSyncEnabled: true,
|
||||
showSVGCursors: true,
|
||||
});
|
||||
|
||||
const commandsManagerConfig = {
|
||||
getAppState: () => store.getState(),
|
||||
getActiveContexts: () => getActiveContexts(store.getState()),
|
||||
@ -49,9 +42,6 @@ const commandsManagerConfig = {
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
const hotkeysManager = new HotkeysManager(commandsManager);
|
||||
const extensionManager = new ExtensionManager({ commandsManager });
|
||||
|
||||
// CornerstoneTools and labeling/measurements?
|
||||
setupTools(store);
|
||||
// ~~~~ END APP SETUP
|
||||
|
||||
// TODO[react] Use a provider when the whole tree is React
|
||||
@ -175,8 +165,9 @@ class App extends Component {
|
||||
function _initExtensions(extensions, hotkeys) {
|
||||
const defaultExtensions = [
|
||||
GenericViewerCommands,
|
||||
MeasurementsPanel,
|
||||
OHIFCornerstoneExtension,
|
||||
// WARNING: MUST BE REGISTERED _AFTER_ OHIFCORNERSTONEEXTENSION
|
||||
MeasurementsPanel,
|
||||
];
|
||||
const mergedExtensions = defaultExtensions.concat(extensions);
|
||||
extensionManager.registerExtensions(mergedExtensions);
|
||||
|
||||
@ -6,7 +6,7 @@ import cornerstone from 'cornerstone-core';
|
||||
|
||||
//
|
||||
import jumpToRowItem from './jumpToRowItem.js';
|
||||
import getMeasurementLocationCallback from './../../lib/getMeasurementLocationCallback';
|
||||
import getMeasurementLocationCallback from './getMeasurementLocationCallback';
|
||||
|
||||
const { setViewportSpecificData } = OHIF.redux.actions;
|
||||
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 updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData';
|
||||
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData.js';
|
||||
|
||||
export default function getMeasurementLocationCallback(
|
||||
eventData,
|
||||
@ -1,7 +1,15 @@
|
||||
import ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
|
||||
import init from './init.js';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'measurements-table',
|
||||
|
||||
preRegistration(configuration = {}) {
|
||||
init(configuration);
|
||||
},
|
||||
getPanelModule() {
|
||||
return {
|
||||
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 cornerstone from "cornerstone-core";
|
||||
import OHIF from '@ohif/core';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
export default function updateTableWithNewMeasurementData({
|
||||
toolType,
|
||||
measurementNumber,
|
||||
location,
|
||||
description
|
||||
description,
|
||||
}) {
|
||||
// Update all measurements by measurement number
|
||||
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
|
||||
@ -4,7 +4,7 @@
|
||||
}
|
||||
|
||||
.entry-header.header-big {
|
||||
background: rgba(21,25,30,0.7);
|
||||
background: rgba(21, 25, 30, 0.7);
|
||||
padding: 10px var(--study-list-padding);
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
@ -32,7 +32,6 @@
|
||||
padding: 25px 0;
|
||||
}
|
||||
|
||||
|
||||
.header-logo-image {
|
||||
fill: transparent;
|
||||
height: 100%;
|
||||
@ -47,18 +46,9 @@
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.header-logo-text {
|
||||
display: inline-block;
|
||||
font-family: var(--logo-font-family);
|
||||
font-size: 14px;
|
||||
font-weight: var(--logo-font-weight);
|
||||
color: var(--text-primary-color);
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.entry-header.header-big .header-logo-text {
|
||||
font-size: 30px;
|
||||
line-height: 36px;
|
||||
width: 474px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
|
||||
@ -2,23 +2,19 @@
|
||||
height: 30px;
|
||||
text-decoration: none;
|
||||
text-decoration-color: none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-primary-color);
|
||||
font-size: 14px;
|
||||
font-weight: var(--logo-font-weight);
|
||||
font-family: var(--logo-font-family);
|
||||
}
|
||||
|
||||
.header-brand:hover,
|
||||
.header-brand:active,
|
||||
.header-band:visited {
|
||||
.header-brand:hover, .header-brand:active, .header-band:visited {
|
||||
color: var(--text-primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header-logo-text {
|
||||
line-height: 30px;
|
||||
width: 224px;
|
||||
height: auto;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ function OHIFLogo() {
|
||||
href="http://ohif.org"
|
||||
>
|
||||
<Icon name="ohif-logo" className="header-logo-image" />
|
||||
<div className="header-logo-text">Open Health Imaging Foundation</div>
|
||||
<Icon name="ohif-text-logo" className="header-logo-text" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,18 +2,19 @@ import { LayoutButton } from '@ohif/ui';
|
||||
import OHIF from '@ohif/core';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const { setLayout } = OHIF.redux.actions;
|
||||
const { setLayout, setViewportActive } = OHIF.redux.actions;
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
currentLayout: state.viewports.layout,
|
||||
activeViewportIndex: state.viewports.activeViewportIndex
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
// TODO: Change if layout switched becomes more complex
|
||||
onChange: (selectedCell, currentLayout) => {
|
||||
onChange: (selectedCell, currentLayout, activeViewportIndex) => {
|
||||
let viewports = [];
|
||||
const rows = selectedCell.row + 1;
|
||||
const columns = selectedCell.col + 1;
|
||||
@ -36,6 +37,11 @@ const mapDispatchToProps = dispatch => {
|
||||
viewports,
|
||||
};
|
||||
|
||||
const maxActiveIndex = rows * columns - 1;
|
||||
if (activeViewportIndex > maxActiveIndex) {
|
||||
dispatch(setViewportActive(0));
|
||||
}
|
||||
|
||||
dispatch(setLayout(layout));
|
||||
},
|
||||
};
|
||||
@ -43,10 +49,10 @@ const mapDispatchToProps = dispatch => {
|
||||
|
||||
const mergeProps = (propsFromState, propsFromDispatch) => {
|
||||
const onChangeFromDispatch = propsFromDispatch.onChange;
|
||||
const { currentLayout } = propsFromState;
|
||||
const { currentLayout, activeViewportIndex } = propsFromState;
|
||||
|
||||
return {
|
||||
onChange: (selectedCell) => onChangeFromDispatch(selectedCell, currentLayout)
|
||||
onChange: selectedCell => onChangeFromDispatch(selectedCell, currentLayout, activeViewportIndex)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -10,19 +10,9 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const { viewportIndex } = ownProps;
|
||||
const toolContextMenuData = state.ui.contextMenu[viewportIndex];
|
||||
let availableTools;
|
||||
|
||||
if (
|
||||
state.extensions &&
|
||||
state.extensions.cornerstone &&
|
||||
state.extensions.cornerstone.availableTools
|
||||
) {
|
||||
availableTools = state.extensions.cornerstone.availableTools;
|
||||
}
|
||||
|
||||
return {
|
||||
...toolContextMenuData,
|
||||
availableTools,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -2,7 +2,8 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
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';
|
||||
|
||||
@ -97,12 +98,11 @@ function getNearbyToolData(element, coords, toolTypes) {
|
||||
return pointNearTool ? nearbyTool : undefined;
|
||||
}
|
||||
|
||||
function getDropdownItems(eventData, isTouchEvent = false, availableTools) {
|
||||
function getDropdownItems(eventData, isTouchEvent = false) {
|
||||
const nearbyToolData = getNearbyToolData(
|
||||
eventData.element,
|
||||
eventData.currentPoints.canvas,
|
||||
toolTypes,
|
||||
availableTools
|
||||
toolTypes
|
||||
);
|
||||
|
||||
// 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,
|
||||
eventData: PropTypes.object,
|
||||
onClose: PropTypes.func,
|
||||
availableTools: PropTypes.array,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
@ -168,12 +167,8 @@ class ToolContextMenu extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { isTouchEvent, eventData, availableTools } = this.props;
|
||||
const dropdownItems = getDropdownItems(
|
||||
eventData,
|
||||
isTouchEvent,
|
||||
availableTools
|
||||
);
|
||||
const { isTouchEvent, eventData } = this.props;
|
||||
const dropdownItems = getDropdownItems(eventData, isTouchEvent);
|
||||
|
||||
// Skip if there is no dropdown item
|
||||
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);
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
/* Sizes */
|
||||
|
||||
:root {
|
||||
--top-bar-height: 40px;
|
||||
--top-bar-expanded-height: 160px;
|
||||
@ -10,14 +11,8 @@
|
||||
--study-list-padding-medium-screen: 10px;
|
||||
}
|
||||
|
||||
/* Fonts */
|
||||
/* Logo font should be SVG so we don't need to load an entire font */
|
||||
:root {
|
||||
--logo-font-family: 'Sanchez';
|
||||
--logo-font-weight: 300; /* Sanchez, 300 does not exist */
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
|
||||
:root {
|
||||
--transition-duration: 0.3s;
|
||||
--transition-effect: ease;
|
||||
@ -25,6 +20,7 @@
|
||||
}
|
||||
|
||||
/* Thicknesses */
|
||||
|
||||
:root {
|
||||
--viewport-border-thickness: 1px;
|
||||
--ui-border-thickness: 1px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user