From 123e3a569b6df7989139f7f23a53bcebb311a048 Mon Sep 17 00:00:00 2001 From: Alireza Date: Fri, 3 Feb 2023 09:33:28 -0500 Subject: [PATCH] fix(Segmentation): bump packages to fix segmentation rendering (#3138) * add test mode * bump packages * add reference lines * update yarn lock * fix netlify build * fix num workers * update year in doc --- extensions/cornerstone-dicom-sr/package.json | 4 +- extensions/cornerstone/package.json | 6 +- extensions/measurement-tracking/package.json | 4 +- modes/basic-test-mode/.webpack/webpack.dev.js | 8 + .../basic-test-mode/.webpack/webpack.prod.js | 50 ++ modes/basic-test-mode/LICENSE | 20 + modes/basic-test-mode/README.md | 5 + modes/basic-test-mode/babel.config.js | 1 + modes/basic-test-mode/package.json | 50 ++ modes/basic-test-mode/src/id.js | 5 + modes/basic-test-mode/src/index.js | 217 +++++++ modes/basic-test-mode/src/initToolGroups.js | 225 ++++++++ modes/basic-test-mode/src/toolbarButtons.js | 544 ++++++++++++++++++ modes/longitudinal/src/initToolGroups.js | 5 +- package.json | 3 +- platform/viewer/netlify.toml | 2 +- platform/viewer/public/config/default.js | 3 +- yarn.lock | 63 +- 18 files changed, 1172 insertions(+), 43 deletions(-) create mode 100644 modes/basic-test-mode/.webpack/webpack.dev.js create mode 100644 modes/basic-test-mode/.webpack/webpack.prod.js create mode 100644 modes/basic-test-mode/LICENSE create mode 100644 modes/basic-test-mode/README.md create mode 100644 modes/basic-test-mode/babel.config.js create mode 100644 modes/basic-test-mode/package.json create mode 100644 modes/basic-test-mode/src/id.js create mode 100644 modes/basic-test-mode/src/index.js create mode 100644 modes/basic-test-mode/src/initToolGroups.js create mode 100644 modes/basic-test-mode/src/toolbarButtons.js diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index ebd90b91d..e27ebca0f 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -45,7 +45,7 @@ "dependencies": { "@babel/runtime": "7.16.3", "classnames": "^2.2.6", - "@cornerstonejs/core": "^0.25.0", - "@cornerstonejs/tools": "^0.34.2" + "@cornerstonejs/core": "^0.27.1", + "@cornerstonejs/tools": "^0.36.3" } } diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index d65c76703..28246bfd0 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -43,9 +43,9 @@ }, "dependencies": { "@babel/runtime": "7.17.9", - "@cornerstonejs/core": "^0.25.0", - "@cornerstonejs/streaming-image-volume-loader": "^0.8.2", - "@cornerstonejs/tools": "^0.34.2", + "@cornerstonejs/core": "^0.27.1", + "@cornerstonejs/streaming-image-volume-loader": "^0.11.2", + "@cornerstonejs/tools": "^0.36.3", "@kitware/vtk.js": "25.9.0", "html2canvas": "^1.4.1", "lodash.debounce": "4.0.8", diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index ab4066bf7..0a4df87c2 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -32,8 +32,8 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "classnames": "^2.2.6", - "@cornerstonejs/core": "^0.25.0", - "@cornerstonejs/tools": "^0.34.2", + "@cornerstonejs/core": "^0.27.1", + "@cornerstonejs/tools": "^0.36.3", "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", "dcmjs": "^0.28.3", "prop-types": "^15.6.2", diff --git a/modes/basic-test-mode/.webpack/webpack.dev.js b/modes/basic-test-mode/.webpack/webpack.dev.js new file mode 100644 index 000000000..db7c206b1 --- /dev/null +++ b/modes/basic-test-mode/.webpack/webpack.dev.js @@ -0,0 +1,8 @@ +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + return webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); +}; diff --git a/modes/basic-test-mode/.webpack/webpack.prod.js b/modes/basic-test-mode/.webpack/webpack.prod.js new file mode 100644 index 000000000..ecd887419 --- /dev/null +++ b/modes/basic-test-mode/.webpack/webpack.prod.js @@ -0,0 +1,50 @@ +const webpack = require('webpack'); +const { merge } = require('webpack-merge'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const pkg = require('./../package.json'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); + +const ROOT_DIR = path.join(__dirname, './../'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +const fileName = 'index.umd.js'; +module.exports = (env, argv) => { + const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); + + 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: true, + }, + output: { + path: ROOT_DIR, + library: 'OHIFExtCornerstone', + libraryTarget: 'umd', + libraryExport: 'default', + filename: pkg.main, + }, + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + new MiniCssExtractPlugin({ + filename: './dist/[name].css', + chunkFilename: './dist/[id].css', + }), + ], + }); +}; diff --git a/modes/basic-test-mode/LICENSE b/modes/basic-test-mode/LICENSE new file mode 100644 index 000000000..983c5ef34 --- /dev/null +++ b/modes/basic-test-mode/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2023 Open Health Imaging Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/modes/basic-test-mode/README.md b/modes/basic-test-mode/README.md new file mode 100644 index 000000000..318df9164 --- /dev/null +++ b/modes/basic-test-mode/README.md @@ -0,0 +1,5 @@ +# Test mode + +This mode is used to test the basic functionality of the OHIF viewer +in a controlled environment. It is not intended to be used for +development or production. diff --git a/modes/basic-test-mode/babel.config.js b/modes/basic-test-mode/babel.config.js new file mode 100644 index 000000000..325ca2a8e --- /dev/null +++ b/modes/basic-test-mode/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js'); diff --git a/modes/basic-test-mode/package.json b/modes/basic-test-mode/package.json new file mode 100644 index 000000000..06dded827 --- /dev/null +++ b/modes/basic-test-mode/package.json @@ -0,0 +1,50 @@ +{ + "name": "mode-basic-test-mode", + "version": "0.0.1", + "description": "Basic mode for testing", + "author": "OHIF", + "license": "MIT", + "repository": "OHIF/Viewers", + "main": "dist/index.umd.js", + "module": "src/index.js", + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1.16.0" + }, + "files": [ + "dist", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "keywords": [ + "ohif-mode" + ], + "scripts": { + "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo", + "dev:cornerstone": "yarn run dev", + "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", + "build:package": "yarn run build", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" + }, + "peerDependencies": { + "@ohif/core": "^3.0.0", + "@ohif/extension-default": "^3.0.0", + "@ohif/extension-cornerstone": "^3.0.0", + "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", + "@ohif/extension-dicom-pdf": "^3.0.1", + "@ohif/extension-dicom-video": "^3.0.1", + "@ohif/extension-measurement-tracking": "^3.0.0" + }, + "dependencies": { + "@babel/runtime": "7.16.3" + }, + "devDependencies": { + "webpack": "^5.50.0", + "webpack-merge": "^5.7.3" + } +} diff --git a/modes/basic-test-mode/src/id.js b/modes/basic-test-mode/src/id.js new file mode 100644 index 000000000..ebe5acd98 --- /dev/null +++ b/modes/basic-test-mode/src/id.js @@ -0,0 +1,5 @@ +import packageJson from '../package.json'; + +const id = packageJson.name; + +export { id }; diff --git a/modes/basic-test-mode/src/index.js b/modes/basic-test-mode/src/index.js new file mode 100644 index 000000000..cd96d37c3 --- /dev/null +++ b/modes/basic-test-mode/src/index.js @@ -0,0 +1,217 @@ +import { hotkeys } from '@ohif/core'; +import toolbarButtons from './toolbarButtons.js'; +import { id } from './id.js'; +import initToolGroups from './initToolGroups.js'; + +// Allow this mode by excluding non-imaging modalities such as SR, SEG +// Also, SM is not a simple imaging modalities, so exclude it. +const NON_IMAGE_MODALITIES = ['SM', 'ECG', 'SR', 'SEG']; + +const ohif = { + layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', + sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack', + thumbnailList: '@ohif/extension-default.panelModule.seriesList', +}; + +const tracked = { + measurements: + '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', + thumbnailList: '@ohif/extension-measurement-tracking.panelModule.seriesList', + viewport: + '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', +}; + +const dicomsr = { + sopClassHandler: + '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', +}; + +const dicomvideo = { + sopClassHandler: + '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', +}; + +const dicompdf = { + sopClassHandler: '@ohif/extension-dicom-pdf.sopClassHandlerModule.dicom-pdf', + viewport: '@ohif/extension-dicom-pdf.viewportModule.dicom-pdf', +}; + +const dicomSeg = { + 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', +}; + +const extensionDependencies = { + // Can derive the versions at least process.env.from npm_package_version + '@ohif/extension-default': '^3.0.0', + '@ohif/extension-cornerstone': '^3.0.0', + '@ohif/extension-measurement-tracking': '^3.0.0', + '@ohif/extension-cornerstone-dicom-sr': '^3.0.0', + '@ohif/extension-cornerstone-dicom-seg': '^3.0.0', + '@ohif/extension-dicom-pdf': '^3.0.1', + '@ohif/extension-dicom-video': '^3.0.1', +}; + +function modeFactory() { + return { + // TODO: We're using this as a route segment + // We should not be. + id, + routeName: 'basic-test', + displayName: 'Basic Test Mode', + /** + * Lifecycle hooks + */ + onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { + const { + MeasurementService, + ToolBarService, + ToolGroupService, + } = servicesManager.services; + + MeasurementService.clearMeasurements(); + + // Init Default and SR ToolGroups + initToolGroups(extensionManager, ToolGroupService, commandsManager); + + 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.init(extensionManager); + ToolBarService.addButtons(toolbarButtons); + ToolBarService.createButtonSection('primary', [ + 'MeasurementTools', + 'Zoom', + 'WindowLevel', + 'Pan', + 'Capture', + 'Layout', + 'MPR', + 'Crosshairs', + 'MoreTools', + ]); + }, + onModeExit: ({ servicesManager }) => { + const { + ToolGroupService, + SyncGroupService, + ToolBarService, + SegmentationService, + CornerstoneViewportService, + } = servicesManager.services; + + ToolBarService.reset(); + ToolGroupService.destroy(); + SyncGroupService.destroy(); + SegmentationService.destroy(); + CornerstoneViewportService.destroy(); + }, + validationTags: { + study: [], + series: [], + }, + + isValidMode: function({ modalities }) { + const modalities_list = modalities.split('\\'); + + // Exclude non-image modalities + return !!modalities_list.filter( + modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1 + ).length; + }, + routes: [ + { + path: 'basic-test', + /*init: ({ servicesManager, extensionManager }) => { + //defaultViewerRouteInit + },*/ + layoutTemplate: () => { + return { + id: ohif.layout, + props: { + leftPanels: [tracked.thumbnailList], + rightPanels: [dicomSeg.panel, tracked.measurements], + // rightPanelDefaultClosed: true, // optional prop to start with collapse panels + viewports: [ + { + namespace: tracked.viewport, + displaySetsToDisplay: [ohif.sopClassHandler], + }, + { + namespace: dicomsr.viewport, + displaySetsToDisplay: [dicomsr.sopClassHandler], + }, + { + namespace: dicomvideo.viewport, + displaySetsToDisplay: [dicomvideo.sopClassHandler], + }, + { + namespace: dicompdf.viewport, + displaySetsToDisplay: [dicompdf.sopClassHandler], + }, + { + namespace: dicomSeg.viewport, + displaySetsToDisplay: [dicomSeg.sopClassHandler], + }, + ], + }, + }; + }, + }, + ], + extensions: extensionDependencies, + // Default protocol gets self-registered by default in the init + hangingProtocol: 'default', + // Order is important in sop class handlers when two handlers both use + // the same sop class under different situations. In that case, the more + // general handler needs to come last. For this case, the dicomvideo must + // come first to remove video transfer syntax before ohif uses images + sopClassHandlers: [ + dicomvideo.sopClassHandler, + dicomSeg.sopClassHandler, + ohif.sopClassHandler, + dicompdf.sopClassHandler, + dicomsr.sopClassHandler, + ], + hotkeys: [...hotkeys.defaults.hotkeyBindings], + }; +} + +const mode = { + id, + modeFactory, + extensionDependencies, +}; + +export default mode; diff --git a/modes/basic-test-mode/src/initToolGroups.js b/modes/basic-test-mode/src/initToolGroups.js new file mode 100644 index 000000000..74057f146 --- /dev/null +++ b/modes/basic-test-mode/src/initToolGroups.js @@ -0,0 +1,225 @@ +function initDefaultToolGroup( + extensionManager, + ToolGroupService, + commandsManager, + toolGroupId +) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + + const { toolNames, Enums } = utilityModule.exports; + + const tools = { + active: [ + { + toolName: toolNames.WindowLevel, + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }, + { + toolName: toolNames.Pan, + bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], + }, + { + toolName: toolNames.Zoom, + bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], + }, + { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + ], + passive: [ + { toolName: toolNames.Length }, + { toolName: toolNames.ArrowAnnotate }, + { toolName: toolNames.Bidirectional }, + { toolName: toolNames.DragProbe }, + { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.RectangleROI }, + { toolName: toolNames.StackScroll }, + { toolName: toolNames.Angle }, + { toolName: toolNames.Magnify }, + { toolName: toolNames.SegmentationDisplay }, + ], + // enabled + // disabled + disabled: [{ toolName: toolNames.ReferenceLines }], + }; + + const toolsConfig = { + [toolNames.ArrowAnnotate]: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }; + + ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); +} + +function initSRToolGroup(extensionManager, ToolGroupService, commandsManager) { + const SRUtilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone-dicom-sr.utilityModule.tools' + ); + + const CS3DUtilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + + const { toolNames: SRToolNames } = SRUtilityModule.exports; + const { toolNames, Enums } = CS3DUtilityModule.exports; + const tools = { + active: [ + { + toolName: toolNames.WindowLevel, + bindings: [ + { + mouseButton: Enums.MouseBindings.Primary, + }, + ], + }, + { + toolName: toolNames.Pan, + bindings: [ + { + mouseButton: Enums.MouseBindings.Auxiliary, + }, + ], + }, + { + toolName: toolNames.Zoom, + bindings: [ + { + mouseButton: Enums.MouseBindings.Secondary, + }, + ], + }, + { + toolName: toolNames.StackScrollMouseWheel, + bindings: [], + }, + ], + passive: [ + { toolName: SRToolNames.SRLength }, + { toolName: SRToolNames.SRArrowAnnotate }, + { toolName: SRToolNames.SRBidirectional }, + { toolName: SRToolNames.SREllipticalROI }, + ], + enabled: [ + { + toolName: SRToolNames.DICOMSRDisplay, + bindings: [], + }, + ], + // disabled + }; + + const toolsConfig = { + [toolNames.ArrowAnnotate]: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }; + + const toolGroupId = 'SRToolGroup'; + ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); +} + +function initMPRToolGroup(extensionManager, ToolGroupService, commandsManager) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + + const { toolNames, Enums } = utilityModule.exports; + + const tools = { + active: [ + { + toolName: toolNames.WindowLevel, + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }, + { + toolName: toolNames.Pan, + bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], + }, + { + toolName: toolNames.Zoom, + bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], + }, + { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + ], + passive: [ + { toolName: toolNames.Length }, + { toolName: toolNames.ArrowAnnotate }, + { toolName: toolNames.Bidirectional }, + { toolName: toolNames.DragProbe }, + { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.RectangleROI }, + { toolName: toolNames.StackScroll }, + { toolName: toolNames.Angle }, + { toolName: toolNames.SegmentationDisplay }, + ], + disabled: [ + { toolName: toolNames.Crosshairs }, + { toolName: toolNames.ReferenceLines }, + ], + + // enabled + // disabled + }; + + const toolsConfig = { + [toolNames.Crosshairs]: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, + [toolNames.ArrowAnnotate]: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }; + + ToolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); +} + +function initToolGroups(extensionManager, ToolGroupService, commandsManager) { + initDefaultToolGroup( + extensionManager, + ToolGroupService, + commandsManager, + 'default' + ); + initSRToolGroup(extensionManager, ToolGroupService, commandsManager); + initMPRToolGroup(extensionManager, ToolGroupService, commandsManager); +} + +export default initToolGroups; diff --git a/modes/basic-test-mode/src/toolbarButtons.js b/modes/basic-test-mode/src/toolbarButtons.js new file mode 100644 index 000000000..a8c8a8535 --- /dev/null +++ b/modes/basic-test-mode/src/toolbarButtons.js @@ -0,0 +1,544 @@ +// 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'; + +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, + }; +} + +function _createCommands(commandName, toolName, toolGroupIds) { + return toolGroupIds.map(toolGroupId => ({ + /* It's a command that is being run when the button is clicked. */ + commandName, + commandOptions: { + toolName, + toolGroupId, + }, + context: 'CORNERSTONE', + })); +} + +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 toolbarButtons = [ + // Measurement + { + id: 'MeasurementTools', + type: 'ohif.splitButton', + props: { + groupId: 'MeasurementTools', + isRadio: true, // ? + // Switch? + primary: _createToolButton( + 'Length', + 'tool-length', + 'Length', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Length', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRLength', + toolGroupId: 'SRToolGroup', + }, + // we can use the setToolActive command for this from Cornerstone commandsModule + context: 'CORNERSTONE', + }, + ], + 'Length' + ), + secondary: { + icon: 'chevron-down', + label: '', + isActive: true, + tooltip: 'More Measure Tools', + }, + items: [ + _createToolButton( + 'Length', + 'tool-length', + 'Length', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Length', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRLength', + toolGroupId: 'SRToolGroup', + }, + // we can use the setToolActive command for this from Cornerstone commandsModule + context: 'CORNERSTONE', + }, + ], + 'Length Tool' + ), + _createToolButton( + 'Bidirectional', + 'tool-bidirectional', + 'Bidirectional', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Bidirectional', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRBidirectional', + toolGroupId: 'SRToolGroup', + }, + context: 'CORNERSTONE', + }, + ], + 'Bidirectional Tool' + ), + _createToolButton( + 'ArrowAnnotate', + 'tool-annotate', + 'Annotation', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'ArrowAnnotate', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRArrowAnnotate', + toolGroupId: 'SRToolGroup', + }, + context: 'CORNERSTONE', + }, + ], + 'Arrow Annotate' + ), + _createToolButton( + 'EllipticalROI', + 'tool-elipse', + 'Ellipse', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'EllipticalROI', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SREllipticalROI', + toolGroupId: 'SRToolGroup', + }, + context: 'CORNERSTONE', + }, + ], + 'Ellipse Tool' + ), + ], + }, + }, + // Zoom.. + { + id: 'Zoom', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-zoom', + label: 'Zoom', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Zoom', + }, + context: 'CORNERSTONE', + }, + ], + }, + }, + // 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: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Pan', + }, + context: 'CORNERSTONE', + }, + ], + }, + }, + { + id: 'Capture', + type: 'ohif.action', + props: { + icon: 'tool-capture', + label: 'Capture', + type: 'action', + commands: [ + { + commandName: 'showDownloadViewportModal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + }, + }, + { + id: 'Layout', + type: 'ohif.layoutSelector', + props: { + rows: 3, + columns: 3, + }, + }, + { + id: 'MPR', + type: 'ohif.action', + props: { + type: 'toggle', + icon: 'icon-mpr', + label: 'MPR', + commands: [ + { + commandName: 'toggleMPR', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + }, + }, + { + id: 'Crosshairs', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-crosshair', + label: 'Crosshairs', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolGroupId: 'mpr', + toolName: 'Crosshairs', + }, + context: 'CORNERSTONE', + }, + ], + }, + }, + // 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' + ), + _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' + ), + _createActionButton( + 'TagBrowser', + 'list-bullets', + 'Dicom Tag Browser', + [ + { + commandName: 'openDICOMTagViewer', + commandOptions: {}, + context: 'DEFAULT', + }, + ], + 'Dicom Tag Browser' + ), + ], + }, + }, +]; + +export default toolbarButtons; diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js index 4f80e5cb6..74057f146 100644 --- a/modes/longitudinal/src/initToolGroups.js +++ b/modes/longitudinal/src/initToolGroups.js @@ -175,7 +175,10 @@ function initMPRToolGroup(extensionManager, ToolGroupService, commandsManager) { { toolName: toolNames.Angle }, { toolName: toolNames.SegmentationDisplay }, ], - disabled: [{ toolName: toolNames.Crosshairs }], + disabled: [ + { toolName: toolNames.Crosshairs }, + { toolName: toolNames.ReferenceLines }, + ], // enabled // disabled diff --git a/package.json b/package.json index ac21bda71..633b8010d 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "devDependencies": { "@babel/core": "^7.17.8", "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.18.6", "@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", @@ -93,6 +93,7 @@ "eslint-config-prettier": "^7.2.0", "eslint-config-react-app": "^6.0.0", "eslint-plugin-flowtype": "^7.0.0", + "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-node": "^11.1.0", diff --git a/platform/viewer/netlify.toml b/platform/viewer/netlify.toml index 65af33324..4f86618bb 100644 --- a/platform/viewer/netlify.toml +++ b/platform/viewer/netlify.toml @@ -12,7 +12,7 @@ base = "" build = "yarn run build:viewer:ci" publish = "dist" - ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../ui/ ../core/ ../i18n/" + ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../ui/ ../core/ ../i18n ../../extensions/ ../../modes" # NODE_VERSION in root `.nvmrc` takes priority diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js index b81f34f31..bc3d7e04f 100644 --- a/platform/viewer/public/config/default.js +++ b/platform/viewer/public/config/default.js @@ -8,7 +8,8 @@ window.config = { // helloPage: '@ohif/extension-default.customizationModule.helloPage', }, showStudyList: true, - maxNumberOfWebWorkers: 4, + // some windows systems have issues with more than 3 web workers + maxNumberOfWebWorkers: 3, // below flag is for performance reasons, but it might not work for all servers omitQuotationForMultipartRequest: true, showLoadingIndicator: true, diff --git a/yarn.lock b/yarn.lock index 7a52ed704..9317f3d64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2397,36 +2397,28 @@ resolved "https://registry.npmjs.org/@cornerstonejs/codec-openjph/-/codec-openjph-1.0.3.tgz#4c82642e8a6beb0d263f5a263723aec4225e5300" integrity sha512-PK+9N/JL7ZMGum6OKuReRGGhWXpWjRC9WnltQ6aEzRKEQPMg+3WFiQPZowKCCG1I4whv35DYFRi6wu7RyRaEMQ== -"@cornerstonejs/core@^0.25.0": - version "0.25.0" - resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.25.0.tgz#b392f5526e087c591d5d7ae68782a980aa3488f7" - integrity sha512-MtT3N1+c3apalilsQs8SWaBLD6A0TKo1NLRMY1gFVYfZ9BkyCPlvKxhtgAJthRbr8ME2NMEMs/VjV3/RrFyMVg== +"@cornerstonejs/core@^0.27.1": + version "0.27.1" + resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.27.1.tgz#1c56697cee8071eacfd9b1ee6f59c0fa6071accc" + integrity sha512-RyZicIallTZtr8i1BDkZ3U1JrwPZF7DYj9dj5uwE4WdhoPB+Rp77k5T7BH7gHsNWPyJjPgZwyq+tgV5sdD7zXg== dependencies: detect-gpu "^4.0.45" lodash.clonedeep "4.5.0" -"@cornerstonejs/core@^0.25.1": - version "0.25.1" - resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-0.25.1.tgz#7e5c70858018d99417b61965ac606754542f77ad" - integrity sha512-/Ve9qwGyRRK1uUCrefh9cVG3OIaaS9LQoTUk9YMUbsoWxd6JitXh31XVHjlvNtBYGMRVdz+uGPS2zSxq1vi4aA== +"@cornerstonejs/streaming-image-volume-loader@^0.11.2": + version "0.11.2" + resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.11.2.tgz#75a8502a381a402a28ca2022f4bd2733eb90427f" + integrity sha512-cFkAKjcIk6V2p3hZ2gpvTGMBmPNrDlg/Ku6CTCqz7D+U6pEQPvct2e42G1tG+Y4MtnYgGpC+oYAeu9Zt/C6XlQ== dependencies: - detect-gpu "^4.0.45" - lodash.clonedeep "4.5.0" + "@cornerstonejs/core" "^0.27.1" + cornerstone-wado-image-loader "^4.8.0" -"@cornerstonejs/streaming-image-volume-loader@^0.8.2": - version "0.8.2" - resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.8.2.tgz#058a591501e7fe7ebbe195695c92d047a288290f" - integrity sha512-50Ihn/tp5GJG7Uu0ZtrjSdS/JRfYIyeuRVAXZKvj2jbjuE2fqf+lgXULPgDKQhKiT7nBy6nb2PEpMRzTH/vlnA== +"@cornerstonejs/tools@^0.36.3": + version "0.36.3" + resolved "https://registry.npmjs.org/@cornerstonejs/tools/-/tools-0.36.3.tgz#22769dbcac57f6811f37376ac45ea1041286d4a0" + integrity sha512-popsYtA5Tvr9u7AoRFASwAr9vdxT3S697sb14clydczbbURAk/vHJw1K70P55BJ38jVx/yaGqfZWObFMWbqPLg== dependencies: - "@cornerstonejs/core" "^0.25.0" - cornerstone-wado-image-loader "^4.7.0" - -"@cornerstonejs/tools@^0.34.2": - version "0.34.2" - resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-0.34.2.tgz#645c97583066a872fd35188153cd21b26625473e" - integrity sha512-rbdQuSXGJ++ARS6eUAkBZi0tjmUKVtad0QPk0SZZXlDC2QAQRyIfq7n7oF2AaIR/0ngarTekUzuIy2nKiaOT+Q== - dependencies: - "@cornerstonejs/core" "^0.25.1" + "@cornerstonejs/core" "^0.27.1" lodash.clonedeep "4.5.0" lodash.get "^4.4.2" @@ -9533,10 +9525,10 @@ cornerstone-wado-image-loader@^4.2.1: dicom-parser "^1.8.9" pako "^2.0.4" -cornerstone-wado-image-loader@^4.7.0: - version "4.7.0" - resolved "https://registry.npmjs.org/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.7.0.tgz#0b36ea3e8b0f4554234e3fc6db6260e95afea545" - integrity sha512-tq8YhiS4eQ9FmmxSCuoFmntSPzM9rccPq3WkeHbfglAEwmQjRaZMOR9r0MQaT0se/A9hY37q3xRfe9inhK1Ezw== +cornerstone-wado-image-loader@^4.8.0: + version "4.8.0" + resolved "https://registry.npmjs.org/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.8.0.tgz#64237a807227815bd117af839db4c05ab028d5f0" + integrity sha512-pvAAeH9tzL9PZydYOQqRSmIVoWiaxGDdgp0l/U8KafC0UNTiRyB6Sk0pTXTQqvGK2ZnwM4evjSDS9zyD3PxCmg== dependencies: "@babel/eslint-parser" "^7.19.1" "@cornerstonejs/codec-charls" "^0.1.1" @@ -10105,7 +10097,7 @@ cypress-file-upload@^3.5.3: cypress@^9.5.4: version "9.7.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.7.0.tgz#bf55b2afd481f7a113ef5604aa8b693564b5e744" + resolved "https://registry.npmjs.org/cypress/-/cypress-9.7.0.tgz#bf55b2afd481f7a113ef5604aa8b693564b5e744" integrity sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q== dependencies: "@cypress/request" "^2.88.10" @@ -11336,6 +11328,13 @@ eslint-module-utils@^2.7.3: debug "^3.2.7" find-up "^2.1.0" +eslint-plugin-cypress@^2.12.1: + version "2.12.1" + resolved "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz#9aeee700708ca8c058e00cdafe215199918c2632" + integrity sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA== + dependencies: + globals "^11.12.0" + eslint-plugin-es@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" @@ -11704,9 +11703,9 @@ event-stream@=3.3.4: through "~2.3.1" eventemitter2@^6.4.3: - version "6.4.5" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.5.tgz#97380f758ae24ac15df8353e0cc27f8b95644655" - integrity sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw== + version "6.4.9" + resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" + integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== eventemitter3@^3.1.0: version "3.1.2" @@ -12933,7 +12932,7 @@ global@^4.3.0, global@^4.4.0: min-document "^2.19.0" process "^0.11.10" -globals@^11.1.0, globals@^11.7.0: +globals@^11.1.0, globals@^11.12.0, globals@^11.7.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==