diff --git a/plugins/ohif/monai-label/src/components/SettingsTable.js b/plugins/ohif/monai-label/src/components/SettingsTable.js index 12683b5ec..dfa2deb43 100644 --- a/plugins/ohif/monai-label/src/components/SettingsTable.js +++ b/plugins/ohif/monai-label/src/components/SettingsTable.js @@ -63,7 +63,7 @@ export default class SettingsTable extends Component { name="aiaaServerURL" type="text" defaultValue={this.state.url} - onBlur={this.onBlurSeverURL} + onChange={this.onBlurSeverURL} />   diff --git a/plugins/ohifv3/build.sh b/plugins/ohifv3/build.sh index 5c0e53aec..febe3ad31 100755 --- a/plugins/ohifv3/build.sh +++ b/plugins/ohifv3/build.sh @@ -27,7 +27,7 @@ cd ${my_dir} rm -rf Viewers git clone https://github.com/OHIF/Viewers.git cd Viewers -git checkout 33f125940863607f8dba82c71b27a43f35431dd5 +git checkout d8ef36ed24466988586e19b855d2bbb86f8c657a #cp -r ../extensions/monai-label extensions/ #cp -r ../modes/monai-label modes/monai-label @@ -45,6 +45,8 @@ cp ../config/monai_label.js platform/app/public/config/monai_label.js yarn config set workspaces-experimental true yarn install +yarn run cli list + APP_CONFIG=config/monai_label.js PUBLIC_URL=/ohif/ QUICK_BUILD=true yarn run build rm -rf ${install_dir} diff --git a/plugins/ohifv3/extensions.patch b/plugins/ohifv3/extensions.patch index 83d3be2bc..3275adf64 100644 --- a/plugins/ohifv3/extensions.patch +++ b/plugins/ohifv3/extensions.patch @@ -1,26 +1,26 @@ diff --git a/platform/app/pluginConfig.json b/platform/app/pluginConfig.json -index 08a42deb0..69e5aa005 100644 +index 06ae62e55..d0be6b022 100644 --- a/platform/app/pluginConfig.json +++ b/platform/app/pluginConfig.json -@@ -22,6 +22,11 @@ +@@ -57,6 +57,10 @@ "default": false, "version": "3.0.0" }, + { + "packageName": "@ohif/extension-monai-label", -+ "default": false, -+ "version": "0.0.1" ++ "version": "3.0.0" + }, { - "packageName": "@ohif/extension-dicom-microscopy", + "packageName": "@ohif/extension-cornerstone-dicom-rt", "default": false, -@@ -60,6 +65,9 @@ - { - "packageName": "@ohif/mode-segmentation" +@@ -84,6 +88,10 @@ + "default": false, + "version": "3.0.0" }, + { -+ "packageName": "@ohif/mode-monai-label" ++ "packageName": "@ohif/mode-monai-label", ++ "version": "3.0.0" + }, { - "packageName": "@ohif/mode-tmtv" - }, + "packageName": "@ohif/mode-basic-dev-mode", + "default": false, diff --git a/plugins/ohifv3/extensions/monai-label/.webpack/webpack.dev.js b/plugins/ohifv3/extensions/monai-label/.webpack/webpack.dev.js new file mode 100644 index 000000000..6aea859ca --- /dev/null +++ b/plugins/ohifv3/extensions/monai-label/.webpack/webpack.dev.js @@ -0,0 +1,12 @@ +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +const ENTRY = { + app: `${SRC_DIR}/index.tsx`, +}; + +module.exports = (env, argv) => { + return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); +}; diff --git a/plugins/ohifv3/extensions/monai-label/package.json b/plugins/ohifv3/extensions/monai-label/package.json index 753bfafff..e96570b1c 100644 --- a/plugins/ohifv3/extensions/monai-label/package.json +++ b/plugins/ohifv3/extensions/monai-label/package.json @@ -1,10 +1,11 @@ { "name": "@ohif/extension-monai-label", - "version": "0.0.1", + "version": "3.0.0", "description": "OHIFv3 extension for MONAI Label", "author": "OHIF,NVIDIA,KCL", "license": "MIT", - "main": "dist/umd/extension-monai-label/index.umd.js", + "main": "dist/ohif-extension-monai-label.umd.js", + "module": "src/index.tsx", "files": [ "dist/**", "public/**", @@ -14,7 +15,6 @@ "keywords": [ "ohif-extension" ], - "module": "src/index.tsx", "publishConfig": { "access": "public" }, @@ -24,41 +24,48 @@ "yarn": ">=1.18.0" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", - "dev:my-extension": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", - "build:package": "yarn run build", - "start": "yarn run dev" + "build:package-1": "yarn run build", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "^3.7.0-beta.80", - "@ohif/extension-default": "^3.7.0-beta.80", - "@ohif/extension-cornerstone": "^3.7.0-beta.80", - "@ohif/i18n": "^3.7.0-beta.80", + "@ohif/core": "3.10.0-beta.5", + "@ohif/extension-cornerstone": "3.10.0-beta.5", + "@ohif/extension-default": "3.10.0-beta.5", + "@ohif/i18n": "3.10.0-beta.5", "prop-types": "^15.6.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-i18next": "^12.2.2", - "react-router": "^6.8.1", - "react-router-dom": "^6.8.1" + "react-router": "^6.23.1", + "react-router-dom": "^6.23.1" }, "dependencies": { "@babel/runtime": "^7.20.13", + "@cornerstonejs/adapters": "^2.2.3", + "@cornerstonejs/core": "^2.2.3", + "@kitware/vtk.js": "32.1.0", + "react-color": "^2.19.3", "md5.js": "^1.3.5", "axios": "^0.21.1", "arraybuffer-concat": "^0.0.1", "ndarray": "^1.0.19", "nrrd-js": "^0.2.1", "pako": "^2.0.3", - "react-color": "^2.19.3", "bootstrap": "^5.0.2", "react-select": "^4.3.1", - "chroma-js": "^2.1.2", - "itk": "^14.1.1" + "chroma-js": "^2.1.2" }, "devDependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/tools": "^1.16.4", + "@cornerstonejs/adapters": "^2.2.3", + "@cornerstonejs/core": "^2.2.3", + "@cornerstonejs/tools": "^2.2.3", "react-color": "^2.19.3" } } diff --git a/plugins/ohifv3/extensions/monai-label/src/components/ModelSelector.css b/plugins/ohifv3/extensions/monai-label/src/components/ModelSelector.css index 153d01263..f09f184e8 100644 --- a/plugins/ohifv3/extensions/monai-label/src/components/ModelSelector.css +++ b/plugins/ohifv3/extensions/monai-label/src/components/ModelSelector.css @@ -1,3 +1,16 @@ +/* +Copyright (c) MONAI Consortium +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + .modelSelector .table { border-collapse: collapse; border: 0 solid; @@ -8,23 +21,26 @@ } .modelSelector .selectBox { width: 100%; + color: #000; + font-size: smaller; + height: 18px; } .modelSelector .actionButton { border: 2px solid #000; border-radius: 15px; - background-color: #add8e6; + background-color: #00a4d9; color: var(--ui-gray-dark); - line-height: 25px; + line-height: 20px; padding: 0 15px; outline: none; cursor: pointer; } .modelSelector .actionButton:hover, .modelSelector .actionButton:active { - background-color: var(--ui-sky-blue); + background-color: #00a4d9; } .modelSelector .actionButton:disabled { - background-color: var(--ui-sky-blue); + background-color: #00a4d9; } .modelSelector .actionButton svg { margin-right: 4px; diff --git a/plugins/ohifv3/extensions/monai-label/src/components/ModelSelector.tsx b/plugins/ohifv3/extensions/monai-label/src/components/ModelSelector.tsx index b70475148..ed0ca1e63 100644 --- a/plugins/ohifv3/extensions/monai-label/src/components/ModelSelector.tsx +++ b/plugins/ohifv3/extensions/monai-label/src/components/ModelSelector.tsx @@ -1,3 +1,16 @@ +/* +Copyright (c) MONAI Consortium +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import React, { Component } from 'react'; import PropTypes from 'prop-types'; @@ -12,7 +25,6 @@ export default class ModelSelector extends Component { usage: PropTypes.any, onClick: PropTypes.func, onSelectModel: PropTypes.func, - scribblesSelector: PropTypes.any, }; constructor(props) { @@ -21,8 +33,8 @@ export default class ModelSelector extends Component { const currentModel = props.currentModel ? props.currentModel : props.models.length > 0 - ? props.models[0] - : ''; + ? props.models[0] + : ''; this.state = { models: props.models, currentModel: currentModel, @@ -83,7 +95,7 @@ export default class ModelSelector extends Component {   - -   - + { - this.setState({ strategy: evt.target.value }); - }; - - onSegmentSelected = (id) => { - this.setState({ segmentId: id }); - }; - - onSegmentDeleted = (id) => { - this.setState({ segmentId: null }); - }; - onClickNextSample = async () => { const nid = this.notification.show({ title: 'MONAI Label', @@ -109,79 +95,20 @@ export default class OptionTable extends BaseTab { } }; - onClickSubmitLabel = async () => { - const labelmaps3D = cornerstone.cache.getVolume('1'); - - if (!labelmaps3D) { - console.info('LabelMap3D is empty.. so zero segments'); - return; - } - - this.notification.show({ - title: 'MONAI Label', - message: 'Preparing the labelmap to submit', - type: 'info', - duration: 5000, - }); - - const labelNames = this.props.info.labels; - const segments = []; - for (let i = 0; i < labelNames.length; i++) { - if (labelNames[i] === 'background') { - console.debug('Ignore Background...'); - continue; - } - const segment = createSegmentMetadata(labelNames[i], i, ''); - segments.push(segment); - } - - const params = { label_info: segments }; - - const image = this.props.viewConstants.SeriesInstanceUID; - - const label = new Blob([labelmaps3D.scalarData], { - type: 'application/octet-stream', - }); - - const response = await this.props.client().save_label(image, label, params); - - if (response.status !== 200) { - this.notification.show({ - title: 'MONAI Label', - message: 'Failed to save label', - type: 'error', - duration: 5000, - }); - } else { - this.notification.show({ - title: 'MONAI Label', - message: 'Label submitted to server', - type: 'success', - duration: 2000, - }); - } - }; - async componentDidMount() { const training = await this.props.client().is_train_running(); this.setState({ training: training }); } render() { - /* const segmentId = this.state.segmentId - ? this.state.segmentId - : getFirstSegmentId(this.props.viewConstants.element); */ - - const segmentId = this.state.segmentId; - - const ds = this.props.info.datastore; + const ds = this.props.info.data.datastore; const completed = ds && ds.completed ? ds.completed : 0; const total = ds && ds.total ? ds.total : 1; const activelearning = Math.round(100 * (completed / total)) + '%'; const activelearningTip = completed + '/' + total + ' samples annotated'; - const ts = this.props.info.train_stats - ? Object.values(this.props.info.train_stats)[0] + const ts = this.props.info.data.train_stats + ? Object.values(this.props.info.data.train_stats)[0] : null; const epochs = ts ? (ts.total_time ? 0 : ts.epoch ? ts.epoch : 1) : 0; @@ -198,8 +125,8 @@ export default class OptionTable extends BaseTab { ? accuracy + ' is current best metric' : 'not determined'; - const strategies = this.props.info.strategies - ? this.props.info.strategies + const strategies = this.props.info.data.strategies + ? this.props.info.data.strategies : {}; return ( @@ -209,8 +136,7 @@ export default class OptionTable extends BaseTab { type="checkbox" id={this.tabId} name="activelearning" - value="activelearning" - defaultChecked + defaultValue="activelearning" />
+ + +
+ + {/*

*/} + {/*  */} + {/* Auto Run on every*/} + {/* click*/} + {/*

*/} +
+

Select an anatomy from the segments menu below.

+

To guide the inference, add foreground clicks:

+ +
this.clearPoints()} + > + Clear Points + + {' '} + |{' '} + + this.clearAllPoints()} + > + Clear All Points + + +
+ } + /> +
+
+

Available Organ(s):

+
+
+ + + this.onChangeLabel('background')} + > + + + + {labels + .filter((l) => l !== 'background') + .map((label) => ( + this.onChangeLabel(label)} + > + + + + ))} + +
+ {/* Content for the "background" entry */} + + background
+ + {label}
+
+
+
+ + ); + } +} diff --git a/plugins/ohifv3/extensions/monai-label/src/components/actions/SmartEdit.tsx b/plugins/ohifv3/extensions/monai-label/src/components/actions/SmartEdit.tsx deleted file mode 100644 index a2774c48a..000000000 --- a/plugins/ohifv3/extensions/monai-label/src/components/actions/SmartEdit.tsx +++ /dev/null @@ -1,327 +0,0 @@ -/* -Copyright (c) MONAI Consortium -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import ModelSelector from '../ModelSelector'; -import BaseTab from './BaseTab'; -import * as cornerstoneTools from '@cornerstonejs/tools'; -import { vec3 } from 'gl-matrix'; -/* import { getFirstSegmentId } from '../../utils/SegmentationUtils'; */ - -export default class SmartEdit extends BaseTab { - constructor(props) { - super(props); - - this.modelSelector = React.createRef(); - - this.state = { - segmentId: null, - currentPoint: null, - deepgrowPoints: new Map(), - currentEvent: null, - currentModel: null, - }; - } - - componentDidMount() { - const { segmentationService, toolGroupService, viewportGridService } = - this.props.servicesManager.services; - - const added = segmentationService.EVENTS.SEGMENTATION_ADDED; - const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED; - const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED; - const subscriptions = []; - - [added, updated, removed].forEach((evt) => { - const { unsubscribe } = segmentationService.subscribe(evt, () => { - const segmentations = segmentationService.getSegmentations(); - - if (!segmentations?.length) { - return; - } - - // get the first segmentation Todo: fix this to be active - const segmentation = segmentations[0]; - const { segments, activeSegmentIndex } = segmentation; - - const selectedSegment = segments[activeSegmentIndex]; - - const color = selectedSegment.color; - - // get the active viewport toolGroup - const { viewports, activeViewportId } = viewportGridService.getState(); - const viewport = viewports.get(activeViewportId); - const { viewportOptions } = viewport; - const toolGroupId = viewportOptions.toolGroupId; - - toolGroupService.setToolConfiguration(toolGroupId, 'ProbeMONAILabel', { - customColor: `rgb(${color[0]}, ${color[1]}, ${color[2]})`, - }); - }); - subscriptions.push(unsubscribe); - }); - - this.unsubscribe = () => { - subscriptions.forEach((unsubscribe) => unsubscribe()); - }; - } - - componentWillUnmount() { - this.unsubscribe(); - } - - onSelectModel = (model) => { - this.setState({ currentModel: model }); - }; - - onDeepgrow = async () => { - const { - segmentationService, - cornerstoneViewportService, - viewportGridService, - } = this.props.servicesManager.services; - const { info, viewConstants } = this.props; - const image = viewConstants.SeriesInstanceUID; - const model = this.modelSelector.current.currentModel(); - - const activeSegment = segmentationService.getActiveSegment(); - const segmentId = activeSegment.label; - - if (segmentId && !this.state.segmentId) { - this.onSegmentSelected(segmentId); - } - - const is3D = info.models[model].dimension === 3; - if (!segmentId) { - this.notification.show({ - title: 'MONAI Label', - message: 'Please create/select a label first', - type: 'warning', - }); - return; - } - - /* const points = this.state.deepgrowPoints.get(segmentId); */ - - // Getting the clicks in IJK format - - const { activeViewportId } = viewportGridService.getState(); - const viewPort = - cornerstoneViewportService.getCornerstoneViewport(activeViewportId); - - const pts = cornerstoneTools.annotation.state.getAnnotations( - 'ProbeMONAILabel', - viewPort.element - ); - - const pointsWorld = pts.map((pt) => pt.data.handles.points[0]); - const { imageData } = viewPort.getImageData(); - const ijk = vec3.fromValues(0, 0, 0); - - // Rounding is not working - /* const pointsIJK = pointsWorld.map((world) => - Math.round(imageData.worldToIndex(world, ijk)) - ); */ - - const pointsIJK = pointsWorld.map((world) => - imageData.worldToIndex(world, ijk) - ); - - /* const roundPointsIJK = pointsIJK.map(ind => Math.round(ind)) */ - - this.state.deepgrowPoints.set(segmentId, pointsIJK); - - // when changing label, delete previous? or just keep track of all provided clicks per labels - const points = this.state.deepgrowPoints.get(segmentId); - - // Error as ctrlKey is part of the points? - - /* if (!points.length) { - return; - } - - const currentPoint = points[points.length - 1]; */ - - const config = this.props.onOptionsConfig(); - - const labels = info.models[model].labels; - - const params = - config && config.infer && config.infer[model] ? config.infer[model] : {}; - - // block the cursor while waiting for MONAI Label response? - - for (const l in labels) { - if (l === segmentId) { - console.log('This is the segmentId'); - const p = []; - for (let i = 0; i < pointsIJK.length; i++) { - p.push(Array.from(pointsIJK[i])); - console.log(p[i]); - } - params[l] = p; - continue; - } - console.log(l); - params[l] = []; - } - - const response = await this.props.client().infer(model, image, params); - - if (response.status !== 200) { - this.notification.show({ - title: 'MONAI Label', - message: 'Failed to Run Deepgrow', - type: 'error', - duration: 3000, - }); - } else { - await this.props.updateView( - response, - labels, - 'override', - is3D ? -1 : currentPoint.z - ); - } - - // Remove the segmentation and create a new one with a differen index - /* debugger; - this.props.servicesManager.services.segmentationService.remove('1') */ - }; - - getPointData = (evt) => { - const { x, y, imageId } = evt.detail; - const z = this.props.viewConstants.imageIdsToIndex.get(imageId); - - console.debug('X: ' + x + '; Y: ' + y + '; Z: ' + z); - return { x, y, z, data: evt.detail, imageId }; - }; - - onSegmentDeleted = (id) => { - this.clearPoints(id); - this.setState({ segmentId: null }); - }; - onSegmentSelected = (id) => { - this.initPoints(id); - this.setState({ segmentId: id }); - }; - - initPoints = (id) => { - console.log('Initializing points'); - }; - - clearPoints = (id) => { - cornerstoneTools.annotation.state - .getAnnotationManager() - .removeAllAnnotations(); - this.props.servicesManager.services.cornerstoneViewportService - .getRenderingEngine() - .render(); - console.log('Clearing all points'); - }; - - onSelectActionTab = (evt) => { - this.props.onSelectActionTab(evt.currentTarget.value); - }; - - onEnterActionTab = () => { - this.props.commandsManager.runCommand('setToolActive', { - toolName: 'ProbeMONAILabel', - }); - console.info('Here we activate the probe'); - }; - - onLeaveActionTab = () => { - this.props.commandsManager.runCommand('setToolDisable', { - toolName: 'ProbeMONAILabel', - }); - console.info('Here we deactivate the probe'); - /* cornerstoneTools.setToolDisabled('DeepgrowProbe', {}); - this.removeEventListeners(); */ - }; - - addEventListeners = (eventName, handler) => { - this.removeEventListeners(); - - const { element } = this.props.viewConstants; - element.addEventListener(eventName, handler); - this.setState({ currentEvent: { name: eventName, handler: handler } }); - }; - - removeEventListeners = () => { - if (!this.state.currentEvent) { - return; - } - - const { element } = this.props.viewConstants; - const { currentEvent } = this.state; - - element.removeEventListener(currentEvent.name, currentEvent.handler); - this.setState({ currentEvent: null }); - }; - - render() { - const models = []; - if (this.props.info && this.props.info.models) { - for (const [name, model] of Object.entries(this.props.info.models)) { - if ( - model.type === 'deepgrow' || - model.type === 'deepedit' || - model.type === 'vista' - ) { - models.push(name); - } - } - } - - return ( -
- - -
- -

- Create a label and annotate any organ. -

- this.clearPoints()} - > - Clear Points - -
- } - /> -
- - ); - } -} diff --git a/plugins/ohifv3/extensions/monai-label/src/components/callInputDialog.tsx b/plugins/ohifv3/extensions/monai-label/src/components/callInputDialog.tsx deleted file mode 100644 index c7db40fdb..000000000 --- a/plugins/ohifv3/extensions/monai-label/src/components/callInputDialog.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { Input, Dialog, ButtonEnums } from '@ohif/ui'; - -function callInputDialog(uiDialogService, label, callback) { - const dialogId = 'enter-segment-label'; - - const onSubmitHandler = ({ action, value }) => { - switch (action.id) { - case 'save': - callback(value.label, action.id); - break; - case 'cancel': - callback('', action.id); - break; - } - uiDialogService.dismiss({ id: dialogId }); - }; - - if (uiDialogService) { - uiDialogService.create({ - id: dialogId, - centralize: true, - isDraggable: false, - showOverlay: true, - content: Dialog, - contentProps: { - title: 'Segment', - value: { label }, - noCloseButton: true, - onClose: () => uiDialogService.dismiss({ id: dialogId }), - actions: [ - { id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary }, - { id: 'save', text: 'Confirm', type: ButtonEnums.type.primary }, - ], - onSubmit: onSubmitHandler, - body: ({ value, setValue }) => { - return ( - { - event.persist(); - setValue((value) => ({ ...value, label: event.target.value })); - }} - onKeyPress={(event) => { - if (event.key === 'Enter') { - onSubmitHandler({ value, action: { id: 'save' } }); - } - }} - /> - ); - }, - }, - }); - } -} - -export default callInputDialog; diff --git a/plugins/ohifv3/extensions/monai-label/src/components/colorPickerDialog.css b/plugins/ohifv3/extensions/monai-label/src/components/colorPickerDialog.css deleted file mode 100644 index 1c6bb2067..000000000 --- a/plugins/ohifv3/extensions/monai-label/src/components/colorPickerDialog.css +++ /dev/null @@ -1,3 +0,0 @@ -.chrome-picker { - background: #090c29 !important; -} diff --git a/plugins/ohifv3/extensions/monai-label/src/components/colorPickerDialog.tsx b/plugins/ohifv3/extensions/monai-label/src/components/colorPickerDialog.tsx deleted file mode 100644 index 1337ee103..000000000 --- a/plugins/ohifv3/extensions/monai-label/src/components/colorPickerDialog.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import { Dialog } from '@ohif/ui'; -import { ChromePicker } from 'react-color'; - -import './colorPickerDialog.css'; - -function callColorPickerDialog(uiDialogService, rgbaColor, callback) { - const dialogId = 'pick-color'; - - const onSubmitHandler = ({ action, value }) => { - switch (action.id) { - case 'save': - callback(value.rgbaColor, action.id); - break; - case 'cancel': - callback('', action.id); - break; - } - uiDialogService.dismiss({ id: dialogId }); - }; - - if (uiDialogService) { - uiDialogService.create({ - id: dialogId, - centralize: true, - isDraggable: false, - showOverlay: true, - content: Dialog, - contentProps: { - title: 'Segment Color', - value: { rgbaColor }, - noCloseButton: true, - onClose: () => uiDialogService.dismiss({ id: dialogId }), - actions: [ - { id: 'cancel', text: 'Cancel', type: 'primary' }, - { id: 'save', text: 'Save', type: 'secondary' }, - ], - onSubmit: onSubmitHandler, - body: ({ value, setValue }) => { - const handleChange = (color) => { - setValue({ rgbaColor: color.rgb }); - }; - - return ( - - ); - }, - }, - }); - } -} - -export default callColorPickerDialog; diff --git a/plugins/ohifv3/extensions/monai-label/src/components/w3.css b/plugins/ohifv3/extensions/monai-label/src/components/w3.css deleted file mode 100644 index 3e78e3b2c..000000000 --- a/plugins/ohifv3/extensions/monai-label/src/components/w3.css +++ /dev/null @@ -1,235 +0,0 @@ -/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */ -html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} -/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ -html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} -article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item} -audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline} -audio:not([controls]){display:none;height:0}[hidden],template{display:none} -a{background-color:transparent}a:active,a:hover{outline-width:0} -abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted} -b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000} -small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} -sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none} -code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible} -button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold} -button,input{overflow:visible}button,select{text-transform:none} -button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button} -button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0} -button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText} -fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} -legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto} -[type=checkbox],[type=radio]{padding:0} -[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto} -[type=search]{-webkit-appearance:textfield;outline-offset:-2px} -[type=search]::-webkit-search-decoration{-webkit-appearance:none} -::-webkit-file-upload-button{-webkit-appearance:button;font:inherit} -/* End extract */ -html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden} -h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px} -.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace} -h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px} -hr{border:0;border-top:1px solid #eee;margin:20px 0} -.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit} -.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} -.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} -.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} -.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} -.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} -.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} -.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap} -.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} -.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} -.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none} -.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none} -.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%} -.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none} -.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block} -.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s} -.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%} -.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc} -.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} -.w3-dropdown-hover:hover .w3-dropdown-content{display:block} -.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000} -.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000} -.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1} -.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px} -.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} -.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%} -.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%} -.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px} -.w3-main,#main{transition:margin-left .4s} -.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} -.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px} -.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto} -.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0} -.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left} -.w3-bar .w3-button{white-space:normal} -.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0} -.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%} -.w3-responsive{display:block;overflow-x:auto} -.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before, -.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both} -.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} -.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%} -.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%} -.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%} -@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%} -.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%} -.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}} -@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%} -.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%} -.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}} -.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px} -.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px} -.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell} -.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom} -.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} -@media (max-width:1205px){.w3-auto{max-width:95%}} -@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} -.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative} -.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center} -.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}} -@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}} -@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}} -@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} -@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}} -.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} -.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} -.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} -.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} -.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)} -.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)} -.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)} -.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} -.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} -.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none} -.w3-display-position{position:absolute} -.w3-circle{border-radius:50%} -.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} -.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} -.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} -.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} -.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} -.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} -.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)} -.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)} -.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} -.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} -.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}} -.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} -.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} -.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} -.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} -.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} -.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} -.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1} -.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75} -.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)} -.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)} -.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)} -.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important} -.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important} -.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important} -.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important} -.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} -.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} -.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} -.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} -.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important} -.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} -.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} -.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important} -.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} -.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} -.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} -.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important} -.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important} -.w3-left{float:left!important}.w3-right{float:right!important} -.w3-button:hover{color:#000!important;background-color:#ccc!important} -.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} -.w3-hover-none:hover{box-shadow:none!important} -/* Colors */ -.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} -.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} -.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} -.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} -.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} -.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} -.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important} -.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} -.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} -.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} -.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} -.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} -.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} -.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} -.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} -.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} -.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} -.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} -.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} -.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} -.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} -.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} -.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} -.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} -.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} -.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} -.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} -.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} -.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} -.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important} -.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} -.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} -.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} -.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} -.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} -.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} -.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important} -.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} -.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} -.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} -.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} -.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} -.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} -.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} -.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} -.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} -.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} -.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} -.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} -.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} -.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} -.w3-text-white,.w3-hover-text-white:hover{color:#fff!important} -.w3-text-black,.w3-hover-text-black:hover{color:#000!important} -.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important} -.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important} -.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important} -.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} -.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} -.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} -.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} -.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} -.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} -.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important} -.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} -.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} -.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} -.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} -.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} -.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} -.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} -.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} -.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} -.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} -.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} -.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} -.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} -.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} -.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} -.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} -.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important} -.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important} -.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important} -.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} -.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} diff --git a/plugins/ohifv3/extensions/monai-label/src/getCommandsModule.ts b/plugins/ohifv3/extensions/monai-label/src/getCommandsModule.ts index f61ec9bc0..c1f72915b 100644 --- a/plugins/ohifv3/extensions/monai-label/src/getCommandsModule.ts +++ b/plugins/ohifv3/extensions/monai-label/src/getCommandsModule.ts @@ -1,22 +1,5 @@ -import { ServicesManager, CommandsManager, ExtensionManager } from '@ohif/core'; -import { Enums } from '@cornerstonejs/tools'; - -export default function getCommandsModule({ - servicesManager, - commandsManager, - extensionManager, -}: { - servicesManager: ServicesManager; - commandsManager: CommandsManager; - extensionManager: ExtensionManager; -}) { - const { - viewportGridService, - toolGroupService, - cineService, - toolbarService, - uiNotificationService, - } = servicesManager.services; +export default function getCommandsModule({ servicesManager }) { + const { uiNotificationService } = servicesManager.services; const actions = { setToolActive: ({ toolName }) => { @@ -30,9 +13,6 @@ export default function getCommandsModule({ }; const definitions = { - /* setToolActive: { - commandFn: actions.setToolActive, - }, */ }; return { diff --git a/plugins/ohifv3/extensions/monai-label/src/index.tsx b/plugins/ohifv3/extensions/monai-label/src/index.tsx index 6ff0b7294..37578c659 100644 --- a/plugins/ohifv3/extensions/monai-label/src/index.tsx +++ b/plugins/ohifv3/extensions/monai-label/src/index.tsx @@ -5,52 +5,7 @@ import preRegistration from './init'; export default { id, - preRegistration, - getPanelModule, - - getViewportModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, - - getToolbarModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, - - getLayoutTemplateModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, - - getSopClassHandlerModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, - - getHangingProtocolModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, - getCommandsModule, - - getContextModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, - - getDataSourcesModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, }; diff --git a/plugins/ohifv3/extensions/monai-label/src/services/MonaiLabelClient.js b/plugins/ohifv3/extensions/monai-label/src/services/MonaiLabelClient.js index 45ebe6cfa..84e18b275 100644 --- a/plugins/ohifv3/extensions/monai-label/src/services/MonaiLabelClient.js +++ b/plugins/ohifv3/extensions/monai-label/src/services/MonaiLabelClient.js @@ -1,3 +1,16 @@ +/* +Copyright (c) MONAI Consortium +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import axios from 'axios'; export default class MonaiLabelClient { @@ -10,28 +23,17 @@ export default class MonaiLabelClient { return await MonaiLabelClient.api_get(url.toString()); } - async segmentation(model, image, params = {}, label = null) { - // label is used to send label volumes, e.g. scribbles, - // that are to be used during segmentation - return this.infer(model, image, params, label); - } - - async deepgrow(model, image, foreground, background, params = {}) { - params['foreground'] = foreground; - params['background'] = background; - return this.infer(model, image, params); - } + async infer(model, image, params, label = null, result_extension = '.nrrd', output='image') { + console.log('Running Infer for: ', { model, image, params, result_extension, output }); - async infer(model, image, params, label = null, result_extension = '.nrrd') { let url = new URL('infer/' + encodeURIComponent(model), this.server_url); url.searchParams.append('image', image); - url.searchParams.append('output', 'all'); - // url.searchParams.append('output', 'image'); + url.searchParams.append('output', output); url = url.toString(); if (result_extension) { params.result_extension = result_extension; - params.result_dtype = 'uint16'; + params.result_dtype = 'uint8'; params.result_compress = false; } diff --git a/plugins/ohifv3/extensions/monai-label/src/tools/ProbeMONAILabelTool.ts b/plugins/ohifv3/extensions/monai-label/src/tools/ProbeMONAILabelTool.ts index 8997271da..0d0f36a48 100644 --- a/plugins/ohifv3/extensions/monai-label/src/tools/ProbeMONAILabelTool.ts +++ b/plugins/ohifv3/extensions/monai-label/src/tools/ProbeMONAILabelTool.ts @@ -1,4 +1,16 @@ -import { Types, metaData, utilities as csUtils } from '@cornerstonejs/core'; +/* +Copyright (c) MONAI Consortium +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import { ProbeTool, annotation, drawing } from '@cornerstonejs/tools'; const { getAnnotations } = annotation.state; diff --git a/plugins/ohifv3/extensions/monai-label/src/utils/GenericAnatomyColors.js b/plugins/ohifv3/extensions/monai-label/src/utils/GenericAnatomyColors.js index 666b3e552..f2d454a3d 100644 --- a/plugins/ohifv3/extensions/monai-label/src/utils/GenericAnatomyColors.js +++ b/plugins/ohifv3/extensions/monai-label/src/utils/GenericAnatomyColors.js @@ -1,3 +1,16 @@ +/* +Copyright (c) MONAI Consortium +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + function componentToHex(c) { const hex = c.toString(16); return hex.length === 1 ? '0' + hex : hex; @@ -407,6 +420,8 @@ export const GenericAnatomyColors = [ { label: 'thorax', value: rgbToHex(177, 122, 101) }, { label: 'trachea', value: rgbToHex(182, 228, 255) }, { label: 'bronchi', value: rgbToHex(175, 216, 244) }, + { label: 'lung', value: rgbToHex(197, 165, 145) }, + { label: 'lung tumor', value: rgbToHex(144, 238, 144) }, { label: 'right lung', value: rgbToHex(197, 165, 145) }, { label: 'left lung', value: rgbToHex(197, 165, 145) }, { label: 'superior lobe of right lung', value: rgbToHex(172, 138, 115) }, @@ -452,11 +467,15 @@ export const GenericAnatomyColors = [ { label: 'colon', value: rgbToHex(204, 168, 143) }, { label: 'anus', value: rgbToHex(255, 224, 199) }, { label: 'liver', value: rgbToHex(221, 130, 101) }, + { label: 'liver tumor', value: rgbToHex(144, 238, 144) }, { label: 'biliary tree', value: rgbToHex(0, 145, 30) }, { label: 'gallbladder', value: rgbToHex(139, 150, 98) }, { label: 'pancreas', value: rgbToHex(249, 180, 111) }, + { label: 'pancreatic tumor', value: rgbToHex(144, 238, 144) }, { label: 'spleen', value: rgbToHex(157, 108, 162) }, { label: 'urinary system', value: rgbToHex(203, 136, 116) }, + { label: 'kidney', value: rgbToHex(185, 102, 83) }, + { label: 'kidney tumor', value: rgbToHex(144, 238, 144) }, { label: 'right kidney', value: rgbToHex(185, 102, 83) }, { label: 'left kidney', value: rgbToHex(185, 102, 83) }, { label: 'right ureter', value: rgbToHex(247, 182, 164) }, diff --git a/plugins/ohifv3/extensions/monai-label/src/utils/GenericUtils.js b/plugins/ohifv3/extensions/monai-label/src/utils/GenericUtils.js index b28881df7..b9a52a739 100644 --- a/plugins/ohifv3/extensions/monai-label/src/utils/GenericUtils.js +++ b/plugins/ohifv3/extensions/monai-label/src/utils/GenericUtils.js @@ -1,3 +1,16 @@ +/* +Copyright (c) MONAI Consortium +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import { GenericAnatomyColors, GenericNames } from './GenericAnatomyColors'; function getRandomInt(min, max) { @@ -6,11 +19,14 @@ function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive } -function randomRGB() { +function randomRGB(toHex = false) { const o = Math.round, r = Math.random, s = 255; - return rgbToHex(o(r() * s), o(r() * s), o(r() * s)); + const x = o(r() * s); + const y = o(r() * s); + const z = o(r() * s); + return toHex ? rgbToHex(x, y, z) : { r: x, g: y, b: z }; } function randomName() { @@ -37,16 +53,43 @@ function hexToRgb(hex) { : null; } -function getLabelColor(label, rgb = true) { +function fixedRGBForLabel(str, toHex = false) { + const r = generateIntForString(str); + const x = (2 * r) % 256; + const y = (3 * r) % 256; + const z = (5 * r) % 256; + return toHex ? rgbToHex(x, y, z) : { r: x, g: y, b: z }; +} + +function generateIntForString(str) { + let hash = str.length * 4; + for (let i = 0; i < str.length; ++i) { + hash += str.charCodeAt(i); + } + return hash; +} + +function getLabelColor(label, rgb = true, random = true) { const name = label.toLowerCase(); for (const i of GenericAnatomyColors) { if (i.label === name) { return rgb ? hexToRgb(i.value) : i.value; } } + if (random) { + return fixedRGBForLabel(label, !rgb); + } return null; } +function hideNotification(nid, notification) { + if (!nid) { + window.snackbar.hideAll(); + } else { + notification.hide(nid); + } +} + export class CookieUtils { static setCookie(name, value, exp_y, exp_m, exp_d, path, domain, secure) { let cookie_string = name + '=' + escape(value); @@ -70,6 +113,7 @@ export class CookieUtils { let results = document.cookie.match( '(^|;) ?' + cookie_name + '=([^;]*)(;|$)' ); + // console.log('Cookie results: ', results); if (results) { return unescape(results[2]); } else { @@ -79,8 +123,8 @@ export class CookieUtils { static getCookieString(name, defaultVal = '') { const val = CookieUtils.getCookie(name); - console.debug(name + ' = ' + val + ' (default: ' + defaultVal + ' )'); - if (!val) { + // console.log(name + ' = ' + val + ' (default: ' + defaultVal + ' )'); + if (!val || val === 'undefined' || val === 'null' || val === '') { CookieUtils.setCookie(name, defaultVal); return defaultVal; } @@ -88,12 +132,12 @@ export class CookieUtils { } static getCookieBool(name, defaultVal = false) { - const val = CookieUtils.getCookie(name, defaultVal); + const val = CookieUtils.getCookieString(name, defaultVal); return !!JSON.parse(String(val).toLowerCase()); } static getCookieNumber(name, defaultVal = 0) { - const val = CookieUtils.getCookie(name, defaultVal); + const val = CookieUtils.getCookieString(name, defaultVal); return Number(val); } } @@ -105,4 +149,5 @@ export { rgbToHex, hexToRgb, getLabelColor, + hideNotification, }; diff --git a/plugins/ohifv3/extensions/monai-label/src/utils/SegUtils.js b/plugins/ohifv3/extensions/monai-label/src/utils/SegUtils.js new file mode 100644 index 000000000..3f58f792a --- /dev/null +++ b/plugins/ohifv3/extensions/monai-label/src/utils/SegUtils.js @@ -0,0 +1,34 @@ +/* +Copyright (c) MONAI Consortium +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +function currentSegmentsInfo(segmentationService) { + const info = {}; + const indices = new Set(); + + const segmentations = segmentationService.getSegmentations(); + if (segmentations && Object.keys(segmentations).length > 0) { + const segmentation = segmentations['0']; + const { segments } = segmentation.config; + for (const segmentIndex of Object.keys(segments)) { + const segment = segments[segmentIndex]; + info[segment.label] = { + segmentIndex: segment.segmentIndex, + color: segment.color, + }; + indices.add(segment.segmentIndex); + } + } + return { info, indices }; +} + +export { currentSegmentsInfo }; diff --git a/plugins/ohifv3/extensions/monai-label/src/utils/SegmentationReader.js b/plugins/ohifv3/extensions/monai-label/src/utils/SegmentationReader.js index ca117754a..5426f89bc 100644 --- a/plugins/ohifv3/extensions/monai-label/src/utils/SegmentationReader.js +++ b/plugins/ohifv3/extensions/monai-label/src/utils/SegmentationReader.js @@ -13,13 +13,6 @@ limitations under the License. import nrrd from 'nrrd-js'; import pako from 'pako'; -import readImageArrayBuffer from 'itk/readImageArrayBuffer'; -import writeArrayBuffer from 'itk/writeArrayBuffer'; -import config from 'itk/itkConfig'; - -const pkgJSON = require('../../package.json'); -const itkVersion = pkgJSON.dependencies.itk.substring(1); -config.itkModulesPath = 'https://unpkg.com/itk@' + itkVersion; // HACK to use ITK from CDN export default class SegmentationReader { static parseNrrdData(data) { @@ -44,55 +37,4 @@ export default class SegmentationReader { image, }; } - - static saveFile(blob, filename) { - if (window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveOrOpenBlob(blob, filename); - } else { - const a = document.createElement('a'); - document.body.appendChild(a); - - const url = window.URL.createObjectURL(blob); - a.href = url; - a.download = filename; - a.click(); - - setTimeout(() => { - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - }, 0); - } - } - - // GZIP write not supported by nrrd-js (so use ITK save with compressed = true) - static serializeNrrdCompressed(header, image, filename) { - const nrrdBuffer = SegmentationReader.serializeNrrd(header, image); - - const reader = readImageArrayBuffer(null, nrrdBuffer, 'temp.nrrd'); - reader.then(function (response) { - const writer = writeArrayBuffer( - response.webWorker, - true, - response.image, - filename - ); - writer.then(function (response) { - SegmentationReader.saveFile(new Blob([response.arrayBuffer]), filename); - console.debug('File downloaded: ' + filename); - }); - }); - } - - static serializeNrrd(header, image, filename) { - let nrrdOrg = Object.assign({}, header); - nrrdOrg.buffer = image; - nrrdOrg.data = new Uint16Array(image); - - const nrrdBuffer = nrrd.serialize(nrrdOrg); - if (filename) { - SegmentationReader.saveFile(new Blob([nrrdBuffer]), filename); - console.debug('File downloaded: ' + filename); - } - return nrrdBuffer; - } } diff --git a/plugins/ohifv3/extensions/monai-label/src/utils/SegmentationUtils.tsx b/plugins/ohifv3/extensions/monai-label/src/utils/SegmentationUtils.tsx deleted file mode 100644 index 5a762d957..000000000 --- a/plugins/ohifv3/extensions/monai-label/src/utils/SegmentationUtils.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { getLabelColor } from './GenericUtils'; - -function createSegmentMetadata( - label, - segmentId, - description = '', - newLabelMap = false -) { - const labelMeta = { - SegmentedPropertyCategoryCodeSequence: { - CodeValue: 'T-D0050', - CodingSchemeDesignator: 'SRT', - CodeMeaning: 'Tissue', - }, - SegmentNumber: 1, - SegmentLabel: label ? label : 'label-0-1', - SegmentDescription: description, - SegmentAlgorithmType: 'SEMIAUTOMATIC', - SegmentAlgorithmName: 'MONAI', - SegmentedPropertyTypeCodeSequence: { - CodeValue: 'T-D0050', - CodingSchemeDesignator: 'SRT', - CodeMeaning: 'Tissue', - }, - }; - - if (newLabelMap) { - console.debug('Logic to create a new segment'); - } - - const color = getLabelColor(label); - - const rgbColor = []; - for (const key in color) { - rgbColor.push(color[key]); - } - - rgbColor.push(255); - - return { - id: '0+' + segmentId, - color: rgbColor, - labelmapIndex: 0, - name: label, - segmentIndex: segmentId, - description: description, - meta: labelMeta, - }; -} - -export { createSegmentMetadata }; diff --git a/plugins/ohifv3/extensions/monai-label/src/utils/addToolInstance.ts b/plugins/ohifv3/extensions/monai-label/src/utils/addToolInstance.ts index 889926d81..b4d2c1e10 100644 --- a/plugins/ohifv3/extensions/monai-label/src/utils/addToolInstance.ts +++ b/plugins/ohifv3/extensions/monai-label/src/utils/addToolInstance.ts @@ -1,3 +1,16 @@ +/* +Copyright (c) MONAI Consortium +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import { addTool } from '@cornerstonejs/tools'; export default function addToolInstance( @@ -8,5 +21,6 @@ export default function addToolInstance( class InstanceClass extends toolClass { static toolName = name; } + addTool(InstanceClass); } diff --git a/plugins/ohifv3/modes/monai-label/.webpack/webpack.prod.js b/plugins/ohifv3/modes/monai-label/.webpack/webpack.prod.js index 163392a69..4403d12a1 100644 --- a/plugins/ohifv3/modes/monai-label/.webpack/webpack.prod.js +++ b/plugins/ohifv3/modes/monai-label/.webpack/webpack.prod.js @@ -1,62 +1,54 @@ +const webpack = require('webpack'); +const { merge } = require('webpack-merge'); const path = require('path'); -const pkg = require('../package.json'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const outputFile = 'index.umd.js'; -const rootDir = path.resolve(__dirname, '../'); -const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`); +const pkg = require('./../package.json'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); -// Todo: add ESM build for the mode in addition to umd build -const config = { - mode: 'production', - entry: rootDir + '/' + pkg.module, - devtool: 'source-map', - output: { - path: outputFolder, - filename: outputFile, - library: pkg.name, - libraryTarget: 'umd', - chunkFilename: '[name].chunk.js', - umdNamedDefine: true, - globalObject: "typeof self !== 'undefined' ? self : this", - }, - externals: [ - { - react: { - root: 'React', - commonjs2: 'react', - commonjs: 'react', - amd: 'react', - }, - '@ohif/core': { - commonjs2: '@ohif/core', - commonjs: '@ohif/core', - amd: '@ohif/core', - root: '@ohif/core', - }, - '@ohif/ui': { - commonjs2: '@ohif/ui', - commonjs: '@ohif/ui', - amd: '@ohif/ui', - root: '@ohif/ui', - }, +const ROOT_DIR = path.join(__dirname, './../'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +const ENTRY = { + app: `${SRC_DIR}/index.ts`, +}; + +module.exports = (env, argv) => { + const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); + + return merge(commonConfig, { + stats: { + colors: true, + hash: true, + timings: true, + assets: true, + chunks: false, + chunkModules: false, + modules: false, + children: false, + warnings: true, }, - ], - module: { - rules: [ - { - test: /(\.jsx|\.js|\.tsx|\.ts)$/, - loader: 'babel-loader', - exclude: /(node_modules|bower_components)/, - resolve: { - extensions: ['.js', '.jsx', '.ts', '.tsx'], - }, - }, + optimization: { + minimize: true, + sideEffects: false, + }, + output: { + path: ROOT_DIR, + library: 'ohif-mode-monai-label', + libraryTarget: 'umd', + libraryExport: 'default', + filename: pkg.main, + }, + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + // new MiniCssExtractPlugin({ + // filename: './dist/[name].css', + // chunkFilename: './dist/[id].css', + // }), ], - }, - resolve: { - modules: [path.resolve('./node_modules'), path.resolve('./src')], - extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'], - }, + }); }; - -module.exports = config; diff --git a/plugins/ohifv3/modes/monai-label/babel.config.js b/plugins/ohifv3/modes/monai-label/babel.config.js index a38ddda21..92fbbdeaf 100644 --- a/plugins/ohifv3/modes/monai-label/babel.config.js +++ b/plugins/ohifv3/modes/monai-label/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - '@babel/preset-typescript', + "@babel/preset-typescript", ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - '@babel/preset-typescript', + "@babel/preset-typescript", ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - '@babel/preset-typescript', + "@babel/preset-typescript", ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/plugins/ohifv3/modes/monai-label/package.json b/plugins/ohifv3/modes/monai-label/package.json index ecbae9558..b9d8028d7 100644 --- a/plugins/ohifv3/modes/monai-label/package.json +++ b/plugins/ohifv3/modes/monai-label/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-monai-label", - "version": "0.0.1", + "version": "3.0.0", "description": "OHIFv3 mode for MONAI Label", "author": "OHIF,NVIDIA,KCL", "license": "MIT", @@ -14,6 +14,9 @@ "keywords": [ "ohif-mode" ], + "publishConfig": { + "access": "public" + }, "module": "src/index.tsx", "engines": { "node": ">=14", @@ -21,6 +24,8 @@ "yarn": ">=1.16.0" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:cornerstone": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -30,35 +35,36 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "^3.0.0" + "@ohif/core": "3.10.0-beta.5" }, "dependencies": { - "@babel/runtime": "^7.20.13" + "@babel/runtime": "^7.20.13", + "i18next": "^17.0.3" }, "devDependencies": { - "@babel/core": "^7.21.4", + "@babel/core": "7.24.7", "@babel/plugin-proposal-class-properties": "^7.16.7", "@babel/plugin-proposal-object-rest-spread": "^7.17.3", "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.16.7", "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/plugin-transform-runtime": "7.24.7", "@babel/plugin-transform-typescript": "^7.13.0", - "@babel/preset-env": "^7.16.11", + "@babel/preset-env": "7.23.2", "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.13.0", - "babel-eslint": "^8.0.3", + "@svgr/webpack": "^8.1.0", + "babel-eslint": "^10.1.0", "babel-loader": "^8.0.0-beta.4", - "babel-plugin-inline-react-svg": "^2.0.1", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^10.2.0", "cross-env": "^7.0.3", "dotenv": "^14.1.0", - "eslint": "^5.0.1", + "eslint": "^8.39.0", "eslint-loader": "^2.0.0", - "webpack": "^5.50.0", - "webpack-merge": "^5.7.3", - "webpack-cli": "^4.7.2" + "webpack": "5.94.0", + "webpack-cli": "^4.7.2", + "webpack-merge": "^5.7.3" } } diff --git a/plugins/ohifv3/modes/monai-label/src/index.tsx b/plugins/ohifv3/modes/monai-label/src/index.tsx index 00e4be7da..301219d03 100644 --- a/plugins/ohifv3/modes/monai-label/src/index.tsx +++ b/plugins/ohifv3/modes/monai-label/src/index.tsx @@ -1,28 +1,28 @@ import { hotkeys } from '@ohif/core'; -import toolbarButtons from './toolbarButtons.js'; -import { id } from './id.js'; -import initToolGroups from './initToolGroups.js'; +import { id } from './id'; +import toolbarButtons from './toolbarButtons'; +import initToolGroups from './initToolGroups'; + +const monailabel = { + monaiLabel: '@ohif/extension-monai-label.panelModule.monailabel', +}; const ohif = { layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack', hangingProtocol: '@ohif/extension-default.hangingProtocolModule.default', leftPanel: '@ohif/extension-default.panelModule.seriesList', -}; - -const monailabel = { - monaiLabel: '@ohif/extension-monai-label.panelModule.monailabel', + rightPanel: '@ohif/extension-default.panelModule.measure', }; const cornerstone = { viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone', + panelTool: '@ohif/extension-cornerstone.panelModule.panelSegmentationWithTools', }; -const dicomSeg = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', +const segmentation = { + sopClassHandler: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', viewport: '@ohif/extension-cornerstone-dicom-seg.viewportModule.dicom-seg', - panel: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentation', }; /** @@ -33,8 +33,7 @@ const extensionDependencies = { '@ohif/extension-default': '^3.0.0', '@ohif/extension-cornerstone': '^3.0.0', '@ohif/extension-cornerstone-dicom-seg': '^3.0.0', - '@ohif/extension-test': '^0.0.1', - '@ohif/extension-monai-label': '^0.0.1', + '@ohif/extension-monai-label': '^3.0.0', }; function modeFactory({ modeConfiguration }) { @@ -54,75 +53,42 @@ function modeFactory({ modeConfiguration }) { * Runs when the Mode Route is mounted to the DOM. Usually used to initialize * Services and other resources. */ - onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { - const { - measurementService, - toolbarService, - toolGroupService, - customizationService, - } = servicesManager.services; + onModeEnter: ({ servicesManager, extensionManager, commandsManager }: withAppTypes) => { + const { measurementService, toolbarService, toolGroupService } = servicesManager.services; measurementService.clearMeasurements(); // Init Default and SR ToolGroups initToolGroups(extensionManager, toolGroupService, commandsManager); - // init customizations - customizationService.addModeCustomizations([ - '@ohif/extension-test.customizationModule.custom-context-menu', - ]); - - let unsubscribe; - - const activateTool = () => { - toolbarService.recordInteraction({ - groupId: 'WindowLevel', - itemId: 'WindowLevel', - interactionType: 'tool', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - }); - - // We don't need to reset the active tool whenever a viewport is getting - // added to the toolGroup. - unsubscribe(); - }; - - // Since we only have one viewport for the basic cs3d mode and it has - // only one hanging protocol, we can just use the first viewport - ({ unsubscribe } = toolGroupService.subscribe( - toolGroupService.EVENTS.VIEWPORT_ADDED, - activateTool - )); - toolbarService.addButtons(toolbarButtons); + // toolbarService.addButtons(segmentationButtons); + toolbarService.createButtonSection('primary', [ - 'MeasurementTools', - 'Zoom', 'WindowLevel', 'Pan', + 'Zoom', + 'TrackballRotate', 'Capture', 'Layout', 'MPR', 'Crosshairs', 'MoreTools', ]); + toolbarService.createButtonSection('segmentationToolbox', ['BrushTools', 'Shapes']); }, - onModeExit: ({ servicesManager }) => { + onModeExit: ({ servicesManager }: withAppTypes) => { const { toolGroupService, syncGroupService, segmentationService, cornerstoneViewportService, + uiDialogService, + uiModalService, } = servicesManager.services; + uiDialogService.dismissAll(); + uiModalService.hide(); toolGroupService.destroy(); syncGroupService.destroy(); segmentationService.destroy(); @@ -135,14 +101,18 @@ function modeFactory({ modeConfiguration }) { }, /** * A boolean return value that indicates whether the mode is valid for the - * modalities of the selected studies. For instance a PET/CT mode should be + * modalities of the selected studies. Currently we don't have stack viewport + * segmentations and we should exclude them */ - isValidMode: function ({ modalities }) { - const modalities_list = modalities.split('\\'); - const isValid = - modalities_list.includes('CT') || modalities_list.includes('MR'); - // Only CT or MR modalities - return isValid; + isValidMode: ({ modalities }) => { + // Don't show the mode if the selected studies have only one modality + // that is not supported by the mode + const modalitiesArray = modalities.split('\\'); + return { + valid: modalitiesArray.includes('CT') || modalitiesArray.includes('MR'), + description: + 'The mode does not support studies that ONLY include the following modalities: SM, OT, DOC', + }; }, /** * Mode Routes are used to define the mode's behavior. A list of Mode Route @@ -173,8 +143,8 @@ function modeFactory({ modeConfiguration }) { displaySetsToDisplay: [ohif.sopClassHandler], }, { - namespace: dicomSeg.viewport, - displaySetsToDisplay: [dicomSeg.sopClassHandler], + namespace: segmentation.viewport, + displaySetsToDisplay: [segmentation.sopClassHandler], }, ], }, @@ -188,10 +158,8 @@ function modeFactory({ modeConfiguration }) { hangingProtocol: 'mpr', // hangingProtocol: [''], /** SopClassHandlers used by the mode */ - sopClassHandlers: [ - dicomSeg.sopClassHandler, - ohif.sopClassHandler, - ] /** hotkeys for mode */, + sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler], + /** hotkeys for mode */ hotkeys: [...hotkeys.defaults.hotkeyBindings], }; } diff --git a/plugins/ohifv3/modes/monai-label/src/initToolGroups.js b/plugins/ohifv3/modes/monai-label/src/initToolGroups.js index 81bc09eb6..2f540d32a 100644 --- a/plugins/ohifv3/modes/monai-label/src/initToolGroups.js +++ b/plugins/ohifv3/modes/monai-label/src/initToolGroups.js @@ -1,105 +1,105 @@ -const brushInstanceNames = { - CircularBrush: 'CircularBrush', - CircularEraser: 'CircularEraser', - SphereBrush: 'SphereBrush', - SphereEraser: 'SphereEraser', - ThresholdCircularBrush: 'ThresholdCircularBrush', - ThresholdSphereBrush: 'ThresholdSphereBrush', +const colours = { + 'viewport-0': 'rgb(200, 0, 0)', + 'viewport-1': 'rgb(200, 200, 0)', + 'viewport-2': 'rgb(0, 200, 0)', }; -const brushStrategies = { - [brushInstanceNames.CircularBrush]: 'FILL_INSIDE_CIRCLE', - [brushInstanceNames.CircularEraser]: 'ERASE_INSIDE_CIRCLE', - [brushInstanceNames.SphereBrush]: 'FILL_INSIDE_SPHERE', - [brushInstanceNames.SphereEraser]: 'ERASE_INSIDE_SPHERE', - [brushInstanceNames.ThresholdCircularBrush]: 'THRESHOLD_INSIDE_CIRCLE', - [brushInstanceNames.ThresholdSphereBrush]: 'THRESHOLD_INSIDE_SPHERE', +const colorsByOrientation = { + axial: 'rgb(200, 0, 0)', + sagittal: 'rgb(200, 200, 0)', + coronal: 'rgb(0, 200, 0)', }; -function initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - toolGroupId -) { - const utilityModule = extensionManager.getModuleEntry( - '@ohif/extension-cornerstone.utilityModule.tools' - ); - +function createTools(utilityModule) { const { toolNames, Enums } = utilityModule.exports; - - const tools = { + return { active: [ - { - toolName: toolNames.WindowLevel, - bindings: [{ mouseButton: Enums.MouseBindings.Primary }], - }, - { - toolName: toolNames.Pan, - bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], - }, - { - toolName: toolNames.Zoom, - bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], - }, - { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + { toolName: toolNames.WindowLevel, bindings: [{ mouseButton: Enums.MouseBindings.Primary }] }, + { toolName: toolNames.Pan, bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }] }, + { toolName: toolNames.Zoom, bindings: [{ mouseButton: Enums.MouseBindings.Secondary }] }, + { toolName: toolNames.StackScroll, bindings: [{ mouseButton: Enums.MouseBindings.Wheel }] }, ], passive: [ - { toolName: toolNames.CircleScissors }, - { toolName: toolNames.RectangleScissors }, - { toolName: toolNames.SphereScissors }, { - toolName: brushInstanceNames.CircularBrush, + toolName: 'CircularBrush', + parentTool: 'Brush', + configuration: { + activeStrategy: 'FILL_INSIDE_CIRCLE', + }, + }, + { + toolName: 'CircularEraser', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.CircularBrush, + activeStrategy: 'ERASE_INSIDE_CIRCLE', }, }, { - toolName: brushInstanceNames.CircularEraser, + toolName: 'SphereBrush', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.CircularEraser, + activeStrategy: 'FILL_INSIDE_SPHERE', }, }, { - toolName: brushInstanceNames.SphereEraser, + toolName: 'SphereEraser', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.SphereEraser, + activeStrategy: 'ERASE_INSIDE_SPHERE', }, }, { - toolName: brushInstanceNames.SphereBrush, + toolName: 'ThresholdCircularBrush', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.SphereBrush, + activeStrategy: 'THRESHOLD_INSIDE_CIRCLE', }, }, { - toolName: brushInstanceNames.ThresholdCircularBrush, + toolName: 'ThresholdSphereBrush', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.ThresholdCircularBrush, + activeStrategy: 'THRESHOLD_INSIDE_SPHERE', }, }, { - toolName: brushInstanceNames.ThresholdSphereBrush, + toolName: 'ThresholdCircularBrushDynamic', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.ThresholdSphereBrush, + activeStrategy: 'THRESHOLD_INSIDE_CIRCLE', + // preview: { + // enabled: true, + // }, + strategySpecificConfiguration: { + // to use the use the center segment index to determine + // if inside -> same segment, if outside -> eraser + // useCenterSegmentIndex: true, + THRESHOLD: { + isDynamic: true, + dynamicRadius: 3, + }, + }, }, }, + { toolName: toolNames.CircleScissors }, + { toolName: toolNames.RectangleScissors }, + { toolName: toolNames.SphereScissors }, { toolName: toolNames.StackScroll }, { toolName: toolNames.Magnify }, - { toolName: toolNames.SegmentationDisplay }, - { toolName: 'ProbeMONAILabel' }, + { toolName: toolNames.WindowLevelRegion }, + + { toolName: toolNames.UltrasoundDirectional }, + { toolName: 'ProbeMONAILabel' } ], - // enabled - // disabled - disabled: [{ toolName: toolNames.ReferenceLines }], + disabled: [{ toolName: toolNames.ReferenceLines }, { toolName: toolNames.AdvancedMagnify }], }; +} +function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + const tools = createTools(utilityModule); toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } @@ -107,103 +107,76 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); + const servicesManager = extensionManager._servicesManager; + const { cornerstoneViewportService } = servicesManager.services; + const tools = createTools(utilityModule); + tools.disabled.push( + { + toolName: utilityModule.exports.toolNames.Crosshairs, + configuration: { + viewportIndicators: true, + viewportIndicatorsConfig: { + circleRadius: 5, + xOffset: 0.95, + yOffset: 0.05, + }, + disableOnPassive: true, + autoPan: { + enabled: false, + panSize: 10, + }, + getReferenceLineColor: viewportId => { + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); + const viewportOptions = viewportInfo?.viewportOptions; + if (viewportOptions) { + return ( + colours[viewportOptions.id] || + colorsByOrientation[viewportOptions.orientation] || + '#0c0' + ); + } else { + console.warn('missing viewport?', viewportId); + return '#0c0'; + } + }, + }, + }, + { toolName: utilityModule.exports.toolNames.ReferenceLines } + ); + toolGroupService.createToolGroupAndAddTools('mpr', tools); +} + +function initVolume3DToolGroup(extensionManager, toolGroupService) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); const { toolNames, Enums } = utilityModule.exports; const tools = { active: [ { - toolName: toolNames.WindowLevel, + toolName: toolNames.TrackballRotateTool, bindings: [{ mouseButton: Enums.MouseBindings.Primary }], }, - { - toolName: toolNames.Pan, - bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], - }, { toolName: toolNames.Zoom, bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], }, - { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, - ], - passive: [ - { toolName: toolNames.CircleScissors }, - { toolName: toolNames.RectangleScissors }, - { toolName: toolNames.SphereScissors }, - { - toolName: brushInstanceNames.CircularBrush, - parentTool: 'Brush', - configuration: { - activeStrategy: brushStrategies.CircularBrush, - }, - }, - { - toolName: brushInstanceNames.CircularEraser, - parentTool: 'Brush', - configuration: { - activeStrategy: brushStrategies.CircularEraser, - }, - }, - { - toolName: brushInstanceNames.SphereEraser, - parentTool: 'Brush', - configuration: { - activeStrategy: brushStrategies.SphereEraser, - }, - }, { - toolName: brushInstanceNames.SphereBrush, - parentTool: 'Brush', - configuration: { - activeStrategy: brushStrategies.SphereBrush, - }, - }, - { - toolName: brushInstanceNames.ThresholdCircularBrush, - parentTool: 'Brush', - configuration: { - activeStrategy: brushStrategies.ThresholdCircularBrush, - }, - }, - { - toolName: brushInstanceNames.ThresholdSphereBrush, - parentTool: 'Brush', - configuration: { - activeStrategy: brushStrategies.ThresholdSphereBrush, - }, - }, - { toolName: toolNames.SegmentationDisplay }, - { toolName: 'ProbeMONAILabel' }, - { toolName: 'ProbeMONAILabel' }, - ], - disabled: [ - { - toolName: toolNames.Crosshairs, - configuration: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, + toolName: toolNames.Pan, + bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], }, - { toolName: toolNames.ReferenceLines }, ], - // enabled - // disabled }; - toolGroupService.createToolGroupAndAddTools('mpr', tools); + toolGroupService.createToolGroupAndAddTools('volume3d', tools); } function initToolGroups(extensionManager, toolGroupService, commandsManager) { - initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - 'default' - ); + initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, 'default'); initMPRToolGroup(extensionManager, toolGroupService, commandsManager); + initVolume3DToolGroup(extensionManager, toolGroupService); } export default initToolGroups; diff --git a/plugins/ohifv3/modes/monai-label/src/toolbarButtons.js b/plugins/ohifv3/modes/monai-label/src/toolbarButtons.js index 5b71b36c2..a59c994eb 100644 --- a/plugins/ohifv3/modes/monai-label/src/toolbarButtons.js +++ b/plugins/ohifv3/modes/monai-label/src/toolbarButtons.js @@ -1,605 +1,288 @@ -// TODO: torn, can either bake this here; or have to create a whole new button type -// Only ways that you can pass in a custom React component for render :l -import { - // ExpandableToolbarButton, - // ListMenu, - WindowLevelMenuItem, -} from '@ohif/ui'; -import { defaults } from '@ohif/core'; +import type { Button } from '@ohif/core/types'; +import { ToolbarService, ViewportGridService } from '@ohif/core'; -const { windowLevelPresets } = defaults; -/** - * - * @param {*} type - 'tool' | 'action' | 'toggle' - * @param {*} id - * @param {*} icon - * @param {*} label - */ -function _createButton(type, id, icon, label, commands, tooltip, uiType) { - return { - id, - icon, - label, - type, - commands, - tooltip, - uiType, - }; -} +const { createButton } = ToolbarService; -const _createActionButton = _createButton.bind(null, 'action'); -const _createToggleButton = _createButton.bind(null, 'toggle'); -const _createToolButton = _createButton.bind(null, 'tool'); - -/** - * - * @param {*} preset - preset number (from above import) - * @param {*} title - * @param {*} subtitle - */ -function _createWwwcPreset(preset, title, subtitle) { - return { - id: preset.toString(), - title, - subtitle, - type: 'action', - commands: [ - { - commandName: 'setWindowLevel', - commandOptions: { - ...windowLevelPresets[preset], - }, - context: 'CORNERSTONE', - }, - ], - }; -} - -const toolGroupIds = ['default', 'mpr', 'SRToolGroup']; - -/** - * Creates an array of 'setToolActive' commands for the given toolName - one for - * each toolGroupId specified in toolGroupIds. - * @param {string} toolName - * @returns {Array} an array of 'setToolActive' commands - */ -function _createSetToolActiveCommands(toolName) { - const temp = toolGroupIds.map((toolGroupId) => ({ - commandName: 'setToolActive', - commandOptions: { - toolGroupId, - toolName, - }, +const ReferenceLinesListeners: RunCommand = [ + { + commandName: 'setSourceViewportForReferenceLinesTool', context: 'CORNERSTONE', - })); - return temp; -} + }, +]; -const toolbarButtons = [ - // Measurement - { - id: 'MeasurementTools', - type: 'ohif.splitButton', - props: { - groupId: 'MeasurementTools', - isRadio: true, // ? - // Switch? - primary: _createToolButton( - 'Length', - 'tool-length', - 'Length', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Length', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRLength', - toolGroupId: 'SRToolGroup', - }, - // we can use the setToolActive command for this from Cornerstone commandsModule - context: 'CORNERSTONE', - }, - ], - 'Length' - ), - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Measure Tools', - }, - items: [ - _createToolButton( - 'Length', - 'tool-length', - 'Length', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Length', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRLength', - toolGroupId: 'SRToolGroup', - }, - // we can use the setToolActive command for this from Cornerstone commandsModule - context: 'CORNERSTONE', - }, - ], - 'Length Tool' - ), - _createToolButton( - 'Bidirectional', - 'tool-bidirectional', - 'Bidirectional', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Bidirectional', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRBidirectional', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Bidirectional Tool' - ), - _createToolButton( - 'ArrowAnnotate', - 'tool-annotate', - 'Annotation', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'ArrowAnnotate', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRArrowAnnotate', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Arrow Annotate' - ), - _createToolButton( - 'EllipticalROI', - 'tool-elipse', - 'Ellipse', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'EllipticalROI', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SREllipticalROI', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Ellipse Tool' - ), - _createToolButton( - 'CircleROI', - 'tool-circle', - 'Circle', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'CircleROI', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRCircleROI', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Circle Tool' - ), - ], - }, +export const setToolActiveToolbar = { + commandName: 'setToolActiveToolbar', + commandOptions: { + toolGroupIds: ['default', 'mpr', 'SRToolGroup', 'volume3d'], }, - // Zoom.. +}; + +const toolbarButtons: Button[] = [ { id: 'Zoom', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { - type: 'tool', icon: 'tool-zoom', label: 'Zoom', - commands: _createSetToolActiveCommands('Zoom'), + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, - // Window Level + Presets... { id: 'WindowLevel', - type: 'ohif.splitButton', + uiType: 'ohif.radioGroup', props: { - groupId: 'WindowLevel', - primary: _createToolButton( - 'WindowLevel', - 'tool-window-level', - 'Window Level', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - 'Window Level' - ), - secondary: { - icon: 'chevron-down', - label: 'W/L Manual', - isActive: true, - tooltip: 'W/L Presets', - }, - isAction: true, // ? - renderer: WindowLevelMenuItem, - items: [ - _createWwwcPreset(1, 'Soft tissue', '400 / 40'), - _createWwwcPreset(2, 'Lung', '1500 / -600'), - _createWwwcPreset(3, 'Liver', '150 / 90'), - _createWwwcPreset(4, 'Bone', '2500 / 480'), - _createWwwcPreset(5, 'Brain', '80 / 40'), - ], + icon: 'tool-window-level', + label: 'Window Level', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, - // Pan... { id: 'Pan', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { - type: 'tool', icon: 'tool-move', label: 'Pan', - commands: _createSetToolActiveCommands('Pan'), + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }, + }, + { + id: 'TrackballRotate', + uiType: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-3d-rotate', + label: '3D Rotate', + commands: setToolActiveToolbar, + evaluate: { + name: 'evaluate.cornerstoneTool', + disabledText: 'Select a 3D viewport to enable this tool', + }, }, }, { id: 'Capture', - type: 'ohif.action', + uiType: 'ohif.radioGroup', props: { icon: 'tool-capture', label: 'Capture', - type: 'action', - commands: [ + commands: 'showDownloadViewportModal', + evaluate: [ + 'evaluate.action', { - commandName: 'showDownloadViewportModal', - commandOptions: {}, - context: 'CORNERSTONE', + name: 'evaluate.viewport.supported', + unsupportedViewportTypes: ['video', 'wholeSlide'], }, ], }, }, { id: 'Layout', - type: 'ohif.layoutSelector', + uiType: 'ohif.layoutSelector', props: { rows: 3, - columns: 3, - }, - }, - { - id: 'MPR', - type: 'ohif.action', - props: { - type: 'toggle', - icon: 'icon-mpr', - label: 'MPR', - commands: [ - { - commandName: 'toggleHangingProtocol', - commandOptions: { - protocolId: 'mpr', - }, - context: 'DEFAULT', - }, - ], + columns: 4, + evaluate: 'evaluate.action', + commands: 'setViewportGridLayout', }, }, { id: 'Crosshairs', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { - type: 'tool', icon: 'tool-crosshair', label: 'Crosshairs', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Crosshairs', - toolGroupId: 'mpr', - }, - context: 'CORNERSTONE', + commands: { + commandName: 'setToolActiveToolbar', + commandOptions: { + toolGroupIds: ['mpr'], }, - ], + }, + evaluate: { + name: 'evaluate.cornerstoneTool', + disabledText: 'Select an MPR viewport to enable this tool', + }, }, }, - // More... { id: 'MoreTools', - type: 'ohif.splitButton', + uiType: 'ohif.splitButton', props: { - isRadio: true, // ? groupId: 'MoreTools', - primary: _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), + evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', + primary: createButton({ + id: 'Reset', + icon: 'tool-reset', + tooltip: 'Reset View', + label: 'Reset', + commands: 'resetViewport', + evaluate: 'evaluate.action', + }), secondary: { icon: 'chevron-down', label: '', - isActive: true, tooltip: 'More Tools', }, items: [ - _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), - _createActionButton( - 'rotate-right', - 'tool-rotate-right', - 'Rotate Right', - [ + createButton({ + id: 'Reset', + icon: 'tool-reset', + label: 'Reset View', + tooltip: 'Reset View', + commands: 'resetViewport', + evaluate: 'evaluate.action', + }), + createButton({ + id: 'rotate-right', + icon: 'tool-rotate-right', + label: 'Rotate Right', + tooltip: 'Rotate +90', + commands: 'rotateViewportCW', + evaluate: 'evaluate.action', + }), + createButton({ + id: 'flipHorizontal', + icon: 'tool-flip-horizontal', + label: 'Flip Horizontal', + tooltip: 'Flip Horizontally', + commands: 'flipViewportHorizontal', + evaluate: [ + 'evaluate.viewportProperties.toggle', { - commandName: 'rotateViewportCW', - commandOptions: {}, - context: 'CORNERSTONE', + name: 'evaluate.viewport.supported', + unsupportedViewportTypes: ['volume3d'], }, ], - 'Rotate +90' - ), - _createActionButton( - 'flip-horizontal', - 'tool-flip-horizontal', - 'Flip Horizontally', - [ - { - commandName: 'flipViewportHorizontal', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Flip Horizontal' - ), - _createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [ - { - commandName: 'toggleStackImageSync', - commandOptions: {}, - context: 'CORNERSTONE', + }), + createButton({ + id: 'ReferenceLines', + icon: 'tool-referenceLines', + label: 'Reference Lines', + tooltip: 'Show Reference Lines', + commands: 'toggleEnabledDisabledToolbar', + listeners: { + [ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners, + [ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners, }, - ]), - _createToggleButton( - 'ReferenceLines', - 'tool-referenceLines', // change this with the new icon - 'Reference Lines', - [ - { - commandName: 'toggleReferenceLines', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ] - ), - _createToolButton( - 'StackScroll', - 'tool-stack-scroll', - 'Stack Scroll', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'StackScroll', - }, - context: 'CORNERSTONE', - }, - ], - 'Stack Scroll' - ), - _createActionButton( - 'invert', - 'tool-invert', - 'Invert', - [ - { - commandName: 'invertViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Invert Colors' - ), - _createToolButton( - 'Probe', - 'tool-probe', - 'Probe', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'DragProbe', - }, - context: 'CORNERSTONE', - }, - ], - 'Probe' - ), - _createToggleButton( - 'cine', - 'tool-cine', - 'Cine', - [ - { - commandName: 'toggleCine', - context: 'CORNERSTONE', - }, - ], - 'Cine' - ), - _createToolButton( - 'Angle', - 'tool-angle', - 'Angle', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Angle', - }, - context: 'CORNERSTONE', - }, - ], - 'Angle' - ), - - // Next two tools can be added once icons are added - // _createToolButton( - // 'Cobb Angle', - // 'tool-cobb-angle', - // 'Cobb Angle', - // [ - // { - // commandName: 'setToolActive', - // commandOptions: { - // toolName: 'CobbAngle', - // }, - // context: 'CORNERSTONE', - // }, - // ], - // 'Cobb Angle' - // ), - // _createToolButton( - // 'Planar Freehand ROI', - // 'tool-freehand', - // 'PlanarFreehandROI', - // [ - // { - // commandName: 'setToolActive', - // commandOptions: { - // toolName: 'PlanarFreehandROI', - // }, - // context: 'CORNERSTONE', - // }, - // ], - // 'Planar Freehand ROI' - // ), - _createToolButton( - 'Magnify', - 'tool-magnify', - 'Magnify', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Magnify', - }, - context: 'CORNERSTONE', - }, - ], - 'Magnify' - ), - _createToolButton( - 'Rectangle', - 'tool-rectangle', - 'Rectangle', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'RectangleROI', - }, - context: 'CORNERSTONE', - }, - ], - 'Rectangle' - ), - _createToolButton( - 'CalibrationLine', - 'tool-calibration', - 'Calibration', - [ + evaluate: 'evaluate.cornerstoneTool.toggle', + }), + createButton({ + id: 'ImageOverlayViewer', + icon: 'toggle-dicom-overlay', + label: 'Image Overlay', + tooltip: 'Toggle Image Overlay', + commands: 'toggleEnabledDisabledToolbar', + evaluate: 'evaluate.cornerstoneTool.toggle', + }), + createButton({ + id: 'StackScroll', + icon: 'tool-stack-scroll', + label: 'Stack Scroll', + tooltip: 'Stack Scroll', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'invert', + icon: 'tool-invert', + label: 'Invert', + tooltip: 'Invert Colors', + commands: 'invertViewport', + evaluate: 'evaluate.viewportProperties.toggle', + }), + createButton({ + id: 'Probe', + icon: 'tool-probe', + label: 'Probe', + tooltip: 'Probe', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Cine', + icon: 'tool-cine', + label: 'Cine', + tooltip: 'Cine', + commands: 'toggleCine', + evaluate: [ + 'evaluate.cine', { - commandName: 'setToolActive', - commandOptions: { - toolName: 'CalibrationLine', - }, - context: 'CORNERSTONE', + name: 'evaluate.viewport.supported', + unsupportedViewportTypes: ['volume3d'], }, ], - 'Calibration Line' - ), - _createActionButton( - 'TagBrowser', - 'list-bullets', - 'Dicom Tag Browser', - [ + }), + createButton({ + id: 'Angle', + icon: 'tool-angle', + label: 'Angle', + tooltip: 'Angle', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Magnify', + icon: 'tool-magnify', + label: 'Zoom-in', + tooltip: 'Zoom-in', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'RectangleROI', + icon: 'tool-rectangle', + label: 'Rectangle', + tooltip: 'Rectangle', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'CalibrationLine', + icon: 'tool-calibration', + label: 'Calibration', + tooltip: 'Calibration Line', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'TagBrowser', + icon: 'dicom-tag-browser', + label: 'Dicom Tag Browser', + tooltip: 'Dicom Tag Browser', + commands: 'openDICOMTagViewer', + }), + createButton({ + id: 'AdvancedMagnify', + icon: 'icon-tool-loupe', + label: 'Magnify Probe', + tooltip: 'Magnify Probe', + commands: 'toggleActiveDisabledToolbar', + evaluate: 'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled', + }), + createButton({ + id: 'UltrasoundDirectionalTool', + icon: 'icon-tool-ultrasound-bidirectional', + label: 'Ultrasound Directional', + tooltip: 'Ultrasound Directional', + commands: setToolActiveToolbar, + evaluate: [ + 'evaluate.cornerstoneTool', { - commandName: 'openDICOMTagViewer', - commandOptions: {}, - context: 'DEFAULT', + name: 'evaluate.modality.supported', + supportedModalities: ['US'], }, ], - 'Dicom Tag Browser' - ), + }), + createButton({ + id: 'WindowLevelRegion', + icon: 'icon-tool-window-region', + label: 'Window Level Region', + tooltip: 'Window Level Region', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), ], }, }, diff --git a/runtests.sh b/runtests.sh index 1f7cabc5f..6cd607bb8 100755 --- a/runtests.sh +++ b/runtests.sh @@ -428,7 +428,7 @@ function run_integration_tests() { echo "$1 - Starting MONAILabel server..." rm -rf tests/data/apps monailabel apps -n $1 -o tests/data/apps -d - monailabel start_server -a tests/data/apps/$1 -c models "$3" -s $2 -p ${MONAILABEL_SERVER_PORT:-8000} & + monailabel start_server -a tests/data/apps/$1 -c models "$3" -s $2 -p ${MONAILABEL_SERVER_PORT:-8000} -c sam2 $5 & wait_time=0 server_is_up=0 @@ -459,9 +459,9 @@ function run_integration_tests() { # network training/inference/eval integration tests if [ $doNetTests = true ]; then - run_integration_tests "radiology" "tests/data/dataset/local/spleen" "deepedit,segmentation_spleen,segmentation,deepgrow_2d,deepgrow_3d" "." - run_integration_tests "pathology" "tests/data/pathology" "segmentation_nuclei,nuclick,classification_nuclei" "." - run_integration_tests "monaibundle" "tests/data/dataset/local/spleen" "spleen_ct_segmentation" "bundles" - run_integration_tests "endoscopy" "tests/data/endoscopy" "tooltracking,inbody,deepedit" "." - run_integration_tests "monaibundle" "tests/data/detection" "lung_nodule_ct_detection" "detection" + run_integration_tests "radiology" "tests/data/dataset/local/spleen" "deepedit,segmentation_spleen,segmentation,deepgrow_2d,deepgrow_3d" "." true + run_integration_tests "pathology" "tests/data/pathology" "segmentation_nuclei,nuclick,classification_nuclei" "." false + run_integration_tests "monaibundle" "tests/data/dataset/local/spleen" "spleen_ct_segmentation" "bundles" false + run_integration_tests "endoscopy" "tests/data/endoscopy" "tooltracking,inbody,deepedit" "." false + run_integration_tests "monaibundle" "tests/data/detection" "lung_nodule_ct_detection" "detection" false fi