feat: Cornerstone extension viewport default enabledElement handler and installation guides (#2636)
* feat: Add initial doc for mode installation * fix: cornerstone viewport should have default enabledElement handler * docs: Add custom mode and extension installation guides * docs: Add route address for template mode
This commit is contained in:
parent
1d0b310d92
commit
65563edb54
@ -34,7 +34,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
|||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
mode: isProdBuild ? 'production' : 'development',
|
mode: isProdBuild ? 'production' : 'development',
|
||||||
devtool: isProdBuild ? 'source-map' : 'eval',
|
devtool: isProdBuild ? 'source-map' : 'eval-source-map',
|
||||||
entry: {
|
entry: {
|
||||||
app: `${SRC_DIR}/index.js`,
|
app: `${SRC_DIR}/index.js`,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,14 +4,21 @@ import cornerstone from 'cornerstone-core';
|
|||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
import CornerstoneViewport from 'react-cornerstone-viewport';
|
import CornerstoneViewport from 'react-cornerstone-viewport';
|
||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import ViewportLoadingIndicator from './ViewportLoadingIndicator';
|
|
||||||
import setCornerstoneMeasurementActive from './_shared/setCornerstoneMeasurementActive';
|
|
||||||
import ViewportOverlay from './ViewportOverlay';
|
|
||||||
|
|
||||||
import { useCine, useViewportGrid } from '@ohif/ui';
|
import { useCine, useViewportGrid } from '@ohif/ui';
|
||||||
|
|
||||||
|
import ViewportLoadingIndicator from './ViewportLoadingIndicator';
|
||||||
|
import setCornerstoneMeasurementActive from './_shared/setCornerstoneMeasurementActive';
|
||||||
|
import setActiveAndPassiveToolsForElement from './_shared/setActiveAndPassiveToolsForElement';
|
||||||
|
import getTools from './_shared/getTools';
|
||||||
|
|
||||||
|
import ViewportOverlay from './ViewportOverlay';
|
||||||
|
|
||||||
const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex');
|
const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex');
|
||||||
|
|
||||||
|
const BaseAnnotationTool = cornerstoneTools.importInternal(
|
||||||
|
'base/BaseAnnotationTool'
|
||||||
|
);
|
||||||
|
|
||||||
const { StackManager } = OHIF.utils;
|
const { StackManager } = OHIF.utils;
|
||||||
|
|
||||||
function OHIFCornerstoneViewport({
|
function OHIFCornerstoneViewport({
|
||||||
@ -45,6 +52,42 @@ function OHIFCornerstoneViewport({
|
|||||||
element.addEventListener(cornerstone.EVENTS.IMAGE_RENDERED, handler);
|
element.addEventListener(cornerstone.EVENTS.IMAGE_RENDERED, handler);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultOnElementEnabled = evt => {
|
||||||
|
const eventData = evt.detail;
|
||||||
|
const targetElement = eventData.element;
|
||||||
|
const tools = getTools();
|
||||||
|
const toolAlias = ToolBarService.state.primaryToolId;
|
||||||
|
|
||||||
|
// Activate appropriate tool bindings for element
|
||||||
|
setActiveAndPassiveToolsForElement(targetElement, tools);
|
||||||
|
cornerstoneTools.setToolActiveForElement(targetElement, toolAlias, {
|
||||||
|
mouseButtonMask: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set dashed, based on tracking, for this viewport
|
||||||
|
const allTools = cornerstoneTools.store.state.tools;
|
||||||
|
const toolsForElement = allTools.filter(
|
||||||
|
tool => tool.element === targetElement
|
||||||
|
);
|
||||||
|
|
||||||
|
toolsForElement.forEach(tool => {
|
||||||
|
if (tool instanceof BaseAnnotationTool) {
|
||||||
|
const configuration = tool.configuration;
|
||||||
|
|
||||||
|
configuration.renderDashed = true;
|
||||||
|
|
||||||
|
tool.configuration = configuration;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update image after setting tool config
|
||||||
|
const enabledElement = cornerstone.getEnabledElement(targetElement);
|
||||||
|
|
||||||
|
if (enabledElement.image) {
|
||||||
|
cornerstone.updateImage(targetElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isMounted.current = true;
|
isMounted.current = true;
|
||||||
return () => {
|
return () => {
|
||||||
@ -174,7 +217,9 @@ function OHIFCornerstoneViewport({
|
|||||||
return (
|
return (
|
||||||
<div className="relative flex flex-row w-full h-full overflow-hidden">
|
<div className="relative flex flex-row w-full h-full overflow-hidden">
|
||||||
<CornerstoneViewport
|
<CornerstoneViewport
|
||||||
onElementEnabled={onElementEnabled}
|
onElementEnabled={
|
||||||
|
onElementEnabled ? onElementEnabled : defaultOnElementEnabled
|
||||||
|
}
|
||||||
viewportIndex={viewportIndex}
|
viewportIndex={viewportIndex}
|
||||||
imageIds={imageIds}
|
imageIds={imageIds}
|
||||||
imageIdIndex={initialImageIdIndex}
|
imageIdIndex={initialImageIdIndex}
|
||||||
|
|||||||
BIN
platform/docs/docs/assets/img/template-extension-files.png
Normal file
BIN
platform/docs/docs/assets/img/template-extension-files.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
platform/docs/docs/assets/img/template-mode-files.png
Normal file
BIN
platform/docs/docs/assets/img/template-mode-files.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
platform/docs/docs/assets/img/template-mode-ui.png
Normal file
BIN
platform/docs/docs/assets/img/template-mode-ui.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
121
platform/docs/docs/platform/extensions/installation.md
Normal file
121
platform/docs/docs/platform/extensions/installation.md
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 5
|
||||||
|
sidebar_label: Installation
|
||||||
|
---
|
||||||
|
|
||||||
|
# Extension: Installation
|
||||||
|
|
||||||
|
OHIF-v3 provides the ability to utilize external extensions. In this document we
|
||||||
|
will describe how to add/install external extensions.
|
||||||
|
|
||||||
|
> Our long-term plan is to make OHIF-v3 capable of installing extensions from
|
||||||
|
> `npm` with a command line. Until then, please use the instructions below to
|
||||||
|
> manually install extensions.
|
||||||
|
|
||||||
|
## Installing an Extension
|
||||||
|
|
||||||
|
### 1) Extension Files Copy
|
||||||
|
|
||||||
|
We use a [Template Extension](https://github.com/OHIF/extension-template)
|
||||||
|
repository to describe the necessary steps to use a new extension. You can use
|
||||||
|
this repository as a starting point to create your own extension.
|
||||||
|
|
||||||
|
As you can see in the extension code base, folders structure are similar to the
|
||||||
|
OHIF-maintained extensions. Let's look at our `Template Extension`:
|
||||||
|
|
||||||
|
- `src/index.js`: The most important file in any extension. This file is where
|
||||||
|
extensions' authors hav defined the extension modules, lifecycle hooks, and
|
||||||
|
other configurations.
|
||||||
|
|
||||||
|
For instance the `Template Extension` has the following modules which will be
|
||||||
|
registered in OHIF by [Extension Manager](./extension.md)
|
||||||
|
|
||||||
|
Each extension has an ID which is used to register the extension in OHIF. For
|
||||||
|
instance, for the `Template Extension`, the extension ID is
|
||||||
|
`extension.template`.
|
||||||
|
|
||||||
|
```js {2} title="templateExtension/src/index.js"
|
||||||
|
export default {
|
||||||
|
id: 'extension.template',
|
||||||
|
getPanelModule,
|
||||||
|
getCommandsModule,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Package.json
|
||||||
|
|
||||||
|
Extension package name is defined in the `package.json` file. The `package.json`
|
||||||
|
file is a JSON file that defines the extension name, version, and dependencies.
|
||||||
|
For instance for the Template extension, the `package.json` file looks like
|
||||||
|
this:
|
||||||
|
|
||||||
|
```js {2} title="templateExtension/package.json"
|
||||||
|
{
|
||||||
|
"name": "@ohif/extension-template",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A template extension to show extension installation",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note 1: We will use the `@ohif/extension-template` inside OHIF to let OHIF know
|
||||||
|
about existence of this extension.
|
||||||
|
|
||||||
|
Note 2: You don't need to use the `@ohif` scope for your extensions. You can use
|
||||||
|
any scope you want.
|
||||||
|
|
||||||
|
Note 3: Although folders names are not important and the `package.json` file
|
||||||
|
contains the mode name, we recommend to use the same name as the folder name.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 2) Configuring OHIF
|
||||||
|
|
||||||
|
There are couple of places inside OHIF which we need to modify in order to add
|
||||||
|
the extension. The following lines of code should be added to the OHIF:
|
||||||
|
|
||||||
|
#### Viewer's package.json
|
||||||
|
|
||||||
|
```js {8} titl="platform/viewer/package.json"
|
||||||
|
/* ... */
|
||||||
|
"dependencies": {
|
||||||
|
/* ... */
|
||||||
|
"@babel/runtime": "7.7.6",
|
||||||
|
"@ohif/core": "^2.5.1",
|
||||||
|
"@ohif/extension-cornerstone": "^2.4.0",
|
||||||
|
"@ohif/extension-measurement-tracking": "^0.0.1",
|
||||||
|
"@ohif/extension-template": "^0.0.1",
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### index.js
|
||||||
|
|
||||||
|
```js {4,13} title="platform/viewer/src/index.js"
|
||||||
|
/* ... */
|
||||||
|
import OHIFMeasurementTrackingExtension from '@ohif/extension-measurement-tracking';
|
||||||
|
import OHIFDICOMSRExtension from '@ohif/extension-dicom-sr';
|
||||||
|
import OHIFTemplateExtension from '@ohif/extension-template';
|
||||||
|
|
||||||
|
const appProps = {
|
||||||
|
config: window ? window.config : {},
|
||||||
|
defaultExtensions: [
|
||||||
|
OHIFDefaultExtension,
|
||||||
|
OHIFCornerstoneExtension,
|
||||||
|
OHIFMeasurementTrackingExtension,
|
||||||
|
OHIFDICOMSRExtension,
|
||||||
|
OHIFTemplateExtension,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
/* ... */
|
||||||
|
```
|
||||||
|
|
||||||
|
After you followed the above steps, you should run `yarn install` in the root
|
||||||
|
folder of the OHIF repository to install the registered extension.
|
||||||
|
|
||||||
|
Now you have added the extension to the OHIF, and its modules (layout, commands,
|
||||||
|
panels, toolbars, hangingProtocols, etc.) are made available to the OHIF
|
||||||
|
`modes`. Read more on how to consume extensions
|
||||||
|
[here](../modes/index.md#consuming-extensions)
|
||||||
|
|
||||||
|
Congrats! 🎉
|
||||||
208
platform/docs/docs/platform/modes/installation.md
Normal file
208
platform/docs/docs/platform/modes/installation.md
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 5
|
||||||
|
sidebar_label: Installation
|
||||||
|
---
|
||||||
|
|
||||||
|
# Mode: Installation
|
||||||
|
|
||||||
|
OHIF-v3 provides the ability to utilize external modes and extensions. In this
|
||||||
|
document we will describe how to add/install external modes.
|
||||||
|
|
||||||
|
> Our long-term plan is to make OHIF-v3 capable of installing modes at runtime,
|
||||||
|
> however in the meantime, please use the instructions below to manually install
|
||||||
|
> modes and their extensions.
|
||||||
|
|
||||||
|
## Installing a Mode
|
||||||
|
|
||||||
|
### 1) Mode Files Copy
|
||||||
|
|
||||||
|
We use a [Template Mode](https://github.com/OHIF/mode-template) repository to
|
||||||
|
demonstrate how to install an external mode. This repository also includes all
|
||||||
|
the files required to create a new mode. You can use this repository as a
|
||||||
|
starting point to create your own mode.
|
||||||
|
|
||||||
|
As you can see in the Template mode
|
||||||
|
[code base](https://github.com/OHIF/mode-template), folders structure are
|
||||||
|
similar to the OHIF-maintained modes. Let's have more detailed look at the
|
||||||
|
structure of the `Template mode`:
|
||||||
|
|
||||||
|
- `src/index.js`: The most important file in any mode. This file is where modes'
|
||||||
|
authors hav defined the mode configurations such as:
|
||||||
|
- The layout and the panels for left and right side.
|
||||||
|
- LifeCycle hooks such as `onModeEnter` and `onModeExit`
|
||||||
|
- Other files/folders/configs: .webpack, LICENSE, README.md, babel.config.js
|
||||||
|
|
||||||
|
Note: It is highly recommended to use the `Template Mode` as a starting point
|
||||||
|
for your own mode. This way, you can easily reuse the necessary files and
|
||||||
|
folders.
|
||||||
|
|
||||||
|
#### Package.json
|
||||||
|
|
||||||
|
Mode name is defined in the `package.json` file. The `package.json` file is a
|
||||||
|
JSON file that defines the mode name, version, and dependencies. For instance
|
||||||
|
for the Template mode, the `package.json` file looks like this:
|
||||||
|
|
||||||
|
```js {2} title="templateMode/package.json"
|
||||||
|
{
|
||||||
|
"name": "@ohif/mode-template",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A template mode for installation demonstration",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note 1: We will use the `@ohif/mode-template` inside OHIF to let OHIF know about
|
||||||
|
existence of this mode.
|
||||||
|
|
||||||
|
Note 2: You don't need to use the `@ohif` scope for your modes/extensions. You
|
||||||
|
can use any scope you want or none at all.
|
||||||
|
|
||||||
|
Note 3: Although folders names are not important and the `package.json` file
|
||||||
|
contains the mode name, we recommend to use the same name for the folder name.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 2) Configuring OHIF
|
||||||
|
|
||||||
|
In order to install/register the mode, we must make changes to a few areas
|
||||||
|
inside OHIF. The OHIF should be updated using the following lines of code:
|
||||||
|
|
||||||
|
#### Viewer's package.json
|
||||||
|
|
||||||
|
```js {6} titl="platform/viewer/package.json"
|
||||||
|
/* ... */
|
||||||
|
"dependencies": {
|
||||||
|
/* ... */
|
||||||
|
"@ohif/i18n": "^0.52.8",
|
||||||
|
"@ohif/mode-longitudinal": "^0.0.1",
|
||||||
|
"@ohif/mode-template": "^0.0.1",
|
||||||
|
"@ohif/ui": "^2.0.0",
|
||||||
|
"@types/react": "^16.0.0",
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### App.jsx
|
||||||
|
|
||||||
|
```js {3} title="platform/viewer/src/App.jsx"
|
||||||
|
/* ... */
|
||||||
|
import '@ohif/mode-longitudinal';
|
||||||
|
import '@ohif/mode-template';
|
||||||
|
/* ... */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### appInit.js
|
||||||
|
|
||||||
|
```js {4} title="platform/viewer/src/appInit.js"
|
||||||
|
/* ... */
|
||||||
|
if (!appConfig.modes.length) {
|
||||||
|
appConfig.modes.push(window.longitudinalMode);
|
||||||
|
appConfig.modes.push(window.templateMode);
|
||||||
|
}
|
||||||
|
/* ... */
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we are assigning mode configuration objects from the `window` object;
|
||||||
|
therefore, we should use the reference to the name of the mode which were
|
||||||
|
defined in the last line of `src/index.js` file in mode configuration
|
||||||
|
|
||||||
|
```js {8} title="templateMode/src/index.js"
|
||||||
|
/* ... */
|
||||||
|
export default function mode({ modeConfiguration }) {
|
||||||
|
return {
|
||||||
|
/** */
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.templateMode = mode({});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Required Extensions for a Mode
|
||||||
|
|
||||||
|
Some modes require external extensions to be installed. For instance, the
|
||||||
|
`@ohif/mode-longitudinal` mode requires the `@ohif/cornerstone` extension to be
|
||||||
|
registered/installed in OHIF which is available in the OHIF-v3 repository.
|
||||||
|
|
||||||
|
How do we know that a mode requires an extension? (Until we have a more proper
|
||||||
|
dependency management for modes and extensions) you can take a look inside the
|
||||||
|
mode itself. Mode is a configuration file that defines the layout
|
||||||
|
(layoutModule), panels (panelModule), viewport (viewportModule), and tools
|
||||||
|
(commands) that are used to create an application at a given route. By looking
|
||||||
|
inside the mode configuration file (`index.js`), you can see which extensions
|
||||||
|
are required by the mode in the `extensions` property.
|
||||||
|
|
||||||
|
```js {12-16} title="platform/viewer/src/appInit.js"
|
||||||
|
export default function mode({ modeConfiguration }) {
|
||||||
|
return {
|
||||||
|
id: 'template',
|
||||||
|
displayName: 'Template Mode',
|
||||||
|
/** ... */
|
||||||
|
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
/** ... */
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extensions: [
|
||||||
|
'extension.template',
|
||||||
|
'org.ohif.default',
|
||||||
|
'org.ohif.cornerstone',
|
||||||
|
],
|
||||||
|
/** ... */
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As seen, our `Template Mode` requires the `org.ohif.default`,
|
||||||
|
`org.ohif.cornerstone` and `extension.template` extensions.
|
||||||
|
|
||||||
|
> Note: Currently extensions dependencies are not handled by OHIF from the
|
||||||
|
> `extensions` property. We will be adding this feature in the future.
|
||||||
|
|
||||||
|
In addition to the `extensions` property, the `mode` configuration object also
|
||||||
|
has the reference for each module that is used. For instance, the `index.js`
|
||||||
|
file in the `@ohif/mode-template` mode looks like this:
|
||||||
|
|
||||||
|
```js {10} title="clockMode/src/index.js"
|
||||||
|
// ....
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: "template",
|
||||||
|
layoutTemplate: ({ location, servicesManager }) => {
|
||||||
|
return {
|
||||||
|
id: ohif.layout,
|
||||||
|
props: {
|
||||||
|
leftPanels: [],
|
||||||
|
rightPanels: ["extension.template.panelModule.clockPanel"],
|
||||||
|
viewports: [
|
||||||
|
{
|
||||||
|
namespace: "org.ohif.cornerstone.viewportModule.cornerstone",
|
||||||
|
displaySetsToDisplay: ["org.ohif.default.sopClassHandlerModule.stack"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// ....
|
||||||
|
```
|
||||||
|
|
||||||
|
As seen, the right panel is defined as
|
||||||
|
`"extension.template.panelModule.clockPanel"` which means that the
|
||||||
|
`@ohif/mode-template` mode requires the `extension.template`. You can read more
|
||||||
|
about installing extensions in the
|
||||||
|
[Extension Installation](../extensions/installation.md)
|
||||||
|
|
||||||
|
> Additionally you can check the commands that the toolbar buttons will execute
|
||||||
|
> in the `toolbarButtons` and see if any of them requires an extension.
|
||||||
|
|
||||||
|
After you installed the extension, you need to run `yarn install` in the root
|
||||||
|
folder of the OHIF repository to install the registered extension and modes.
|
||||||
|
|
||||||
|
Running `yarn dev` will then start the application with the installed mode, by
|
||||||
|
navigating to the `/template` route (e.g.,
|
||||||
|
http://localhost:3000/template?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.2744.7002.150059977302243314164020079415)
|
||||||
|
you can see the clock panel. Congrats! 🎉
|
||||||
|
|
||||||
|

|
||||||
@ -15,6 +15,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
"start": "docusaurus start --port 8001",
|
"start": "docusaurus start --port 8001",
|
||||||
|
"dev": "docusaurus start --port 8001",
|
||||||
"docs": "docusaurus start --port 8001",
|
"docs": "docusaurus start --port 8001",
|
||||||
"build": "docusaurus build",
|
"build": "docusaurus build",
|
||||||
"swizzle": "docusaurus swizzle",
|
"swizzle": "docusaurus swizzle",
|
||||||
|
|||||||
@ -207,7 +207,6 @@ article header h3 {
|
|||||||
--ifm-button-background-color: #e8f7f7;
|
--ifm-button-background-color: #e8f7f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.admonition-icon svg {
|
.admonition-icon svg {
|
||||||
fill: #0151d9;
|
fill: #0151d9;
|
||||||
}
|
}
|
||||||
@ -225,6 +224,19 @@ article header h3 {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docusaurus-highlight-code-line {
|
||||||
|
background-color: rgb(206, 208, 211);
|
||||||
|
display: block;
|
||||||
|
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||||
|
padding: 0 var(--ifm-pre-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If you have a different syntax highlighting theme for dark mode. */
|
||||||
|
html[data-theme='dark'] .docusaurus-highlight-code-line {
|
||||||
|
/* Color which works with dark mode syntax highlighting theme */
|
||||||
|
background-color: rgb(100, 100, 100);
|
||||||
|
}
|
||||||
|
|
||||||
/* .DocSearch {
|
/* .DocSearch {
|
||||||
display: none;
|
display: none;
|
||||||
} */
|
} */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user