diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index 16663b355..40249f39b 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -34,7 +34,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => { const config = { mode: isProdBuild ? 'production' : 'development', - devtool: isProdBuild ? 'source-map' : 'eval', + devtool: isProdBuild ? 'source-map' : 'eval-source-map', entry: { app: `${SRC_DIR}/index.js`, }, diff --git a/extensions/cornerstone/src/OHIFCornerstoneViewport.js b/extensions/cornerstone/src/OHIFCornerstoneViewport.js index c866e1716..f1dd4197c 100644 --- a/extensions/cornerstone/src/OHIFCornerstoneViewport.js +++ b/extensions/cornerstone/src/OHIFCornerstoneViewport.js @@ -4,14 +4,21 @@ import cornerstone from 'cornerstone-core'; import cornerstoneTools from 'cornerstone-tools'; import CornerstoneViewport from 'react-cornerstone-viewport'; 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 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 BaseAnnotationTool = cornerstoneTools.importInternal( + 'base/BaseAnnotationTool' +); + const { StackManager } = OHIF.utils; function OHIFCornerstoneViewport({ @@ -45,6 +52,42 @@ function OHIFCornerstoneViewport({ 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(() => { isMounted.current = true; return () => { @@ -174,7 +217,9 @@ function OHIFCornerstoneViewport({ return (
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. + +![Template Extension](../../assets/img/template-extension-files.png) + +### 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! 🎉 diff --git a/platform/docs/docs/platform/modes/installation.md b/platform/docs/docs/platform/modes/installation.md new file mode 100644 index 000000000..74a627564 --- /dev/null +++ b/platform/docs/docs/platform/modes/installation.md @@ -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. + +![Template Mode](../../assets/img/template-mode-files.png) + +### 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! 🎉 + +![](../../assets/img/template-mode-ui.png) diff --git a/platform/docs/package.json b/platform/docs/package.json index 7f1dfdf40..f352b2a7b 100644 --- a/platform/docs/package.json +++ b/platform/docs/package.json @@ -15,6 +15,7 @@ "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start --port 8001", + "dev": "docusaurus start --port 8001", "docs": "docusaurus start --port 8001", "build": "docusaurus build", "swizzle": "docusaurus swizzle", diff --git a/platform/docs/src/css/custom.css b/platform/docs/src/css/custom.css index ded58bf18..a7517bbd5 100644 --- a/platform/docs/src/css/custom.css +++ b/platform/docs/src/css/custom.css @@ -207,7 +207,6 @@ article header h3 { --ifm-button-background-color: #e8f7f7; } - .admonition-icon svg { fill: #0151d9; } @@ -225,6 +224,19 @@ article header h3 { 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 { display: none; } */