WIP SR viewport.

This commit is contained in:
James A. Petts 2020-06-03 18:12:13 +01:00
parent 8674e676b7
commit 558ab00c23
22 changed files with 904 additions and 8 deletions

View File

@ -0,0 +1,8 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
};

View File

@ -0,0 +1,44 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
return merge(commonConfig, {
devtool: 'source-map',
stats: {
colors: true,
hash: true,
timings: true,
assets: true,
chunks: false,
chunkModules: false,
modules: false,
children: false,
warnings: true,
},
optimization: {
minimize: true,
sideEffects: true,
},
output: {
path: ROOT_DIR,
library: 'OHIFExtCornerstone',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
});
};

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,121 @@
# @ohif/extension-cornerstone
![npm (scoped)](https://img.shields.io/npm/v/@ohif/extension-cornerstone.svg?style=flat-square)
This extension adds support for viewing and manipulating 2D medical images via a
viewport. The underlying implementation wraps the
`cornerstonejs/react-cornerstone-viewport`, and provides basic commands and
toolbar buttons for common actions.
<!-- TODO: Simple image or GIF? -->
#### Index
Extension Id: `cornerstone`
- [Commands Module](#commands-module)
- [Toolbar Module](#toolbar-module)
- [Viewport Module](#viewport-module)
## Commands Module
This extensions includes the following `Commands` and `Command Definitions`.
These can be registered with `@ohif/core`'s `CommandManager`. After registering
the commands, they can be bound to `hotkeys` using the `HotkeysManager` and
listed in the `UserPreferences` modal.
You can read more about [`Commands`][docs-commands], [`Hotkeys`][docs-hotkeys],
and the [`UserPreferences` Modal][docs-userprefs] in their respective locations
in the OHIF Viewer's documentation.
| Command Name | Description | Store Contexts |
| ---------------------------- | --------------------------------------- | -------------- |
| `rotateViewportCW` | | viewports |
| `rotateViewportCCW` | | viewports |
| `invertViewport` | | viewports |
| `flipViewportVertical` | | viewports |
| `flipViewportHorizontal` | | viewports |
| `scaleUpViewport` | | viewports |
| `scaleDownViewport` | | viewports |
| `fitViewportToWindow` | | viewports |
| `resetViewport` | | viewports |
| clearAnnotations | TODO | |
| next/previous Image | TODO | |
| first/last Image | TODO | |
| `nextViewportDisplaySet` | | |
| `previousViewportDisplaySet` | | |
| `setToolActive` | Activates tool for primary button/touch | |
## Toolbar Module
Our toolbar module contains definitions for:
- `StackScroll`
- `Zoom`
- `Wwwc`
- `Pan`
- `Length`
- `Angle`
- `Reset`
- `Cine`
All use the `ACTIVE_VIEWPORT::CORNERSTONE` context.
## Viewport Module
Our Viewport wraps [cornerstonejs/react-cornerstone-viewport][react-viewport]
and is connected the redux store. This module is the most prone to change as we
hammer out our Viewport interface.
## Tool Configuration
Tools can be configured through extension configuration using the tools key:
```js
...
cornerstoneExtensionConfig: {
tools: {
ArrowAnnotate: {
configuration: {
getTextCallback: (callback, eventDetails) => callback(prompt('Enter your custom annotation')),
},
},
},
},
...
```
## Annotate Tools Configuration
*We currently support one property for annotation tools.*
### Hide handles
This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
```js
...
cornerstoneExtensionConfig: {
hideHandles: true,
},
...
## Resources
### Repositories
- [cornerstonejs/react-cornerstone-viewport][react-viewport]
- [cornerstonejs/cornerstoneTools][cornerstone-tools]
- [cornerstonejs/cornerstone][cornerstone]
<!--
Links
-->
<!-- prettier-ignore-start -->
[docs-commands]: https://www.com
[docs-hotkeys]: https://www.com
[docs-userprefs]: htt
[react-viewport]: https://github.com/cornerstonejs/react-cornerstone-viewport
[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools
[cornerstone]: https://github.com/cornerstonejs/cornerstone
<!-- prettier-ignore-end -->

View File

@ -0,0 +1 @@
module.exports = require('../../babel.config.js');

View File

@ -0,0 +1,55 @@
{
"name": "@ohif/extension-dicom-sr",
"version": "0.0.1",
"description": "OHIF extension for an SR Cornerstone Viewport",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"npm": ">=6",
"yarn": ">=1.16.0"
},
"files": [
"dist",
"README.md"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:cornerstone": "yarn run dev",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"start": "yarn run dev",
"test:unit": "jest --watchAll",
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.3.0",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.15.1",
"dcmjs": "^0.12.3",
"cornerstone-wado-image-loader": "^3.1.2",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-redux": "^6.0.0",
"react-resize-detector": "^3.4.0",
"redux": "^4.0.1"
},
"dependencies": {
"@babel/runtime": "7.7.6",
"classnames": "^2.2.6",
"lodash.merge": "^4.6.2",
"lodash.throttle": "^4.1.1",
"react-cornerstone-viewport": "2.3.8"
}
}

View File

@ -0,0 +1,232 @@
import React, { Component } from 'react';
import CornerstoneViewport from 'react-cornerstone-viewport';
//import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import debounce from 'lodash.debounce';
import throttle from 'lodash.throttle';
// const {
// onAdded,
// onRemoved,
// onModified,
// } = OHIF.measurements.MeasurementHandlers;
// // TODO: Transition to enums for the action names so that we can ensure they stay up to date
// // everywhere they're used.
// const MEASUREMENT_ACTION_MAP = {
// added: onAdded,
// removed: onRemoved,
// modified: throttle(event => {
// return onModified(event);
// }, 300),
// };
// const cine = viewportSpecificData.cine;
// isPlaying = cine.isPlaying === true;
// frameRate = cine.cineFrameRate || frameRate;
const { StackManager } = OHIF.utils;
class OHIFCornerstoneViewport extends Component {
state = {
viewportData: null,
};
static defaultProps = {
customProps: {},
};
static propTypes = {
displaySet: PropTypes.object,
viewportIndex: PropTypes.number,
dataSource: PropTypes.object,
children: PropTypes.node,
customProps: PropTypes.object,
};
static name = 'OHIFCornerstoneViewport';
static init() {
console.log('OHIFCornerstoneViewport init()');
}
static destroy() {
console.log('OHIFCornerstoneViewport destroy()');
StackManager.clearStacks();
}
/**
* Obtain the CornerstoneTools Stack for the specified display set.
*
* @param {Object} displaySet
* @param {Object} dataSource
* @return {Object} CornerstoneTools Stack
*/
static getCornerstoneStack(displaySet, dataSource) {
const { frameIndex } = displaySet;
// Get stack from Stack Manager
const storedStack = StackManager.findOrCreateStack(displaySet, dataSource);
// Clone the stack here so we don't mutate it
const stack = Object.assign({}, storedStack);
stack.currentImageIdIndex = frameIndex;
// TODO -> Do we ever use this like this?
// if (SOPInstanceUID) {
// const index = stack.imageIds.findIndex(imageId => {
// const imageIdSOPInstanceUID = cornerstone.metaData.get(
// 'SOPInstanceUID',
// imageId
// );
// return imageIdSOPInstanceUID === SOPInstanceUID;
// });
// if (index > -1) {
// stack.currentImageIdIndex = index;
// } else {
// console.warn(
// 'SOPInstanceUID provided was not found in specified DisplaySet'
// );
// }
// }
return stack;
}
getViewportData = async displaySet => {
let viewportData;
const { dataSource } = this.props;
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
displaySet,
dataSource
);
viewportData = {
StudyInstanceUID: displaySet.StudyInstanceUID,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
stack,
};
return viewportData;
};
setStateFromProps() {
const { displaySet } = this.props;
const {
StudyInstanceUID,
displaySetInstanceUID,
sopClassUids,
} = displaySet;
if (!StudyInstanceUID || !displaySetInstanceUID) {
return;
}
if (sopClassUids && sopClassUids.length > 1) {
console.warn(
'More than one SOPClassUID in the same series is not yet supported.'
);
}
this.getViewportData(displaySet).then(viewportData => {
this.setState({
viewportData,
});
});
}
componentDidMount() {
this.setStateFromProps();
}
componentDidUpdate(prevProps) {
const { displaySet } = this.props;
const prevDisplaySet = prevProps.displaySet;
if (
displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
this.setStateFromProps();
}
}
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 (
child &&
React.cloneElement(child, {
viewportIndex: this.props.viewportIndex,
key: index,
})
);
});
}
const debouncedNewImageHandler = debounce(
({ currentImageIdIndex, sopInstanceUid }) => {
const { displaySet } = this.props;
const { StudyInstanceUID } = displaySet;
if (currentImageIdIndex > 0) {
this.props.onNewImage({
StudyInstanceUID,
SOPInstanceUID: sopInstanceUid,
frameIndex: currentImageIdIndex,
activeViewportIndex: viewportIndex,
});
}
},
700
);
// TODO -> We may still want a wrapped component to define all the measurement api stuff.
return (
<>
<CornerstoneViewport
viewportIndex={viewportIndex}
imageIds={imageIds}
imageIdIndex={currentImageIdIndex}
onNewImage={debouncedNewImageHandler}
// TODO: ViewportGrid Context?
isActive={true} // todo
isStackPrefetchEnabled={true} // todo
isPlaying={false}
frameRate={24}
/>
{childrenWithProps}
</>
);
}
}
const temp = () => <div>Hello SR Viewport!</div>;
//export default OHIFCornerstoneViewport;
export default temp;

View File

@ -0,0 +1,146 @@
import id from './id';
import { utils } from '@ohif/core';
const sopClassHandlerName = 'dicom-sr';
const sopClassUids = [
'1.2.840.10008.5.1.4.1.1.88.11', //BASIC_TEXT_SR:
'1.2.840.10008.5.1.4.1.1.88.22', //ENHANCED_SR:
'1.2.840.10008.5.1.4.1.1.88.33', //COMPREHENSIVE_SR:
];
const scoordTypes = ['POINT', 'MULTIPOINT', 'POLYLINE', 'CIRCLE', 'ELLIPSE'];
const CodeNameCodeSequenceValues = {
ImagingMeasurementReport: '126000',
ImageLibrary: '111028',
ImagingMeasurements: '126010',
MeasurementGroup: '125007',
ImageLibraryGroup: '126200',
};
/**
* Basic SOPClassHandler:
* - For all Image types that are stackable, create
* a displaySet with a stack of images
*
* @param {Array} sopClassHandlerModules List of SOP Class Modules
* @param {SeriesMetadata} series The series metadata object from which the display sets will be created
* @returns {Array} The list of display sets created for the given series object
*/
function getDisplaySetsFromSeries(instances) {
// If the series has no instances, stop here
if (!instances || !instances.length) {
throw new Error('No instances were provided');
}
const instance = instances[0];
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
const { ConceptNameCodeSequence, ContentSequence } = instance;
if (
ConceptNameCodeSequence.CodeValue !==
CodeNameCodeSequenceValues.ImagingMeasurementReport
) {
console.warn(
'Only support Imaging Measurement Report SRs (TID1500) for now'
);
return [];
}
const referencedImages = _getReferencedImagesList(ContentSequence);
const measurements = _getMeasurements(ContentSequence, SOPInstanceUID);
const displaySet = {
plugin: id,
Modality: 'SR',
displaySetInstanceUID: utils.guid(),
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`,
referencedImages,
};
return [displaySet];
}
function getSopClassHandlerModule() {
return [
{
name: sopClassHandlerName,
sopClassUids,
getDisplaySetsFromSeries,
},
];
}
function _getMeasurements(
ImagingMeasurementReportContentSequence,
SOPInstanceUID
) {
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
item =>
item.ConceptNameCodeSequence.CodeValue ===
CodeNameCodeSequenceValues.ImagingMeasurements
);
if (SOPInstanceUID === '2.25.435452399240481307327287169369305113868') {
debugger;
}
const MeasurementGroup = _getSequenceAsArray(
ImagingMeasurements.ContentSequence
).find(
item =>
item.ConceptNameCodeSequence.CodeValue ===
CodeNameCodeSequenceValues.MeasurementGroup
);
const Measurements = _getSequenceAsArray(
MeasurementGroup.ContentSequence
).filter(group => group.ValueType === 'NUM');
debugger;
_getSequenceAsArray(MeasurementGroup.ContentSequence).forEach(item => {});
}
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
item =>
item.ConceptNameCodeSequence.CodeValue ===
CodeNameCodeSequenceValues.ImageLibrary
);
const ImageLibraryGroup = _getSequenceAsArray(
ImageLibrary.ContentSequence
).find(
item =>
item.ConceptNameCodeSequence.CodeValue ===
CodeNameCodeSequenceValues.ImageLibraryGroup
);
const referencedImages = [];
_getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => {
const { ReferencedSOPSequence } = item;
const {
ReferencedSOPClassUID,
ReferencedSOPInstanceUID,
} = ReferencedSOPSequence;
referencedImages.push({ ReferencedSOPClassUID, ReferencedSOPInstanceUID });
});
return referencedImages;
}
function _getSequenceAsArray(sequence) {
return Array.isArray(sequence) ? sequence : [sequence];
}
export default getSopClassHandlerModule;

View File

@ -0,0 +1 @@
export default 'org.ohif.dicom-sr';

View File

@ -0,0 +1,45 @@
import React from 'react';
import getSopClassHandlerModule from './getSopClassHandlerModule';
import id from './id.js';
const Component = React.lazy(() => {
return import('./OHIFCornerstoneSRViewport');
});
const OHIFCornerstoneSRViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
/**
*
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id,
/**
*
*
* @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/
getViewportModule({ commandsManager }) {
const ExtendedOHIFCornerstoneSRViewport = props => {
const onNewImageHandler = jumpData => {
commandsManager.runCommand('jumpToImage', jumpData);
};
return (
<OHIFCornerstoneSRViewport {...props} onNewImage={onNewImageHandler} />
);
};
return [{ name: 'dicom-sr', component: ExtendedOHIFCornerstoneSRViewport }];
},
getSopClassHandlerModule,
};

View File

@ -0,0 +1,8 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
};

View File

@ -0,0 +1,44 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
return merge(commonConfig, {
devtool: 'source-map',
stats: {
colors: true,
hash: true,
timings: true,
assets: true,
chunks: false,
chunkModules: false,
modules: false,
children: false,
warnings: true,
},
optimization: {
minimize: true,
sideEffects: true,
},
output: {
path: ROOT_DIR,
library: 'OHIFExtCornerstone',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
});
};

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1 @@
module.exports = require('../../babel.config.js');

View File

@ -0,0 +1,35 @@
{
"name": "@ohif/sr-mode-example",
"version": "0.0.1",
"description": "Example SR mode for OHIF",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"npm": ">=6",
"yarn": ">=1.16.0"
},
"files": [
"dist",
"README.md"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:cornerstone": "yarn run dev",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"start": "yarn run dev",
"test:unit": "jest --watchAll",
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
},
"peerDependencies": {},
"dependencies": {
"@babel/runtime": "7.7.6"
}
}

View File

@ -0,0 +1,104 @@
export default function mode({ modeConfiguration }) {
return {
id: 'sr-example-mode',
validationTags: {
study: [],
series: [],
},
isValidMode: (studyTags, seriesTags) => {
// All series are welcome in this mode!
return true;
},
routes: [
{
path: 'viewer',
init: ({ servicesManager, extensionManager }) => {
const { ToolBarService } = servicesManager.services;
ToolBarService.init(extensionManager);
ToolBarService.addButtons([
{
id: 'Zoom',
namespace: 'org.ohif.cornerstone.toolbarModule.Zoom',
},
{
id: 'Levels',
namespace: 'org.ohif.cornerstone.toolbarModule.Wwwc',
},
{
id: 'Pan',
namespace: 'org.ohif.cornerstone.toolbarModule.Pan',
},
{
id: 'Capture',
namespace: 'org.ohif.cornerstone.toolbarModule.Capture',
},
{
id: 'Layout',
namespace: 'org.ohif.default.toolbarModule.Layout',
},
{
id: 'Annotate',
namespace: 'org.ohif.cornerstone.toolbarModule.Annotate',
},
{
id: 'Bidirectional',
namespace: 'org.ohif.cornerstone.toolbarModule.Bidirectional',
},
{
id: 'Ellipse',
namespace: 'org.ohif.cornerstone.toolbarModule.Ellipse',
},
{
id: 'Length',
namespace: 'org.ohif.cornerstone.toolbarModule.Length',
},
]);
// Could import layout selector here from org.ohif.default (when it exists!)
ToolBarService.setToolBarLayout([
// Primary
{
tools: ['Zoom', 'Levels', 'Pan', 'Capture', 'Layout'],
moreTools: ['Zoom'],
},
// Secondary
{
tools: ['Annotate', 'Bidirectional', 'Ellipse', 'Length'],
},
]);
},
layoutTemplate: ({ routeProps }) => {
return {
id: 'org.ohif.default.layoutTemplateModule.viewerLayout',
props: {
// named slots
leftPanels: ['org.ohif.default.panelModule.seriesList'],
rightPanels: ['org.ohif.default.panelModule.measure'],
viewports: [
{
namespace: 'org.ohif.cornerstone.viewportModule.cornerstone',
displaySetsToDisplay: [
'org.ohif.default.sopClassHandlerModule.stack',
],
},
{
namespace: 'org.ohif.dicom-sr.viewportModule.dicom-sr',
displaySetsToDisplay: [
'org.ohif.dicom-sr.sopClassHandlerModule.dicom-sr',
],
},
],
},
};
},
},
],
extensions: ['org.ohif.default', 'org.ohif.cornerstone'],
sopClassHandlers: [
'org.ohif.default.sopClassHandlerModule.stack',
'org.ohif.dicom-sr.sopClassHandlerModule.dicom-sr',
],
};
}
window.SRViewportExample = mode({});

View File

@ -59,6 +59,7 @@
"@ohif/extension-dicom-pdf": "^1.0.1",
"@ohif/extension-lesion-tracker": "^0.2.0",
"@ohif/extension-measurement-tracking": "^0.0.1",
"@ohif/extension-dicom-sr": "^0.0.1",
"@ohif/extension-vtk": "^1.5.6",
"@ohif/i18n": "^0.52.8",
"@ohif/mode-longitudinal": "^0.0.1",

View File

@ -19,6 +19,7 @@ import appInit from './appInit.js';
// TODO: Temporarily for testing
import '@ohif/mode-example';
import '@ohif/sr-mode-example';
import '@ohif/mode-longitudinal';
/**
@ -48,7 +49,12 @@ function App({ config, defaultExtensions }) {
extensionManager,
servicesManager
);
const { UIDialogService, UIModalService, UINotificationService } = servicesManager.services;
const {
UIDialogService,
UIModalService,
UINotificationService,
} = servicesManager.services;
// A UI Service may need to use the ViewportGrid context
const viewportGridReducer = (state, action) => {

View File

@ -67,6 +67,7 @@ function appInit(appConfigOrFunc, defaultExtensions) {
if (!appConfig.modes.length) {
appConfig.modes.push(window.exampleMode);
appConfig.modes.push(window.longitudinalMode);
appConfig.modes.push(window.SRViewportExample);
}
return {

View File

@ -12,6 +12,7 @@ function ViewerViewportGrid(props) {
{ numCols, numRows, activeViewportIndex, viewports },
dispatch,
] = useViewportGrid();
const setActiveViewportIndex = index => {
dispatch({ type: 'SET_ACTIVE_VIEWPORT_INDEX', payload: index });
};
@ -87,6 +88,7 @@ function ViewerViewportGrid(props) {
const viewportIndex = i;
const paneMetadata = viewports[i] || {};
const { displaySetInstanceUID } = paneMetadata;
const displaySet =
DisplaySetService.getDisplaySetByUID(displaySetInstanceUID) || {};
const ViewportComponent = _getViewportComponent(

View File

@ -24,6 +24,7 @@ import ReactDOM from 'react-dom';
import OHIFDefaultExtension from '@ohif/extension-default';
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
import OHIFMeasurementTrackingExtension from '@ohif/extension-measurement-tracking';
import OHIFDICOMSRExtension from '@ohif/extension-dicom-sr';
/** Combine our appConfiguration and "baked-in" extensions */
const appProps = {
@ -32,6 +33,7 @@ const appProps = {
OHIFDefaultExtension,
OHIFCornerstoneExtension,
OHIFMeasurementTrackingExtension,
OHIFDICOMSRExtension,
],
};

View File

@ -2,11 +2,8 @@ import React, { useEffect } from 'react';
import { useParams } from 'react-router';
import PropTypes from 'prop-types';
// TODO: DicomMetadataStore should be injected?
import { DicomMetadataStore, ToolBarManager } from '@ohif/core';
import {
DragAndDropProvider,
ImageViewerProvider,
} from '@ohif/ui';
import { DicomMetadataStore } from '@ohif/core';
import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui';
//
import { useQuery } from '@hooks';
import ViewportGrid from '@components/ViewportGrid';
@ -131,8 +128,8 @@ export default function ModeRoute({
>
<CombinedContextProvider>
{/* TODO: extensionManager is already provided to the extension module.
* Use it from there instead of passing as a prop here.
*/}
* Use it from there instead of passing as a prop here.
*/}
<DragAndDropProvider>
<LayoutComponent
{...layoutTemplateData.props}