From b7413ebf2b92ac6085d5ee4bd0c091688685b0b9 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 24 Oct 2024 16:26:26 -0400 Subject: [PATCH 1/4] fix(mode): no need to initialize toolbar service --- plugins/ohifv3/modes/monai-label/src/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/ohifv3/modes/monai-label/src/index.tsx b/plugins/ohifv3/modes/monai-label/src/index.tsx index f832dd869..199bafce6 100644 --- a/plugins/ohifv3/modes/monai-label/src/index.tsx +++ b/plugins/ohifv3/modes/monai-label/src/index.tsx @@ -102,7 +102,6 @@ function modeFactory({ modeConfiguration }) { activateTool )); - toolbarService.init(extensionManager); toolbarService.addButtons(toolbarButtons); toolbarService.createButtonSection('primary', [ 'MeasurementTools', From 0ab90f558ce735587d81df9a0e2ef5fab47b9dc9 Mon Sep 17 00:00:00 2001 From: Alireza Date: Mon, 4 Nov 2024 16:36:28 -0500 Subject: [PATCH 2/4] monai-label: Refactor webpack configuration and update panel components - Migrate from using webpack-merge to a simplified webpack configuration for production builds - Update MonaiLabelPanel to use the new PanelSegmentation component - Replace deprecated segmentation event names and adjust logic accordingly - Remove unused MonaiSegmentation and SegmentationToolbox components to streamline the codebase - Enhance toolbar buttons for --- .../monai-label/.webpack/webpack.prod.js | 129 ++- .../extensions/monai-label/babel.config.js | 18 +- .../src/components/MonaiLabelPanel.tsx | 48 +- .../src/components/MonaiSegmentation.tsx | 319 -------- .../src/components/SegmentationToolbox.tsx | 368 --------- .../src/components/actions/SmartEdit.tsx | 42 +- .../monai-label/src/getCommandsModule.ts | 37 +- .../extensions/monai-label/src/index.tsx | 46 -- .../ohifv3/extensions/monai-label/src/init.ts | 6 +- .../src/tools/ProbeMONAILabelTool.ts | 10 +- plugins/ohifv3/modes/monai-label/package.json | 2 +- .../ohifv3/modes/monai-label/src/index.tsx | 61 +- .../modes/monai-label/src/initToolGroups.js | 234 +++--- .../modes/monai-label/src/toolbarButtons.js | 749 ++++-------------- 14 files changed, 469 insertions(+), 1600 deletions(-) delete mode 100644 plugins/ohifv3/extensions/monai-label/src/components/MonaiSegmentation.tsx delete mode 100644 plugins/ohifv3/extensions/monai-label/src/components/SegmentationToolbox.tsx diff --git a/plugins/ohifv3/extensions/monai-label/.webpack/webpack.prod.js b/plugins/ohifv3/extensions/monai-label/.webpack/webpack.prod.js index 2398e36fb..7a7041f03 100644 --- a/plugins/ohifv3/extensions/monai-label/.webpack/webpack.prod.js +++ b/plugins/ohifv3/extensions/monai-label/.webpack/webpack.prod.js @@ -1,48 +1,95 @@ -const webpack = require('webpack'); -const { merge } = require('webpack-merge'); const path = require('path'); -const webpackCommon = require('./../../../.webpack/webpack.base.js'); -const pkg = require('./../package.json'); +const pkg = require('../package.json'); -const ROOT_DIR = path.join(__dirname, './..'); -const SRC_DIR = path.join(__dirname, '../src'); -const DIST_DIR = path.join(__dirname, '../dist'); +const outputFile = 'index.umd.js'; +const rootDir = path.resolve(__dirname, '../'); +const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`); -const ENTRY = { - app: `${SRC_DIR}/index.tsx`, -}; - -module.exports = (env, argv) => { - const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); +// Todo: add ESM build for the extension in addition to umd build - return merge(commonConfig, { - stats: { - colors: true, - hash: true, - timings: true, - assets: true, - chunks: false, - chunkModules: false, - modules: false, - children: false, - warnings: true, - }, - optimization: { - minimize: true, - sideEffects: false, +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', + }, }, - output: { - path: ROOT_DIR, - library: 'ohif-extension-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, - }), + ], + module: { + rules: [ + { + test: /\.svg?$/, + oneOf: [ + { + use: [ + { + loader: '@svgr/webpack', + options: { + svgoConfig: { + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + removeViewBox: false, + }, + }, + }, + ], + }, + prettier: false, + svgo: true, + titleProp: true, + }, + }, + ], + issuer: { + and: [/\.(ts|tsx|js|jsx|md|mdx)$/], + }, + }, + ], + }, + { + test: /(\.jsx|\.js|\.tsx|\.ts)$/, + loader: 'babel-loader', + exclude: /(node_modules|bower_components)/, + resolve: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, ], - }); + }, + resolve: { + modules: [path.resolve('./node_modules'), path.resolve('./src')], + extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'], + }, }; + +module.exports = config; diff --git a/plugins/ohifv3/extensions/monai-label/babel.config.js b/plugins/ohifv3/extensions/monai-label/babel.config.js index 371f77fcf..4772105a8 100644 --- a/plugins/ohifv3/extensions/monai-label/babel.config.js +++ b/plugins/ohifv3/extensions/monai-label/babel.config.js @@ -1,21 +1,5 @@ -// https://babeljs.io/docs/en/options#babelrcroots -const { extendDefaultPlugins } = require('svgo'); - module.exports = { plugins: [ - [ - 'inline-react-svg', - { - svgo: { - plugins: extendDefaultPlugins([ - { - name: 'removeViewBox', - active: false, - }, - ]), - }, - }, - ], ['@babel/plugin-proposal-class-properties', { loose: true }], '@babel/plugin-transform-typescript', ['@babel/plugin-proposal-private-property-in-object', { loose: true }], @@ -59,7 +43,7 @@ module.exports = { '@babel/preset-react', '@babel/preset-typescript', ], - plugins: ['react-hot-loader/babel'], + plugins: ['react-refresh/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, }, diff --git a/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx b/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx index f62e3f5d6..18174c929 100644 --- a/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx +++ b/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx @@ -10,8 +10,7 @@ import OptionTable from './actions/OptionTable'; import ActiveLearning from './actions/ActiveLearning'; import MonaiLabelClient from '../services/MonaiLabelClient'; import SegmentationReader from '../utils/SegmentationReader'; -import MonaiSegmentation from './MonaiSegmentation'; -import SegmentationToolbox from './SegmentationToolbox'; +import { PanelSegmentation, } from '@ohif/extension-cornerstone' export default class MonaiLabelPanel extends Component { static propTypes = { @@ -59,6 +58,11 @@ export default class MonaiLabelPanel extends Component { setTimeout(() => { const { viewports, activeViewportId } = viewportGridService.getState(); const viewport = viewports.get(activeViewportId); + + if (!viewport) { + return; + } + const displaySet = displaySetService.getDisplaySetByUID( viewport.displaySetInstanceUIDs[0] ); @@ -73,7 +77,7 @@ export default class MonaiLabelPanel extends Component { async componentDidMount() { const { segmentationService } = this.props.servicesManager.services; const added = segmentationService.EVENTS.SEGMENTATION_ADDED; - const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED; + const updated = segmentationService.EVENTS.SEGMENTATION_MODIFIED; const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED; const subscriptions = []; @@ -123,18 +127,23 @@ export default class MonaiLabelPanel extends Component { // remove the background const labels = response.data.labels.splice(1) - const segmentations = [ - { - id: '1', - label: 'Segmentations', - segments: labels.map((label, index) => ({ - segmentIndex: index + 1, - label - })), - isActive: true, - activeSegmentIndex: 1, + const segmentations = [{ + segmentationId: '1', + representation: { + type: Enums.SegmentationRepresentations.Labelmap, }, - ]; + config: { + label: 'Segmentations', + segments: labels.reduce((acc, label, index) => { + acc[index + 1] = { + label, + active: index === 0, // First segment is active + locked: false + }; + return acc; + }, {}) + } + }]; this.props.commandsManager.runCommand('loadSegmentationsForViewport', { segmentations @@ -387,16 +396,7 @@ delete onInfoLabelNames.background; /> - {this.state.segmentations?.map((segmentation) => ( - <> - - - - ))} + ); } diff --git a/plugins/ohifv3/extensions/monai-label/src/components/MonaiSegmentation.tsx b/plugins/ohifv3/extensions/monai-label/src/components/MonaiSegmentation.tsx deleted file mode 100644 index f112a84c8..000000000 --- a/plugins/ohifv3/extensions/monai-label/src/components/MonaiSegmentation.tsx +++ /dev/null @@ -1,319 +0,0 @@ -import { createReportAsync } from '@ohif/extension-default'; -import React, { useEffect, useState, useCallback } from 'react'; -import PropTypes from 'prop-types'; -import { SegmentationGroupTable } from '@ohif/ui'; - -import callInputDialog from './callInputDialog'; -import callColorPickerDialog from './colorPickerDialog'; -import { useTranslation } from 'react-i18next'; - -export default function MonaiSegmentation({ - servicesManager, - commandsManager, - extensionManager, - configuration, -}) { - const { segmentationService, uiDialogService } = servicesManager.services; - - const { t } = useTranslation('PanelSegmentation'); - - const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); - const [segmentationConfiguration, setSegmentationConfiguration] = useState( - segmentationService.getConfiguration() - ); - - const [segmentations, setSegmentations] = useState(() => - segmentationService.getSegmentations() - ); - - useEffect(() => { - // ~~ Subscription - 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(); - setSegmentations(segmentations); - setSegmentationConfiguration(segmentationService.getConfiguration()); - }); - subscriptions.push(unsubscribe); - }); - - return () => { - subscriptions.forEach((unsub) => { - unsub(); - }); - }; - }, []); - - const getToolGroupIds = (segmentationId) => { - const toolGroupIds = - segmentationService.getToolGroupIdsWithSegmentation(segmentationId); - - return toolGroupIds; - }; - - const onSegmentationAdd = async () => { - commandsManager.runCommand('addSegmentationForActiveViewport'); - }; - - const onSegmentationClick = (segmentationId: string) => { - segmentationService.setActiveSegmentationForToolGroup(segmentationId); - }; - - const onSegmentationDelete = (segmentationId: string) => { - segmentationService.remove(segmentationId); - }; - - const onSegmentAdd = (segmentationId) => { - segmentationService.addSegment(segmentationId); - }; - - const onSegmentClick = (segmentationId, segmentIndex) => { - segmentationService.setActiveSegment(segmentationId, segmentIndex); - - const toolGroupIds = getToolGroupIds(segmentationId); - - toolGroupIds.forEach((toolGroupId) => { - // const toolGroupId = - segmentationService.setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId - ); - segmentationService.jumpToSegmentCenter( - segmentationId, - segmentIndex, - toolGroupId - ); - }); - }; - - const onSegmentEdit = (segmentationId, segmentIndex) => { - const segmentation = segmentationService.getSegmentation(segmentationId); - - const segment = segmentation.segments[segmentIndex]; - const { label } = segment; - - callInputDialog(uiDialogService, label, (label, actionId) => { - if (label === '') { - return; - } - - segmentationService.setSegmentLabel(segmentationId, segmentIndex, label); - }); - }; - - const onSegmentationEdit = (segmentationId) => { - const segmentation = segmentationService.getSegmentation(segmentationId); - const { label } = segmentation; - - callInputDialog(uiDialogService, label, (label, actionId) => { - if (label === '') { - return; - } - - segmentationService.addOrUpdateSegmentation( - { - id: segmentationId, - label, - }, - false, // suppress event - true // notYetUpdatedAtSource - ); - }); - }; - - const onSegmentColorClick = (segmentationId, segmentIndex) => { - const segmentation = segmentationService.getSegmentation(segmentationId); - - const segment = segmentation.segments[segmentIndex]; - const { color, opacity } = segment; - - const rgbaColor = { - r: color[0], - g: color[1], - b: color[2], - a: opacity / 255.0, - }; - - callColorPickerDialog( - uiDialogService, - rgbaColor, - (newRgbaColor, actionId) => { - if (actionId === 'cancel') { - return; - } - - segmentationService.setSegmentRGBAColor(segmentationId, segmentIndex, [ - newRgbaColor.r, - newRgbaColor.g, - newRgbaColor.b, - newRgbaColor.a * 255.0, - ]); - } - ); - }; - - const onSegmentDelete = (segmentationId, segmentIndex) => { - segmentationService.removeSegment(segmentationId, segmentIndex); - }; - - const onToggleSegmentVisibility = (segmentationId, segmentIndex) => { - const segmentation = segmentationService.getSegmentation(segmentationId); - const segmentInfo = segmentation.segments[segmentIndex]; - const isVisible = !segmentInfo.isVisible; - const toolGroupIds = getToolGroupIds(segmentationId); - - // Todo: right now we apply the visibility to all tool groups - toolGroupIds.forEach((toolGroupId) => { - segmentationService.setSegmentVisibility( - segmentationId, - segmentIndex, - isVisible, - toolGroupId - ); - }); - }; - - const onToggleSegmentLock = (segmentationId, segmentIndex) => { - segmentationService.toggleSegmentLocked(segmentationId, segmentIndex); - }; - - const onToggleSegmentationVisibility = (segmentationId) => { - segmentationService.toggleSegmentationVisibility(segmentationId); - }; - - const _setSegmentationConfiguration = useCallback( - (segmentationId, key, value) => { - segmentationService.setConfiguration({ - segmentationId, - [key]: value, - }); - }, - [segmentationService] - ); - - const onSegmentationDownload = (segmentationId) => { - commandsManager.runCommand('downloadSegmentation', { - segmentationId, - }); - }; - - const onSegmentationDownloadRTSS = (segmentationId) => {}; - - const storeSegmentation = (segmentationId) => { - const datasources = extensionManager.getActiveDataSource(); - - const getReport = async () => { - return await commandsManager.runCommand('storeSegmentation', { - segmentationId, - dataSource: datasources[0], - }); - }; - - createReportAsync({ - servicesManager, - getReport, - reportType: 'Segmentation', - }); - }; - - return ( - <> -
- - _setSegmentationConfiguration( - selectedSegmentationId, - 'renderOutline', - value - ) - } - setOutlineOpacityActive={(value) => - _setSegmentationConfiguration( - selectedSegmentationId, - 'outlineOpacity', - value - ) - } - setRenderFill={(value) => - _setSegmentationConfiguration( - selectedSegmentationId, - 'renderFill', - value - ) - } - setRenderInactiveSegmentations={(value) => - _setSegmentationConfiguration( - selectedSegmentationId, - 'renderInactiveSegmentations', - value - ) - } - setOutlineWidthActive={(value) => - _setSegmentationConfiguration( - selectedSegmentationId, - 'outlineWidthActive', - value - ) - } - setFillAlpha={(value) => - _setSegmentationConfiguration( - selectedSegmentationId, - 'fillAlpha', - value - ) - } - setFillAlphaInactive={(value) => - _setSegmentationConfiguration( - selectedSegmentationId, - 'fillAlphaInactive', - value - ) - } - /> -
- - ); -} - -MonaiSegmentation.propTypes = { - commandsManager: PropTypes.shape({ - runCommand: PropTypes.func.isRequired, - }), - servicesManager: PropTypes.shape({ - services: PropTypes.shape({ - segmentationService: PropTypes.shape({ - getSegmentation: PropTypes.func.isRequired, - getSegmentations: PropTypes.func.isRequired, - toggleSegmentationVisibility: PropTypes.func.isRequired, - subscribe: PropTypes.func.isRequired, - EVENTS: PropTypes.object.isRequired, - }).isRequired, - }).isRequired, - }).isRequired, -}; diff --git a/plugins/ohifv3/extensions/monai-label/src/components/SegmentationToolbox.tsx b/plugins/ohifv3/extensions/monai-label/src/components/SegmentationToolbox.tsx deleted file mode 100644 index 4577a56b7..000000000 --- a/plugins/ohifv3/extensions/monai-label/src/components/SegmentationToolbox.tsx +++ /dev/null @@ -1,368 +0,0 @@ -import React, { useCallback, useEffect, useState, useReducer } from 'react'; -import { AdvancedToolbox, InputDoubleRange, useViewportGrid } from '@ohif/ui'; -import { Types } from '@ohif/extension-cornerstone'; -import { utilities } from '@cornerstonejs/tools'; - -const { segmentation: segmentationUtils } = utilities; - -const ACTIONS = { - SET_BRUSH_SIZE: 'SET_BRUSH_SIZE', - SET_TOOL_CONFIG: 'SET_TOOL_CONFIG', - SET_ACTIVE_TOOL: 'SET_ACTIVE_TOOL', -}; - -const initialState = { - Brush: { - brushSize: 15, - mode: 'CircularBrush', // Can be 'CircularBrush' or 'SphereBrush' - }, - Eraser: { - brushSize: 15, - mode: 'CircularEraser', // Can be 'CircularEraser' or 'SphereEraser' - }, - Scissors: { - brushSize: 15, - mode: 'CircleScissor', // E.g., 'CircleScissor', 'RectangleScissor', or 'SphereScissor' - }, - ThresholdBrush: { - brushSize: 15, - thresholdRange: [-500, 500], - }, - activeTool: null, -}; - -function toolboxReducer(state, action) { - switch (action.type) { - case ACTIONS.SET_TOOL_CONFIG: - const { tool, config } = action.payload; - return { - ...state, - [tool]: { - ...state[tool], - ...config, - }, - }; - case ACTIONS.SET_ACTIVE_TOOL: - return { ...state, activeTool: action.payload }; - default: - return state; - } -} - -function SegmentationToolbox({ servicesManager, extensionManager }) { - const { toolbarService, segmentationService, toolGroupService } = - servicesManager.services as Types.CornerstoneServices; - - const [viewportGrid] = useViewportGrid(); - const { viewports, activeViewportId } = viewportGrid; - - const [toolsEnabled, setToolsEnabled] = useState(false); - const [state, dispatch] = useReducer(toolboxReducer, initialState); - - const updateActiveTool = useCallback(() => { - if (!viewports?.size || activeViewportId === undefined) { - return; - } - - const viewport = viewports.get(activeViewportId); - - dispatch({ - type: ACTIONS.SET_ACTIVE_TOOL, - payload: toolGroupService.getActiveToolForViewport(viewport.viewportId), - }); - }, [activeViewportId, viewports, toolGroupService, dispatch]); - - /** - * sets the tools enabled IF there are segmentations - */ - useEffect(() => { - const events = [ - segmentationService.EVENTS.SEGMENTATION_ADDED, - segmentationService.EVENTS.SEGMENTATION_UPDATED, - ]; - - const unsubscriptions = []; - - events.forEach(event => { - const { unsubscribe } = segmentationService.subscribe(event, () => { - const segmentations = segmentationService.getSegmentations(); - - const activeSegmentation = segmentations?.find(seg => seg.isActive); - - setToolsEnabled(activeSegmentation?.segmentCount > 0); - }); - - unsubscriptions.push(unsubscribe); - }); - - return () => { - unsubscriptions.forEach(unsubscribe => unsubscribe()); - }; - }, [activeViewportId, viewports, segmentationService]); - - /** - * Update the active tool when the toolbar state changes - */ - useEffect(() => { - const { unsubscribe } = toolbarService.subscribe( - toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, - () => { - updateActiveTool(); - } - ); - - return () => { - unsubscribe(); - }; - }, [toolbarService, updateActiveTool]); - - const setToolActive = useCallback( - toolName => { - toolbarService.recordInteraction({ - groupId: 'SegmentationTools', - itemId: 'Brush', - interactionType: 'tool', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName, - }, - }, - ], - }); - - dispatch({ type: ACTIONS.SET_ACTIVE_TOOL, payload: toolName }); - }, - [toolbarService, dispatch] - ); - - const updateBrushSize = useCallback( - (toolName, brushSize) => { - toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { - segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName); - }); - }, - [toolGroupService] - ); - - const onBrushSizeChange = useCallback( - (valueAsStringOrNumber, toolCategory) => { - const value = Number(valueAsStringOrNumber); - - _getToolNamesFromCategory(toolCategory).forEach(toolName => { - updateBrushSize(toolName, value); - }); - - dispatch({ - type: ACTIONS.SET_TOOL_CONFIG, - payload: { - tool: toolCategory, - config: { brushSize: value }, - }, - }); - }, - [toolGroupService, dispatch] - ); - - const handleRangeChange = useCallback( - newRange => { - if ( - newRange[0] === state.ThresholdBrush.thresholdRange[0] && - newRange[1] === state.ThresholdBrush.thresholdRange[1] - ) { - return; - } - - const toolNames = _getToolNamesFromCategory('ThresholdBrush'); - - toolNames.forEach(toolName => { - toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { - const toolGroup = toolGroupService.getToolGroup(toolGroupId); - toolGroup.setToolConfiguration(toolName, { - strategySpecificConfiguration: { - THRESHOLD_INSIDE_CIRCLE: { - threshold: newRange, - }, - }, - }); - }); - }); - - dispatch({ - type: ACTIONS.SET_TOOL_CONFIG, - payload: { - tool: 'ThresholdBrush', - config: { thresholdRange: newRange }, - }, - }); - }, - [toolGroupService, dispatch, state.ThresholdBrush.thresholdRange] - ); - - return ( - setToolActive('CircularBrush'), - options: [ - { - name: 'Radius (mm)', - id: 'brush-radius', - type: 'range', - min: 0.01, - max: 100, - value: state.Brush.brushSize, - step: 0.5, - onChange: value => onBrushSizeChange(value, 'Brush'), - }, - { - name: 'Mode', - type: 'radio', - id: 'brush-mode', - value: state.Brush.mode, - values: [ - { value: 'CircularBrush', label: 'Circle' }, - { value: 'SphereBrush', label: 'Sphere' }, - ], - onChange: value => setToolActive(value), - }, - ], - }, - { - name: 'Eraser', - icon: 'icon-tool-eraser', - disabled: !toolsEnabled, - active: state.activeTool === 'CircularEraser' || state.activeTool === 'SphereEraser', - onClick: () => setToolActive('CircularEraser'), - options: [ - { - name: 'Radius (mm)', - type: 'range', - id: 'eraser-radius', - min: 0.01, - max: 100, - value: state.Eraser.brushSize, - step: 0.5, - onChange: value => onBrushSizeChange(value, 'Eraser'), - }, - { - name: 'Mode', - type: 'radio', - id: 'eraser-mode', - value: state.Eraser.mode, - values: [ - { value: 'CircularEraser', label: 'Circle' }, - { value: 'SphereEraser', label: 'Sphere' }, - ], - onChange: value => setToolActive(value), - }, - ], - }, - { - name: 'Scissor', - icon: 'icon-tool-scissor', - disabled: !toolsEnabled, - active: - state.activeTool === 'CircleScissor' || - state.activeTool === 'RectangleScissor' || - state.activeTool === 'SphereScissor', - onClick: () => setToolActive('CircleScissor'), - options: [ - { - name: 'Mode', - type: 'radio', - value: state.Scissors.mode, - id: 'scissor-mode', - values: [ - { value: 'CircleScissor', label: 'Circle' }, - { value: 'RectangleScissor', label: 'Rectangle' }, - { value: 'SphereScissor', label: 'Sphere' }, - ], - onChange: value => setToolActive(value), - }, - ], - }, - { - name: 'Threshold Tool', - icon: 'icon-tool-threshold', - disabled: !toolsEnabled, - active: - state.activeTool === 'ThresholdCircularBrush' || - state.activeTool === 'ThresholdSphereBrush', - onClick: () => setToolActive('ThresholdCircularBrush'), - options: [ - { - name: 'Radius (mm)', - id: 'threshold-radius', - type: 'range', - min: 0.01, - max: 100, - value: state.ThresholdBrush.brushSize, - step: 0.5, - onChange: value => onBrushSizeChange(value, 'ThresholdBrush'), - }, - { - name: 'Mode', - type: 'radio', - id: 'threshold-mode', - value: state.activeTool, - values: [ - { value: 'ThresholdCircularBrush', label: 'Circle' }, - { value: 'ThresholdSphereBrush', label: 'Sphere' }, - ], - onChange: value => setToolActive(value), - }, - { - type: 'custom', - children: () => { - return ( -
-
-
Threshold
- -
- ); - }, - }, - ], - }, - ]} - /> - ); -} - -function _getToolNamesFromCategory(category) { - let toolNames = []; - switch (category) { - case 'Brush': - toolNames = ['CircularBrush', 'SphereBrush']; - break; - case 'Eraser': - toolNames = ['CircularEraser', 'SphereEraser']; - break; - case 'ThresholdBrush': - toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush']; - break; - default: - break; - } - - return toolNames; -} - -export default SegmentationToolbox; diff --git a/plugins/ohifv3/extensions/monai-label/src/components/actions/SmartEdit.tsx b/plugins/ohifv3/extensions/monai-label/src/components/actions/SmartEdit.tsx index ba607d19a..e1356274a 100644 --- a/plugins/ohifv3/extensions/monai-label/src/components/actions/SmartEdit.tsx +++ b/plugins/ohifv3/extensions/monai-label/src/components/actions/SmartEdit.tsx @@ -10,7 +10,6 @@ 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'; @@ -38,7 +37,7 @@ export default class SmartEdit extends BaseTab { this.props.servicesManager.services; const added = segmentationService.EVENTS.SEGMENTATION_ADDED; - const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED; + const updated = segmentationService.EVENTS.SEGMENTATION_MODIFIED; const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED; const subscriptions = []; @@ -52,17 +51,28 @@ export default class SmartEdit extends BaseTab { // get the first segmentation Todo: fix this to be active const segmentation = segmentations[0]; - const { segments, activeSegmentIndex } = segmentation; + const { segmentationId } = segmentation; + + // const selectedSegment = segments.find((segment) => segment.active); + const activeSegmentIndex= cornerstoneTools.segmentation.segmentIndex.getActiveSegmentIndex(segmentationId) + - const selectedSegment = segments[activeSegmentIndex]; + if (!activeSegmentIndex) { + return; + } - const color = selectedSegment.color; + + const viewport = this.getActiveViewportInfo(); + const { viewportOptions, viewportId } = viewport; + + // const color = selectedSegment.color; + const color = + cornerstoneTools.segmentation.config.color.getSegmentIndexColor( + viewportId, + segmentationId, + activeSegmentIndex + ) || [0, 0, 0]; - // 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', { @@ -77,6 +87,13 @@ export default class SmartEdit extends BaseTab { }; } + getActiveViewportInfo = () => { + const { viewportGridService } = this.props.servicesManager.services; + const { viewports, activeViewportId } = viewportGridService.getState(); + const viewport = viewports.get(activeViewportId); + return viewport; + } + componentWillUnmount() { this.unsubscribe(); } @@ -92,7 +109,10 @@ export default class SmartEdit extends BaseTab { const image = viewConstants.SeriesInstanceUID; const model = this.modelSelector.current.currentModel(); - const activeSegment = segmentationService.getActiveSegment(); + const viewport = this.getActiveViewportInfo(); + const { viewportId } = viewport; + + const activeSegment = segmentationService.getActiveSegment(viewportId); const segmentId = activeSegment.label; if (segmentId && !this.state.segmentId) { diff --git a/plugins/ohifv3/extensions/monai-label/src/getCommandsModule.ts b/plugins/ohifv3/extensions/monai-label/src/getCommandsModule.ts index a1f4c751a..d2868acc6 100644 --- a/plugins/ohifv3/extensions/monai-label/src/getCommandsModule.ts +++ b/plugins/ohifv3/extensions/monai-label/src/getCommandsModule.ts @@ -1,35 +1,14 @@ -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 }) => { - - uiNotificationService.show({ - title: 'MONAI Label probe', - message: - 'MONAI Label Probe Activated.', - type: 'info', - duration: 3000, - }); + uiNotificationService.show({ + title: 'MONAI Label probe', + message: 'MONAI Label Probe Activated.', + type: 'info', + duration: 3000, + }); }, }; diff --git a/plugins/ohifv3/extensions/monai-label/src/index.tsx b/plugins/ohifv3/extensions/monai-label/src/index.tsx index 5b3e76f6b..37578c659 100644 --- a/plugins/ohifv3/extensions/monai-label/src/index.tsx +++ b/plugins/ohifv3/extensions/monai-label/src/index.tsx @@ -5,53 +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/init.ts b/plugins/ohifv3/extensions/monai-label/src/init.ts index 58c3afdef..bee5f4836 100644 --- a/plugins/ohifv3/extensions/monai-label/src/init.ts +++ b/plugins/ohifv3/extensions/monai-label/src/init.ts @@ -1,7 +1,5 @@ -import { - addTool, -} from '@cornerstonejs/tools'; -import { Types } from '@ohif/core'; +import { addTool } from '@cornerstonejs/tools'; +import type { Types } from '@ohif/core'; import ProbeMONAILabelTool from './tools/ProbeMONAILabelTool'; /** diff --git a/plugins/ohifv3/extensions/monai-label/src/tools/ProbeMONAILabelTool.ts b/plugins/ohifv3/extensions/monai-label/src/tools/ProbeMONAILabelTool.ts index 8997271da..206caa175 100644 --- a/plugins/ohifv3/extensions/monai-label/src/tools/ProbeMONAILabelTool.ts +++ b/plugins/ohifv3/extensions/monai-label/src/tools/ProbeMONAILabelTool.ts @@ -1,4 +1,3 @@ -import { Types, metaData, utilities as csUtils } from '@cornerstonejs/core'; import { ProbeTool, annotation, drawing } from '@cornerstonejs/tools'; const { getAnnotations } = annotation.state; @@ -37,18 +36,15 @@ export default class ProbeMONAILabelTool extends ProbeTool { return renderStatus; } - const targetId = this.getTargetId(viewport); - const renderingEngine = viewport.getRenderingEngine(); - - const styleSpecifier: StyleSpecifier = { + const styleSpecifier = { toolGroupId: this.toolGroupId, toolName: this.getToolName(), viewportId: enabledElement.viewport.id, }; for (let i = 0; i < annotations.length; i++) { - const annotation = annotations[i] as ProbeAnnotation; - const annotationUID = annotation.annotationUID; + const annotation = annotations[i]; + const annotationUID = annotation.annotationUID as string; const data = annotation.data; const point = data.handles.points[0]; const canvasCoordinates = viewport.worldToCanvas(point); diff --git a/plugins/ohifv3/modes/monai-label/package.json b/plugins/ohifv3/modes/monai-label/package.json index ecbae9558..171a49d4b 100644 --- a/plugins/ohifv3/modes/monai-label/package.json +++ b/plugins/ohifv3/modes/monai-label/package.json @@ -30,7 +30,7 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "^3.0.0" + "@ohif/core": "^3.9.0-beta.101" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/plugins/ohifv3/modes/monai-label/src/index.tsx b/plugins/ohifv3/modes/monai-label/src/index.tsx index 199bafce6..c9602ce7c 100644 --- a/plugins/ohifv3/modes/monai-label/src/index.tsx +++ b/plugins/ohifv3/modes/monai-label/src/index.tsx @@ -3,28 +3,34 @@ import toolbarButtons from './toolbarButtons.js'; import { id } from './id.js'; import initToolGroups from './initToolGroups.js'; + +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', + rightPanel: '@ohif/extension-default.panelModule.measure', }; -const monailabel = { - monaiLabel: '@ohif/extension-monai-label.panelModule.monailabel', -} - const cornerstone = { viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone', + panelTool: + '@ohif/extension-cornerstone.panelModule.panelSegmentationWithTools', }; -const dicomSeg = { +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', }; + + /** * Just two dependencies to be able to render a viewport with panels in order * to make sure that the mode is working. @@ -67,41 +73,12 @@ function modeFactory({ modeConfiguration }) { // Init Default and SR ToolGroups initToolGroups(extensionManager, toolGroupService, commandsManager); + toolbarService.addButtons(toolbarButtons); // 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.createButtonSection('primary', [ 'MeasurementTools', @@ -121,8 +98,12 @@ function modeFactory({ modeConfiguration }) { syncGroupService, segmentationService, cornerstoneViewportService, + uiDialogService, + uiModalService, } = servicesManager.services; + uiDialogService.dismissAll(); + uiModalService.hide(); toolGroupService.destroy(); syncGroupService.destroy(); segmentationService.destroy(); @@ -140,7 +121,7 @@ function modeFactory({ modeConfiguration }) { isValidMode: function ({ modalities }) { const modalities_list = modalities.split('\\'); const isValid = - modalities_list.includes('CT') || modalities_list.includes('MR'); + modalities_list.includes('CT') || modalities_list.includes('MR'); // Only CT or MR modalities return isValid; }, @@ -173,8 +154,8 @@ function modeFactory({ modeConfiguration }) { displaySetsToDisplay: [ohif.sopClassHandler], }, { - namespace: dicomSeg.viewport, - displaySetsToDisplay: [dicomSeg.sopClassHandler], + namespace: segmentation.viewport, + displaySetsToDisplay: [segmentation.sopClassHandler], }, ], }, @@ -189,7 +170,7 @@ function modeFactory({ modeConfiguration }) { // hangingProtocol: [''], /** SopClassHandlers used by the mode */ sopClassHandlers: [ - dicomSeg.sopClassHandler, + segmentation.sopClassHandler, ohif.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..35ab84861 100644 --- a/plugins/ohifv3/modes/monai-label/src/initToolGroups.js +++ b/plugins/ohifv3/modes/monai-label/src/initToolGroups.js @@ -1,34 +1,18 @@ -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, @@ -42,64 +26,100 @@ function initDefaultToolGroup( toolName: toolNames.Zoom, bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], }, - { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + { + 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: brushStrategies.CircularBrush, + activeStrategy: 'FILL_INSIDE_CIRCLE', }, }, { - toolName: brushInstanceNames.CircularEraser, + toolName: 'CircularEraser', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.CircularEraser, + activeStrategy: 'ERASE_INSIDE_CIRCLE', }, }, { - toolName: brushInstanceNames.SphereEraser, + toolName: 'SphereBrush', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.SphereEraser, + activeStrategy: 'FILL_INSIDE_SPHERE', }, }, { - toolName: brushInstanceNames.SphereBrush, + toolName: 'SphereEraser', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.SphereBrush, + activeStrategy: 'ERASE_INSIDE_SPHERE', }, }, { - toolName: brushInstanceNames.ThresholdCircularBrush, + toolName: 'ThresholdCircularBrush', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.ThresholdCircularBrush, + activeStrategy: 'THRESHOLD_INSIDE_CIRCLE', }, }, { - toolName: brushInstanceNames.ThresholdSphereBrush, + toolName: 'ThresholdSphereBrush', parentTool: 'Brush', configuration: { - activeStrategy: brushStrategies.ThresholdSphereBrush, + activeStrategy: 'THRESHOLD_INSIDE_SPHERE', }, }, + { + toolName: 'ThresholdCircularBrushDynamic', + parentTool: 'Brush', + configuration: { + 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: 'ProbeMONAILabel' }, + { 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 }, + ], + disabled: [ + { toolName: toolNames.ReferenceLines }, + { toolName: toolNames.AdvancedMagnify }, ], - // enabled - // disabled - disabled: [{ toolName: toolNames.ReferenceLines }], }; +} +function initDefaultToolGroup( + extensionManager, + toolGroupService, + commandsManager, + toolGroupId +) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + const tools = createTools(utilityModule); toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } @@ -107,93 +127,72 @@ 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) { @@ -204,6 +203,7 @@ function initToolGroups(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 92de37e89..5a6945096 100644 --- a/plugins/ohifv3/modes/monai-label/src/toolbarButtons.js +++ b/plugins/ohifv3/modes/monai-label/src/toolbarButtons.js @@ -1,608 +1,205 @@ -// 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'; -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 _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, - }, - context: 'CORNERSTONE', - })); - return temp; -} - -const toolbarButtons = [ - // Measurement +const toolbarButtons: Button[] = [ { - id: 'MeasurementTools', - type: 'ohif.splitButton', + id: 'BrushTools', + uiType: 'ohif.buttonGroup', 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', - }, + groupId: 'BrushTools', 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', - [ + { + id: 'Brush', + icon: 'icon-tool-brush', + label: 'Brush', + evaluate: { + name: 'evaluate.cornerstone.segmentation', + toolNames: ['CircularBrush', 'SphereBrush'], + disabledText: 'Create new segmentation to enable this tool.', + }, + options: [ { - commandName: 'setToolActive', - commandOptions: { - toolName: 'ArrowAnnotate', + name: 'Radius (mm)', + id: 'brush-radius', + type: 'range', + min: 0.5, + max: 99.5, + step: 0.5, + value: 25, + commands: { + commandName: 'setBrushSize', + commandOptions: { toolNames: ['CircularBrush', 'SphereBrush'] }, }, - context: 'CORNERSTONE', }, { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRArrowAnnotate', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', + name: 'Shape', + type: 'radio', + id: 'brush-mode', + value: 'CircularBrush', + values: [ + { value: 'CircularBrush', label: 'Circle' }, + { value: 'SphereBrush', label: 'Sphere' }, + ], + commands: 'setToolActiveToolbar', }, ], - 'Arrow Annotate' - ), - _createToolButton( - 'EllipticalROI', - 'tool-elipse', - 'Ellipse', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'EllipticalROI', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SREllipticalROI', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', + }, + { + id: 'Eraser', + icon: 'icon-tool-eraser', + label: 'Eraser', + evaluate: { + name: 'evaluate.cornerstone.segmentation', + toolNames: ['CircularEraser', 'SphereEraser'], + }, + options: [ + { + name: 'Radius (mm)', + id: 'eraser-radius', + type: 'range', + min: 0.5, + max: 99.5, + step: 0.5, + value: 25, + commands: { + commandName: 'setBrushSize', + commandOptions: { + toolNames: ['CircularEraser', 'SphereEraser'], + }, + }, + }, + { + name: 'Shape', + type: 'radio', + id: 'eraser-mode', + value: 'CircularEraser', + values: [ + { value: 'CircularEraser', label: 'Circle' }, + { value: 'SphereEraser', label: 'Sphere' }, + ], + commands: 'setToolActiveToolbar', }, ], - 'Ellipse Tool' - ), - _createToolButton( - 'CircleROI', - 'tool-circle', - 'Circle', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'CircleROI', + }, + { + id: 'Threshold', + icon: 'icon-tool-threshold', + label: 'Threshold Tool', + evaluate: { + name: 'evaluate.cornerstone.segmentation', + toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'], + }, + options: [ + { + name: 'Radius (mm)', + id: 'threshold-radius', + type: 'range', + min: 0.5, + max: 99.5, + step: 0.5, + value: 25, + commands: { + commandName: 'setBrushSize', + commandOptions: { + toolNames: [ + 'ThresholdCircularBrush', + 'ThresholdSphereBrush', + 'ThresholdCircularBrushDynamic', + ], + }, }, - context: 'CORNERSTONE', }, + { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRCircleROI', - toolGroupId: 'SRToolGroup', + name: 'Threshold', + type: 'radio', + id: 'dynamic-mode', + value: 'ThresholdRange', + values: [ + { value: 'ThresholdDynamic', label: 'Dynamic' }, + { value: 'ThresholdRange', label: 'Range' }, + ], + commands: ({ value, commandsManager, options }) => { + if (value === 'ThresholdDynamic') { + commandsManager.run('setToolActive', { + toolName: 'ThresholdCircularBrushDynamic', + }); + + return; + } + + // check the condition of the threshold-range option + const thresholdRangeOption = options.find( + (option) => option.id === 'threshold-shape' + ); + + commandsManager.run('setToolActiveToolbar', { + toolName: thresholdRangeOption.value, + }); + }, + }, + { + name: 'Shape', + type: 'radio', + id: 'threshold-shape', + value: 'ThresholdCircularBrush', + values: [ + { value: 'ThresholdCircularBrush', label: 'Circle' }, + { value: 'ThresholdSphereBrush', label: 'Sphere' }, + ], + condition: ({ options }) => + options.find((option) => option.id === 'dynamic-mode').value === + 'ThresholdRange', + commands: 'setToolActiveToolbar', + }, + { + name: 'ThresholdRange', + type: 'double-range', + id: 'threshold-range', + min: -1000, + max: 1000, + step: 1, + value: [100, 600], + condition: ({ options }) => + options.find((option) => option.id === 'dynamic-mode').value === + 'ThresholdRange', + commands: { + commandName: 'setThresholdRange', + commandOptions: { + toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'], + }, }, - context: 'CORNERSTONE', }, ], - 'Circle Tool' - ), - ], - }, - }, - // Zoom.. - { - id: 'Zoom', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-zoom', - label: 'Zoom', - commands: _createSetToolActiveCommands('Zoom'), - }, - }, - // Window Level + Presets... - { - id: 'WindowLevel', - type: 'ohif.splitButton', - 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'), - ], - }, - }, - // Pan... - { - id: 'Pan', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-move', - label: 'Pan', - commands: _createSetToolActiveCommands('Pan'), - }, - }, - { - id: 'Capture', - type: 'ohif.action', - props: { - icon: 'tool-capture', - label: 'Capture', - type: 'action', - commands: [ - { - commandName: 'showDownloadViewportModal', - commandOptions: {}, - context: 'CORNERSTONE', }, ], }, }, { - id: 'Layout', - type: 'ohif.layoutSelector', + id: 'Shapes', + uiType: 'ohif.radioGroup', 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', - }, - ], - }, - }, - { - id: 'Crosshairs', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-crosshair', - label: 'Crosshairs', - commands: [ + label: 'Shapes', + evaluate: { + name: 'evaluate.cornerstone.segmentation', + toolNames: ['CircleScissor', 'SphereScissor', 'RectangleScissor'], + }, + icon: 'icon-tool-shape', + options: [ { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Crosshairs', - toolGroupId: 'mpr', - }, - context: 'CORNERSTONE', + name: 'Shape', + type: 'radio', + value: 'CircleScissor', + id: 'shape-mode', + values: [ + { value: 'CircleScissor', label: 'Circle' }, + { value: 'SphereScissor', label: 'Sphere' }, + { value: 'RectangleScissor', label: 'Rectangle' }, + ], + commands: 'setToolActiveToolbar', }, ], }, }, - // More... - { - id: 'MoreTools', - type: 'ohif.splitButton', - props: { - isRadio: true, // ? - groupId: 'MoreTools', - primary: _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), - 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', - [ - { - commandName: 'rotateViewportCW', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - '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', - }, - ]), - _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', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'CalibrationLine', - }, - context: 'CORNERSTONE', - }, - ], - 'Calibration Line' - ), - _createActionButton( - 'TagBrowser', - 'list-bullets', - 'Dicom Tag Browser', - [ - { - commandName: 'openDICOMTagViewer', - commandOptions: {}, - context: 'DEFAULT', - }, - ], - 'Dicom Tag Browser' - ), - ], - }, - }, ]; export default toolbarButtons; From d76abb809f8bd8ab83b014b6158afd6d9ea78205 Mon Sep 17 00:00:00 2001 From: Alireza Date: Mon, 4 Nov 2024 16:58:25 -0500 Subject: [PATCH 3/4] MonaiLabel: Refactor MonaiLabelPanel and remove W3.CSS dependency - Remove W3.CSS import from MonaiLabelPanel.css - Integrate Toolbox component into MonaiLabelPanel for segmentation tools - Update viewport subscription logic for improved state management - Clean up segmentation loading logic and ensure --- .../src/components/MonaiLabelPanel.css | 1 - .../src/components/MonaiLabelPanel.tsx | 97 +++++--- .../src/components/actions/ActiveLearning.tsx | 3 + .../monai-label/src/components/w3.css | 235 ------------------ .../ohifv3/modes/monai-label/src/index.tsx | 7 + 5 files changed, 69 insertions(+), 274 deletions(-) delete mode 100644 plugins/ohifv3/extensions/monai-label/src/components/w3.css diff --git a/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.css b/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.css index 6c069c0ed..445c62bf1 100644 --- a/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.css +++ b/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.css @@ -1,4 +1,3 @@ -@import url("w3.css"); .monaiLabelPanel { background-color: #789; diff --git a/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx b/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx index 18174c929..04cc826be 100644 --- a/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx +++ b/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { cache, triggerEvent, eventTarget } from '@cornerstonejs/core'; import { Enums } from '@cornerstonejs/tools'; +import { Toolbox } from '@ohif/ui-next'; import './MonaiLabelPanel.css'; import SettingsTable from './SettingsTable'; import AutoSegmentation from './actions/AutoSegmentation'; @@ -10,7 +11,7 @@ import OptionTable from './actions/OptionTable'; import ActiveLearning from './actions/ActiveLearning'; import MonaiLabelClient from '../services/MonaiLabelClient'; import SegmentationReader from '../utils/SegmentationReader'; -import { PanelSegmentation, } from '@ohif/extension-cornerstone' +import { PanelSegmentation } from '@ohif/extension-cornerstone'; export default class MonaiLabelPanel extends Component { static propTypes = { @@ -54,24 +55,27 @@ export default class MonaiLabelPanel extends Component { segmentations: [], }; - // Todo: fix this hack - setTimeout(() => { - const { viewports, activeViewportId } = viewportGridService.getState(); - const viewport = viewports.get(activeViewportId); + viewportGridService.subscribe( + viewportGridService.EVENTS.GRID_SIZE_CHANGED, + () => { + const { viewports, activeViewportId } = viewportGridService.getState(); + const viewport = viewports.get(activeViewportId); - if (!viewport) { - return; - } - const displaySet = displaySetService.getDisplaySetByUID( - viewport.displaySetInstanceUIDs[0] - ); + if (!viewport) { + return; + } + + const displaySet = displaySetService.getDisplaySetByUID( + viewport.displaySetInstanceUIDs[0] + ); - this.SeriesInstanceUID = displaySet.SeriesInstanceUID; - this.StudyInstanceUID = displaySet.StudyInstanceUID; - this.FrameOfReferenceUID = displaySet.instances[0].FrameOfReferenceUID; - this.displaySetInstanceUID = displaySet.displaySetInstanceUID; - }, 1000); + this.SeriesInstanceUID = displaySet.SeriesInstanceUID; + this.StudyInstanceUID = displaySet.StudyInstanceUID; + this.FrameOfReferenceUID = displaySet.instances[0].FrameOfReferenceUID; + this.displaySetInstanceUID = displaySet.displaySetInstanceUID; + } + ); } async componentDidMount() { @@ -125,28 +129,30 @@ export default class MonaiLabelPanel extends Component { const response = await this.client().info(); // remove the background - const labels = response.data.labels.splice(1) + const labels = response.data.labels.splice(1); - const segmentations = [{ - segmentationId: '1', - representation: { - type: Enums.SegmentationRepresentations.Labelmap, + const segmentations = [ + { + segmentationId: '1', + representation: { + type: Enums.SegmentationRepresentations.Labelmap, + }, + config: { + label: 'Segmentations', + segments: labels.reduce((acc, label, index) => { + acc[index + 1] = { + label, + active: index === 0, // First segment is active + locked: false, + }; + return acc; + }, {}), + }, }, - config: { - label: 'Segmentations', - segments: labels.reduce((acc, label, index) => { - acc[index + 1] = { - label, - active: index === 0, // First segment is active - locked: false - }; - return acc; - }, {}) - } - }]; + ]; this.props.commandsManager.runCommand('loadSegmentationsForViewport', { - segmentations + segmentations, }); if (response.status !== 200) { @@ -202,11 +208,10 @@ export default class MonaiLabelPanel extends Component { console.info('These are the predicted labels'); console.info(onInfoLabelNames); - if (onInfoLabelNames.hasOwnProperty('background')){ -delete onInfoLabelNames.background; + if (onInfoLabelNames.hasOwnProperty('background')) { + delete onInfoLabelNames.background; } - const ret = SegmentationReader.parseNrrdData(response.data); if (!ret) { @@ -224,6 +229,7 @@ delete onInfoLabelNames.background; centroidsIJK.set(segmentIndex, { image: image, world: [] }); } + debugger; const segmentations = [ { id: '1', @@ -396,7 +402,22 @@ delete onInfoLabelNames.background; /> - +
+ + +
); } diff --git a/plugins/ohifv3/extensions/monai-label/src/components/actions/ActiveLearning.tsx b/plugins/ohifv3/extensions/monai-label/src/components/actions/ActiveLearning.tsx index dd0bd2fc4..e7546cb9e 100644 --- a/plugins/ohifv3/extensions/monai-label/src/components/actions/ActiveLearning.tsx +++ b/plugins/ohifv3/extensions/monai-label/src/components/actions/ActiveLearning.tsx @@ -146,6 +146,9 @@ export default class OptionTable extends BaseTab { const image = this.props.viewConstants.SeriesInstanceUID; + debugger + const scalarData = labelmaps3D + const label = new Blob([labelmaps3D.scalarData], { type: 'application/octet-stream', }); 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/modes/monai-label/src/index.tsx b/plugins/ohifv3/modes/monai-label/src/index.tsx index c9602ce7c..e4adb9aa6 100644 --- a/plugins/ohifv3/modes/monai-label/src/index.tsx +++ b/plugins/ohifv3/modes/monai-label/src/index.tsx @@ -91,6 +91,13 @@ function modeFactory({ modeConfiguration }) { 'Crosshairs', 'MoreTools', ]); + + toolbarService.createButtonSection('segmentationToolbox', [ + 'BrushTools', + 'Shapes', + ]); + + }, onModeExit: ({ servicesManager }) => { const { From bae9a1194e55c4428fc58b833c5e9b0928157a9e Mon Sep 17 00:00:00 2001 From: Alireza Date: Tue, 5 Nov 2024 15:47:22 -0500 Subject: [PATCH 4/4] fix --- .../extensions/monai-label/package.json | 137 ++++++++++-------- .../src/components/MonaiLabelPanel.tsx | 47 +++--- 2 files changed, 100 insertions(+), 84 deletions(-) diff --git a/plugins/ohifv3/extensions/monai-label/package.json b/plugins/ohifv3/extensions/monai-label/package.json index 753bfafff..5c5c14d24 100644 --- a/plugins/ohifv3/extensions/monai-label/package.json +++ b/plugins/ohifv3/extensions/monai-label/package.json @@ -1,64 +1,77 @@ { - "name": "@ohif/extension-monai-label", - "version": "0.0.1", - "description": "OHIFv3 extension for MONAI Label", - "author": "OHIF,NVIDIA,KCL", - "license": "MIT", - "main": "dist/umd/extension-monai-label/index.umd.js", - "files": [ - "dist/**", - "public/**", - "README.md" - ], - "repository": "OHIF/Viewers", - "keywords": [ - "ohif-extension" - ], - "module": "src/index.tsx", - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1.18.0" - }, - "scripts": { - "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" - }, - "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", - "prop-types": "^15.6.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-i18next": "^12.2.2", - "react-router": "^6.8.1", - "react-router-dom": "^6.8.1" - }, - "dependencies": { - "@babel/runtime": "^7.20.13", - "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" - }, - "devDependencies": { - "@babel/runtime": "^7.20.13", - "@cornerstonejs/tools": "^1.16.4", - "react-color": "^2.19.3" - } + "name": "@ohif/extension-monai-label", + "version": "0.0.1", + "description": "OHIFv3 extension for MONAI Label", + "author": "OHIF,NVIDIA,KCL", + "license": "MIT", + "main": "dist/umd/extension-monai-label/index.umd.js", + "files": [ + "dist/**", + "public/**", + "README.md" + ], + "repository": "OHIF/Viewers", + "keywords": [ + "ohif-extension" + ], + "module": "src/index.tsx", + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1.18.0" + }, + "scripts": { + "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" + }, + "dependencies": { + "@babel/runtime": "^7.20.13", + "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", + "bootstrap": "^5.0.2", + "chroma-js": "^2.1.2", + "itk": "^14.1.1", + "@ohif/core": "workspace:*", + "@ohif/extension-default": "workspace:*", + "@ohif/extension-cornerstone": "workspace:*" + }, + "devDependencies": { + "@babel/runtime": "^7.20.13", + "cross-env": "^5.2.0", + "@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.24.7", + "@babel/plugin-transform-typescript": "^7.13.0", + "@babel/preset-env": "7.24.7", + "@babel/preset-react": "^7.16.7", + "@babel/preset-typescript": "^7.13.0", + "@babel/plugin-proposal-private-property-in-object": "7.21.11", + "babel-eslint": "9.x", + "babel-loader": "^8.2.4", + "@svgr/webpack": "^8.1.0", + "babel-plugin-module-resolver": "^5.0.0", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^10.2.0", + "dotenv": "^14.1.0", + "eslint": "^8.39.0", + "eslint-loader": "^2.0.0", + "webpack": "5.89.0", + "webpack-merge": "^5.7.3", + "webpack-cli": "^5.0.2" + } } diff --git a/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx b/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx index 04cc826be..72f10c42f 100644 --- a/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx +++ b/plugins/ohifv3/extensions/monai-label/src/components/MonaiLabelPanel.tsx @@ -61,7 +61,6 @@ export default class MonaiLabelPanel extends Component { const { viewports, activeViewportId } = viewportGridService.getState(); const viewport = viewports.get(activeViewportId); - if (!viewport) { return; } @@ -201,6 +200,7 @@ export default class MonaiLabelPanel extends Component { }; _update = async (response, labelNames) => { + const { segmentationService } = this.props.servicesManager.services; // Process the obtained binary file from the MONAI Label server /* const onInfoLabelNames = this.state.info.labels */ const onInfoLabelNames = labelNames; @@ -229,37 +229,40 @@ export default class MonaiLabelPanel extends Component { centroidsIJK.set(segmentIndex, { image: image, world: [] }); } - debugger; - const segmentations = [ + + const segmentationsNew = [ { - id: '1', - label: 'Segmentations', - segments: Object.keys(onInfoLabelNames).map((key) => ({ - segmentIndex: onInfoLabelNames[key], - label: key, - })), - isActive: true, - activeSegmentIndex: 1, - scalarData: data, - FrameOfReferenceUID: this.FrameOfReferenceUID, + segmentationId: '1', + representation: { + type: Enums.SegmentationRepresentations.Labelmap, + }, + config: { + FrameOfReferenceUID: this.FrameOfReferenceUID, + label: 'Segmentations', + segments: Object.keys(onInfoLabelNames).reduce((acc, key) => { + const segmentIndex = onInfoLabelNames[key]; + acc[segmentIndex] = { + label: key, + active: segmentIndex === 1, // First segment is active + locked: false, + }; + return acc; + }, {}), + }, centroidsIJK: centroidsIJK, }, ]; + const volume = segmentationService.getLabelmapVolume('1'); // Todo: rename volumeId - const volumeLoadObject = cache.getVolume('1'); - if (volumeLoadObject) { - const { scalarData } = volumeLoadObject; - scalarData.set(data); + if (volume) { + const { voxelManager } = volume; + voxelManager?.setCompleteScalarDataArray(data); triggerEvent(eventTarget, Enums.Events.SEGMENTATION_DATA_MODIFIED, { segmentationId: '1', }); console.debug("updated the segmentation's scalar data"); - } else { - this.props.commandsManager.runCommand('hydrateSegmentationsForViewport', { - segmentations, - }); - } + } }; _debug = async () => {