feat(toolbox): Refactor Toolbar and Toolbox to enable sections (#4825)

This commit is contained in:
Alireza 2025-03-06 22:19:47 -05:00 committed by GitHub
parent 86c12246ac
commit cc5cdfb081
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 3527 additions and 2992 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
# Packages # Packages
node_modules node_modules
.cursor/
# Output # Output
build build

314
bun.lock
View File

@ -26,13 +26,13 @@
"optionalDependencies": { "optionalDependencies": {
"@percy/cypress": "^3.1.1", "@percy/cypress": "^3.1.1",
"@playwright/test": "^1.48.0", "@playwright/test": "^1.48.0",
"cypress": "^14.0.0", "cypress": "^14.1.0",
"cypress-file-upload": "^5.0.8", "cypress-file-upload": "^5.0.8",
}, },
}, },
"addOns/externals/devDependencies": { "addOns/externals/devDependencies": {
"name": "@externals/devDependencies", "name": "@externals/devDependencies",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@kitware/vtk.js": "32.1.1", "@kitware/vtk.js": "32.1.1",
@ -120,20 +120,20 @@
}, },
"addOns/externals/dicom-microscopy-viewer": { "addOns/externals/dicom-microscopy-viewer": {
"name": "@externals/dicom-microscopy-viewer", "name": "@externals/dicom-microscopy-viewer",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"dicom-microscopy-viewer": "^0.46.1", "dicom-microscopy-viewer": "^0.46.1",
}, },
}, },
"extensions/cornerstone": { "extensions/cornerstone": {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1", "@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/polymorphic-segmentation": "^3.0.1", "@cornerstonejs/polymorphic-segmentation": "^3.0.1",
"@cornerstonejs/tools": "^3.0.1", "@cornerstonejs/tools": "^3.0.4",
"@itk-wasm/morphological-contour-interpolation": "1.1.0", "@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.1.1", "@kitware/vtk.js": "32.1.1",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
@ -150,9 +150,9 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5", "@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.0.1", "@cornerstonejs/dicom-image-loader": "^3.0.4",
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*", "dcmjs": "*",
"dicom-parser": "^1.8.21", "dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -164,19 +164,19 @@
}, },
"extensions/cornerstone-dicom-pmap": { "extensions/cornerstone-dicom-pmap": {
"name": "@ohif/extension-cornerstone-dicom-pmap", "name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1", "@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@kitware/vtk.js": "32.1.1", "@kitware/vtk.js": "32.1.1",
"react-color": "^2.19.3", "react-color": "^2.19.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.113", "@ohif/i18n": "3.10.0-beta.115",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@ -187,16 +187,16 @@
}, },
"extensions/cornerstone-dicom-rt": { "extensions/cornerstone-dicom-rt": {
"name": "@ohif/extension-cornerstone-dicom-rt", "name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"react-color": "^2.19.3", "react-color": "^2.19.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.113", "@ohif/i18n": "3.10.0-beta.115",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@ -207,19 +207,19 @@
}, },
"extensions/cornerstone-dicom-seg": { "extensions/cornerstone-dicom-seg": {
"name": "@ohif/extension-cornerstone-dicom-seg", "name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1", "@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@kitware/vtk.js": "32.1.1", "@kitware/vtk.js": "32.1.1",
"react-color": "^2.19.3", "react-color": "^2.19.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.113", "@ohif/i18n": "3.10.0-beta.115",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@ -230,19 +230,19 @@
}, },
"extensions/cornerstone-dicom-sr": { "extensions/cornerstone-dicom-sr": {
"name": "@ohif/extension-cornerstone-dicom-sr", "name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1", "@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.1", "@cornerstonejs/tools": "^3.0.4",
"classnames": "^2.3.2", "classnames": "^2.3.2",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-measurement-tracking": "3.10.0-beta.113", "@ohif/extension-measurement-tracking": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*", "dcmjs": "*",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -252,19 +252,19 @@
}, },
"extensions/cornerstone-dynamic-volume": { "extensions/cornerstone-dynamic-volume": {
"name": "@ohif/extension-cornerstone-dynamic-volume", "name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.1", "@cornerstonejs/tools": "^3.0.4",
"classnames": "^2.3.2", "classnames": "^2.3.2",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.113", "@ohif/i18n": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*", "dcmjs": "*",
"dicom-parser": "^1.8.21", "dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -274,7 +274,7 @@
}, },
"extensions/default": { "extensions/default": {
"name": "@ohif/extension-default", "name": "@ohif/extension-default",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/calculate-suv": "^1.1.0", "@cornerstonejs/calculate-suv": "^1.1.0",
@ -282,8 +282,8 @@
"lodash.uniqby": "^4.7.0", "lodash.uniqby": "^4.7.0",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.113", "@ohif/i18n": "3.10.0-beta.115",
"dcmjs": "*", "dcmjs": "*",
"dicomweb-client": "^0.10.4", "dicomweb-client": "^0.10.4",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
@ -297,7 +297,7 @@
}, },
"extensions/dicom-microscopy": { "extensions/dicom-microscopy": {
"name": "@ohif/extension-dicom-microscopy", "name": "@ohif/extension-dicom-microscopy",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-charls": "^1.2.3",
@ -308,10 +308,10 @@
"mathjs": "^12.4.2", "mathjs": "^12.4.2",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.113", "@ohif/i18n": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@ -322,14 +322,14 @@
}, },
"extensions/dicom-pdf": { "extensions/dicom-pdf": {
"name": "@ohif/extension-dicom-pdf", "name": "@ohif/extension-dicom-pdf",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*", "dcmjs": "*",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -339,14 +339,14 @@
}, },
"extensions/dicom-video": { "extensions/dicom-video": {
"name": "@ohif/extension-dicom-video", "name": "@ohif/extension-dicom-video",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*", "dcmjs": "*",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -356,20 +356,20 @@
}, },
"extensions/measurement-tracking": { "extensions/measurement-tracking": {
"name": "@ohif/extension-measurement-tracking", "name": "@ohif/extension-measurement-tracking",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"@xstate/react": "^3.2.2", "@xstate/react": "^3.2.2",
"xstate": "^4.10.0", "xstate": "^4.10.0",
}, },
"peerDependencies": { "peerDependencies": {
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.1", "@cornerstonejs/tools": "^3.0.4",
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"dcmjs": "*", "dcmjs": "*",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
@ -382,14 +382,14 @@
}, },
"extensions/test-extension": { "extensions/test-extension": {
"name": "@ohif/extension-test", "name": "@ohif/extension-test",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"dcmjs": "0.38.0", "dcmjs": "0.38.0",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -399,14 +399,14 @@
}, },
"extensions/tmtv": { "extensions/tmtv": {
"name": "@ohif/extension-tmtv", "name": "@ohif/extension-tmtv",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*", "dcmjs": "*",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -416,7 +416,7 @@
}, },
"modes/basic-dev-mode": { "modes/basic-dev-mode": {
"name": "@ohif/mode-basic-dev-mode", "name": "@ohif/mode-basic-dev-mode",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"i18next": "^17.0.3", "i18next": "^17.0.3",
@ -426,17 +426,17 @@
"webpack-merge": "^5.7.3", "webpack-merge": "^5.7.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113", "@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.113", "@ohif/extension-dicom-video": "3.10.0-beta.115",
}, },
}, },
"modes/basic-test-mode": { "modes/basic-test-mode": {
"name": "@ohif/mode-test", "name": "@ohif/mode-test",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"i18next": "^17.0.3", "i18next": "^17.0.3",
@ -446,19 +446,19 @@
"webpack-merge": "^5.7.3", "webpack-merge": "^5.7.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113", "@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.113", "@ohif/extension-dicom-video": "3.10.0-beta.115",
"@ohif/extension-measurement-tracking": "3.10.0-beta.113", "@ohif/extension-measurement-tracking": "3.10.0-beta.115",
"@ohif/extension-test": "3.10.0-beta.113", "@ohif/extension-test": "3.10.0-beta.115",
}, },
}, },
"modes/longitudinal": { "modes/longitudinal": {
"name": "@ohif/mode-longitudinal", "name": "@ohif/mode-longitudinal",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"i18next": "^17.0.3", "i18next": "^17.0.3",
@ -468,32 +468,32 @@
"webpack-merge": "^5.7.3", "webpack-merge": "^5.7.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113", "@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.113", "@ohif/extension-dicom-video": "3.10.0-beta.115",
"@ohif/extension-measurement-tracking": "3.10.0-beta.113", "@ohif/extension-measurement-tracking": "3.10.0-beta.115",
}, },
}, },
"modes/microscopy": { "modes/microscopy": {
"name": "@ohif/mode-microscopy", "name": "@ohif/mode-microscopy",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"i18next": "^17.0.3", "i18next": "^17.0.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.113", "@ohif/extension-dicom-microscopy": "3.10.0-beta.115",
}, },
}, },
"modes/preclinical-4d": { "modes/preclinical-4d": {
"name": "@ohif/mode-preclinical-4d", "name": "@ohif/mode-preclinical-4d",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
}, },
@ -502,17 +502,17 @@
"webpack-merge": "^5.7.3", "webpack-merge": "^5.7.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dynamic-volume": "3.10.0-beta.113", "@ohif/extension-cornerstone-dynamic-volume": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-tmtv": "3.10.0-beta.113", "@ohif/extension-tmtv": "3.10.0-beta.115",
}, },
}, },
"modes/segmentation": { "modes/segmentation": {
"name": "@ohif/mode-segmentation", "name": "@ohif/mode-segmentation",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"i18next": "^17.0.3", "i18next": "^17.0.3",
@ -544,19 +544,19 @@
"webpack-merge": "^5.7.3", "webpack-merge": "^5.7.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113", "@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.113", "@ohif/extension-dicom-video": "3.10.0-beta.115",
}, },
}, },
"modes/tmtv": { "modes/tmtv": {
"name": "@ohif/mode-tmtv", "name": "@ohif/mode-tmtv",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"i18next": "^17.0.3", "i18next": "^17.0.3",
@ -566,43 +566,43 @@
"webpack-merge": "^5.7.3", "webpack-merge": "^5.7.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113", "@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.113", "@ohif/extension-dicom-video": "3.10.0-beta.115",
"@ohif/extension-measurement-tracking": "3.10.0-beta.113", "@ohif/extension-measurement-tracking": "3.10.0-beta.115",
}, },
}, },
"platform/app": { "platform/app": {
"name": "@ohif/app", "name": "@ohif/app",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-charls": "^1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5", "@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.0.1", "@cornerstonejs/dicom-image-loader": "^3.0.4",
"@emotion/serialize": "^1.1.3", "@emotion/serialize": "^1.1.3",
"@ohif/core": "3.10.0-beta.113", "@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.113", "@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113", "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.113", "@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.113", "@ohif/extension-dicom-microscopy": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113", "@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.113", "@ohif/extension-dicom-video": "3.10.0-beta.115",
"@ohif/extension-test": "3.10.0-beta.113", "@ohif/extension-test": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.113", "@ohif/i18n": "3.10.0-beta.115",
"@ohif/mode-basic-dev-mode": "3.10.0-beta.113", "@ohif/mode-basic-dev-mode": "3.10.0-beta.115",
"@ohif/mode-longitudinal": "3.10.0-beta.113", "@ohif/mode-longitudinal": "3.10.0-beta.115",
"@ohif/mode-microscopy": "3.10.0-beta.113", "@ohif/mode-microscopy": "3.10.0-beta.115",
"@ohif/mode-test": "3.10.0-beta.113", "@ohif/mode-test": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"@ohif/ui-next": "3.10.0-beta.113", "@ohif/ui-next": "3.10.0-beta.115",
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"classnames": "^2.3.2", "classnames": "^2.3.2",
@ -643,7 +643,7 @@
}, },
"platform/cli": { "platform/cli": {
"name": "@ohif/cli", "name": "@ohif/cli",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"bin": { "bin": {
"ohif-cli": "src/index.js" "ohif-cli": "src/index.js"
}, },
@ -667,7 +667,7 @@
}, },
"platform/core": { "platform/core": {
"name": "@ohif/core", "name": "@ohif/core",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"dcmjs": "*", "dcmjs": "*",
@ -692,15 +692,15 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5", "@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.0.1", "@cornerstonejs/dicom-image-loader": "^3.0.4",
"@ohif/ui": "3.10.0-beta.113", "@ohif/ui": "3.10.0-beta.115",
"cornerstone-math": "0.1.9", "cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21", "dicom-parser": "^1.8.21",
}, },
}, },
"platform/i18n": { "platform/i18n": {
"name": "@ohif/i18n", "name": "@ohif/i18n",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"i18next-locize-backend": "^2.0.0", "i18next-locize-backend": "^2.0.0",
@ -725,7 +725,7 @@
}, },
"platform/ui": { "platform/ui": {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@testing-library/react": "^13.1.0", "@testing-library/react": "^13.1.0",
"browser-detect": "^0.2.28", "browser-detect": "^0.2.28",
@ -785,7 +785,7 @@
}, },
"platform/ui-next": { "platform/ui-next": {
"name": "@ohif/ui-next", "name": "@ohif/ui-next",
"version": "3.10.0-beta.113", "version": "3.10.0-beta.115",
"dependencies": { "dependencies": {
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
@ -1092,7 +1092,7 @@
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.0.1", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "ndarray": "^1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "^3.0.1", "@cornerstonejs/tools": "^3.0.1" } }, "sha512-Ym6s4cTpMkojap5QZ8MXZ8hDbs2TMMN/ypqU7wKMwAY2LGoltoeGw0Z/7LL+iostbaSw3fVs+TZzJSKhKw8Bug=="], "@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.0.4", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "ndarray": "^1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "^3.0.4", "@cornerstonejs/tools": "^3.0.4" } }, "sha512-TJtW0biDf0wgi+XxQFqvKXT/Cf8ueyUruohH9n/RJsWiyE8z1/LOBxOqCZNkToGGs1EbO51T/PMPIUWordW43w=="],
"@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="], "@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="],
@ -1104,13 +1104,13 @@
"@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="], "@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="],
"@cornerstonejs/core": ["@cornerstonejs/core@3.0.1", "", { "dependencies": { "@kitware/vtk.js": "32.9.0", "comlink": "^4.4.1", "gl-matrix": "^3.4.3" } }, "sha512-K+cOoN/TzJIPRRoaWWNkhJ+2CdkBBX8BA64DG0aBVOthlwJPRqK2YQE6PTTVRx18mjMA4to/m3de0emFNKrTzQ=="], "@cornerstonejs/core": ["@cornerstonejs/core@3.0.4", "", { "dependencies": { "@kitware/vtk.js": "32.9.0", "comlink": "^4.4.1", "gl-matrix": "^3.4.3" } }, "sha512-6rMiqhBjyz3wsalPOAFjThfPUJnWVOs7Xqe3xLFvqd1qIFW1q42bqlQ7ak2DRMUMtC1nLURk6YIMhkWxK+t+7A=="],
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.0.1", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.5", "comlink": "^4.4.1", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.0.1", "dicom-parser": "^1.8.9" } }, "sha512-t4bI6rVOt4J8YOHRR9Mh9ClInajc98qqI6fwsByiXmtc2Y4INrZ1uaRspFnOJBosbr+EwsJBV7iT2yt6Jk6CjA=="], "@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.0.4", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.5", "comlink": "^4.4.1", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.0.4", "dicom-parser": "^1.8.9" } }, "sha512-w0NoutE7+/C2FeT1YHo0cnMJpZ17ARH0qwyGwWockFwuJkUVjRcU5ZW1sT0ABNOPUHqZse/K/YmH2IX+ppaScQ=="],
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.0.1", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.0.1", "@cornerstonejs/tools": "^3.0.1", "@kitware/vtk.js": "^32.9.0" } }, "sha512-oCT+U7iTv5oKDHOcEobtZ9gez2aNh/uJkyudO+edd5ndzwXpU+mXp/wre5D9xH5CBCigFG3nDJTZJrpzUHYKfg=="], "@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.0.1", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.0.1", "@cornerstonejs/tools": "^3.0.1", "@kitware/vtk.js": "^32.9.0" } }, "sha512-oCT+U7iTv5oKDHOcEobtZ9gez2aNh/uJkyudO+edd5ndzwXpU+mXp/wre5D9xH5CBCigFG3nDJTZJrpzUHYKfg=="],
"@cornerstonejs/tools": ["@cornerstonejs/tools@3.0.1", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.0.1", "@kitware/vtk.js": "32.9.0", "@types/d3-array": "^3.0.4", "@types/d3-interpolate": "^3.0.1", "d3-array": "^3.2.3", "d3-interpolate": "^3.0.1", "gl-matrix": "^3.4.3" } }, "sha512-fRjoXJ46mdpYc5JZ/FUn0g1vCK4LohnNjgZl+O72HK2reMdc5/dQeCueJCT+tBMay04sJMUHm0tAke2DN10FBQ=="], "@cornerstonejs/tools": ["@cornerstonejs/tools@3.0.4", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.0.4", "@kitware/vtk.js": "32.9.0", "@types/d3-array": "^3.0.4", "@types/d3-interpolate": "^3.0.1", "d3-array": "^3.2.3", "d3-interpolate": "^3.0.1", "gl-matrix": "^3.4.3" } }, "sha512-gkenXhOS6lYYmQhKDwayojeObTU/KiG9k+JEC/hoOBhRdoFZljyXfQtnljKFHL+G9rkdbNKxXq/a3+C9Bn7EVg=="],
"@csstools/postcss-cascade-layers": ["@csstools/postcss-cascade-layers@1.1.1", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.2", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA=="], "@csstools/postcss-cascade-layers": ["@csstools/postcss-cascade-layers@1.1.1", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.2", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA=="],
@ -2638,7 +2638,7 @@
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"cypress": ["cypress@14.0.1", "", { "dependencies": { "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, "bin": { "cypress": "bin/cypress" } }, "sha512-gBAvKZE3f6eBaW1v8OtrwAFP90rjNZjjOO40M2KvOvmwVXk96Ps5Yjyck1EzGkXmNCaC/8kXFOY/1KD/wsaWpQ=="], "cypress": ["cypress@14.1.0", "", { "dependencies": { "@cypress/request": "^3.0.7", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", "ci-info": "^4.1.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, "bin": { "cypress": "bin/cypress" } }, "sha512-pPPj8Uu9NwjaaiXAEcjYZZmgsq6v9Zs1Nw6a+zRF+ANgYSNhH4S32SjFRsvMcuOHR/8dp4GBJhBPqIPSs+TxaA=="],
"cypress-file-upload": ["cypress-file-upload@5.0.8", "", { "peerDependencies": { "cypress": ">3.0.0" } }, "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g=="], "cypress-file-upload": ["cypress-file-upload@5.0.8", "", { "peerDependencies": { "cypress": ">3.0.0" } }, "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g=="],

View File

@ -46,8 +46,8 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1", "@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@kitware/vtk.js": "32.1.1", "@kitware/vtk.js": "32.1.1",
"react-color": "^2.19.3" "react-color": "^2.19.3"
} }

View File

@ -46,8 +46,8 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1", "@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@kitware/vtk.js": "32.1.1", "@kitware/vtk.js": "32.1.1",
"react-color": "^2.19.3" "react-color": "^2.19.3"
} }

View File

@ -329,9 +329,19 @@ const commandsModule = ({
}, },
setThresholdRange: ({ setThresholdRange: ({
value, value,
toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush'], toolNames = [
'ThresholdCircularBrush',
'ThresholdSphereBrush',
'ThresholdCircularBrushDynamic',
'ThresholdSphereBrushDynamic',
],
}) => { }) => {
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { const toolGroupIds = toolGroupService.getToolGroupIds();
if (!toolGroupIds?.length) {
return;
}
for (const toolGroupId of toolGroupIds) {
const toolGroup = toolGroupService.getToolGroup(toolGroupId); const toolGroup = toolGroupService.getToolGroup(toolGroupId);
toolNames?.forEach(toolName => { toolNames?.forEach(toolName => {
toolGroup.setToolConfiguration(toolName, { toolGroup.setToolConfiguration(toolName, {
@ -340,7 +350,7 @@ const commandsModule = ({
}, },
}); });
}); });
}); }
}, },
}; };

View File

@ -46,9 +46,9 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1", "@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.1", "@cornerstonejs/tools": "^3.0.4",
"classnames": "^2.3.2" "classnames": "^2.3.2"
} }
} }

View File

@ -42,8 +42,8 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.1", "@cornerstonejs/tools": "^3.0.4",
"classnames": "^2.3.2" "classnames": "^2.3.2"
} }
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { DynamicDataPanel } from './panels'; import { DynamicDataPanel } from './panels';
import { Toolbox } from '@ohif/ui-next'; import { Toolbox } from '@ohif/extension-default';
import { PanelSegmentation } from '@ohif/extension-cornerstone'; import { PanelSegmentation } from '@ohif/extension-cornerstone';
import DynamicExport from './panels/DynamicExport'; import DynamicExport from './panels/DynamicExport';
@ -19,9 +19,6 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager, co
return ( return (
<> <>
<Toolbox <Toolbox
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
buttonSectionId="dynamic-toolbox" buttonSectionId="dynamic-toolbox"
title="Threshold Tools" title="Threshold Tools"
/> />

View File

@ -38,7 +38,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5", "@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.0.1", "@cornerstonejs/dicom-image-loader": "^3.0.4",
"@ohif/core": "3.10.0-beta.116", "@ohif/core": "3.10.0-beta.116",
"@ohif/ui": "3.10.0-beta.116", "@ohif/ui": "3.10.0-beta.116",
"dcmjs": "*", "dcmjs": "*",
@ -51,10 +51,10 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1", "@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/polymorphic-segmentation": "^3.0.1", "@cornerstonejs/polymorphic-segmentation": "^3.0.1",
"@cornerstonejs/tools": "^3.0.1", "@cornerstonejs/tools": "^3.0.4",
"@itk-wasm/morphological-contour-interpolation": "1.1.0", "@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.1.1", "@kitware/vtk.js": "32.1.1",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",

View File

@ -388,7 +388,12 @@ function commandsModule({
arrowTextCallback: ({ callback }) => { arrowTextCallback: ({ callback }) => {
const labelConfig = customizationService.getCustomization('measurementLabels'); const labelConfig = customizationService.getCustomization('measurementLabels');
const renderContent = customizationService.getCustomization('ui.labellingComponent'); const renderContent = customizationService.getCustomization('ui.labellingComponent');
callInputDialogAutoComplete(uiDialogService, callback, {}, labelConfig, renderContent);
callInputDialogAutoComplete({
uiDialogService,
labelConfig,
renderContent,
});
}, },
toggleCine: () => { toggleCine: () => {
const { viewports } = viewportGridService.getState(); const { viewports } = viewportGridService.getState();

View File

@ -23,7 +23,6 @@ export function getWindowLevelActionMenu({
const WindowLevelActionMenu = customizationService.getCustomization( const WindowLevelActionMenu = customizationService.getCustomization(
'viewportActionMenu.windowLevelActionMenu' 'viewportActionMenu.windowLevelActionMenu'
); );
console.debug('🚀 ~ WindowLevelActionMenu:', WindowLevelActionMenu);
const displaySetPresets = displaySets const displaySetPresets = displaySets
.filter(displaySet => presets[displaySet.Modality]) .filter(displaySet => presets[displaySet.Modality])
.map(displaySet => { .map(displaySet => {

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Toolbox } from '@ohif/ui-next'; import { Toolbox } from '@ohif/extension-default';
import PanelSegmentation from './panels/PanelSegmentation'; import PanelSegmentation from './panels/PanelSegmentation';
import ActiveViewportWindowLevel from './components/ActiveViewportWindowLevel'; import ActiveViewportWindowLevel from './components/ActiveViewportWindowLevel';
import PanelMeasurement from './panels/PanelMeasurement'; import PanelMeasurement from './panels/PanelMeasurement';
@ -36,14 +36,8 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
return ( return (
<> <>
<Toolbox <Toolbox
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
buttonSectionId="segmentationToolbox" buttonSectionId="segmentationToolbox"
title="Segmentation Tools" title="Segmentation Tools"
configuration={{
...configuration,
}}
/> />
<PanelSegmentation <PanelSegmentation
commandsManager={commandsManager} commandsManager={commandsManager}

View File

@ -87,61 +87,10 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
return { return {
disabled: false, disabled: false,
// Todo: isActive right now is used for nested buttons where the primary
// button needs to be fully rounded (vs partial rounded) when active
// otherwise it does not have any other use
isActive: isPrimaryActive, isActive: isPrimaryActive,
}; };
}, },
}, },
{
name: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
evaluate: ({ viewportId, button, itemId }) => {
const { items } = button.props;
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
if (!toolGroup) {
return {
primary: button.props.primary,
items,
};
}
const activeToolName = toolGroup.getActivePrimaryMouseButtonTool();
// check if the active toolName is part of the items then we need
// to move it to the primary button
const activeToolIndex = items.findIndex(item => {
const toolName = toolbarService.getToolNameForButton(item);
return toolName === activeToolName;
});
// if there is an active tool in the items dropdown bound to the primary mouse/touch
// we should show that no matter what
if (activeToolIndex > -1) {
return {
primary: items[activeToolIndex],
items,
};
}
if (!itemId) {
return {
primary: button.props.primary,
items,
};
}
// other wise we can move the clicked tool to the primary button
const clickedItemProps = items.find(item => item.id === itemId || item.itemId === itemId);
return {
primary: clickedItemProps,
items,
};
},
},
{ {
name: 'evaluate.action', name: 'evaluate.action',
evaluate: () => { evaluate: () => {

View File

@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import { roundNumber } from '@ohif/core/src/utils'; import { roundNumber } from '@ohif/core/src/utils';
import { SegmentationData } from '../services/SegmentationService/SegmentationService'; import { SegmentationData } from '../services/SegmentationService/SegmentationService';
import { useSystem } from '@ohif/core';
function mapSegmentationToDisplay(segmentation, customizationService) { function mapSegmentationToDisplay(segmentation, customizationService) {
const { label, segments } = segmentation; const { label, segments } = segmentation;
@ -83,11 +84,12 @@ function mapSegmentationToDisplay(segmentation, customizationService) {
* @param options.debounceTime - Debounce time in milliseconds for updates. * @param options.debounceTime - Debounce time in milliseconds for updates.
* @returns An array of segmentation data. * @returns An array of segmentation data.
*/ */
export function useSegmentations({ export function useSegmentations(options: {
servicesManager, subscribeToDataModified?: boolean;
subscribeToDataModified = false, debounceTime?: number;
debounceTime = 0, }): SegmentationData[] {
}: withAppTypes<{ debounceTime?: number }>): SegmentationData[] { const { subscribeToDataModified = false, debounceTime = 0 } = options || {};
const { servicesManager } = useSystem();
const { segmentationService, customizationService } = servicesManager.services; const { segmentationService, customizationService } = servicesManager.services;
const [segmentations, setSegmentations] = useState<SegmentationData[]>([]); const [segmentations, setSegmentations] = useState<SegmentationData[]>([]);

View File

@ -1,17 +1,24 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { ToolButton } from '@ohif/ui-next'; import { ToolButton } from '@ohif/ui-next';
import { useToolbar } from '@ohif/core/src/hooks/useToolbar';
/** /**
* Wraps the ToolButtonList component to handle the OHIF toolbar button structure * Wraps the ToolButtonList component to handle the OHIF toolbar button structure
* @param props - Component props * @param props - Component props
* @returns Component * @returns Component
*/ */
export function ToolBoxButtonGroupWrapper({ groupId, items, onInteraction, ...props }) { export function ToolBoxButtonGroupWrapper({ groupId, buttonSection, ...props }) {
if (!items || !groupId) { const { onInteraction, toolbarButtons } = useToolbar({
buttonSection,
});
if (!groupId) {
return null; return null;
} }
const items = toolbarButtons.map(button => button.componentProps);
return ( return (
<div className="bg-popover flex flex-row space-x-1 rounded-md px-0 py-0"> <div className="bg-popover flex flex-row space-x-1 rounded-md px-0 py-0">
{items.map(item => ( {items.map(item => (
@ -20,16 +27,22 @@ export function ToolBoxButtonGroupWrapper({ groupId, items, onInteraction, ...pr
key={item.id} key={item.id}
size="small" size="small"
className={props.disabled && 'text-primary'} className={props.disabled && 'text-primary'}
onInteraction={() => onInteraction={event => {
onInteraction?.({ groupId, itemId: item.id, commands: item.commands }) onInteraction?.({
} event,
groupId,
commands: item.commands,
itemId: item.id,
item,
});
}}
/> />
))} ))}
</div> </div>
); );
} }
export function ToolBoxButtonWrapper({ onInteraction, ...props }) { export function ToolBoxButtonWrapper({ onInteraction, options, ...props }) {
return ( return (
<div className="bg-popover flex flex-row rounded-md px-0 py-0"> <div className="bg-popover flex flex-row rounded-md px-0 py-0">
<ToolButton <ToolButton
@ -37,7 +50,14 @@ export function ToolBoxButtonWrapper({ onInteraction, ...props }) {
id={props.id} id={props.id}
size="small" size="small"
className={classNames(props.disabled && 'text-primary')} className={classNames(props.disabled && 'text-primary')}
onInteraction={() => onInteraction?.({ itemId: props.id, commands: props.commands })} onInteraction={event => {
onInteraction?.({
event,
itemId: props.id,
commands: props.commands,
options,
});
}}
/> />
</div> </div>
); );

View File

@ -7,23 +7,11 @@ import {
ToolButtonListItem, ToolButtonListItem,
ToolButtonListDivider, ToolButtonListDivider,
} from '@ohif/ui-next'; } from '@ohif/ui-next';
import { useToolbar } from '@ohif/core/src';
interface ButtonItem {
id: string;
icon?: string;
label?: string;
tooltip?: string;
isActive?: boolean;
disabledText?: string;
commands?: Record<string, unknown>;
disabled?: boolean;
className?: string;
}
interface ToolButtonListWrapperProps { interface ToolButtonListWrapperProps {
groupId: string; groupId: string;
primary: ButtonItem; buttonSection: string;
items: ButtonItem[];
onInteraction?: (details: { onInteraction?: (details: {
groupId: string; groupId: string;
itemId: string; itemId: string;
@ -39,10 +27,22 @@ interface ToolButtonListWrapperProps {
*/ */
export default function ToolButtonListWrapper({ export default function ToolButtonListWrapper({
groupId, groupId,
primary, buttonSection,
items,
onInteraction,
}: ToolButtonListWrapperProps) { }: ToolButtonListWrapperProps) {
const { onInteraction, toolbarButtons } = useToolbar({
buttonSection,
});
if (!toolbarButtons?.length) {
return null;
}
const primary =
toolbarButtons.find(button => button.componentProps.isActive)?.componentProps ||
toolbarButtons[0].componentProps;
const items = toolbarButtons.map(button => button.componentProps);
return ( return (
<ToolButtonList> <ToolButtonList>
<ToolButtonListDefault> <ToolButtonListDefault>
@ -63,20 +63,22 @@ export default function ToolButtonListWrapper({
<ToolButtonListDivider className={primary.isActive ? 'opacity-0' : 'opacity-100'} /> <ToolButtonListDivider className={primary.isActive ? 'opacity-0' : 'opacity-100'} />
<div data-cy={`${groupId}-split-button-secondary`}> <div data-cy={`${groupId}-split-button-secondary`}>
<ToolButtonListDropDown> <ToolButtonListDropDown>
{items.map(item => ( {items.map(item => {
<ToolButtonListItem return (
key={item.id} <ToolButtonListItem
{...item} key={item.id}
data-cy={item.id} {...item}
data-tool={item.id} data-cy={item.id}
data-active={item.isActive} data-tool={item.id}
onSelect={() => data-active={item.isActive}
onInteraction?.({ groupId, itemId: item.id, commands: item.commands }) onSelect={() =>
} onInteraction?.({ groupId, itemId: item.id, commands: item.commands })
> }
<span className="pl-1">{item.label || item.tooltip || item.id}</span> >
</ToolButtonListItem> <span className="pl-1">{item.label || item.tooltip || item.id}</span>
))} </ToolButtonListItem>
);
})}
</ToolButtonListDropDown> </ToolButtonListDropDown>
</div> </div>
</ToolButtonList> </ToolButtonList>

View File

@ -19,6 +19,7 @@ function ToolbarButtonGroupWithServices({ groupId, items, onInteraction, size })
groupId, groupId,
itemId: item.id, itemId: item.id,
commands: item.commands, commands: item.commands,
item,
}); });
}} }}
// Note: this is necessary since tooltip will add // Note: this is necessary since tooltip will add

View File

@ -23,7 +23,7 @@ function ToolbarSplitButtonWithServices({
onInteraction({ onInteraction({
groupId, groupId,
itemId: item.id, itemId: item.id,
commands: item.commands, item,
}); });
}, },
})), })),

View File

@ -16,6 +16,10 @@ import { panelGroupDefinition } from './constants/panels';
* @param width the max and min width to set on the element * @param width the max and min width to set on the element
*/ */
const setMinMaxWidth = (elem, width?) => { const setMinMaxWidth = (elem, width?) => {
if (!elem) {
return;
}
elem.style.minWidth = width === undefined ? '' : `${width}px`; elem.style.minWidth = width === undefined ? '' : `${width}px`;
elem.style.maxWidth = elem.style.minWidth; elem.style.maxWidth = elem.style.minWidth;
}; };

View File

@ -20,7 +20,9 @@ function ReportDialog({ dataSources, hide, onSave, onCancel }: ReportDialogProps
dataSources?.[0]?.value ?? null dataSources?.[0]?.value ?? null
); );
const handleSave = (reportName: string) => { const [reportName, setReportName] = useState('');
const handleSave = () => {
onSave({ onSave({
reportName, reportName,
dataSource: selectedDataSource, dataSource: selectedDataSource,
@ -62,7 +64,11 @@ function ReportDialog({ dataSources, hide, onSave, onCancel }: ReportDialogProps
</div> </div>
)} )}
<div className={showDataSourceSelect ? 'mt-1 w-2/3' : 'w-full'}> <div className={showDataSourceSelect ? 'mt-1 w-2/3' : 'w-full'}>
<InputDialog> <InputDialog
value={reportName}
onChange={setReportName}
submitOnEnter
>
<InputDialog.Field> <InputDialog.Field>
<InputDialog.Input placeholder="Report name" /> <InputDialog.Input placeholder="Report name" />
</InputDialog.Field> </InputDialog.Field>

View File

@ -60,27 +60,6 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
name: 'ohif.progressDropdown', name: 'ohif.progressDropdown',
defaultComponent: ProgressDropdownWithService, defaultComponent: ProgressDropdownWithService,
}, },
{
name: 'evaluate.group.promoteToPrimary',
evaluate: ({ viewportId, button, itemId }) => {
const { items } = button.props;
if (!itemId) {
return {
primary: button.props.primary,
items,
};
}
// other wise we can move the clicked tool to the primary button
const clickedItemProps = items.find(item => item.id === itemId || item.itemId === itemId);
return {
primary: clickedItemProps,
items,
};
},
},
{ {
name: 'evaluate.cine', name: 'evaluate.cine',
evaluate: () => { evaluate: () => {

View File

@ -34,6 +34,7 @@ import promptLabelAnnotation from './utils/promptLabelAnnotation';
import usePatientInfo from './hooks/usePatientInfo'; import usePatientInfo from './hooks/usePatientInfo';
import { PanelStudyBrowserHeader } from './Panels/StudyBrowser/PanelStudyBrowserHeader'; import { PanelStudyBrowserHeader } from './Panels/StudyBrowser/PanelStudyBrowserHeader';
import * as utils from './utils'; import * as utils from './utils';
import { Toolbox } from './utils';
import MoreDropdownMenu from './Components/MoreDropdownMenu'; import MoreDropdownMenu from './Components/MoreDropdownMenu';
import requestDisplaySetCreationForStudy from './Panels/requestDisplaySetCreationForStudy'; import requestDisplaySetCreationForStudy from './Panels/requestDisplaySetCreationForStudy';
const defaultExtension: Types.Extensions.Extension = { const defaultExtension: Types.Extensions.Extension = {
@ -97,6 +98,7 @@ export {
usePatientInfo, usePatientInfo,
PanelStudyBrowserHeader, PanelStudyBrowserHeader,
utils, utils,
Toolbox,
MoreDropdownMenu, MoreDropdownMenu,
requestDisplaySetCreationForStudy, requestDisplaySetCreationForStudy,
callInputDialog, callInputDialog,

View File

@ -0,0 +1,122 @@
import React from 'react';
import { PanelSection, ToolSettings } from '@ohif/ui-next';
import { useSystem, useToolbar } from '@ohif/core';
import classnames from 'classnames';
interface ButtonProps {
isActive?: boolean;
options?: unknown;
}
/**
* A toolbox is a collection of buttons and commands that they invoke, used to provide
* custom control panels to users. This component is a generic UI component that
* interacts with services and commands in a generic fashion. While it might
* seem unconventional to import it from the UI and integrate it into the JSX,
* it belongs in the UI components as there isn't anything in this component that
* couldn't be used for a completely different type of app. It plays a crucial
* role in enhancing the app with a toolbox by providing a way to integrate
* and display various tools and their corresponding options
*/
export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; title: string }) {
const { servicesManager } = useSystem();
const { toolbarService } = servicesManager.services;
const { toolbarButtons: toolboxSections, onInteraction } = useToolbar({
servicesManager,
buttonSection: buttonSectionId,
});
if (!toolboxSections.length) {
return null;
}
// Ensure we have proper button sections at the top level.
if (!toolboxSections.every(section => section.componentProps.buttonSection)) {
throw new Error(
'Toolbox accepts only button sections at the top level, not buttons. Create at least one button section.'
);
}
// Helper to check a list of buttons for an active tool.
const findActiveOptions = (buttons: any[]): unknown => {
for (const tool of buttons) {
if (tool.componentProps.isActive) {
return tool.componentProps.options;
}
if (tool.componentProps.buttonSection) {
const nestedButtons = toolbarService.getButtonPropsInButtonSection(
tool.componentProps.buttonSection
) as ButtonProps[];
const activeNested = nestedButtons.find(nested => nested.isActive);
if (activeNested) {
return activeNested.options;
}
}
}
return null;
};
// Look for active tool options across all sections.
const activeToolOptions = toolboxSections.reduce((activeOptions, section) => {
if (activeOptions) {
return activeOptions;
}
const sectionId = section.componentProps.buttonSection;
const buttons = toolbarService.getButtonSection(sectionId);
return findActiveOptions(buttons);
}, null);
// Define the interaction handler once.
const handleInteraction = ({ itemId }: { itemId: string }) => {
onInteraction?.({ itemId });
};
return (
<PanelSection>
<PanelSection.Header>
<span>{title}</span>
</PanelSection.Header>
<PanelSection.Content className="flex-shrink-0 border-none">
{toolboxSections.map(section => {
const sectionId = section.componentProps.buttonSection;
const buttons = toolbarService.getButtonSection(sectionId) as any[];
return (
<div
key={sectionId}
className="bg-muted flex flex-wrap space-x-2 py-2 px-1"
>
{buttons.map(tool => {
if (!tool) {
return null;
}
const { id, Component, componentProps } = tool;
return (
<div
key={id}
className={classnames('ml-1')}
>
<Component
{...componentProps}
id={id}
onInteraction={handleInteraction}
size="toolbox"
servicesManager={servicesManager}
/>
</div>
);
})}
</div>
);
})}
{activeToolOptions && (
<div className="bg-primary-dark h-auto px-2">
<ToolSettings options={activeToolOptions} />
</div>
)}
</PanelSection.Content>
</PanelSection>
);
}

View File

@ -57,6 +57,10 @@ export async function callInputDialog({
submitOnEnter = true, submitOnEnter = true,
}: { }: {
uiDialogService: AppTypes.UIDialogService; uiDialogService: AppTypes.UIDialogService;
defaultValue?: string;
title?: string;
placeholder?: string;
submitOnEnter?: boolean;
}) { }) {
const dialogId = 'dialog-enter-annotation'; const dialogId = 'dialog-enter-annotation';

View File

@ -1 +1,2 @@
export { addIcon } from './addIcon'; export { addIcon } from './addIcon';
export * from './Toolbox';

View File

@ -32,8 +32,8 @@
"start": "yarn run dev" "start": "yarn run dev"
}, },
"peerDependencies": { "peerDependencies": {
"@cornerstonejs/core": "^3.0.1", "@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.1", "@cornerstonejs/tools": "^3.0.4",
"@ohif/core": "3.10.0-beta.116", "@ohif/core": "3.10.0-beta.116",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.116", "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.116",
"@ohif/extension-default": "3.10.0-beta.116", "@ohif/extension-default": "3.10.0-beta.116",

View File

@ -1,9 +1,11 @@
import React, { useState, useCallback, useReducer, useEffect } from 'react'; import React, { useCallback, useReducer } from 'react';
import { Button } from '@ohif/ui'; import { Button } from '@ohif/ui';
import ROIThresholdConfiguration, { import ROIThresholdConfiguration, {
ROI_STAT, ROI_STAT,
} from './PanelROIThresholdSegmentation/ROIThresholdConfiguration'; } from './PanelROIThresholdSegmentation/ROIThresholdConfiguration';
import * as cs3dTools from '@cornerstonejs/tools'; import * as cs3dTools from '@cornerstonejs/tools';
import { useSystem } from '@ohif/core';
import { useSegmentations } from '@ohif/extension-cornerstone';
const LOWER_CT_THRESHOLD_DEFAULT = -1024; const LOWER_CT_THRESHOLD_DEFAULT = -1024;
const UPPER_CT_THRESHOLD_DEFAULT = 1024; const UPPER_CT_THRESHOLD_DEFAULT = 1024;
@ -40,9 +42,10 @@ function reducer(state, action) {
} }
} }
function RectangleROIOptions({ servicesManager, commandsManager }: withAppTypes) { function RectangleROIOptions() {
const { segmentationService } = servicesManager.services; const { commandsManager } = useSystem();
const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); const segmentations = useSegmentations();
const activeSegmentation = segmentations[0];
const runCommand = useCallback( const runCommand = useCallback(
(commandName, commandOptions = {}) => { (commandName, commandOptions = {}) => {
@ -61,58 +64,20 @@ function RectangleROIOptions({ servicesManager, commandsManager }: withAppTypes)
}); });
const handleROIThresholding = useCallback(() => { const handleROIThresholding = useCallback(() => {
const segmentationId = selectedSegmentationId; if (!activeSegmentation) {
return;
}
const segmentationId = activeSegmentation.segmentationId;
const activeSegmentIndex = const activeSegmentIndex =
cs3dTools.segmentation.segmentIndex.getActiveSegmentIndex(segmentationId); cs3dTools.segmentation.segmentIndex.getActiveSegmentIndex(segmentationId);
// run the threshold based on the active segment index
// Todo: later find a way to associate each rectangle with a segment (e.g., maybe with color?)
runCommand('thresholdSegmentationByRectangleROITool', { runCommand('thresholdSegmentationByRectangleROITool', {
segmentationId, segmentationId,
config, config,
segmentIndex: activeSegmentIndex, segmentIndex: activeSegmentIndex,
}); });
}, [selectedSegmentationId, config]); }, [activeSegmentation, config]);
useEffect(() => {
const segmentations = segmentationService.getSegmentationRepresentations();
if (!segmentations.length) {
return;
}
const isActive = segmentations.find(seg => seg.isActive);
setSelectedSegmentationId(isActive.id);
}, []);
/**
* Update UI based on segmentation changes (added, removed, updated)
*/
useEffect(() => {
// ~~ Subscription
const updated = segmentationService.EVENTS.SEGMENTATION_MODIFIED;
const subscriptions = [];
[updated].forEach(evt => {
const { unsubscribe } = segmentationService.subscribe(evt, () => {
const segmentations = segmentationService.getSegmentationRepresentations();
if (!segmentations.length) {
return;
}
const isActive = segmentations.find(seg => seg.isActive);
setSelectedSegmentationId(isActive.id);
});
subscriptions.push(unsubscribe);
});
return () => {
subscriptions.forEach(unsub => {
unsub();
});
};
}, []);
return ( return (
<div className="invisible-scrollbar mb-2 flex flex-col overflow-y-auto overflow-x-hidden"> <div className="invisible-scrollbar mb-2 flex flex-col overflow-y-auto overflow-x-hidden">
@ -121,7 +86,7 @@ function RectangleROIOptions({ servicesManager, commandsManager }: withAppTypes)
dispatch={dispatch} dispatch={dispatch}
runCommand={runCommand} runCommand={runCommand}
/> />
{selectedSegmentationId !== null && ( {activeSegmentation && (
<Button <Button
className="mt-2 !h-[26px] !w-[75px]" className="mt-2 !h-[26px] !w-[75px]"
onClick={handleROIThresholding} onClick={handleROIThresholding}

View File

@ -102,7 +102,7 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
// corrected PT vs the non-attenuation correct PT) // corrected PT vs the non-attenuation correct PT)
let ptDisplaySet = null; let ptDisplaySet = null;
for (const [viewportId, viewportDetails] of viewportMatchDetails) { for (const [, viewportDetails] of viewportMatchDetails) {
const { displaySetsInfo } = viewportDetails; const { displaySetsInfo } = viewportDetails;
const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) => const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) =>
displaySetService.getDisplaySetByUID(displaySetInstanceUID) displaySetService.getDisplaySetByUID(displaySetInstanceUID)
@ -199,15 +199,21 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
const { representationData } = segmentation; const { representationData } = segmentation;
const { displaySetMatchDetails: matchDetails } = hangingProtocolService.getMatchDetails(); const { displaySetMatchDetails: matchDetails } = hangingProtocolService.getMatchDetails();
const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use const ctDisplaySetMatch = matchDetails.get('ctDisplaySet');
const ptDisplaySetMatch = matchDetails.get('ptDisplaySet');
const ctDisplaySet = matchDetails.get('ctDisplaySet'); const ctDisplaySet = displaySetService.getDisplaySetByUID(
const ctVolumeId = `${volumeLoaderScheme}:${ctDisplaySet.displaySetInstanceUID}`; // VolumeId with loader id + volume id ctDisplaySetMatch.displaySetInstanceUID
);
const ptDisplaySet = displaySetService.getDisplaySetByUID(
ptDisplaySetMatch.displaySetInstanceUID
);
const { volumeId: segVolumeId } = representationData[ const { volumeId: segVolumeId } = representationData[
SegmentationRepresentations.Labelmap SegmentationRepresentations.Labelmap
] as csTools.Types.LabelmapToolOperationDataVolume; ] as csTools.Types.LabelmapToolOperationDataVolume;
const { referencedVolumeId } = cs.cache.getVolume(segVolumeId);
const labelmapVolume = cs.cache.getVolume(segVolumeId);
const annotationUIDs = _getAnnotationsSelectedByToolNames(ROI_THRESHOLD_MANUAL_TOOL_IDS); const annotationUIDs = _getAnnotationsSelectedByToolNames(ROI_THRESHOLD_MANUAL_TOOL_IDS);
@ -220,71 +226,40 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
return; return;
} }
const labelmapVolume = cs.cache.getVolume(segmentationId);
let referencedVolume = cs.cache.getVolume(referencedVolumeId);
const ctReferencedVolume = cs.cache.getVolume(ctVolumeId);
// check if viewport is
if (!referencedVolume) {
throw new Error('No Reference volume found');
}
if (!labelmapVolume) {
throw new Error('No Reference labelmap found');
}
const annotation = csTools.annotation.state.getAnnotation(annotationUIDs[0]);
const {
metadata: {
enabledElement: { viewport },
},
} = annotation;
const showingReferenceVolume = viewport.hasVolumeId(referencedVolumeId);
if (!showingReferenceVolume) {
// if the reference volume is not being displayed, we can't
// rely on it for thresholding, we have couple of options here
// 1. We choose whatever volume is being displayed
// 2. We check if it is a fusion viewport, we pick the volume
// that matches the size and dimensions of the labelmap. This might
// happen if the 4D PT is converted to a computed volume and displayed
// and wants to threshold the labelmap
// 3. We throw an error
const displaySetInstanceUIDs = viewportGridService.getDisplaySetsUIDsForViewport(
viewport.id
);
displaySetInstanceUIDs.forEach(displaySetInstanceUID => {
const volume = cs.cache
.getVolumes()
.find(volume => volume.volumeId.includes(displaySetInstanceUID));
if (
cs.utilities.isEqual(volume.dimensions, labelmapVolume.dimensions) &&
cs.utilities.isEqual(volume.spacing, labelmapVolume.spacing)
) {
referencedVolume = volume;
}
});
}
const { ptLower, ptUpper, ctLower, ctUpper } = getThresholdValues( const { ptLower, ptUpper, ctLower, ctUpper } = getThresholdValues(
annotationUIDs, annotationUIDs,
[referencedVolume, ctReferencedVolume], ptDisplaySet,
config config
); );
const { imageIds: ptImageIds } = ptDisplaySet;
const ptVolumeInfo = cs.cache.getVolumeContainingImageId(ptImageIds[0]);
if (!ptVolumeInfo) {
uiNotificationService.error('No PT volume found');
return;
}
const { imageIds: ctImageIds } = ctDisplaySet;
const ctVolumeInfo = cs.cache.getVolumeContainingImageId(ctImageIds[0]);
if (!ctVolumeInfo) {
uiNotificationService.error('No CT volume found');
return;
}
const ptVolume = ptVolumeInfo.volume;
const ctVolume = ctVolumeInfo.volume;
return csTools.utilities.segmentation.rectangleROIThresholdVolumeByRange( return csTools.utilities.segmentation.rectangleROIThresholdVolumeByRange(
annotationUIDs, annotationUIDs,
labelmapVolume, labelmapVolume,
[ [
{ volume: referencedVolume, lower: ptLower, upper: ptUpper }, { volume: ptVolume, lower: ptLower, upper: ptUpper },
{ volume: ctReferencedVolume, lower: ctLower, upper: ctUpper }, { volume: ctVolume, lower: ctLower, upper: ctUpper },
], ],
{ overwrite: true, segmentIndex } { overwrite: true, segmentIndex, segmentationId }
); );
}, },
calculateSuvPeak: async ({ segmentationId, segmentIndex }) => { calculateSuvPeak: async ({ segmentationId, segmentIndex }) => {

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { PanelPetSUV, PanelROIThresholdExport } from './Panels'; import { PanelPetSUV, PanelROIThresholdExport } from './Panels';
import { Toolbox } from '@ohif/ui-next'; import { Toolbox } from '@ohif/extension-default';
import PanelTMTV from './Panels/PanelTMTV'; import PanelTMTV from './Panels/PanelTMTV';
function getPanelModule({ commandsManager, extensionManager, servicesManager }) { function getPanelModule({ commandsManager, extensionManager, servicesManager }) {
@ -17,9 +17,6 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
const wrappedROIThresholdToolbox = () => { const wrappedROIThresholdToolbox = () => {
return ( return (
<Toolbox <Toolbox
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
buttonSectionId="ROIThresholdToolbox" buttonSectionId="ROIThresholdToolbox"
title="Threshold Tools" title="Threshold Tools"
/> />
@ -39,9 +36,6 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
return ( return (
<> <>
<Toolbox <Toolbox
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
buttonSectionId="ROIThresholdToolbox" buttonSectionId="ROIThresholdToolbox"
title="Threshold Tools" title="Threshold Tools"
/> />

View File

@ -1,10 +1,10 @@
import RectangleROIOptions from './Panels/RectangleROIOptions'; import RectangleROIOptions from './Panels/RectangleROIOptions';
export default function getToolbarModule({ commandsManager, servicesManager }) { export default function getToolbarModule() {
return [ return [
{ {
name: 'tmtv.RectangleROIThresholdOptions', name: 'tmtv.RectangleROIThresholdOptions',
defaultComponent: () => RectangleROIOptions({ commandsManager, servicesManager }), defaultComponent: RectangleROIOptions,
}, },
]; ];
} }

View File

@ -1,9 +1,17 @@
import { cache } from '@cornerstonejs/core';
import * as csTools from '@cornerstonejs/tools'; import * as csTools from '@cornerstonejs/tools';
function getRoiStats(referencedVolume, annotations) { function getRoiStats(displaySet, annotations) {
// roiStats const { imageIds } = displaySet;
const { imageData } = referencedVolume;
const values = imageData.getPointData().getScalars().getData(); const ptVolumeInfo = cache.getVolumeContainingImageId(imageIds[0]);
if (!ptVolumeInfo) {
throw new Error('No volume found for display set');
}
const { volume } = ptVolumeInfo;
const { voxelManager } = volume;
// Todo: add support for other strategies // Todo: add support for other strategies
const { fn, baseValue } = _getStrategyFn('max'); const { fn, baseValue } = _getStrategyFn('max');
@ -11,25 +19,25 @@ function getRoiStats(referencedVolume, annotations) {
const boundsIJK = csTools.utilities.rectangleROITool.getBoundsIJKFromRectangleAnnotations( const boundsIJK = csTools.utilities.rectangleROITool.getBoundsIJKFromRectangleAnnotations(
annotations, annotations,
referencedVolume volume
); );
const [[iMin, iMax], [jMin, jMax], [kMin, kMax]] = boundsIJK; // Use the voxelManager's forEach method to iterate over the bounds
voxelManager.forEach(
for (let i = iMin; i <= iMax; i++) { ({ value: voxelValue }) => {
for (let j = jMin; j <= jMax; j++) { value = fn(voxelValue, value);
for (let k = kMin; k <= kMax; k++) { },
const offset = imageData.computeOffsetIndex([i, j, k]); {
value = fn(values[offset], value); boundsIJK,
}
} }
} );
return value; return value;
} }
function getThresholdValues( function getThresholdValues(
annotationUIDs, annotationUIDs,
referencedVolumes, ptDisplaySet,
config config
): { ptLower: number; ptUpper: number; ctLower: number; ctUpper: number } { ): { ptLower: number; ptUpper: number; ctLower: number; ctUpper: number } {
if (config.strategy === 'range') { if (config.strategy === 'range') {
@ -46,7 +54,7 @@ function getThresholdValues(
csTools.annotation.state.getAnnotation(annotationUID) csTools.annotation.state.getAnnotation(annotationUID)
); );
const ptValue = getRoiStats(referencedVolumes[0], annotations); const ptValue = getRoiStats(ptDisplaySet, annotations);
return { return {
ctLower: -Infinity, ctLower: -Infinity,

View File

@ -96,7 +96,7 @@ function modeFactory({ modeConfiguration }) {
toolbarService.addButtons(toolbarButtons); toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', [ toolbarService.createButtonSection('primary', [
'MeasurementTools', 'measurementSection',
'Zoom', 'Zoom',
'WindowLevel', 'WindowLevel',
'Pan', 'Pan',

View File

@ -1,5 +1,5 @@
import { WindowLevelMenuItem } from '@ohif/ui'; import { WindowLevelMenuItem } from '@ohif/ui';
import { defaults, ToolbarService } from '@ohif/core'; import { defaults } from '@ohif/core';
import type { Button } from '@ohif/core/types'; import type { Button } from '@ohif/core/types';
const { windowLevelPresets } = defaults; const { windowLevelPresets } = defaults;
@ -9,74 +9,110 @@ function _createWwwcPreset(preset, title, subtitle) {
id: preset.toString(), id: preset.toString(),
title, title,
subtitle, subtitle,
commands: [ commands: {
{ commandName: 'setWindowLevel',
commandName: 'setWindowLevel', commandOptions: {
commandOptions: { ...windowLevelPresets[preset],
...windowLevelPresets[preset],
},
context: 'CORNERSTONE',
}, },
], context: 'CORNERSTONE',
},
}; };
} }
function _createSetToolActiveCommands(toolName, toolGroupIds = ['default', 'mpr']) { export const setToolActiveToolbar = {
return toolGroupIds.map(toolGroupId => ({ commandName: 'setToolActive',
commandName: 'setToolActive', commandOptions: {
commandOptions: { toolGroupIds: ['default', 'mpr'],
toolGroupId, },
toolName, context: 'CORNERSTONE',
}, };
context: 'CORNERSTONE',
}));
}
const toolbarButtons: Button[] = [ const toolbarButtons: Button[] = [
// sections
{ {
id: 'MeasurementTools', id: 'measurementSection',
uiType: 'ohif.toolButtonList', uiType: 'ohif.toolButtonList',
props: { props: {
groupId: 'MeasurementTools', buttonSection: 'measurementSection',
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', groupId: 'measurementSection',
primary: ToolbarService.createButton({ },
id: 'Length', },
icon: 'tool-length', {
label: 'Length', id: 'MoreTools',
tooltip: 'Length Tool', uiType: 'ohif.toolButtonList',
commands: _createSetToolActiveCommands('Length'), props: {
evaluate: 'evaluate.cornerstoneTool', buttonSection: 'moreToolsSection',
}), groupId: 'MoreTools',
secondary: { },
icon: 'chevron-down', },
tooltip: 'More Measure Tools',
// tool defs
{
id: 'Length',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: {
...setToolActiveToolbar,
commandOptions: {
...setToolActiveToolbar.commandOptions,
toolName: 'Length',
},
}, },
items: [ evaluate: 'evaluate.cornerstoneTool',
ToolbarService.createButton({ },
id: 'Bidirectional', },
icon: 'tool-bidirectional', {
label: 'Bidirectional', id: 'Bidirectional',
tooltip: 'Bidirectional Tool', uiType: 'ohif.toolButton',
commands: _createSetToolActiveCommands('Bidirectional'), props: {
evaluate: 'evaluate.cornerstoneTool', icon: 'tool-bidirectional',
}), label: 'Bidirectional',
ToolbarService.createButton({ tooltip: 'Bidirectional Tool',
id: 'EllipticalROI', commands: {
icon: 'tool-ellipse', ...setToolActiveToolbar,
label: 'Ellipse', commandOptions: {
tooltip: 'Ellipse ROI', ...setToolActiveToolbar.commandOptions,
commands: _createSetToolActiveCommands('EllipticalROI'), toolName: 'Bidirectional',
evaluate: 'evaluate.cornerstoneTool', },
}), },
ToolbarService.createButton({ evaluate: 'evaluate.cornerstoneTool',
id: 'CircleROI', },
icon: 'tool-circle', },
label: 'Circle', {
tooltip: 'Circle Tool', id: 'EllipticalROI',
commands: _createSetToolActiveCommands('CircleROI'), uiType: 'ohif.toolButton',
evaluate: 'evaluate.cornerstoneTool', props: {
}), icon: 'tool-ellipse',
], label: 'Ellipse',
tooltip: 'Ellipse ROI',
commands: {
...setToolActiveToolbar,
commandOptions: {
...setToolActiveToolbar.commandOptions,
toolName: 'EllipticalROI',
},
},
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'CircleROI',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-circle',
label: 'Circle',
tooltip: 'Circle Tool',
commands: {
...setToolActiveToolbar,
commandOptions: {
...setToolActiveToolbar.commandOptions,
toolName: 'CircleROI',
},
},
evaluate: 'evaluate.cornerstoneTool',
}, },
}, },
{ {
@ -85,35 +121,15 @@ const toolbarButtons: Button[] = [
props: { props: {
icon: 'tool-zoom', icon: 'tool-zoom',
label: 'Zoom', label: 'Zoom',
commands: _createSetToolActiveCommands('Zoom'), tooltip: 'Zoom',
evaluate: 'evaluate.cornerstoneTool', commands: {
}, ...setToolActiveToolbar,
}, commandOptions: {
{ ...setToolActiveToolbar.commandOptions,
id: 'WindowLevel', toolName: 'Zoom',
uiType: 'ohif.toolButtonList', },
props: {
groupId: 'WindowLevel',
primary: ToolbarService.createButton({
id: 'WindowLevel',
icon: 'tool-window-level',
label: 'Window Level',
tooltip: 'Window Level',
commands: _createSetToolActiveCommands('WindowLevel'),
evaluate: 'evaluate.cornerstoneTool',
}),
secondary: {
icon: 'chevron-down',
tooltip: 'W/L Presets',
}, },
renderer: WindowLevelMenuItem, evaluate: 'evaluate.cornerstoneTool',
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'),
],
}, },
}, },
{ {
@ -122,7 +138,14 @@ const toolbarButtons: Button[] = [
props: { props: {
icon: 'tool-move', icon: 'tool-move',
label: 'Pan', label: 'Pan',
commands: _createSetToolActiveCommands('Pan'), tooltip: 'Pan',
commands: {
...setToolActiveToolbar,
commandOptions: {
...setToolActiveToolbar.commandOptions,
toolName: 'Pan',
},
},
evaluate: 'evaluate.cornerstoneTool', evaluate: 'evaluate.cornerstoneTool',
}, },
}, },
@ -132,12 +155,8 @@ const toolbarButtons: Button[] = [
props: { props: {
icon: 'tool-capture', icon: 'tool-capture',
label: 'Capture', label: 'Capture',
commands: [ tooltip: 'Capture',
{ commands: 'showDownloadViewportModal',
commandName: 'showDownloadViewportModal',
context: 'CORNERSTONE',
},
],
evaluate: [ evaluate: [
'evaluate.action', 'evaluate.action',
{ {
@ -154,106 +173,86 @@ const toolbarButtons: Button[] = [
rows: 3, rows: 3,
columns: 4, columns: 4,
evaluate: 'evaluate.action', evaluate: 'evaluate.action',
commands: [ commands: 'setViewportGridLayout',
{ },
commandName: 'setViewportGridLayout', },
},
], {
id: 'Reset',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-reset',
label: 'Reset View',
tooltip: 'Reset View',
commands: 'resetViewport',
evaluate: 'evaluate.action',
}, },
}, },
{ {
id: 'MoreTools', id: 'RotateRight',
uiType: 'ohif.toolButtonList', uiType: 'ohif.toolButton',
props: { props: {
groupId: 'MoreTools', icon: 'tool-rotate-right',
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', label: 'Rotate Right',
primary: ToolbarService.createButton({ tooltip: 'Rotate Right +90',
id: 'Reset', commands: 'rotateViewportCW',
icon: 'tool-reset', evaluate: 'evaluate.action',
label: 'Reset View', },
tooltip: 'Reset View', },
commands: [ {
{ id: 'FlipHorizontal',
commandName: 'resetViewport', uiType: 'ohif.toolButton',
context: 'CORNERSTONE', props: {
}, icon: 'tool-flip-horizontal',
], label: 'Flip Horizontally',
evaluate: 'evaluate.action', tooltip: 'Flip Horizontally',
}), commands: 'flipViewportHorizontal',
secondary: { evaluate: 'evaluate.action',
icon: 'chevron-down', },
tooltip: 'More Tools', },
{
id: 'StackScroll',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
commands: {
...setToolActiveToolbar,
commandOptions: {
...setToolActiveToolbar.commandOptions,
toolName: 'StackScroll',
},
}, },
items: [ evaluate: 'evaluate.cornerstoneTool',
ToolbarService.createButton({ },
id: 'Reset', },
icon: 'tool-reset', {
label: 'Reset View', id: 'Invert',
tooltip: 'Reset View', uiType: 'ohif.toolButton',
commands: [ props: {
{ icon: 'tool-invert',
commandName: 'resetViewport', label: 'Invert Colors',
context: 'CORNERSTONE', tooltip: 'Invert Colors',
}, commands: 'invertViewport',
], evaluate: 'evaluate.action',
evaluate: 'evaluate.action', },
}), },
ToolbarService.createButton({ {
id: 'RotateRight', id: 'CalibrationLine',
icon: 'tool-rotate-right', uiType: 'ohif.toolButton',
label: 'Rotate Right', props: {
tooltip: 'Rotate Right +90', icon: 'tool-calibration',
commands: [ label: 'Calibration Line',
{ tooltip: 'Calibration Line',
commandName: 'rotateViewportCW', commands: {
context: 'CORNERSTONE', ...setToolActiveToolbar,
}, commandOptions: {
], ...setToolActiveToolbar.commandOptions,
evaluate: 'evaluate.action', toolName: 'CalibrationLine',
}), },
ToolbarService.createButton({ },
id: 'FlipHorizontal', evaluate: 'evaluate.cornerstoneTool',
icon: 'tool-flip-horizontal',
label: 'Flip Horizontally',
tooltip: 'Flip Horizontally',
commands: [
{
commandName: 'flipViewportHorizontal',
context: 'CORNERSTONE',
},
],
evaluate: 'evaluate.action',
}),
ToolbarService.createButton({
id: 'StackScroll',
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
commands: _createSetToolActiveCommands('StackScroll'),
evaluate: 'evaluate.cornerstoneTool',
}),
ToolbarService.createButton({
id: 'Invert',
icon: 'tool-invert',
label: 'Invert Colors',
tooltip: 'Invert Colors',
commands: [
{
commandName: 'invertViewport',
context: 'CORNERSTONE',
},
],
evaluate: 'evaluate.action',
}),
ToolbarService.createButton({
id: 'CalibrationLine',
icon: 'tool-calibration',
label: 'Calibration Line',
tooltip: 'Calibration Line',
commands: _createSetToolActiveCommands('CalibrationLine'),
evaluate: 'evaluate.cornerstoneTool',
}),
],
}, },
}, },
]; ];

View File

@ -1,7 +1,6 @@
import toolbarButtons from './toolbarButtons'; import toolbarButtons from './toolbarButtons';
import { id } from './id'; import { id } from './id';
import initToolGroups from './initToolGroups'; import initToolGroups from './initToolGroups';
import moreTools from './moreTools';
import i18n from 'i18next'; import i18n from 'i18next';
// Allow this mode by excluding non-imaging modalities such as SR, SEG // Allow this mode by excluding non-imaging modalities such as SR, SEG
@ -53,7 +52,6 @@ const dicomPmap = {
}; };
const extensionDependencies = { const extensionDependencies = {
// Can derive the versions at least process.env.from npm_package_version
'@ohif/extension-default': '^3.0.0', '@ohif/extension-default': '^3.0.0',
'@ohif/extension-cornerstone': '^3.0.0', '@ohif/extension-cornerstone': '^3.0.0',
'@ohif/extension-measurement-tracking': '^3.0.0', '@ohif/extension-measurement-tracking': '^3.0.0',
@ -67,8 +65,6 @@ const extensionDependencies = {
function modeFactory() { function modeFactory() {
return { return {
// TODO: We're using this as a route segment
// We should not be.
id, id,
routeName: 'basic-test', routeName: 'basic-test',
displayName: i18n.t('Modes:Basic Test Mode'), displayName: i18n.t('Modes:Basic Test Mode'),
@ -89,11 +85,12 @@ function modeFactory() {
'@ohif/extension-test.customizationModule.custom-context-menu', '@ohif/extension-test.customizationModule.custom-context-menu',
]); ]);
toolbarService.addButtons([...toolbarButtons, ...moreTools]); toolbarService.addButtons(toolbarButtons);
console.debug('toolbarButtons', toolbarButtons);
toolbarService.createButtonSection('primary', [ toolbarService.createButtonSection('primary', [
'MeasurementTools', 'MeasurementTools',
'Zoom', 'Zoom',
'WindowLevel', 'WindowLevelGroup',
'Pan', 'Pan',
'Capture', 'Capture',
'Layout', 'Layout',
@ -102,6 +99,48 @@ function modeFactory() {
'MoreTools', 'MoreTools',
]); ]);
toolbarService.createButtonSection('windowLevelSection', [
'WindowLevel',
'Soft tissue',
'Lung',
'Liver',
'Bone',
'Brain',
]);
toolbarService.createButtonSection('measurementSection', [
'Length',
'Bidirectional',
'ArrowAnnotate',
'EllipticalROI',
'CircleROI',
'PlanarFreehandROI',
'SplineROI',
'LivewireContour',
]);
toolbarService.createButtonSection('moreToolsSection', [
'Reset',
'rotate-right',
'flipHorizontal',
'ImageSliceSync',
'ReferenceLines',
'ImageOverlayViewer',
'StackScroll',
'invert',
'Probe',
'Cine',
'Angle',
'CobbAngle',
'Magnify',
'RectangleROI',
'CalibrationLine',
'TagBrowser',
'AdvancedMagnify',
'UltrasoundDirectionalTool',
'WindowLevelRegion',
]);
customizationService.setCustomizations( customizationService.setCustomizations(
{ {
'ohif.hotkeyBindings': { 'ohif.hotkeyBindings': {
@ -154,22 +193,14 @@ function modeFactory() {
routes: [ routes: [
{ {
path: 'basic-test', path: 'basic-test',
/*init: ({ servicesManager, extensionManager }) => {
//defaultViewerRouteInit
},*/
layoutTemplate: () => { layoutTemplate: () => {
return { return {
id: ohif.layout, id: ohif.layout,
props: { props: {
// Use the first two for an untracked view
// leftPanels: [ohif.thumbnailList],
// rightPanels: [dicomSeg.panel, ohif.measurements],
leftPanels: [tracked.thumbnailList], leftPanels: [tracked.thumbnailList],
leftPanelResizable: true, leftPanelResizable: true,
// Can use cornerstone.measurements for all measurements
rightPanels: [cornerstone.panel, tracked.measurements, cornerstone.measurements], rightPanels: [cornerstone.panel, tracked.measurements, cornerstone.measurements],
rightPanelResizable: true, rightPanelResizable: true,
// rightPanelClosed: true, // optional prop to start with collapse panels
viewports: [ viewports: [
{ {
namespace: tracked.viewport, namespace: tracked.viewport,
@ -206,12 +237,7 @@ function modeFactory() {
}, },
], ],
extensions: extensionDependencies, extensions: extensionDependencies,
// Default protocol gets self-registered by default in the init
hangingProtocol: 'default', 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: [ sopClassHandlers: [
dicomvideo.sopClassHandler, dicomvideo.sopClassHandler,
dicomSeg.sopClassHandler, dicomSeg.sopClassHandler,
@ -221,8 +247,6 @@ function modeFactory() {
dicomsr.sopClassHandler, dicomsr.sopClassHandler,
], ],
hotkeys: { hotkeys: {
// Don't store the hotkeys for basic-test-mode under the same key
// because they get customized by tests
name: 'basic-test-hotkeys', name: 'basic-test-hotkeys',
}, },
}; };

View File

@ -1,212 +0,0 @@
import type { RunCommand } from '@ohif/core/types';
import { EVENTS } from '@cornerstonejs/core';
import { ToolbarService, ViewportGridService } from '@ohif/core';
import { setToolActiveToolbar } from './toolbarButtons';
const { createButton } = ToolbarService;
const ReferenceLinesListeners: RunCommand = [
{
commandName: 'setSourceViewportForReferenceLinesTool',
context: 'CORNERSTONE',
},
];
const moreTools = [
{
id: 'MoreTools',
uiType: 'ohif.toolButtonList',
props: {
groupId: 'MoreTools',
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
primary: createButton({
id: 'Reset',
icon: 'tool-reset',
tooltip: 'Reset View',
label: 'Reset',
commands: 'resetViewport',
evaluate: 'evaluate.action',
}),
secondary: {
icon: 'chevron-down',
label: '',
tooltip: 'More Tools',
},
items: [
createButton({
id: 'Reset',
icon: 'tool-reset',
label: 'Reset View',
tooltip: 'Reset View',
commands: 'resetViewport',
evaluate: 'evaluate.action',
}),
createButton({
id: 'rotate-right',
icon: 'tool-rotate-right',
label: 'Rotate Right',
tooltip: 'Rotate +90',
commands: 'rotateViewportCW',
evaluate: 'evaluate.action',
}),
createButton({
id: 'flipHorizontal',
icon: 'tool-flip-horizontal',
label: 'Flip Horizontal',
tooltip: 'Flip Horizontally',
commands: 'flipViewportHorizontal',
evaluate: 'evaluate.viewportProperties.toggle',
}),
createButton({
id: 'ImageSliceSync',
icon: 'link',
label: 'Image Slice Sync',
tooltip: 'Enable position synchronization on stack viewports',
commands: {
commandName: 'toggleSynchronizer',
commandOptions: {
type: 'imageSlice',
},
},
listeners: {
[EVENTS.VIEWPORT_NEW_IMAGE_SET]: {
commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true },
},
},
evaluate: 'evaluate.cornerstone.synchronizer',
}),
createButton({
id: 'ReferenceLines',
icon: 'tool-referenceLines',
label: 'Reference Lines',
tooltip: 'Show Reference Lines',
commands: 'toggleEnabledDisabledToolbar',
listeners: {
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners,
[ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners,
},
evaluate: 'evaluate.cornerstoneTool.toggle',
}),
createButton({
id: 'ImageOverlayViewer',
icon: 'toggle-dicom-overlay',
label: 'Image Overlay',
tooltip: 'Toggle Image Overlay',
commands: 'toggleEnabledDisabledToolbar',
evaluate: 'evaluate.cornerstoneTool.toggle',
}),
createButton({
id: 'StackScroll',
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'invert',
icon: 'tool-invert',
label: 'Invert',
tooltip: 'Invert Colors',
commands: 'invertViewport',
evaluate: 'evaluate.viewportProperties.toggle',
}),
createButton({
id: 'Probe',
icon: 'tool-probe',
label: 'Probe',
tooltip: 'Probe',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'Cine',
icon: 'tool-cine',
label: 'Cine',
tooltip: 'Cine',
commands: 'toggleCine',
evaluate: 'evaluate.cine',
}),
createButton({
id: 'Angle',
icon: 'tool-angle',
label: 'Angle',
tooltip: 'Angle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'CobbAngle',
icon: 'icon-tool-cobb-angle',
label: 'Cobb Angle',
tooltip: 'Cobb Angle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'Magnify',
icon: 'tool-magnify',
label: 'Zoom-in',
tooltip: 'Zoom-in',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'RectangleROI',
icon: 'tool-rectangle',
label: 'Rectangle',
tooltip: 'Rectangle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'CalibrationLine',
icon: 'tool-calibration',
label: 'Calibration',
tooltip: 'Calibration Line',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'TagBrowser',
icon: 'dicom-tag-browser',
label: 'Dicom Tag Browser',
tooltip: 'Dicom Tag Browser',
commands: 'openDICOMTagViewer',
}),
createButton({
id: 'AdvancedMagnify',
icon: 'icon-tool-loupe',
label: 'Magnify Probe',
tooltip: 'Magnify Probe',
commands: 'toggleActiveDisabledToolbar',
evaluate: 'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled',
}),
createButton({
id: 'UltrasoundDirectionalTool',
icon: 'icon-tool-ultrasound-bidirectional',
label: 'Ultrasound Directional',
tooltip: 'Ultrasound Directional',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.modality.supported',
supportedModalities: ['US'],
},
],
}),
createButton({
id: 'WindowLevelRegion',
icon: 'icon-tool-window-region',
label: 'Window Level Region',
tooltip: 'Window Level Region',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
],
},
},
];
export default moreTools;

View File

@ -1,14 +1,11 @@
// TODO: torn, can either bake this here; or have to create a whole new button type // 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 // Only ways that you can pass in a custom React component for render :l
import {
// ListMenu,
WindowLevelMenuItem,
} from '@ohif/ui';
import { defaults, ToolbarService } from '@ohif/core';
import type { Button } from '@ohif/core/types'; import type { Button } from '@ohif/core/types';
import { EVENTS } from '@cornerstonejs/core';
import { ViewportGridService } from '@ohif/core';
import { defaults } from '@ohif/core';
const { windowLevelPresets } = defaults; const { windowLevelPresets } = defaults;
const { createButton } = ToolbarService;
/** /**
* *
@ -18,18 +15,21 @@ const { createButton } = ToolbarService;
*/ */
function _createWwwcPreset(preset, title, subtitle) { function _createWwwcPreset(preset, title, subtitle) {
return { return {
id: preset.toString(), id: title,
title, uiType: 'ohif.toolButton',
subtitle, props: {
commands: [ title,
{ subtitle,
commandName: 'setWindowLevel', commands: [
commandOptions: { {
...windowLevelPresets[preset], commandName: 'setWindowLevel',
commandOptions: {
...windowLevelPresets[preset],
},
context: 'CORNERSTONE',
}, },
context: 'CORNERSTONE', ],
}, },
],
}; };
} }
@ -40,92 +40,142 @@ export const setToolActiveToolbar = {
}, },
}; };
const ReferenceLinesListeners = [
{
commandName: 'setSourceViewportForReferenceLinesTool',
context: 'CORNERSTONE',
},
];
const toolbarButtons: Button[] = [ const toolbarButtons: Button[] = [
{ {
id: 'MeasurementTools', id: 'MeasurementTools',
uiType: 'ohif.toolButtonList', uiType: 'ohif.toolButtonList',
props: { props: {
buttonSection: 'measurementSection',
groupId: 'MeasurementTools', groupId: 'MeasurementTools',
// group evaluate to determine which item should move to the top },
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', },
primary: createButton({ {
id: 'Length', id: 'MoreTools',
icon: 'tool-length', uiType: 'ohif.toolButtonList',
label: 'Length', props: {
tooltip: 'Length Tool', buttonSection: 'moreToolsSection',
commands: setToolActiveToolbar, groupId: 'MoreTools',
evaluate: 'evaluate.cornerstoneTool', },
}), },
secondary: { {
icon: 'chevron-down', id: 'WindowLevelGroup',
tooltip: 'More Measure Tools', uiType: 'ohif.toolButtonList',
}, props: {
items: [ groupId: 'WindowLevelGroup',
createButton({ buttonSection: 'windowLevelSection',
id: 'Length', },
icon: 'tool-length', },
label: 'Length',
tooltip: 'Length Tool', // tool defs
commands: setToolActiveToolbar, _createWwwcPreset(1, 'Soft tissue', '400 / 40'),
evaluate: 'evaluate.cornerstoneTool', _createWwwcPreset(2, 'Lung', '1500 / -600'),
}), _createWwwcPreset(3, 'Liver', '150 / 90'),
createButton({ _createWwwcPreset(4, 'Bone', '2500 / 480'),
id: 'Bidirectional', _createWwwcPreset(5, 'Brain', '80 / 40'),
icon: 'tool-bidirectional', {
label: 'Bidirectional', id: 'WindowLevel',
tooltip: 'Bidirectional Tool', uiType: 'ohif.toolButton',
commands: setToolActiveToolbar, props: {
evaluate: 'evaluate.cornerstoneTool', icon: 'tool-window-level',
}), label: 'Window Level',
createButton({ tooltip: 'Window Level',
id: 'ArrowAnnotate', commands: setToolActiveToolbar,
icon: 'tool-annotate', evaluate: 'evaluate.cornerstoneTool',
label: 'Annotation', },
tooltip: 'Arrow Annotate', },
commands: setToolActiveToolbar, {
evaluate: 'evaluate.cornerstoneTool', id: 'Length',
}), uiType: 'ohif.toolButton',
createButton({ props: {
id: 'EllipticalROI', icon: 'tool-length',
icon: 'tool-ellipse', label: 'Length',
label: 'Ellipse', tooltip: 'Length Tool',
tooltip: 'Ellipse ROI', commands: setToolActiveToolbar,
commands: setToolActiveToolbar, evaluate: 'evaluate.cornerstoneTool',
evaluate: 'evaluate.cornerstoneTool', },
}), },
createButton({ {
id: 'CircleROI', id: 'Bidirectional',
icon: 'tool-circle', uiType: 'ohif.toolButton',
label: 'Circle', props: {
tooltip: 'Circle Tool', icon: 'tool-bidirectional',
commands: setToolActiveToolbar, label: 'Bidirectional',
evaluate: 'evaluate.cornerstoneTool', tooltip: 'Bidirectional Tool',
}), commands: setToolActiveToolbar,
createButton({ evaluate: 'evaluate.cornerstoneTool',
id: 'PlanarFreehandROI', },
icon: 'icon-tool-freehand-roi', },
label: 'Freehand ROI', {
tooltip: 'Freehand ROI', id: 'ArrowAnnotate',
commands: setToolActiveToolbar, uiType: 'ohif.toolButton',
evaluate: 'evaluate.cornerstoneTool', props: {
}), icon: 'tool-annotate',
createButton({ label: 'Annotation',
id: 'SplineROI', tooltip: 'Arrow Annotate',
icon: 'icon-tool-spline-roi', commands: setToolActiveToolbar,
label: 'Spline ROI', evaluate: 'evaluate.cornerstoneTool',
tooltip: 'Spline ROI', },
commands: setToolActiveToolbar, },
evaluate: 'evaluate.cornerstoneTool', {
}), id: 'EllipticalROI',
createButton({ uiType: 'ohif.toolButton',
id: 'LivewireContour', props: {
icon: 'icon-tool-livewire', icon: 'tool-ellipse',
label: 'Livewire tool', label: 'Ellipse',
tooltip: 'Livewire tool', tooltip: 'Ellipse ROI',
commands: setToolActiveToolbar, commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool', evaluate: 'evaluate.cornerstoneTool',
}), },
], },
{
id: 'CircleROI',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-circle',
label: 'Circle',
tooltip: 'Circle Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'PlanarFreehandROI',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-freehand-roi',
label: 'Freehand ROI',
tooltip: 'Freehand ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'SplineROI',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-spline-roi',
label: 'Spline ROI',
tooltip: 'Spline ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'LivewireContour',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-livewire',
label: 'Livewire tool',
tooltip: 'Livewire tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}, },
}, },
{ {
@ -134,40 +184,11 @@ const toolbarButtons: Button[] = [
props: { props: {
icon: 'tool-zoom', icon: 'tool-zoom',
label: 'Zoom', label: 'Zoom',
tooltip: 'Zoom',
commands: setToolActiveToolbar, commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool', evaluate: 'evaluate.cornerstoneTool',
}, },
}, },
// Window Level
{
id: 'WindowLevel',
uiType: 'ohif.toolButtonList',
props: {
groupId: 'WindowLevel',
primary: createButton({
id: 'WindowLevel',
icon: 'tool-window-level',
label: 'Window Level',
tooltip: 'Window Level',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
secondary: {
icon: 'chevron-down',
label: 'W/L Manual',
tooltip: 'W/L Presets',
},
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', id: 'Pan',
uiType: 'ohif.toolButton', uiType: 'ohif.toolButton',
@ -175,6 +196,7 @@ const toolbarButtons: Button[] = [
type: 'tool', type: 'tool',
icon: 'tool-move', icon: 'tool-move',
label: 'Pan', label: 'Pan',
tooltip: 'Pan',
commands: setToolActiveToolbar, commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool', evaluate: 'evaluate.cornerstoneTool',
}, },
@ -185,24 +207,24 @@ const toolbarButtons: Button[] = [
props: { props: {
icon: 'icon-mpr', icon: 'icon-mpr',
label: 'MPR', label: 'MPR',
commands: [ tooltip: 'MPR',
{ commands: {
commandName: 'toggleHangingProtocol', commandName: 'toggleHangingProtocol',
commandOptions: { commandOptions: {
protocolId: 'mpr', protocolId: 'mpr',
},
}, },
], },
evaluate: 'evaluate.mpr', evaluate: 'evaluate.mpr',
}, },
}, },
{ {
id: 'TrackBallRotate', id: 'TrackBallRotate',
type: 'ohif.toolButton', uiType: 'ohif.toolButton',
props: { props: {
type: 'tool', type: 'tool',
icon: 'tool-3d-rotate', icon: 'tool-3d-rotate',
label: '3D Rotate', label: '3D Rotate',
tooltip: '3D Rotate',
commands: setToolActiveToolbar, commands: setToolActiveToolbar,
}, },
}, },
@ -212,6 +234,7 @@ const toolbarButtons: Button[] = [
props: { props: {
icon: 'tool-capture', icon: 'tool-capture',
label: 'Capture', label: 'Capture',
tooltip: 'Capture',
commands: 'showDownloadViewportModal', commands: 'showDownloadViewportModal',
evaluate: [ evaluate: [
'evaluate.action', 'evaluate.action',
@ -239,6 +262,7 @@ const toolbarButtons: Button[] = [
type: 'tool', type: 'tool',
icon: 'tool-crosshair', icon: 'tool-crosshair',
label: 'Crosshairs', label: 'Crosshairs',
tooltip: 'Crosshairs',
commands: { commands: {
commandName: 'setToolActiveToolbar', commandName: 'setToolActiveToolbar',
commandOptions: { commandOptions: {
@ -248,6 +272,236 @@ const toolbarButtons: Button[] = [
evaluate: 'evaluate.cornerstoneTool', evaluate: 'evaluate.cornerstoneTool',
}, },
}, },
{
id: 'Reset',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-reset',
label: 'Reset View',
tooltip: 'Reset View',
commands: 'resetViewport',
evaluate: 'evaluate.action',
},
},
{
id: 'rotate-right',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-rotate-right',
label: 'Rotate Right',
tooltip: 'Rotate +90',
commands: 'rotateViewportCW',
evaluate: 'evaluate.action',
},
},
{
id: 'flipHorizontal',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-flip-horizontal',
label: 'Flip Horizontal',
tooltip: 'Flip Horizontally',
commands: 'flipViewportHorizontal',
evaluate: 'evaluate.viewportProperties.toggle',
},
},
{
id: 'ImageSliceSync',
uiType: 'ohif.toolButton',
props: {
icon: 'link',
label: 'Image Slice Sync',
tooltip: 'Enable position synchronization on stack viewports',
commands: {
commandName: 'toggleSynchronizer',
commandOptions: {
type: 'imageSlice',
},
},
listeners: {
[EVENTS.VIEWPORT_NEW_IMAGE_SET]: {
commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true },
},
},
evaluate: 'evaluate.cornerstone.synchronizer',
},
},
{
id: 'ReferenceLines',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-referenceLines',
label: 'Reference Lines',
tooltip: 'Show Reference Lines',
commands: 'toggleEnabledDisabledToolbar',
listeners: {
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners,
[ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners,
},
evaluate: 'evaluate.cornerstoneTool.toggle',
},
},
{
id: 'ImageOverlayViewer',
uiType: 'ohif.toolButton',
props: {
icon: 'toggle-dicom-overlay',
label: 'Image Overlay',
tooltip: 'Toggle Image Overlay',
commands: 'toggleEnabledDisabledToolbar',
evaluate: 'evaluate.cornerstoneTool.toggle',
},
},
{
id: 'StackScroll',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'invert',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-invert',
label: 'Invert',
tooltip: 'Invert Colors',
commands: 'invertViewport',
evaluate: 'evaluate.viewportProperties.toggle',
},
},
{
id: 'Probe',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-probe',
label: 'Probe',
tooltip: 'Probe',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Cine',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-cine',
label: 'Cine',
tooltip: 'Cine',
commands: 'toggleCine',
evaluate: 'evaluate.cine',
},
},
{
id: 'Angle',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-angle',
label: 'Angle',
tooltip: 'Angle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'CobbAngle',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-cobb-angle',
label: 'Cobb Angle',
tooltip: 'Cobb Angle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Magnify',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-magnify',
label: 'Zoom-in',
tooltip: 'Zoom-in',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'RectangleROI',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-rectangle',
label: 'Rectangle',
tooltip: 'Rectangle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'CalibrationLine',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-calibration',
label: 'Calibration',
tooltip: 'Calibration Line',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'TagBrowser',
uiType: 'ohif.toolButton',
props: {
icon: 'dicom-tag-browser',
label: 'Dicom Tag Browser',
tooltip: 'Dicom Tag Browser',
commands: 'openDICOMTagViewer',
},
},
{
id: 'AdvancedMagnify',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-loupe',
label: 'Magnify Probe',
tooltip: 'Magnify Probe',
commands: 'toggleActiveDisabledToolbar',
evaluate: 'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled',
},
},
{
id: 'UltrasoundDirectionalTool',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-ultrasound-bidirectional',
label: 'Ultrasound Directional',
tooltip: 'Ultrasound Directional',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.modality.supported',
supportedModalities: ['US'],
},
],
},
},
{
id: 'WindowLevelRegion',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-window-region',
label: 'Window Level Region',
tooltip: 'Window Level Region',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
]; ];
export default toolbarButtons; export default toolbarButtons;

View File

@ -1,9 +1,7 @@
import { hotkeys } from '@ohif/core';
import i18n from 'i18next'; import i18n from 'i18next';
import { id } from './id'; import { id } from './id';
import initToolGroups from './initToolGroups'; import initToolGroups from './initToolGroups';
import toolbarButtons from './toolbarButtons'; import toolbarButtons from './toolbarButtons';
import moreTools from './moreTools';
// Allow this mode by excluding non-imaging modalities such as SR, SEG // Allow this mode by excluding non-imaging modalities such as SR, SEG
// Also, SM is not a simple imaging modalities, so exclude it. // Also, SM is not a simple imaging modalities, so exclude it.
@ -84,15 +82,14 @@ function modeFactory({ modeConfiguration }) {
* Lifecycle hooks * Lifecycle hooks
*/ */
onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) { onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) {
const { measurementService, toolbarService, toolGroupService, customizationService } = const { measurementService, toolbarService, toolGroupService } = servicesManager.services;
servicesManager.services;
measurementService.clearMeasurements(); measurementService.clearMeasurements();
// Init Default and SR ToolGroups // Init Default and SR ToolGroups
initToolGroups(extensionManager, toolGroupService, commandsManager); initToolGroups(extensionManager, toolGroupService, commandsManager);
toolbarService.addButtons([...toolbarButtons, ...moreTools]); toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', [ toolbarService.createButtonSection('primary', [
'MeasurementTools', 'MeasurementTools',
'Zoom', 'Zoom',
@ -105,6 +102,39 @@ function modeFactory({ modeConfiguration }) {
'MoreTools', 'MoreTools',
]); ]);
toolbarService.createButtonSection('measurementSection', [
'Length',
'Bidirectional',
'ArrowAnnotate',
'EllipticalROI',
'RectangleROI',
'CircleROI',
'PlanarFreehandROI',
'SplineROI',
'LivewireContour',
]);
toolbarService.createButtonSection('moreToolsSection', [
'Reset',
'rotate-right',
'flipHorizontal',
'ImageSliceSync',
'ReferenceLines',
'ImageOverlayViewer',
'StackScroll',
'invert',
'Probe',
'Cine',
'Angle',
'CobbAngle',
'Magnify',
'CalibrationLine',
'TagBrowser',
'AdvancedMagnify',
'UltrasoundDirectionalTool',
'WindowLevelRegion',
]);
// // ActivatePanel event trigger for when a segmentation or measurement is added. // // ActivatePanel event trigger for when a segmentation or measurement is added.
// // Do not force activation so as to respect the state the user may have left the UI in. // // Do not force activation so as to respect the state the user may have left the UI in.
// _activatePanelTriggersSubscriptions = [ // _activatePanelTriggersSubscriptions = [
@ -250,4 +280,4 @@ const mode = {
}; };
export default mode; export default mode;
export { initToolGroups, moreTools, toolbarButtons }; export { initToolGroups, toolbarButtons };

View File

@ -1,270 +0,0 @@
import type { RunCommand } from '@ohif/core/types';
import { EVENTS } from '@cornerstonejs/core';
import { ToolbarService, ViewportGridService } from '@ohif/core';
import { setToolActiveToolbar } from './toolbarButtons';
const { createButton } = ToolbarService;
const ReferenceLinesListeners: RunCommand = [
{
commandName: 'setSourceViewportForReferenceLinesTool',
context: 'CORNERSTONE',
},
];
const moreTools = [
{
id: 'MoreTools',
uiType: 'ohif.toolButtonList',
props: {
groupId: 'MoreTools',
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
primary: createButton({
id: 'Reset',
icon: 'tool-reset',
tooltip: 'Reset View',
label: 'Reset',
commands: 'resetViewport',
evaluate: 'evaluate.action',
}),
secondary: {
icon: 'chevron-down',
label: '',
tooltip: 'More Tools',
},
items: [
createButton({
id: 'Reset',
icon: 'tool-reset',
label: 'Reset View',
tooltip: 'Reset View',
commands: 'resetViewport',
evaluate: 'evaluate.action',
}),
createButton({
id: 'rotate-right',
icon: 'tool-rotate-right',
label: 'Rotate Right',
tooltip: 'Rotate +90',
commands: 'rotateViewportCW',
evaluate: [
'evaluate.action',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
}),
createButton({
id: 'flipHorizontal',
icon: 'tool-flip-horizontal',
label: 'Flip Horizontal',
tooltip: 'Flip Horizontally',
commands: 'flipViewportHorizontal',
evaluate: [
'evaluate.viewportProperties.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video', 'volume3d'],
},
],
}),
createButton({
id: 'ImageSliceSync',
icon: 'link',
label: 'Image Slice Sync',
tooltip: 'Enable position synchronization on stack viewports',
commands: {
commandName: 'toggleSynchronizer',
commandOptions: {
type: 'imageSlice',
},
},
listeners: {
[EVENTS.VIEWPORT_NEW_IMAGE_SET]: {
commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true },
},
},
evaluate: [
'evaluate.cornerstone.synchronizer',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video', 'volume3d'],
},
],
}),
createButton({
id: 'ReferenceLines',
icon: 'tool-referenceLines',
label: 'Reference Lines',
tooltip: 'Show Reference Lines',
commands: 'toggleEnabledDisabledToolbar',
listeners: {
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners,
[ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners,
},
evaluate: [
'evaluate.cornerstoneTool.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
}),
createButton({
id: 'ImageOverlayViewer',
icon: 'toggle-dicom-overlay',
label: 'Image Overlay',
tooltip: 'Toggle Image Overlay',
commands: 'toggleEnabledDisabledToolbar',
evaluate: [
'evaluate.cornerstoneTool.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
}),
createButton({
id: 'StackScroll',
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'invert',
icon: 'tool-invert',
label: 'Invert',
tooltip: 'Invert Colors',
commands: 'invertViewport',
evaluate: [
'evaluate.viewportProperties.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
}),
createButton({
id: 'Probe',
icon: 'tool-probe',
label: 'Probe',
tooltip: 'Probe',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'Cine',
icon: 'tool-cine',
label: 'Cine',
tooltip: 'Cine',
commands: 'toggleCine',
evaluate: [
'evaluate.cine',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['volume3d'],
},
],
}),
createButton({
id: 'Angle',
icon: 'tool-angle',
label: 'Angle',
tooltip: 'Angle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'CobbAngle',
icon: 'icon-tool-cobb-angle',
label: 'Cobb Angle',
tooltip: 'Cobb Angle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'Magnify',
icon: 'tool-magnify',
label: 'Zoom-in',
tooltip: 'Zoom-in',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
}),
createButton({
id: 'CalibrationLine',
icon: 'tool-calibration',
label: 'Calibration',
tooltip: 'Calibration Line',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
}),
createButton({
id: 'TagBrowser',
icon: 'dicom-tag-browser',
label: 'Dicom Tag Browser',
tooltip: 'Dicom Tag Browser',
commands: 'openDICOMTagViewer',
}),
createButton({
id: 'AdvancedMagnify',
icon: 'icon-tool-loupe',
label: 'Magnify Probe',
tooltip: 'Magnify Probe',
commands: 'toggleActiveDisabledToolbar',
evaluate: [
'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
}),
createButton({
id: 'UltrasoundDirectionalTool',
icon: 'icon-tool-ultrasound-bidirectional',
label: 'Ultrasound Directional',
tooltip: 'Ultrasound Directional',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.modality.supported',
supportedModalities: ['US'],
},
],
}),
createButton({
id: 'WindowLevelRegion',
icon: 'icon-tool-window-region',
label: 'Window Level Region',
tooltip: 'Window Level Region',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
}),
],
},
},
];
export default moreTools;

View File

@ -1,9 +1,14 @@
// 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 { ToolbarService } from '@ohif/core';
import type { Button } from '@ohif/core/types'; import type { Button } from '@ohif/core/types';
const { createButton } = ToolbarService; import { EVENTS } from '@cornerstonejs/core';
import { ViewportGridService } from '@ohif/core';
const ReferenceLinesListeners = [
{
commandName: 'setSourceViewportForReferenceLinesTool',
context: 'CORNERSTONE',
},
];
export const setToolActiveToolbar = { export const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar', commandName: 'setToolActiveToolbar',
@ -13,107 +18,403 @@ export const setToolActiveToolbar = {
}; };
const toolbarButtons: Button[] = [ const toolbarButtons: Button[] = [
// sections
{ {
id: 'MeasurementTools', id: 'MeasurementTools',
uiType: 'ohif.toolButtonList', uiType: 'ohif.toolButtonList',
props: { props: {
buttonSection: 'measurementSection',
groupId: 'MeasurementTools', groupId: 'MeasurementTools',
// group evaluate to determine which item should move to the top },
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', },
primary: createButton({ {
id: 'Length', id: 'MoreTools',
icon: 'tool-length', uiType: 'ohif.toolButtonList',
label: 'Length', props: {
tooltip: 'Length Tool', buttonSection: 'moreToolsSection',
commands: setToolActiveToolbar, groupId: 'MoreTools',
evaluate: 'evaluate.cornerstoneTool', },
}), },
secondary: { // tool defs
icon: 'chevron-down', {
tooltip: 'More Measure Tools', id: 'Reset',
}, uiType: 'ohif.toolButton',
items: [ props: {
createButton({ icon: 'tool-reset',
id: 'Length', label: 'Reset View',
icon: 'tool-length', tooltip: 'Reset View',
label: 'Length', commands: 'resetViewport',
tooltip: 'Length Tool', evaluate: 'evaluate.action',
commands: setToolActiveToolbar, },
evaluate: 'evaluate.cornerstoneTool', },
}), {
createButton({ id: 'rotate-right',
id: 'Bidirectional', uiType: 'ohif.toolButton',
icon: 'tool-bidirectional', props: {
label: 'Bidirectional', icon: 'tool-rotate-right',
tooltip: 'Bidirectional Tool', label: 'Rotate Right',
commands: setToolActiveToolbar, tooltip: 'Rotate +90',
evaluate: 'evaluate.cornerstoneTool', commands: 'rotateViewportCW',
}), evaluate: [
createButton({ 'evaluate.action',
id: 'ArrowAnnotate', {
icon: 'tool-annotate', name: 'evaluate.viewport.supported',
label: 'Annotation', unsupportedViewportTypes: ['video'],
tooltip: 'Arrow Annotate', },
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'EllipticalROI',
icon: 'tool-ellipse',
label: 'Ellipse',
tooltip: 'Ellipse ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'RectangleROI',
icon: 'tool-rectangle',
label: 'Rectangle',
tooltip: 'Rectangle ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'CircleROI',
icon: 'tool-circle',
label: 'Circle',
tooltip: 'Circle Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'PlanarFreehandROI',
icon: 'icon-tool-freehand-roi',
label: 'Freehand ROI',
tooltip: 'Freehand ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'SplineROI',
icon: 'icon-tool-spline-roi',
label: 'Spline ROI',
tooltip: 'Spline ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'LivewireContour',
icon: 'icon-tool-livewire',
label: 'Livewire tool',
tooltip: 'Livewire tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
], ],
}, },
}, },
{ {
id: 'Zoom', id: 'flipHorizontal',
uiType: 'ohif.toolButton', uiType: 'ohif.toolButton',
props: { props: {
icon: 'tool-zoom', icon: 'tool-flip-horizontal',
label: 'Zoom', label: 'Flip Horizontal',
tooltip: 'Flip Horizontally',
commands: 'flipViewportHorizontal',
evaluate: [
'evaluate.viewportProperties.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video', 'volume3d'],
},
],
},
},
{
id: 'ImageSliceSync',
uiType: 'ohif.toolButton',
props: {
icon: 'link',
label: 'Image Slice Sync',
tooltip: 'Enable position synchronization on stack viewports',
commands: {
commandName: 'toggleSynchronizer',
commandOptions: {
type: 'imageSlice',
},
},
listeners: {
[EVENTS.VIEWPORT_NEW_IMAGE_SET]: {
commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true },
},
},
evaluate: [
'evaluate.cornerstone.synchronizer',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video', 'volume3d'],
},
],
},
},
{
id: 'ReferenceLines',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-referenceLines',
label: 'Reference Lines',
tooltip: 'Show Reference Lines',
commands: 'toggleEnabledDisabledToolbar',
listeners: {
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners,
[ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners,
},
evaluate: [
'evaluate.cornerstoneTool.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
},
},
{
id: 'ImageOverlayViewer',
uiType: 'ohif.toolButton',
props: {
icon: 'toggle-dicom-overlay',
label: 'Image Overlay',
tooltip: 'Toggle Image Overlay',
commands: 'toggleEnabledDisabledToolbar',
evaluate: [
'evaluate.cornerstoneTool.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
},
},
{
id: 'StackScroll',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'invert',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-invert',
label: 'Invert',
tooltip: 'Invert Colors',
commands: 'invertViewport',
evaluate: [
'evaluate.viewportProperties.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
},
},
{
id: 'Probe',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-probe',
label: 'Probe',
tooltip: 'Probe',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Cine',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-cine',
label: 'Cine',
tooltip: 'Cine',
commands: 'toggleCine',
evaluate: [
'evaluate.cine',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['volume3d'],
},
],
},
},
{
id: 'Angle',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-angle',
label: 'Angle',
tooltip: 'Angle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'CobbAngle',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-cobb-angle',
label: 'Cobb Angle',
tooltip: 'Cobb Angle',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Magnify',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-magnify',
label: 'Zoom-in',
tooltip: 'Zoom-in',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
},
},
{
id: 'CalibrationLine',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-calibration',
label: 'Calibration',
tooltip: 'Calibration Line',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
},
},
{
id: 'TagBrowser',
uiType: 'ohif.toolButton',
props: {
icon: 'dicom-tag-browser',
label: 'Dicom Tag Browser',
tooltip: 'Dicom Tag Browser',
commands: 'openDICOMTagViewer',
},
},
{
id: 'AdvancedMagnify',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-loupe',
label: 'Magnify Probe',
tooltip: 'Magnify Probe',
commands: 'toggleActiveDisabledToolbar',
evaluate: [
'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
},
},
{
id: 'UltrasoundDirectionalTool',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-ultrasound-bidirectional',
label: 'Ultrasound Directional',
tooltip: 'Ultrasound Directional',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.modality.supported',
supportedModalities: ['US'],
},
],
},
},
{
id: 'WindowLevelRegion',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-window-region',
label: 'Window Level Region',
tooltip: 'Window Level Region',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstoneTool',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
},
},
{
id: 'Length',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Bidirectional',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-bidirectional',
label: 'Bidirectional',
tooltip: 'Bidirectional Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'ArrowAnnotate',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-annotate',
label: 'Annotation',
tooltip: 'Arrow Annotate',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'EllipticalROI',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-ellipse',
label: 'Ellipse',
tooltip: 'Ellipse ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'RectangleROI',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-rectangle',
label: 'Rectangle',
tooltip: 'Rectangle ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'CircleROI',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-circle',
label: 'Circle',
tooltip: 'Circle Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'PlanarFreehandROI',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-freehand-roi',
label: 'Freehand ROI',
tooltip: 'Freehand ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'SplineROI',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-spline-roi',
label: 'Spline ROI',
tooltip: 'Spline ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'LivewireContour',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-livewire',
label: 'Livewire tool',
tooltip: 'Livewire tool',
commands: setToolActiveToolbar, commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool', evaluate: 'evaluate.cornerstoneTool',
}, },
@ -135,7 +436,6 @@ const toolbarButtons: Button[] = [
], ],
}, },
}, },
// Pan...
{ {
id: 'Pan', id: 'Pan',
uiType: 'ohif.toolButton', uiType: 'ohif.toolButton',
@ -147,6 +447,17 @@ const toolbarButtons: Button[] = [
evaluate: 'evaluate.cornerstoneTool', evaluate: 'evaluate.cornerstoneTool',
}, },
}, },
{
id: 'Zoom',
uiType: 'ohif.toolButton',
props: {
type: 'tool',
icon: 'tool-zoom',
label: 'Zoom',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{ {
id: 'TrackballRotate', id: 'TrackballRotate',
uiType: 'ohif.toolButton', uiType: 'ohif.toolButton',

View File

@ -38,8 +38,6 @@ const extensionDependencies = {
function modeFactory({ modeConfiguration }) { function modeFactory({ modeConfiguration }) {
return { return {
// TODO: We're using this as a route segment
// We should not be.
id, id,
routeName: 'microscopy', routeName: 'microscopy',
displayName: i18n.t('Modes:Microscopy'), displayName: i18n.t('Modes:Microscopy'),
@ -47,11 +45,21 @@ function modeFactory({ modeConfiguration }) {
/** /**
* Lifecycle hooks * Lifecycle hooks
*/ */
onModeEnter: ({ servicesManager, extensionManager, commandsManager }: withAppTypes) => { onModeEnter: ({ servicesManager }: withAppTypes) => {
const { toolbarService } = servicesManager.services; const { toolbarService } = servicesManager.services;
toolbarService.addButtons(toolbarButtons); toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', ['MeasurementTools', 'dragPan', 'TagBrowser']); toolbarService.createButtonSection('primary', ['MeasurementTools', 'dragPan', 'TagBrowser']);
toolbarService.createButtonSection('measurementSection', [
'line',
'point',
'polygon',
'circle',
'box',
'freehandpolygon',
'freehandline',
]);
}, },
onModeExit: ({ servicesManager }: withAppTypes) => { onModeExit: ({ servicesManager }: withAppTypes) => {
@ -79,9 +87,6 @@ function modeFactory({ modeConfiguration }) {
routes: [ routes: [
{ {
path: 'microscopy', path: 'microscopy',
/*init: ({ servicesManager, extensionManager }) => {
//defaultViewerRouteInit
},*/
layoutTemplate: ({ location, servicesManager }) => { layoutTemplate: ({ location, servicesManager }) => {
return { return {
id: ohif.layout, id: ohif.layout,

View File

@ -1,164 +0,0 @@
import { ToolbarService } from '@ohif/core';
const toolbarButtons = [
{
id: 'MeasurementTools',
uiType: 'ohif.toolButtonList',
props: {
groupId: 'MeasurementTools',
// group evaluate to determine which item should move to the top
evaluate: 'evaluate.group.promoteToPrimary',
primary: ToolbarService.createButton({
id: 'line',
icon: 'tool-length',
label: 'Line',
tooltip: 'Line',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'line' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
}),
secondary: {
icon: 'chevron-down',
tooltip: 'More Measure Tools',
},
items: [
ToolbarService.createButton({
id: 'line',
icon: 'tool-length',
label: 'Line',
tooltip: 'Line',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'line' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
}),
ToolbarService.createButton({
id: 'point',
icon: 'tool-point',
label: 'Point',
tooltip: 'Point Tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'point' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
}),
// Point Tool was previously defined
ToolbarService.createButton({
id: 'polygon',
icon: 'tool-polygon',
label: 'Polygon',
tooltip: 'Polygon Tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'polygon' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
}),
ToolbarService.createButton({
id: 'circle',
icon: 'tool-circle',
label: 'Circle',
tooltip: 'Circle Tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'circle' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
}),
ToolbarService.createButton({
id: 'box',
icon: 'tool-rectangle',
label: 'Box',
tooltip: 'Box Tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'box' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
}),
ToolbarService.createButton({
id: 'freehandpolygon',
icon: 'tool-freehand-polygon',
label: 'Freehand Polygon',
tooltip: 'Freehand Polygon Tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'freehandpolygon' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
}),
ToolbarService.createButton({
id: 'freehandline',
icon: 'tool-freehand-line',
label: 'Freehand Line',
tooltip: 'Freehand Line Tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'freehandline' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
}),
],
},
},
{
id: 'dragPan',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-move',
label: 'Pan',
commands: [
{
commandName: 'setToolActive',
commandOptions: { toolName: 'dragPan' },
context: 'MICROSCOPY',
},
],
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'TagBrowser',
uiType: 'ohif.toolButton',
props: {
icon: 'dicom-tag-browser',
label: 'Dicom Tag Browser',
commands: [
{
commandName: 'openDICOMTagViewer',
},
],
evaluate: 'evaluate.action',
},
},
];
export default toolbarButtons;

View File

@ -0,0 +1,141 @@
import type { Button } from '@ohif/core/types';
export const setToolActiveToolbar = {
commandName: 'setToolActive',
commandOptions: {
toolName: 'line',
},
context: 'MICROSCOPY',
};
const toolbarButtons: Button[] = [
{
id: 'MeasurementTools',
uiType: 'ohif.toolButtonList',
props: {
buttonSection: 'measurementSection',
groupId: 'MeasurementTools',
},
},
{
id: 'line',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-length',
label: 'Line',
tooltip: 'Line',
commands: setToolActiveToolbar,
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'point',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-point',
label: 'Point',
tooltip: 'Point Tool',
commands: {
...setToolActiveToolbar,
commandOptions: { toolName: 'point' },
},
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'polygon',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-polygon',
label: 'Polygon',
tooltip: 'Polygon Tool',
commands: {
...setToolActiveToolbar,
commandOptions: { toolName: 'polygon' },
},
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'circle',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-circle',
label: 'Circle',
tooltip: 'Circle Tool',
commands: {
...setToolActiveToolbar,
commandOptions: { toolName: 'circle' },
},
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'box',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-rectangle',
label: 'Box',
tooltip: 'Box Tool',
commands: {
...setToolActiveToolbar,
commandOptions: { toolName: 'box' },
},
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'freehandpolygon',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-freehand-polygon',
label: 'Freehand Polygon',
tooltip: 'Freehand Polygon Tool',
commands: {
...setToolActiveToolbar,
commandOptions: { toolName: 'freehandpolygon' },
},
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'freehandline',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-freehand-line',
label: 'Freehand Line',
tooltip: 'Freehand Line Tool',
commands: {
...setToolActiveToolbar,
commandOptions: { toolName: 'freehandline' },
},
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'dragPan',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-move',
label: 'Pan',
commands: {
...setToolActiveToolbar,
commandOptions: { toolName: 'dragPan' },
},
evaluate: 'evaluate.microscopyTool',
},
},
{
id: 'TagBrowser',
uiType: 'ohif.toolButton',
props: {
icon: 'dicom-tag-browser',
label: 'Dicom Tag Browser',
tooltip: 'Dicom Tag Browser',
commands: 'openDICOMTagViewer',
evaluate: 'evaluate.action',
},
},
];
export default toolbarButtons;

View File

@ -10,10 +10,31 @@ const cornerstone = {
activeViewportWindowLevel: '@ohif/extension-cornerstone.panelModule.activeViewportWindowLevel', activeViewportWindowLevel: '@ohif/extension-cornerstone.panelModule.activeViewportWindowLevel',
}; };
const defaultButtons = { const defaultButtons = [
buttonSection: 'primary', {
buttons: ['MeasurementTools', 'Zoom', 'WindowLevel', 'Crosshairs', 'Pan'], buttonSection: 'primary',
}; buttons: ['MeasurementTools', 'Zoom', 'WindowLevel', 'Crosshairs', 'Pan'],
},
{
buttonSection: 'measurementSection',
buttons: ['Length', 'Bidirectional', 'ArrowAnnotate', 'EllipticalROI'],
},
];
const ROIThresholdToolbox = [
{
buttonSection: 'dynamic-toolbox',
buttons: ['SegmentationTools'],
},
{
buttonSection: 'segmentationToolboxToolsSection',
buttons: ['BrushTools', 'RectangleROIStartEndThreshold'],
},
{
buttonSection: 'brushToolsSection',
buttons: ['Brush', 'Eraser', 'Threshold'],
},
];
const defaultLeftPanel = [[dynamicVolume.leftPanel, cornerstone.activeViewportWindowLevel]]; const defaultLeftPanel = [[dynamicVolume.leftPanel, cornerstone.activeViewportWindowLevel]];
@ -66,13 +87,7 @@ function getWorkflowSettings({ servicesManager }) {
rightPanelClosed: false, rightPanelClosed: false,
}, },
}, },
toolbarButtons: [ toolbarButtons: [...defaultButtons, ...ROIThresholdToolbox],
defaultButtons,
{
buttonSection: 'dynamic-toolbox',
buttons: ['BrushTools', 'RectangleROIStartEndThreshold'],
},
],
hangingProtocol: { hangingProtocol: {
protocolId: 'default4D', protocolId: 'default4D',
stageId: 'roiQuantification', stageId: 'roiQuantification',

View File

@ -1,9 +1,7 @@
import { id } from './id'; import { id } from './id';
import { hotkeys } from '@ohif/core';
import initWorkflowSteps from './initWorkflowSteps'; import initWorkflowSteps from './initWorkflowSteps';
import initToolGroups from './initToolGroups'; import initToolGroups from './initToolGroups';
import toolbarButtons from './toolbarButtons'; import toolbarButtons from './toolbarButtons';
import segmentationButtons from './segmentationButtons';
const extensionDependencies = { const extensionDependencies = {
'@ohif/extension-default': '3.7.0-beta.76', '@ohif/extension-default': '3.7.0-beta.76',
@ -56,7 +54,8 @@ function modeFactory({ modeConfiguration }) {
measurementService.clearMeasurements(); measurementService.clearMeasurements();
initToolGroups({ toolNames, Enums, toolGroupService, commandsManager, servicesManager }); initToolGroups({ toolNames, Enums, toolGroupService, commandsManager, servicesManager });
toolbarService.addButtons([...toolbarButtons, ...segmentationButtons]); toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('secondary', ['ProgressDropdown']); toolbarService.createButtonSection('secondary', ['ProgressDropdown']);
// the primary button section is created in the workflow steps // the primary button section is created in the workflow steps

View File

@ -1,164 +0,0 @@
import type { Button } from '@ohif/core/types';
const toolbarButtons: Button[] = [
{
id: 'BrushTools',
uiType: 'ohif.toolBoxButtonGroup',
props: {
groupId: 'BrushTools',
evaluate: 'evaluate.cornerstone.hasSegmentation',
items: [
{
id: 'Brush',
icon: 'icon-tool-brush',
label: 'Brush',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircularBrush', 'SphereBrush'],
},
options: [
{
name: 'Size (mm)',
id: 'brush-radius',
type: 'range',
min: 0.5,
max: 99.5,
step: 0.5,
value: 7,
commands: {
commandName: 'setBrushSize',
commandOptions: { toolNames: ['CircularBrush', 'SphereBrush'] },
},
},
{
name: 'Shape',
type: 'radio',
id: 'brush-mode',
value: 'CircularBrush',
values: [
{ value: 'CircularBrush', label: 'Circle' },
{ value: 'SphereBrush', label: 'Sphere' },
],
commands: 'setToolActiveToolbar',
},
],
},
{
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: 7,
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',
},
],
},
{
id: 'Threshold',
icon: 'icon-tool-threshold',
label: 'Eraser',
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: 7,
commands: {
commandName: 'setBrushSize',
commandOptions: {
toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
},
},
},
{
name: 'Shape',
type: 'radio',
id: 'eraser-mode',
value: 'ThresholdCircularBrush',
values: [
{ value: 'ThresholdCircularBrush', label: 'Circle' },
{ value: 'ThresholdSphereBrush', label: 'Sphere' },
],
commands: 'setToolActiveToolbar',
},
{
name: 'ThresholdRange',
type: 'double-range',
id: 'threshold-range',
min: 0,
max: 100,
step: 0.5,
value: [2, 50],
commands: {
commandName: 'setThresholdRange',
commandOptions: {
toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
},
},
},
],
},
],
},
},
{
id: 'Shapes',
uiType: 'ohif.toolBoxButton',
props: {
label: 'Shapes',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircleScissor', 'SphereScissor', 'RectangleScissor'],
},
icon: 'icon-tool-shape',
options: [
{
name: 'Shape',
type: 'radio',
value: 'CircleScissor',
id: 'shape-mode',
values: [
{ value: 'CircleScissor', label: 'Circle' },
{ value: 'SphereScissor', label: 'Sphere' },
{ value: 'RectangleScissor', label: 'Rectangle' },
],
commands: 'setToolActiveToolbar',
},
],
},
},
];
export default toolbarButtons;

View File

@ -1,8 +1,5 @@
import { defaults, ToolbarService } from '@ohif/core';
import { toolGroupIds } from './initToolGroups'; import { toolGroupIds } from './initToolGroups';
const { createButton } = ToolbarService;
const setToolActiveToolbar = { const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar', commandName: 'setToolActiveToolbar',
commandOptions: { commandOptions: {
@ -16,53 +13,67 @@ const toolbarButtons = [
uiType: 'ohif.toolButtonList', uiType: 'ohif.toolButtonList',
props: { props: {
groupId: 'MeasurementTools', groupId: 'MeasurementTools',
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', buttonSection: 'measurementSection',
primary: createButton({ },
id: 'Length', },
icon: 'tool-length', {
label: 'Length', id: 'BrushTools',
tooltip: 'Length Tool', uiType: 'ohif.toolBoxButtonGroup',
commands: setToolActiveToolbar, props: {
evaluate: 'evaluate.cornerstoneTool', groupId: 'BrushTools',
}), buttonSection: 'brushToolsSection',
secondary: { },
icon: 'chevron-down', },
tooltip: 'More Measure Tools', {
}, id: 'SegmentationTools',
items: [ uiType: 'ohif.toolBoxButton',
{ props: {
id: 'Length', groupId: 'SegmentationTools',
icon: 'tool-length', buttonSection: 'segmentationToolboxToolsSection',
label: 'Length', },
tooltip: 'Length Tool', },
commands: setToolActiveToolbar, {
evaluate: 'evaluate.cornerstoneTool', id: 'Length',
}, uiType: 'ohif.toolButton',
{ props: {
id: 'Bidirectional', icon: 'tool-length',
icon: 'tool-bidirectional', label: 'Length',
label: 'Bidirectional', tooltip: 'Length Tool',
tooltip: 'Bidirectional Tool', commands: setToolActiveToolbar,
commands: setToolActiveToolbar, evaluate: 'evaluate.cornerstoneTool',
evaluate: 'evaluate.cornerstoneTool', },
}, },
{ {
id: 'ArrowAnnotate', id: 'Bidirectional',
icon: 'tool-annotate', uiType: 'ohif.toolButton',
label: 'Annotation', props: {
tooltip: 'Arrow Annotate', icon: 'tool-bidirectional',
commands: setToolActiveToolbar, label: 'Bidirectional',
evaluate: 'evaluate.cornerstoneTool', tooltip: 'Bidirectional Tool',
}, commands: setToolActiveToolbar,
{ evaluate: 'evaluate.cornerstoneTool',
id: 'EllipticalROI', },
icon: 'tool-ellipse', },
label: 'Ellipse', {
tooltip: 'Ellipse ROI', id: 'ArrowAnnotate',
commands: setToolActiveToolbar, uiType: 'ohif.toolButton',
evaluate: 'evaluate.cornerstoneTool', props: {
}, icon: 'tool-annotate',
], label: 'Annotation',
tooltip: 'Arrow Annotate',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'EllipticalROI',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-ellipse',
label: 'Ellipse',
tooltip: 'Ellipse ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}, },
}, },
{ {
@ -109,7 +120,7 @@ const toolbarButtons = [
}, },
{ {
id: 'Capture', id: 'Capture',
uiType: 'ohif.radioGroup', uiType: 'ohif.toolButton',
props: { props: {
icon: 'tool-capture', icon: 'tool-capture',
label: 'Capture', label: 'Capture',
@ -130,6 +141,7 @@ const toolbarButtons = [
rows: 3, rows: 3,
columns: 4, columns: 4,
evaluate: 'evaluate.action', evaluate: 'evaluate.action',
commands: 'setViewportGridLayout',
}, },
}, },
{ {
@ -149,7 +161,7 @@ const toolbarButtons = [
}, },
{ {
id: 'RectangleROIStartEndThreshold', id: 'RectangleROIStartEndThreshold',
uiType: 'ohif.radioGroup', uiType: 'ohif.toolBoxButton',
props: { props: {
icon: 'tool-create-threshold', icon: 'tool-create-threshold',
label: 'Rectangle ROI Threshold', label: 'Rectangle ROI Threshold',
@ -161,6 +173,163 @@ const toolbarButtons = [
options: 'tmtv.RectangleROIThresholdOptions', options: 'tmtv.RectangleROIThresholdOptions',
}, },
}, },
{
id: 'Brush',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-brush',
label: 'Brush',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircularBrush', 'SphereBrush'],
},
options: [
{
name: 'Size (mm)',
id: 'brush-radius',
type: 'range',
min: 0.5,
max: 99.5,
step: 0.5,
value: 7,
commands: {
commandName: 'setBrushSize',
commandOptions: { toolNames: ['CircularBrush', 'SphereBrush'] },
},
},
{
name: 'Shape',
type: 'radio',
id: 'brush-mode',
value: 'CircularBrush',
values: [
{ value: 'CircularBrush', label: 'Circle' },
{ value: 'SphereBrush', label: 'Sphere' },
],
commands: 'setToolActiveToolbar',
},
],
},
},
{
id: 'Eraser',
uiType: 'ohif.toolButton',
props: {
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: 7,
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',
},
],
},
},
{
id: 'Threshold',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-threshold',
label: 'Threshold',
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: 7,
commands: {
commandName: 'setBrushSize',
commandOptions: {
toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
},
},
},
{
name: 'Shape',
type: 'radio',
id: 'eraser-mode',
value: 'ThresholdCircularBrush',
values: [
{ value: 'ThresholdCircularBrush', label: 'Circle' },
{ value: 'ThresholdSphereBrush', label: 'Sphere' },
],
commands: 'setToolActiveToolbar',
},
{
name: 'ThresholdRange',
type: 'double-range',
id: 'threshold-range',
min: 0,
max: 100,
step: 0.5,
value: [2, 50],
commands: {
commandName: 'setThresholdRange',
commandOptions: {
toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
},
},
},
],
},
},
{
id: 'Shapes',
uiType: 'ohif.toolBoxButton',
props: {
label: 'Shapes',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircleScissor', 'SphereScissor', 'RectangleScissor'],
},
icon: 'icon-tool-shape',
options: [
{
name: 'Shape',
type: 'radio',
value: 'CircleScissor',
id: 'shape-mode',
values: [
{ value: 'CircleScissor', label: 'Circle' },
{ value: 'SphereScissor', label: 'Sphere' },
{ value: 'RectangleScissor', label: 'Rectangle' },
],
commands: 'setToolActiveToolbar',
},
],
},
},
]; ];
export default toolbarButtons; export default toolbarButtons;

View File

@ -1,6 +1,5 @@
import { id } from './id'; import { id } from './id';
import toolbarButtons from './toolbarButtons'; import toolbarButtons from './toolbarButtons';
import segmentationButtons from './segmentationButtons';
import initToolGroups from './initToolGroups'; import initToolGroups from './initToolGroups';
const ohif = { const ohif = {
@ -57,7 +56,6 @@ function modeFactory({ modeConfiguration }) {
initToolGroups(extensionManager, toolGroupService, commandsManager); initToolGroups(extensionManager, toolGroupService, commandsManager);
toolbarService.addButtons(toolbarButtons); toolbarService.addButtons(toolbarButtons);
toolbarService.addButtons(segmentationButtons);
toolbarService.createButtonSection('primary', [ toolbarService.createButtonSection('primary', [
'WindowLevel', 'WindowLevel',
@ -69,7 +67,26 @@ function modeFactory({ modeConfiguration }) {
'Crosshairs', 'Crosshairs',
'MoreTools', 'MoreTools',
]); ]);
toolbarService.createButtonSection('segmentationToolbox', ['BrushTools', 'Shapes']);
toolbarService.createButtonSection('moreToolsSection', [
'Reset',
'rotate-right',
'flipHorizontal',
'ReferenceLines',
'ImageOverlayViewer',
'StackScroll',
'invert',
'Cine',
'Magnify',
'TagBrowser',
]);
toolbarService.createButtonSection('segmentationToolbox', ['SegmentationTools']);
toolbarService.createButtonSection('segmentationToolboxToolsSection', [
'BrushTools',
'Shapes',
]);
toolbarService.createButtonSection('brushToolsSection', ['Brush', 'Eraser', 'Threshold']);
}, },
onModeExit: ({ servicesManager }: withAppTypes) => { onModeExit: ({ servicesManager }: withAppTypes) => {
const { const {

View File

@ -76,6 +76,20 @@ function createTools(utilityModule) {
}, },
}, },
}, },
{
toolName: 'ThresholdSphereBrushDynamic',
parentTool: 'Brush',
configuration: {
activeStrategy: 'THRESHOLD_INSIDE_SPHERE',
// preview: {
// enabled: true,
// },
threshold: {
isDynamic: true,
dynamicRadius: 3,
},
},
},
{ toolName: toolNames.CircleScissors }, { toolName: toolNames.CircleScissors },
{ toolName: toolNames.RectangleScissors }, { toolName: toolNames.RectangleScissors },
{ toolName: toolNames.SphereScissors }, { toolName: toolNames.SphereScissors },

View File

@ -1,204 +0,0 @@
import type { Button } from '@ohif/core/types';
const toolbarButtons: Button[] = [
{
id: 'BrushTools',
uiType: 'ohif.toolBoxButtonGroup',
props: {
groupId: 'BrushTools',
evaluate: 'evaluate.cornerstone.hasSegmentation',
items: [
{
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: [
{
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'] },
},
},
{
name: 'Shape',
type: 'radio',
id: 'brush-mode',
value: 'CircularBrush',
values: [
{ value: 'CircularBrush', label: 'Circle' },
{ value: 'SphereBrush', label: 'Sphere' },
],
commands: 'setToolActiveToolbar',
},
],
},
{
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',
},
],
},
{
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',
],
},
},
},
{
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'],
},
},
},
],
},
],
},
},
{
id: 'Shapes',
uiType: 'ohif.toolBoxButton',
props: {
id: 'Shapes',
icon: 'icon-tool-shape',
label: 'Shapes',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircleScissor', 'SphereScissor', 'RectangleScissor'],
disabledText: 'Create new segmentation to enable shapes tool.',
},
options: [
{
name: 'Shape',
type: 'radio',
value: 'CircleScissor',
id: 'shape-mode',
values: [
{ value: 'CircleScissor', label: 'Circle' },
{ value: 'SphereScissor', label: 'Sphere' },
{ value: 'RectangleScissor', label: 'Rectangle' },
],
commands: 'setToolActiveToolbar',
},
],
},
},
];
export default toolbarButtons;

View File

@ -1,16 +1,6 @@
import type { Button } from '@ohif/core/types'; import type { Button } from '@ohif/core/types';
import { ToolbarService, ViewportGridService } from '@ohif/core';
const { createButton } = ToolbarService; const setToolActiveToolbar = {
const ReferenceLinesListeners: RunCommand = [
{
commandName: 'setSourceViewportForReferenceLinesTool',
context: 'CORNERSTONE',
},
];
export const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar', commandName: 'setToolActiveToolbar',
commandOptions: { commandOptions: {
toolGroupIds: ['default', 'mpr', 'SRToolGroup', 'volume3d'], toolGroupIds: ['default', 'mpr', 'SRToolGroup', 'volume3d'],
@ -18,6 +8,41 @@ export const setToolActiveToolbar = {
}; };
const toolbarButtons: Button[] = [ const toolbarButtons: Button[] = [
// sections
{
id: 'MoreTools',
uiType: 'ohif.toolButtonList',
props: {
buttonSection: 'moreToolsSection',
groupId: 'MoreTools',
},
},
{
id: 'BrushTools',
uiType: 'ohif.toolBoxButtonGroup',
props: {
groupId: 'BrushTools',
buttonSection: 'brushToolsSection',
},
},
// Section containers for the nested toolbox
{
id: 'SegmentationUtilities',
uiType: 'ohif.toolBoxButton',
props: {
groupId: 'SegmentationUtilities',
buttonSection: 'segmentationToolboxUtilitySection',
},
},
{
id: 'SegmentationTools',
uiType: 'ohif.toolBoxButton',
props: {
groupId: 'SegmentationTools',
buttonSection: 'segmentationToolboxToolsSection',
},
},
// tool defs
{ {
id: 'Zoom', id: 'Zoom',
uiType: 'ohif.toolButton', uiType: 'ohif.toolButton',
@ -107,150 +132,345 @@ const toolbarButtons: Button[] = [
}, },
}, },
{ {
id: 'MoreTools', id: 'Reset',
uiType: 'ohif.toolButtonList', uiType: 'ohif.toolButton',
props: { props: {
groupId: 'MoreTools', icon: 'tool-reset',
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', label: 'Reset View',
primary: createButton({ tooltip: 'Reset View',
id: 'Reset', commands: 'resetViewport',
icon: 'tool-reset', evaluate: 'evaluate.action',
tooltip: 'Reset View', },
label: 'Reset', },
commands: 'resetViewport', {
evaluate: 'evaluate.action', id: 'rotate-right',
}), uiType: 'ohif.toolButton',
secondary: { props: {
icon: 'chevron-down', icon: 'tool-rotate-right',
label: '', label: 'Rotate Right',
tooltip: 'More Tools', tooltip: 'Rotate +90',
commands: 'rotateViewportCW',
evaluate: 'evaluate.action',
},
},
{
id: 'flipHorizontal',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-flip-horizontal',
label: 'Flip Horizontal',
tooltip: 'Flip Horizontally',
commands: 'flipViewportHorizontal',
evaluate: [
'evaluate.viewportProperties.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['volume3d'],
},
],
},
},
{
id: 'ReferenceLines',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-referenceLines',
label: 'Reference Lines',
tooltip: 'Show Reference Lines',
commands: 'toggleEnabledDisabledToolbar',
evaluate: 'evaluate.cornerstoneTool.toggle',
},
},
{
id: 'ImageOverlayViewer',
uiType: 'ohif.toolButton',
props: {
icon: 'toggle-dicom-overlay',
label: 'Image Overlay',
tooltip: 'Toggle Image Overlay',
commands: 'toggleEnabledDisabledToolbar',
evaluate: 'evaluate.cornerstoneTool.toggle',
},
},
{
id: 'StackScroll',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'invert',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-invert',
label: 'Invert',
tooltip: 'Invert Colors',
commands: 'invertViewport',
evaluate: 'evaluate.viewportProperties.toggle',
},
},
{
id: 'Cine',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-cine',
label: 'Cine',
tooltip: 'Cine',
commands: 'toggleCine',
evaluate: [
'evaluate.cine',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['volume3d'],
},
],
},
},
{
id: 'Magnify',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-magnify',
label: 'Zoom-in',
tooltip: 'Zoom-in',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'TagBrowser',
uiType: 'ohif.toolButton',
props: {
icon: 'dicom-tag-browser',
label: 'Dicom Tag Browser',
tooltip: 'Dicom Tag Browser',
commands: 'openDICOMTagViewer',
},
},
{
id: 'Brush',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-brush',
label: 'Brush',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircularBrush', 'SphereBrush'],
disabledText: 'Create new segmentation to enable this tool.',
}, },
items: [ options: [
createButton({ {
id: 'Reset', name: 'Radius (mm)',
icon: 'tool-reset', id: 'brush-radius',
label: 'Reset View', type: 'range',
tooltip: 'Reset View', min: 0.5,
commands: 'resetViewport', max: 99.5,
evaluate: 'evaluate.action', step: 0.5,
}), value: 25,
createButton({ commands: {
id: 'rotate-right', commandName: 'setBrushSize',
icon: 'tool-rotate-right', commandOptions: { toolNames: ['CircularBrush', 'SphereBrush'] },
label: 'Rotate Right',
tooltip: 'Rotate +90',
commands: 'rotateViewportCW',
evaluate: 'evaluate.action',
}),
createButton({
id: 'flipHorizontal',
icon: 'tool-flip-horizontal',
label: 'Flip Horizontal',
tooltip: 'Flip Horizontally',
commands: 'flipViewportHorizontal',
evaluate: [
'evaluate.viewportProperties.toggle',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['volume3d'],
},
],
}),
createButton({
id: 'ReferenceLines',
icon: 'tool-referenceLines',
label: 'Reference Lines',
tooltip: 'Show Reference Lines',
commands: 'toggleEnabledDisabledToolbar',
listeners: {
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners,
[ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners,
}, },
evaluate: 'evaluate.cornerstoneTool.toggle', },
}), {
createButton({ name: 'Shape',
id: 'ImageOverlayViewer', type: 'radio',
icon: 'toggle-dicom-overlay', id: 'brush-mode',
label: 'Image Overlay', value: 'CircularBrush',
tooltip: 'Toggle Image Overlay', values: [
commands: 'toggleEnabledDisabledToolbar', { value: 'CircularBrush', label: 'Circle' },
evaluate: 'evaluate.cornerstoneTool.toggle', { value: 'SphereBrush', label: 'Sphere' },
}),
createButton({
id: 'StackScroll',
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'invert',
icon: 'tool-invert',
label: 'Invert',
tooltip: 'Invert Colors',
commands: 'invertViewport',
evaluate: 'evaluate.viewportProperties.toggle',
}),
createButton({
id: 'Cine',
icon: 'tool-cine',
label: 'Cine',
tooltip: 'Cine',
commands: 'toggleCine',
evaluate: [
'evaluate.cine',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['volume3d'],
},
], ],
}), commands: 'setToolActiveToolbar',
createButton({ },
id: 'Magnify', ],
icon: 'tool-magnify', },
label: 'Zoom-in', },
tooltip: 'Zoom-in', {
commands: setToolActiveToolbar, id: 'Eraser',
evaluate: 'evaluate.cornerstoneTool', uiType: 'ohif.toolButton',
}), props: {
createButton({ icon: 'icon-tool-eraser',
id: 'TagBrowser', label: 'Eraser',
icon: 'dicom-tag-browser', evaluate: {
label: 'Dicom Tag Browser', name: 'evaluate.cornerstone.segmentation',
tooltip: 'Dicom Tag Browser', toolNames: ['CircularEraser', 'SphereEraser'],
commands: 'openDICOMTagViewer', },
}), options: [
createButton({ {
id: 'AdvancedMagnify', name: 'Radius (mm)',
icon: 'icon-tool-loupe', id: 'eraser-radius',
label: 'Magnify Probe', type: 'range',
tooltip: 'Magnify Probe', min: 0.5,
commands: 'toggleActiveDisabledToolbar', max: 99.5,
evaluate: 'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled', step: 0.5,
}), value: 25,
createButton({ commands: {
id: 'UltrasoundDirectionalTool', commandName: 'setBrushSize',
icon: 'icon-tool-ultrasound-bidirectional', commandOptions: { toolNames: ['CircularEraser', 'SphereEraser'] },
label: 'Ultrasound Directional', },
tooltip: 'Ultrasound Directional', },
commands: setToolActiveToolbar, {
evaluate: [ name: 'Shape',
'evaluate.cornerstoneTool', type: 'radio',
{ id: 'eraser-mode',
name: 'evaluate.modality.supported', value: 'CircularEraser',
supportedModalities: ['US'], values: [
}, { value: 'CircularEraser', label: 'Circle' },
{ value: 'SphereEraser', label: 'Sphere' },
], ],
}), commands: 'setToolActiveToolbar',
createButton({ },
id: 'WindowLevelRegion', ],
icon: 'icon-tool-window-region', },
label: 'Window Level Region', },
tooltip: 'Window Level Region', {
commands: setToolActiveToolbar, id: 'Threshold',
evaluate: 'evaluate.cornerstoneTool', uiType: 'ohif.toolButton',
}), props: {
icon: 'icon-tool-threshold',
label: 'Threshold Tool',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: [
'ThresholdCircularBrush',
'ThresholdSphereBrush',
'ThresholdCircularBrushDynamic',
'ThresholdSphereBrushDynamic',
],
},
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',
'ThresholdSphereBrushDynamic',
],
},
},
},
{
name: 'Shape',
type: 'radio',
id: 'threshold-shape',
value: 'ThresholdCircularBrush',
values: [
{ value: 'ThresholdCircularBrush', label: 'Circle' },
{ value: 'ThresholdSphereBrush', label: 'Sphere' },
],
commands: ({ value, commandsManager, options }) => {
const optionsDynamic = options.find(option => option.id === 'dynamic-mode');
if (optionsDynamic.value === 'ThresholdDynamic') {
commandsManager.run('setToolActive', {
toolName:
value === 'ThresholdCircularBrush'
? 'ThresholdCircularBrushDynamic'
: 'ThresholdSphereBrushDynamic',
});
} else {
commandsManager.run('setToolActive', {
toolName: value,
});
}
},
},
{
name: 'Threshold',
type: 'radio',
id: 'dynamic-mode',
value: 'ThresholdDynamic',
values: [
{ value: 'ThresholdDynamic', label: 'Dynamic' },
{ value: 'ThresholdRange', label: 'Range' },
],
commands: ({ value, commandsManager, options }) => {
const thresholdRangeOption = options.find(option => option.id === 'threshold-shape');
if (value === 'ThresholdDynamic') {
commandsManager.run('setToolActiveToolbar', {
toolName:
thresholdRangeOption.value === 'ThresholdCircularBrush'
? 'ThresholdCircularBrushDynamic'
: 'ThresholdSphereBrushDynamic',
});
} else {
commandsManager.run('setToolActiveToolbar', {
toolName: thresholdRangeOption.value,
});
const thresholdRangeValue = options.find(
option => option.id === 'threshold-range'
).value;
commandsManager.run('setThresholdRange', {
toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
value: thresholdRangeValue,
});
}
},
},
{
name: 'ThresholdRange',
type: 'double-range',
id: 'threshold-range',
min: -1000,
max: 1000,
step: 1,
value: [50, 600],
condition: ({ options }) =>
options.find(option => option.id === 'dynamic-mode').value === 'ThresholdRange',
commands: {
commandName: 'setThresholdRange',
commandOptions: {
toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
},
},
},
],
},
},
{
id: 'Shapes',
uiType: 'ohif.toolBoxButton',
props: {
icon: 'icon-tool-shape',
label: 'Shapes',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircleScissor', 'SphereScissor', 'RectangleScissor'],
disabledText: 'Create new segmentation to enable shapes tool.',
},
options: [
{
name: 'Shape',
type: 'radio',
value: 'CircleScissor',
id: 'shape-mode',
values: [
{ value: 'CircleScissor', label: 'Circle' },
{ value: 'SphereScissor', label: 'Sphere' },
{ value: 'RectangleScissor', label: 'Rectangle' },
],
commands: 'setToolActiveToolbar',
},
], ],
}, },
}, },

View File

@ -1,5 +1,5 @@
import { hotkeys, classes } from '@ohif/core'; import { classes } from '@ohif/core';
import toolbarButtons from './toolbarButtons.js'; import toolbarButtons from './toolbarButtons';
import { id } from './id.js'; import { id } from './id.js';
import initToolGroups from './initToolGroups.js'; import initToolGroups from './initToolGroups.js';
import setCrosshairsConfiguration from './utils/setCrosshairsConfiguration.js'; import setCrosshairsConfiguration from './utils/setCrosshairsConfiguration.js';
@ -96,11 +96,21 @@ function modeFactory({ modeConfiguration }) {
'Crosshairs', 'Crosshairs',
'Pan', 'Pan',
]); ]);
toolbarService.createButtonSection('ROIThresholdToolbox', [ toolbarService.createButtonSection('measurementSection', [
'Length',
'Bidirectional',
'ArrowAnnotate',
'EllipticalROI',
]);
toolbarService.createButtonSection('ROIThresholdToolbox', ['SegmentationTools']);
toolbarService.createButtonSection('segmentationToolboxToolsSection', [
'RectangleROIStartEndThreshold', 'RectangleROIStartEndThreshold',
'BrushTools', 'BrushTools',
]); ]);
toolbarService.createButtonSection('brushToolsSection', ['Brush', 'Eraser', 'Threshold']);
customizationService.setCustomizations({ customizationService.setCustomizations({
'panelSegmentation.tableMode': { 'panelSegmentation.tableMode': {
$set: 'expanded', $set: 'expanded',

View File

@ -1,285 +0,0 @@
import { ToolbarService } from '@ohif/core';
import { toolGroupIds } from './initToolGroups';
const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar',
commandOptions: {
toolGroupIds: [toolGroupIds.CT, toolGroupIds.PT, toolGroupIds.Fusion],
},
};
const toolbarButtons = [
{
id: 'MeasurementTools',
uiType: 'ohif.toolButtonList',
props: {
groupId: 'MeasurementTools',
primary: ToolbarService.createButton({
id: 'Length',
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
secondary: {
icon: 'chevron-down',
tooltip: 'More Measure Tools',
},
items: [
ToolbarService.createButton({
id: 'Bidirectional',
icon: 'tool-bidirectional',
label: 'Bidirectional',
tooltip: 'Bidirectional Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
ToolbarService.createButton({
id: 'ArrowAnnotate',
icon: 'tool-annotate',
label: 'Arrow Annotate',
tooltip: 'Arrow Annotate Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
ToolbarService.createButton({
id: 'EllipticalROI',
icon: 'tool-ellipse',
label: 'Ellipse',
tooltip: 'Ellipse Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
],
},
},
{
id: 'Zoom',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-zoom',
label: 'Zoom',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
// Window Level + Presets
{
id: 'WindowLevel',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-window-level',
label: 'Window Level',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
// Crosshairs Button
{
id: 'Crosshairs',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-crosshair',
label: 'Crosshairs',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
// Pan Button
{
id: 'Pan',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-move',
label: 'Pan',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
// Rectangle ROI Start End Threshold Button
{
id: 'RectangleROIStartEndThreshold',
uiType: 'ohif.toolBoxButton',
props: {
icon: 'tool-create-threshold',
label: 'Rectangle ROI Threshold',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstone.segmentation',
// need to put the disabled text last, since each evaluator will
// merge the result text into the final result
{
name: 'evaluate.cornerstoneTool',
disabledText: 'Select the PT Axial to enable this tool',
},
],
options: 'tmtv.RectangleROIThresholdOptions',
},
},
{
id: 'BrushTools',
uiType: 'ohif.toolBoxButtonGroup',
props: {
groupId: 'BrushTools',
evaluate: 'evaluate.cornerstone.hasSegmentation',
items: [
{
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: [
{
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'] },
},
},
{
name: 'Shape',
type: 'radio',
id: 'brush-mode',
value: 'CircularBrush',
values: [
{ value: 'CircularBrush', label: 'Circle' },
{ value: 'SphereBrush', label: 'Sphere' },
],
commands: 'setToolActiveToolbar',
},
],
},
{
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',
},
],
},
{
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',
],
},
},
},
{
name: 'Threshold',
type: 'radio',
id: 'dynamic-mode',
value: 'ThresholdRange',
values: [
{ value: 'ThresholdDynamic', label: 'Dynamic' },
{ value: 'ThresholdRange', label: 'Range' },
],
commands: ({ value, commandsManager }) => {
if (value === 'ThresholdDynamic') {
commandsManager.run('setToolActive', {
toolName: 'ThresholdCircularBrushDynamic',
});
} else {
commandsManager.run('setToolActive', {
toolName: 'ThresholdCircularBrush',
});
}
},
},
{
name: 'Shape',
type: 'radio',
id: 'eraser-mode',
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: 0,
max: 50,
step: 0.5,
value: [2.5, 50],
condition: ({ options }) =>
options.find(option => option.id === 'dynamic-mode').value === 'ThresholdRange',
commands: {
commandName: 'setThresholdRange',
commandOptions: {
toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
},
},
},
],
},
],
},
},
];
export default toolbarButtons;

View File

@ -0,0 +1,300 @@
import { toolGroupIds } from './initToolGroups';
const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar',
commandOptions: {
toolGroupIds: [toolGroupIds.CT, toolGroupIds.PT, toolGroupIds.Fusion],
},
};
const toolbarButtons = [
{
id: 'MeasurementTools',
uiType: 'ohif.toolButtonList',
props: {
buttonSection: 'measurementSection',
groupId: 'MeasurementTools',
},
},
{
id: 'SegmentationTools',
uiType: 'ohif.toolBoxButton',
props: {
groupId: 'SegmentationTools',
buttonSection: 'segmentationToolboxToolsSection',
},
},
{
id: 'BrushTools',
uiType: 'ohif.toolBoxButtonGroup',
props: {
buttonSection: 'brushToolsSection',
groupId: 'BrushTools',
},
},
{
id: 'Length',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Bidirectional',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-bidirectional',
label: 'Bidirectional',
tooltip: 'Bidirectional Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'ArrowAnnotate',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-annotate',
label: 'Arrow Annotate',
tooltip: 'Arrow Annotate Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'EllipticalROI',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-ellipse',
label: 'Ellipse',
tooltip: 'Ellipse Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Zoom',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-zoom',
label: 'Zoom',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'WindowLevel',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-window-level',
label: 'Window Level',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Crosshairs',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-crosshair',
label: 'Crosshairs',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Pan',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-move',
label: 'Pan',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'RectangleROIStartEndThreshold',
uiType: 'ohif.toolBoxButton',
props: {
icon: 'tool-create-threshold',
label: 'Rectangle ROI Threshold',
commands: setToolActiveToolbar,
evaluate: [
'evaluate.cornerstone.segmentation',
{
name: 'evaluate.cornerstoneTool',
disabledText: 'Select the PT Axial to enable this tool',
},
],
options: 'tmtv.RectangleROIThresholdOptions',
},
},
{
id: 'Brush',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-brush',
label: 'Brush',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircularBrush', 'SphereBrush'],
disabledText: 'Create new segmentation to enable this tool.',
},
options: [
{
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'] },
},
},
{
name: 'Shape',
type: 'radio',
id: 'brush-mode',
value: 'CircularBrush',
values: [
{ value: 'CircularBrush', label: 'Circle' },
{ value: 'SphereBrush', label: 'Sphere' },
],
commands: 'setToolActiveToolbar',
},
],
},
},
{
id: 'Eraser',
uiType: 'ohif.toolButton',
props: {
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',
},
],
},
},
{
id: 'Threshold',
uiType: 'ohif.toolButton',
props: {
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',
],
},
},
},
{
name: 'Threshold',
type: 'radio',
id: 'dynamic-mode',
value: 'ThresholdRange',
values: [
{ value: 'ThresholdDynamic', label: 'Dynamic' },
{ value: 'ThresholdRange', label: 'Range' },
],
commands: ({ value, commandsManager }) => {
if (value === 'ThresholdDynamic') {
commandsManager.run('setToolActive', {
toolName: 'ThresholdCircularBrushDynamic',
});
} else {
commandsManager.run('setToolActive', {
toolName: 'ThresholdCircularBrush',
});
}
},
},
{
name: 'Shape',
type: 'radio',
id: 'eraser-mode',
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: 0,
max: 50,
step: 0.5,
value: [2.5, 50],
condition: ({ options }) =>
options.find(option => option.id === 'dynamic-mode').value === 'ThresholdRange',
commands: {
commandName: 'setThresholdRange',
commandOptions: {
toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
},
},
},
],
},
},
];
export default toolbarButtons;

View File

@ -76,7 +76,7 @@
"optionalDependencies": { "optionalDependencies": {
"@percy/cypress": "^3.1.1", "@percy/cypress": "^3.1.1",
"@playwright/test": "^1.48.0", "@playwright/test": "^1.48.0",
"cypress": "^14.0.0", "cypress": "^14.1.0",
"cypress-file-upload": "^5.0.8" "cypress-file-upload": "^5.0.8"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,108 +1,95 @@
describe('OHIF Download Snapshot File', () => { // describe('OHIF Download Snapshot File', () => {
beforeEach(() => { // beforeEach(() => {
cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); // cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.expectMinimumThumbnails(3); // cy.expectMinimumThumbnails(3);
cy.openDownloadImageModal(); // cy.openDownloadImageModal();
}); // });
it('checks displayed information for Desktop experience', function () { // it('checks displayed information for Desktop experience', function () {
// Set Desktop resolution // // Set Desktop resolution
// cy.viewport(1750, 720); // // cy.viewport(1750, 720);
// Visual comparison // // Visual comparison
// cy.screenshot('Download Image Modal - Desktop experience'); // // cy.screenshot('Download Image Modal - Desktop experience');
//Check if all elements are displayed // //Check if all elements are displayed
// TODO: need to add this attribute to the modal // // Check image preview
cy.get('[data-cy=modal-header]') // cy.get('[data-cy="image-preview"]').should('contain.text', 'Image preview');
.as('downloadImageModal')
.should('contain.text', 'Download High Quality Image');
// Check input fields // //TODO: This is a canvas now, not an img with src
// TODO: select2 // // cy.get('[data-cy="viewport-preview-img"]')
// cy.get('[data-cy="file-type"]') // // .should('have.attr', 'src')
// .select('png') // // .and('include', 'data:image');
// .should('have.value', 'png')
// .select('jpg')
// .should('have.value', 'jpg');
// Check image preview // // Check buttons
cy.get('[data-cy="image-preview"]').should('contain.text', 'Image preview'); // cy.get('[data-cy="cancel-btn"]').scrollIntoView().should('be.visible');
// cy.get('[data-cy="download-btn"]').scrollIntoView().should('be.visible');
//TODO: This is a canvas now, not an img with src // cy.get('[data-cy="cancel-btn"]').click();
// cy.get('[data-cy="viewport-preview-img"]') // });
// .should('have.attr', 'src')
// .and('include', 'data:image');
// Check buttons // /*it('cancel changes on download modal', function() {
cy.get('[data-cy="cancel-btn"]').scrollIntoView().should('be.visible'); // //Change Image Width, Filename and File Type
cy.get('[data-cy="download-btn"]').scrollIntoView().should('be.visible'); // cy.get('[data-cy="image-width"]')
// .clear()
// .type('300');
// cy.get('[data-cy="image-height"]') //Image Height should be the same as width
// .should('have.value', '300');
// cy.get('[data-cy="file-name"]')
// .clear()
// .type('new-filename');
// cy.get('[data-cy="file-type"]').select('png');
// //Click on Cancel button
// cy.get('[data-cy="cancel-btn"]')
// .scrollIntoView()
// .click();
// //Check modal is closed
// cy.get('[data-cy="modal"]').should('not.exist');
// //Open Modal
// cy.openDownloadImageModal();
// //Verify default values was restored
// cy.get('[data-cy="image-width"]').should('have.value', '512');
// cy.get('[data-cy="file-name"]').should('have.value', 'image');
// cy.get('[data-cy=file-type]').should('have.value', 'jpg');
// });*/
cy.get('[data-cy="cancel-btn"]').click(); // // TO-DO once issue is fixed: https://github.com/OHIF/Viewers/issues/1217
}); // // it('checks error messages for empty fields', function() {
// // //Clear fields Image Width and Filename
// // cy.get('[data-cy="image-width"]').clear();
// // cy.get('[data-cy="file-name"]').clear();
/*it('cancel changes on download modal', function() { // // //Click on Download button
//Change Image Width, Filename and File Type // // cy.get('[data-cy="download-btn"]')
cy.get('[data-cy="image-width"]') // // .scrollIntoView()
.clear() // // .click();
.type('300'); // // //Check error message
cy.get('[data-cy="image-height"]') //Image Height should be the same as width // // });
.should('have.value', '300');
cy.get('[data-cy="file-name"]')
.clear()
.type('new-filename');
cy.get('[data-cy="file-type"]').select('png');
//Click on Cancel button
cy.get('[data-cy="cancel-btn"]')
.scrollIntoView()
.click();
//Check modal is closed
cy.get('[data-cy="modal"]').should('not.exist');
//Open Modal
cy.openDownloadImageModal();
//Verify default values was restored
cy.get('[data-cy="image-width"]').should('have.value', '512');
cy.get('[data-cy="file-name"]').should('have.value', 'image');
cy.get('[data-cy=file-type]').should('have.value', 'jpg');
});*/
// TO-DO once issue is fixed: https://github.com/OHIF/Viewers/issues/1217 // /*it('checks if "Show Annotations" checkbox will display annotations', function() {
// it('checks error messages for empty fields', function() { // // Close modal that is initially opened
// //Clear fields Image Width and Filename // cy.get('[data-cy="close-button"]').click();
// cy.get('[data-cy="image-width"]').clear();
// cy.get('[data-cy="file-name"]').clear();
// //Click on Download button // // Add measurements in the viewport
// cy.get('[data-cy="download-btn"]') // cy.addLengthMeasurement();
// .scrollIntoView() // cy.addAngleMeasurement();
// .click();
// //Check error message
// });
/*it('checks if "Show Annotations" checkbox will display annotations', function() { // // Open Modal
// Close modal that is initially opened // cy.openDownloadImageModal();
cy.get('[data-cy="close-button"]').click(); // // Select "Show Annotations" option
// cy.get('[data-cy="show-annotations"]').check();
// Add measurements in the viewport // // Check image preview
cy.addLengthMeasurement(); // cy.get('[data-cy="image-preview"]').scrollIntoView();
cy.addAngleMeasurement(); // //Compare classes that exists on Image Preview with Annotations and Without Annotation
// cy.get('[data-cy="modal-content"]')
// Open Modal // .find('canvas')
cy.openDownloadImageModal(); // .should('have.class', 'magnifyTool'); //Class "MagnifyTool" exists with annotations displayed on Image preview
// Select "Show Annotations" option // // Uncheck "Show Annotations" option
cy.get('[data-cy="show-annotations"]').check(); // cy.get('[data-cy="show-annotations"]')
// Check image preview // .uncheck()
cy.get('[data-cy="image-preview"]').scrollIntoView(); // .wait(300);
//Compare classes that exists on Image Preview with Annotations and Without Annotation // // Check that class "MagnifyTool" should not exist
cy.get('[data-cy="modal-content"]') // cy.get('[data-cy="modal-content"]')
.find('canvas') // .find('canvas')
.should('have.class', 'magnifyTool'); //Class "MagnifyTool" exists with annotations displayed on Image preview // .should('not.have.class', 'magnifyTool');
// Uncheck "Show Annotations" option // });*/
cy.get('[data-cy="show-annotations"]') // });
.uncheck()
.wait(300);
// Check that class "MagnifyTool" should not exist
cy.get('[data-cy="modal-content"]')
.find('canvas')
.should('not.have.class', 'magnifyTool');
});*/
});

View File

@ -4,8 +4,8 @@ export function initCornerstoneToolsAliases() {
// cy.get('[data-cy="StackScroll"]').as('stackScrollBtn'); // cy.get('[data-cy="StackScroll"]').as('stackScrollBtn');
cy.get('[data-cy="Zoom"]').as('zoomBtn'); cy.get('[data-cy="Zoom"]').as('zoomBtn');
cy.get('[data-cy="WindowLevel-split-button-primary"]').as('wwwcBtnPrimary'); cy.get('[data-cy="WindowLevelGroup-split-button-primary"]').as('wwwcBtnPrimary');
cy.get('[data-cy="WindowLevel-split-button-secondary"]').as('wwwcBtnSecondary'); cy.get('[data-cy="WindowLevelGroup-split-button-secondary"]').as('wwwcBtnSecondary');
cy.get('[data-cy="Pan"]').as('panBtn'); cy.get('[data-cy="Pan"]').as('panBtn');
cy.get('[data-cy="MeasurementTools-split-button-primary"]').as('measurementToolsBtnPrimary'); cy.get('[data-cy="MeasurementTools-split-button-primary"]').as('measurementToolsBtnPrimary');
cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as('measurementToolsBtnSecondary'); cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as('measurementToolsBtnSecondary');

View File

@ -54,7 +54,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5", "@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.0.1", "@cornerstonejs/dicom-image-loader": "^3.0.4",
"@emotion/serialize": "^1.1.3", "@emotion/serialize": "^1.1.3",
"@ohif/core": "3.10.0-beta.116", "@ohif/core": "3.10.0-beta.116",
"@ohif/extension-cornerstone": "3.10.0-beta.116", "@ohif/extension-cornerstone": "3.10.0-beta.116",

View File

@ -6,13 +6,7 @@ window.config = {
// whiteLabeling: {}, // whiteLabeling: {},
extensions: [], extensions: [],
modes: [], modes: [],
customizationService: { customizationService: {},
'viewportActionMenu.windowLevelActionMenu': {
$merge: {
location: 0,
},
},
},
showStudyList: true, showStudyList: true,
// some windows systems have issues with more than 3 web workers // some windows systems have issues with more than 3 web workers
maxNumberOfWebWorkers: 3, maxNumberOfWebWorkers: 3,

View File

@ -26,7 +26,6 @@ import {
ViewportGridProvider, ViewportGridProvider,
DialogProvider, DialogProvider,
TooltipProvider, TooltipProvider,
ToolboxProvider,
Modal as ModalNext, Modal as ModalNext,
ManagedDialog, ManagedDialog,
ModalProvider, ModalProvider,
@ -117,7 +116,6 @@ function App({
[ThemeWrapperNext], [ThemeWrapperNext],
[ThemeWrapper], [ThemeWrapper],
[SystemContextProvider, { commandsManager, extensionManager, hotkeysManager, servicesManager }], [SystemContextProvider, { commandsManager, extensionManager, hotkeysManager, servicesManager }],
[ToolboxProvider],
[ViewportGridProvider, { service: viewportGridService }], [ViewportGridProvider, { service: viewportGridService }],
[ViewportDialogProvider, { service: uiViewportDialogService }], [ViewportDialogProvider, { service: uiViewportDialogService }],
[CineProvider, { service: cineService }], [CineProvider, { service: cineService }],

View File

@ -1,4 +1,3 @@
import { hotkeys } from '@ohif/core';
import { initToolGroups, toolbarButtons } from '@ohif/mode-longitudinal'; import { initToolGroups, toolbarButtons } from '@ohif/mode-longitudinal';
import { id } from './id'; import { id } from './id';
@ -50,14 +49,47 @@ function modeFactory({ modeConfiguration }) {
toolbarService.addButtons(toolbarButtons); toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', [ toolbarService.createButtonSection('primary', [
'MeasurementTools', 'measurementSection',
'Zoom', 'Zoom',
'WindowLevel', 'WindowLevel',
'Pan', 'Pan',
'Capture', 'Capture',
'Layout', 'Layout',
'Crosshairs', 'Crosshairs',
'MoreTools', 'moreToolsSection',
]);
toolbarService.createButtonSection('measurementSection', [
'Length',
'Bidirectional',
'ArrowAnnotate',
'EllipticalROI',
'RectangleROI',
'CircleROI',
'PlanarFreehandROI',
'SplineROI',
'LivewireContour',
]);
toolbarService.createButtonSection('moreToolsSection', [
'Reset',
'rotate-right',
'flipHorizontal',
'ImageSliceSync',
'ReferenceLines',
'ImageOverlayViewer',
'StackScroll',
'invert',
'Probe',
'Cine',
'Angle',
'CobbAngle',
'Magnify',
'CalibrationLine',
'TagBrowser',
'AdvancedMagnify',
'UltrasoundDirectionalTool',
'WindowLevelRegion',
]); ]);
}, },
onModeExit: ({ servicesManager }: withAppTypes) => { onModeExit: ({ servicesManager }: withAppTypes) => {

View File

@ -37,7 +37,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5", "@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.0.1", "@cornerstonejs/dicom-image-loader": "^3.0.4",
"@ohif/ui": "3.10.0-beta.116", "@ohif/ui": "3.10.0-beta.116",
"cornerstone-math": "0.1.9", "cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21" "dicom-parser": "^1.8.21"

View File

@ -1,6 +1,8 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useSystem } from '../contextProviders/SystemProvider';
export function useToolbar({ servicesManager, buttonSection = 'primary' }: withAppTypes) { export function useToolbar({ buttonSection = 'primary' }: withAppTypes) {
const { commandsManager, servicesManager } = useSystem();
const { toolbarService, viewportGridService } = servicesManager.services; const { toolbarService, viewportGridService } = servicesManager.services;
const { EVENTS } = toolbarService; const { EVENTS } = toolbarService;
@ -11,15 +13,54 @@ export function useToolbar({ servicesManager, buttonSection = 'primary' }: withA
// Callback function for handling toolbar interactions // Callback function for handling toolbar interactions
const onInteraction = useCallback( const onInteraction = useCallback(
args => { args => {
args.event?.stopPropagation?.();
const viewportId = viewportGridService.getActiveViewportId(); const viewportId = viewportGridService.getActiveViewportId();
const refreshProps = { const refreshProps = { viewportId };
viewportId,
}; const buttonProps = toolbarService.getButtonProps(args.itemId);
toolbarService.recordInteraction(args, {
refreshProps, if (buttonProps.commands || buttonProps.options) {
}); const allCommands = [];
const options = buttonProps.options || [];
const itemCommands = buttonProps.commands || [];
// Process item commands
if (itemCommands) {
Array.isArray(itemCommands)
? allCommands.push(...itemCommands)
: allCommands.push(itemCommands);
}
// Process commands from options
if (options.length > 0) {
options.forEach(option => {
if (!option.commands) {
return;
}
const valueToUse = option.value;
const commands = Array.isArray(option.commands) ? option.commands : [option.commands];
commands.forEach(command => {
const commandOptions = {
...option,
value: valueToUse,
options: buttonProps.options,
servicesManager: servicesManager,
commandsManager: commandsManager,
};
const processedCommand = () => commandsManager.run(command, commandOptions);
allCommands.push(processedCommand);
});
});
}
buttonProps.commands = allCommands;
}
toolbarService.recordInteraction({ ...args, ...buttonProps }, { refreshProps });
}, },
[toolbarService, viewportGridService] [toolbarService, viewportGridService, toolbarButtons]
); );
// Effect to handle toolbar modification events // Effect to handle toolbar modification events

View File

@ -134,7 +134,7 @@ export default class ToolbarService extends PubSubService {
buttons.forEach(button => { buttons.forEach(button => {
if (replace || !this.state.buttons[button.id]) { if (replace || !this.state.buttons[button.id]) {
if (!button.props) { if (!button.props) {
button.props = {}; button.props = {} as ButtonProps;
} }
this.state.buttons[button.id] = button; this.state.buttons[button.id] = button;
@ -173,12 +173,16 @@ export default class ToolbarService extends PubSubService {
? interaction.commands ? interaction.commands
: [interaction.commands]; : [interaction.commands];
commands = commands.filter(Boolean);
if (!commands?.length) { if (!commands?.length) {
this.refreshToolbarState({ this.refreshToolbarState({
...options?.refreshProps, ...options?.refreshProps,
itemId, itemId,
interaction, interaction,
}); });
return;
} }
const commandOptions = { ...options, ...interaction }; const commandOptions = { ...options, ...interaction };
@ -228,10 +232,6 @@ export default class ToolbarService extends PubSubService {
*/ */
public refreshToolbarState(refreshProps) { public refreshToolbarState(refreshProps) {
const buttons = this.state.buttons; const buttons = this.state.buttons;
// Tracks evaluated buttons to avoid re-evaluating them (this will
// cause issue for toggles where if the button is in primary
// and secondary it will be evaluated twice)
const evaluationResults = new Map(); const evaluationResults = new Map();
const evaluateButtonProps = (button, props, refreshProps) => { const evaluateButtonProps = (button, props, refreshProps) => {
@ -252,59 +252,50 @@ export default class ToolbarService extends PubSubService {
} }
}; };
const refreshedButtons = Object.values(buttons).reduce((acc, button: Button) => { Object.values(buttons).forEach(button => {
const isNested = (button.props as NestedButtonProps)?.groupId; const hasSection = (button.props as NestedButtonProps)?.buttonSection;
if (!isNested) { if (!hasSection) {
this.handleEvaluate(button.props); this.handleEvaluate(button.props);
const buttonProps = button.props as ButtonProps; const buttonProps = button.props as ButtonProps;
const updatedProps = evaluateButtonProps(button, buttonProps, refreshProps); const updatedProps = evaluateButtonProps(button, buttonProps, refreshProps);
acc[button.id] = { buttons[button.id] = {
...button, ...button,
props: updatedProps, props: updatedProps,
}; };
} else { } else {
let buttonProps = button.props as NestedButtonProps; let buttonProps = button.props as NestedButtonProps;
// if it is nested we should perform evaluate on each item in the group
this.handleEvaluateNested(buttonProps);
const { evaluate: groupEvaluate } = buttonProps; const { evaluate: groupEvaluate } = buttonProps;
const groupEvaluated =
typeof groupEvaluate === 'function'
? groupEvaluate({ ...refreshProps, button })
: undefined;
const groupEvaluated = groupEvaluate?.({ ...refreshProps, button });
// handle group evaluate function which might switch the primary
// item in the group
buttonProps = { buttonProps = {
...buttonProps, ...buttonProps,
primary: groupEvaluated?.primary ?? buttonProps.primary,
disabled: groupEvaluated?.disabled ?? buttonProps.disabled, disabled: groupEvaluated?.disabled ?? buttonProps.disabled,
disabledText: groupEvaluated?.disabledText ?? buttonProps.disabledText, disabledText: groupEvaluated?.disabledText ?? buttonProps.disabledText,
}; };
const { primary, items } = buttonProps; const toolButtonIds = this.state.buttonSections[buttonProps.buttonSection];
// primary and items evaluate functions if (!toolButtonIds) {
let updatedPrimary; return;
if (primary) {
updatedPrimary = evaluateButtonProps(primary, primary, refreshProps);
} }
const updatedItems = items.map(item => evaluateButtonProps(item, item, refreshProps));
buttonProps = {
...buttonProps,
primary: updatedPrimary,
items: updatedItems,
};
acc[button.id] = { toolButtonIds.forEach(buttonId => {
...button, const button = buttons[buttonId];
props: buttonProps, const updatedProps = evaluateButtonProps(button, button.props, refreshProps);
}; buttons[buttonId] = {
...button,
props: updatedProps,
};
});
} }
});
return acc; this.setButtons(buttons);
}, {});
this.setButtons(refreshedButtons);
return this.state; return this.state;
} }
@ -349,17 +340,6 @@ export default class ToolbarService extends PubSubService {
* @returns The button properties. * @returns The button properties.
*/ */
public getButtonProps(id: string): ButtonProps { public getButtonProps(id: string): ButtonProps {
for (const buttonId of Object.keys(this.state.buttons)) {
const { primary, items } = (this.state.buttons[buttonId].props as NestedButtonProps) || {};
if (primary?.id === id) {
return primary;
}
const found = items?.find(childButton => childButton.id === id);
if (found) {
return found;
}
}
// This should be checked after we checked the nested buttons, since // This should be checked after we checked the nested buttons, since
// we are checking based on the ids, the nested objects are higher priority // we are checking based on the ids, the nested objects are higher priority
// and more specific // and more specific
@ -421,6 +401,12 @@ export default class ToolbarService extends PubSubService {
); );
} }
getButtonPropsInButtonSection(sectionId: string) {
const buttonSectionIds = this.state.buttonSections[sectionId];
return buttonSectionIds?.map(btnId => this.getButtonProps(btnId)) || [];
}
/** /**
* Retrieves the tool name for a given button. * Retrieves the tool name for a given button.
* @param button - The button object. * @param button - The button object.
@ -448,41 +434,119 @@ export default class ToolbarService extends PubSubService {
* @param {*} metadata * @param {*} metadata
* @param {*} props - Props set by the Viewer layer * @param {*} props - Props set by the Viewer layer
*/ */
_mapButtonToDisplay(btn, props) { _mapButtonToDisplay(btn: Button, props: Record<string, unknown>) {
if (!btn) { if (!btn) {
return; return;
} }
const { id, uiType, component } = btn; const { id, uiType } = btn;
const { groupId } = btn.props; const { groupId } = btn.props as NestedButtonProps;
const buttonTypes = this._getButtonUITypes(); const buttonTypes = this._getButtonUITypes();
const buttonType = buttonTypes[uiType]; const buttonType = buttonTypes[uiType];
if (!buttonType && !component) { if (!btn.component) {
btn.component = buttonType.defaultComponent;
}
if (!buttonType) {
return; return;
} }
!groupId ? this.handleEvaluate(btn.props) : this.handleEvaluateNested(btn.props); !groupId ? this.handleEvaluate(btn.props) : this.handleEvaluateNested(btn.props);
const { id: buttonId, props: componentProps } = btn;
const createEnhancedOptions = (options, itemId) => {
const optionsToUse = Array.isArray(options) ? options : [options];
const toolProps = this.getButtonProps(itemId);
return optionsToUse.map(option => {
if (typeof option.optionComponent === 'function') {
return option;
}
return {
...option,
onChange: value => {
// Update the option's value for UI
option.value = value;
const cmds = Array.isArray(option.commands) ? option.commands : [option.commands];
// Find the parent button and update its options
if (toolProps && toolProps.options) {
// Find the option in the button's options array and update its value
const optionIndex = toolProps.options.findIndex(opt => opt.id === option.id);
if (optionIndex !== -1) {
toolProps.options[optionIndex].value = value;
}
}
cmds.forEach(command => {
const commandOptions = {
...option,
value,
options: toolProps.options,
servicesManager: this._servicesManager,
commandsManager: this._commandsManager,
};
this._commandsManager.run(command, commandOptions);
});
// Notify that toolbar state has been modified
this._broadcastEvent(EVENTS.TOOL_BAR_STATE_MODIFIED, {
buttons: this.state.buttons,
buttonSections: this.state.buttonSections,
});
},
};
});
};
if ((componentProps as NestedButtonProps)?.items?.length) {
const { items = [] } = componentProps as NestedButtonProps;
items.forEach(item => {
if (!item.options) {
return;
}
item.options = createEnhancedOptions(item.options, item.id);
});
} else if ((componentProps as ButtonProps).options?.length) {
(componentProps as ButtonProps).options = createEnhancedOptions(
(componentProps as ButtonProps).options,
buttonId
);
} else if ((componentProps as ButtonProps).optionComponent) {
(componentProps as ButtonProps).optionComponent = options.optionComponent;
}
return { return {
id, id,
Component: component || buttonType.defaultComponent, Component: btn.component,
componentProps: Object.assign({}, btn.props, props), componentProps: Object.assign({ id }, btn.props, props),
}; };
} }
handleEvaluateNested = props => { handleEvaluateNested = props => {
const { primary, items } = props; const { buttonSection } = props;
// handle group evaluate function
this.handleEvaluate(props);
// primary and items evaluate functions if (!buttonSection) {
if (primary) { return;
this.handleEvaluate(primary);
} }
items.forEach(item => this.handleEvaluate(item));
const toolbarButtons = this.getButtonSection(buttonSection);
if (!toolbarButtons?.length) {
return;
}
toolbarButtons.forEach(button => {
this.handleEvaluate(button.componentProps);
});
}; };
handleEvaluate = props => { handleEvaluate = props => {
@ -492,9 +556,7 @@ export default class ToolbarService extends PubSubService {
// get the custom option component from the extension manager and set it as the optionComponent // get the custom option component from the extension manager and set it as the optionComponent
const buttonTypes = this._getButtonUITypes(); const buttonTypes = this._getButtonUITypes();
const optionComponent = buttonTypes[options]?.defaultComponent; const optionComponent = buttonTypes[options]?.defaultComponent;
props.options = { props.options = optionComponent;
optionComponent,
};
} }
if (typeof evaluate === 'function') { if (typeof evaluate === 'function') {

View File

@ -1,4 +1,5 @@
import type { RunCommand } from '../../types/Command'; import type { RunCommand } from '../../types/Command';
import React from 'react';
export type EvaluatePublic = export type EvaluatePublic =
| string | string
@ -17,6 +18,20 @@ export type EvaluateObject = {
[key: string]: unknown; [key: string]: unknown;
}; };
export type ButtonOptions = {
id: string;
type: 'range' | 'radio' | 'double-range' | 'custom';
name?: string;
min?: number;
max?: number;
step?: number;
value?: number | number[] | string;
commands?: (value: unknown) => void;
condition?: (props: Record<string, unknown>) => boolean;
children?: React.ReactNode | (() => React.ReactNode);
options?: Array<{ value: string; label: string }>;
};
export type ButtonProps = { export type ButtonProps = {
id: string; id: string;
icon: string; icon: string;
@ -27,27 +42,15 @@ export type ButtonProps = {
className?: string; className?: string;
evaluate?: EvaluatePublic; evaluate?: EvaluatePublic;
listeners?: Record<string, RunCommand>; listeners?: Record<string, RunCommand>;
}; options?: ButtonOptions[];
buttonSection?: string;
export type NestedButtonProps = {
groupId: string;
// group evaluate which is different
// from the evaluate function for the primary and items
evaluate?: EvaluatePublic;
items: ButtonProps[];
primary: ButtonProps & {
// Todo: this is really ugly but really we don't have any other option
// the ui design requires this since the button should be rounded if
// active otherwise it should not be rounded
isActive?: boolean;
};
secondary: ButtonProps;
}; };
export type Button = { export type Button = {
id: string; id: string;
props: ButtonProps | NestedButtonProps; props: ButtonProps;
// button ui type (e.g. 'ohif.splitButton', 'ohif.radioGroup') // button ui type (e.g. 'ohif.splitButton', 'ohif.radioGroup')
// extensions can provide custom components for these types // extensions can provide custom components for these types
uiType: string; uiType: string;
component?: React.ComponentType<any>;
}; };

View File

@ -27,7 +27,7 @@ export const EVENTS = {
sections: [ sections: [
{ {
key: 'primary', key: 'primary',
buttons: [ 'MeasurementTools', 'Zoom', ... ], buttons: [ 'measurementSection', 'Zoom', ... ],
}, },
], ],
}, },

View File

@ -72,11 +72,114 @@ The `ohif.buttonGroup` and `ohif.radioGroup` types used in the Toolbox have been
The `getToolbarModule` function previously returned `disabled`, `disabledText`, and `className` as part of its evaluation process for the button state. These properties will still be returned, but common class names are now handled internally by the new UI button components, including `ToolButton`, `ToolButtonList`, `Toolbox`, and `ToolBoxGroup`. You can override the `className` if you need to. The `getToolbarModule` function previously returned `disabled`, `disabledText`, and `className` as part of its evaluation process for the button state. These properties will still be returned, but common class names are now handled internally by the new UI button components, including `ToolButton`, `ToolButtonList`, `Toolbox`, and `ToolBoxGroup`. You can override the `className` if you need to.
## Tool Definitions: Moving to Section-Based Definitions
This migration represents a significant step toward a more extension-based toolbar system. We've moved away from the nested primary/items structure in favor of a flatter, more composable section-based approach.
### Deprecated: Nested Toolbar Structures
```diff
- {
- id: 'MeasurementTools',
- uiType: 'ohif.toolButtonList',
- props: {
- groupId: 'MeasurementTools',
- evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
- primary: createButton({
- id: 'Length',
- icon: 'tool-length',
- label: 'Length',
- tooltip: 'Length Tool',
- commands: setToolActiveToolbar,
- evaluate: 'evaluate.cornerstoneTool',
- }),
- secondary: {
- icon: 'chevron-down',
- tooltip: 'More Measure Tools',
- },
- items: [
- createButton({ ... }),
- createButton({ ... }),
- // More nested buttons
- ],
- },
- }
```
### New Approach: Section-Based Definitions
```diff
+ // 1. Define the toolbar section container
+ {
+ id: 'MeasurementTools',
+ uiType: 'ohif.toolButtonList',
+ props: {
+ buttonSection: 'measurementSection',
+ groupId: 'MeasurementTools',
+ },
+ },
+ // 2. Register individual buttons separately
+ {
+ id: 'Length',
+ uiType: 'ohif.toolButton',
+ props: {
+ icon: 'tool-length',
+ label: 'Length',
+ tooltip: 'Length Tool',
+ commands: setToolActiveToolbar,
+ evaluate: 'evaluate.cornerstoneTool',
+ },
+ },
+ {
+ id: 'Bidirectional',
+ uiType: 'ohif.toolButton',
+ props: {
+ icon: 'tool-bidirectional',
+ label: 'Bidirectional',
+ tooltip: 'Bidirectional Tool',
+ commands: setToolActiveToolbar,
+ evaluate: 'evaluate.cornerstoneTool',
+ },
+ },
```
and then in your mode you can compose the section and associate buttons
```diff
+ // 3. In your mode, create the section and associate buttons
+ toolbarService.createButtonSection('primary', [
+ 'MeasurementTools',
+ 'Pan',
+ 'Zoom',
+ ]);
+
+
+ toolbarService.createButtonSection('measurementSection', [
+ 'Length',
+ 'Bidirectional',
+ 'ArrowAnnotate',
+ 'EllipticalROI',
+ ]);
```
:::note
The `measurementSection` is defined in the tool button configuration of the `MeasurementTools` button.
:::
### Group Evaluators Deprecated
Group evaluator functions like `evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList` are now deprecated. Instead, the `uiType` component itself is responsible for grouping and displaying the buttons from a section as needed. This allows for more flexible UI implementations that aren't tied to specific evaluation logic.
## ToolBox ## ToolBox
Previously, the segmentation toolbox was not using an `evaluator` property. This is now taken into account Previously, the segmentation toolbox was not using an `evaluator` property. This is now taken into account
### evaluators in Toolbox
```js ```js
// old // old
{ {
@ -84,7 +187,6 @@ Previously, the segmentation toolbox was not using an `evaluator` property. This
uiType: 'ohif.buttonGroup', uiType: 'ohif.buttonGroup',
props: { props: {
groupId: 'BrushTools', groupId: 'BrushTools',
items: []
} }
} }
@ -95,7 +197,237 @@ Previously, the segmentation toolbox was not using an `evaluator` property. This
props: { props: {
groupId: 'BrushTools', groupId: 'BrushTools',
evaluate: 'evaluate.cornerstone.hasSegmentation', evaluate: 'evaluate.cornerstone.hasSegmentation',
items: []
} }
} }
``` ```
### Replace Toolbox imports from ui-next with extension-default
```diff
- import { Toolbox } from '@ohif/ui-next';
+ import { Toolbox } from '@ohif/extension-default';
```
Ensure you're importing the Toolbox component from the correct location:
```javascript
// New import pattern
import { Toolbox } from '@ohif/extension-default';
// Usage remains similar
<Toolbox
servicesManager={servicesManager}
buttonSectionId="segmentation"
title="Segmentation Tools"
/>
```
### Stacked Sections in Toolbox
The new Toolbox component supports stacked sections, which allows for more complex UI organization. Instead of flat button groups, you can now create deep hierarchies of tool sections and subsections.
Previously you were able to have something like this
```js
// old
// buttons for BrushTools were a giant group of buttons
const buttons = {
id: 'BrushTools',
uiType: 'ohif.toolBoxButtonGroup',
props: {
groupId: 'BrushTools',
evaluate: 'evaluate.cornerstone.hasSegmentation',
items: [
{
id: 'Brush',
icon: 'icon-tool-brush',
label: 'Brush',
evaluate: {
// ...
},
options: [
// ...
],
},
{
id: 'Eraser',
icon: 'icon-tool-eraser',
label: 'Eraser',
evaluate: {
// ...
},
options: [
// ...
],
},
{
id: 'Threshold',
icon: 'icon-tool-threshold',
label: 'Threshold Tool',
evaluate: {
// ...
},
options: [
// ...
],
},
],
},
},
{
id: 'Shapes',
uiType: 'ohif.toolBoxButton',
props: {
id: 'Shapes',
icon: 'icon-tool-shape',
label: 'Shapes',
evaluate: {
// ...
},
options: [
// ...
],
},
},
toolbarService.addButtons(buttons);
toolbarService.createButtonSection('segmentationToolbox', ['BrushTools', 'Shapes']);
```
But now you should have at least one section defined in your toolbar buttons
```js
// separate flat definitions for each button and each section
const buttons = [
{
id: 'Brush',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-brush',
label: 'Brush',
evaluate: {
// ...
},
options: [
// ...
],
},
},
{
id: 'Eraser',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-eraser',
label: 'Eraser',
evaluate: {
// ...
},
options: [
// ...
],
},
},
{
id: 'Threshold',
uiType: 'ohif.toolButton',
props: {
icon: 'icon-tool-threshold',
label: 'Threshold Tool',
evaluate: {
// ...
},
options: [
// ...
],
},
},
{
id: 'Shapes',
uiType: 'ohif.toolBoxButton',
props: {
icon: 'icon-tool-shape',
label: 'Shapes',
evaluate: {
name: 'evaluate.cornerstone.segmentation',
toolNames: ['CircleScissor', 'SphereScissor', 'RectangleScissor'],
disabledText: 'Create new segmentation to enable shapes tool.',
},
options: [
// ...
],
},
},
// Sections
{
id: 'SegmentationTools',
uiType: 'ohif.toolBoxButton',
props: {
groupId: 'SegmentationTools',
buttonSection: 'segmentationToolboxToolsSection',
},
},
{
id: 'BrushTools',
uiType: 'ohif.toolBoxButtonGroup',
props: {
groupId: 'BrushTools',
buttonSection: 'brushToolsSection',
},
},
]
toolbarService.addButtons(buttons);
```
and then
```js
// Step 2: Create the section hierarchy
// Top level toolbox section
toolbarService.createButtonSection('segmentationToolbox', ['SegmentationTools']);
// Next level - subsections within the toolbox
toolbarService.createButtonSection('segmentationToolboxToolsSection', ['BrushTools', 'Shapes']);
// Lowest level - buttons within a subsection
toolbarService.createButtonSection('brushToolsSection', ['Brush', 'Eraser', 'Threshold']);
```
### Remove ToolboxProvider from composition root
If you have the ToolboxProvider in your application composition, remove it:
```diff
// In App.tsx or similar
const appComposition = [
[ThemeWrapperNext],
[ThemeWrapper],
[SystemContextProvider, { commandsManager, extensionManager, hotkeysManager, servicesManager }],
- [ToolboxProvider],
[ViewportGridProvider, { service: viewportGridService }],
// Other providers...
];
```
we now keep the state for toolbar inside the ToolbarService itself
### 3. Update tool option handlers to use onChange instead of commands
```diff
- <RowSegmentedControl
- key={option.id}
- option={option}
- />
+ <RowSegmentedControl
+ key={option.id}
+ option={option}
+ onChange={option.onChange}
+ />
```

View File

@ -13,18 +13,23 @@ interface RadioOption {
name: string; name: string;
value: string; value: string;
values: RadioValue[]; values: RadioValue[];
commands?: (val: string) => void; onChange?: (val: string) => void;
} }
interface RowSegmentedControlProps { interface RowSegmentedControlProps {
option: RadioOption; option: RadioOption;
className?: string; className?: string;
onChange?: (val: string) => void;
} }
export const RowSegmentedControl: React.FC<RowSegmentedControlProps> = ({ option, className }) => { export const RowSegmentedControl: React.FC<RowSegmentedControlProps> = ({
option,
className,
onChange,
}) => {
const handleValueChange = (newVal: string) => { const handleValueChange = (newVal: string) => {
if (option.commands) { if (onChange) {
option.commands(newVal); onChange(newVal);
} }
}; };

View File

@ -56,7 +56,7 @@ const renderRangeSetting = option => {
maxValue={option.max} maxValue={option.max}
step={option.step} step={option.step}
value={option.value} value={option.value}
onChange={value => option.commands?.(value)} onChange={value => option.onChange?.(value)}
allowNumberEdit={true} allowNumberEdit={true}
inputClassName="ml-1 w-4/5 cursor-pointer" inputClassName="ml-1 w-4/5 cursor-pointer"
/> />
@ -70,6 +70,7 @@ const renderRadioSetting = option => {
<RowSegmentedControl <RowSegmentedControl
key={option.id} key={option.id}
option={option} option={option}
onChange={option.onChange}
/> />
); );
}; };
@ -79,7 +80,7 @@ function renderDoubleRangeSetting(option) {
<RowDoubleRange <RowDoubleRange
key={option.id} key={option.id}
values={option.value} values={option.value}
onChange={option.commands} onChange={option.onChange}
minValue={option.min} minValue={option.min}
maxValue={option.max} maxValue={option.max}
step={option.step} step={option.step}

View File

@ -1,162 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { ToolboxUI, useToolbox } from '../../';
import { useToolbar } from '@ohif/core';
/**
* A toolbox is a collection of buttons and commands that they invoke, used to provide
* custom control panels to users. This component is a generic UI component that
* interacts with services and commands in a generic fashion. While it might
* seem unconventional to import it from the UI and integrate it into the JSX,
* it belongs in the UI components as there isn't anything in this component that
* couldn't be used for a completely different type of app. It plays a crucial
* role in enhancing the app with a toolbox by providing a way to integrate
* and display various tools and their corresponding options
*/
function Toolbox({
servicesManager,
buttonSectionId,
commandsManager,
title,
...props
}: withAppTypes) {
// We should move these outside of the platform/ui-next, no file here
// should rely on the managers and services
const { state: toolboxState, api } = useToolbox(buttonSectionId);
const { onInteraction, toolbarButtons } = useToolbar({
servicesManager,
buttonSection: buttonSectionId,
});
const prevButtonIdsRef = useRef('');
const prevToolboxStateRef = useRef('');
useEffect(() => {
const currentButtonIdsStr = JSON.stringify(
toolbarButtons.map(button => {
const { id, componentProps } = button;
if (componentProps.items?.length) {
return componentProps.items.map(item => `${item.id}-${item.disabled}`);
}
return `${id}-${componentProps.disabled}`;
})
);
const currentToolBoxStateStr = JSON.stringify(
Object.keys(toolboxState.toolOptions).map(tool => {
const options = toolboxState.toolOptions[tool];
if (Array.isArray(options)) {
return options?.map(option => `${option.id}-${option.value}`);
}
})
);
if (
prevButtonIdsRef.current === currentButtonIdsStr &&
prevToolboxStateRef.current === currentToolBoxStateStr
) {
return;
}
prevButtonIdsRef.current = currentButtonIdsStr;
prevToolboxStateRef.current = currentToolBoxStateStr;
const initializeOptionsWithEnhancements = toolbarButtons.reduce(
(accumulator, toolbarButton) => {
const { id: buttonId, componentProps } = toolbarButton;
const createEnhancedOptions = (options, parentId) => {
const optionsToUse = Array.isArray(options) ? options : [options];
return optionsToUse.map(option => {
if (typeof option.optionComponent === 'function') {
return option;
}
const value =
toolboxState.toolOptions?.[parentId]?.find(prop => prop.id === option.id)?.value ??
option.value;
const updatedOptions = toolboxState.toolOptions?.[parentId];
return {
...option,
value,
commands: value => {
api.handleToolOptionChange(parentId, option.id, value);
const { isArray } = Array;
const cmds = isArray(option.commands) ? option.commands : [option.commands];
cmds.forEach(command => {
const isString = typeof command === 'string';
const isObject = typeof command === 'object';
const isFunction = typeof command === 'function';
if (isString) {
commandsManager.run(command, { value });
} else if (isObject) {
commandsManager.run({
...command,
commandOptions: {
...command.commandOptions,
...option,
value,
options: updatedOptions,
},
});
} else if (isFunction) {
command({ value, commandsManager, servicesManager, options: updatedOptions });
}
});
},
};
});
};
const { items, options } = componentProps;
if (items?.length) {
items.forEach(({ options, id }) => {
if (!options) {
return;
}
accumulator[id] = createEnhancedOptions(options, id);
});
} else if (options?.length) {
accumulator[buttonId] = createEnhancedOptions(options, buttonId);
} else if (options?.optionComponent) {
accumulator[buttonId] = options.optionComponent;
}
return accumulator;
},
{}
);
api.initializeToolOptions(initializeOptionsWithEnhancements);
}, [toolbarButtons, api, toolboxState, commandsManager, servicesManager]);
const handleToolOptionChange = (toolName, optionName, newValue) => {
api.handleToolOptionChange(toolName, optionName, newValue);
};
useEffect(() => {
return () => {
api.handleToolSelect(null);
};
}, []);
return (
<ToolboxUI
{...props}
title={title}
toolbarButtons={toolbarButtons}
toolboxState={toolboxState}
handleToolSelect={id => api.handleToolSelect(id)}
handleToolOptionChange={handleToolOptionChange}
onInteraction={onInteraction}
/>
);
}
export default Toolbox;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { PanelSection } from '../../components'; import { PanelSection } from '../../components';
@ -6,52 +6,39 @@ import { ToolSettings } from '../OHIFToolSettings';
const ItemsPerRow = 4; const ItemsPerRow = 4;
function usePrevious(value) { interface ToolbarButton {
const ref = useRef(); id: string;
useEffect(() => { Component: React.ComponentType<{
ref.current = value; id: string;
}); onInteraction: (details: { itemId: string }) => void;
return ref.current; size: string;
}>;
componentProps: {
isActive?: boolean;
buttonSection?: string;
options?: unknown;
};
} }
/** interface ToolboxProps {
* Just refactoring from the toolbox component to make it more readable toolbarButtons: ToolbarButton[];
*/ numRows: number;
function ToolboxUI(props: withAppTypes) { title?: string;
useCollapsedPanel?: boolean;
onInteraction?: (details: { itemId: string }) => void;
activeToolOptions?: unknown;
}
function ToolboxUI(props: ToolboxProps) {
const { const {
toolbarButtons, toolbarButtons = [],
handleToolSelect,
toolboxState,
numRows, numRows,
servicesManager,
title, title,
useCollapsedPanel = true, useCollapsedPanel = true,
onInteraction,
activeToolOptions,
} = props; } = props;
const { activeTool, toolOptions, selectedEvent } = toolboxState;
const activeToolOptions = toolOptions?.[activeTool];
const prevToolOptions = usePrevious(activeToolOptions);
useEffect(() => {
if (!activeToolOptions || Array.isArray(activeToolOptions) === false) {
return;
}
activeToolOptions.forEach((option, index) => {
const prevOption = prevToolOptions ? prevToolOptions[index] : undefined;
if (!prevOption || option.value !== prevOption.value || selectedEvent) {
const isOptionValid = option.condition
? option.condition({ options: activeToolOptions })
: true;
if (isOptionValid) {
const { commands } = option;
commands(option.value);
}
}
});
}, [activeToolOptions, selectedEvent]);
const render = () => { const render = () => {
return ( return (
<> <>
@ -67,12 +54,9 @@ function ToolboxUI(props: withAppTypes) {
const toolClasses = `ml-1 ${isLastRow ? '' : 'mb-2'}`; const toolClasses = `ml-1 ${isLastRow ? '' : 'mb-2'}`;
const onInteraction = ({ itemId, id, commands }) => { const handleInteraction = ({ itemId }: { itemId: string }) => {
const idToUse = itemId || id; onInteraction?.({
handleToolSelect(idToUse);
props.onInteraction({
itemId, itemId,
commands,
}); });
}; };
@ -85,10 +69,8 @@ function ToolboxUI(props: withAppTypes) {
> >
<Component <Component
{...componentProps} {...componentProps}
{...props}
id={id} id={id}
servicesManager={servicesManager} onInteraction={handleInteraction}
onInteraction={onInteraction}
size="toolbox" size="toolbox"
/> />
</div> </div>

View File

@ -1,3 +1 @@
import { ToolboxUI } from './ToolboxUI'; export * from './ToolboxUI';
import Toolbox from './Toolbox';
export { ToolboxUI, Toolbox };

View File

@ -41,7 +41,10 @@ const StudyItem = ({
<div className="flex min-w-0 flex-col items-start text-[13px]"> <div className="flex min-w-0 flex-col items-start text-[13px]">
<Tooltip> <Tooltip>
<TooltipContent>{date}</TooltipContent> <TooltipContent>{date}</TooltipContent>
<TooltipTrigger className="w-full"> <TooltipTrigger
className="w-full"
asChild
>
<div className="h-[18px] w-full max-w-[160px] overflow-hidden truncate whitespace-nowrap text-left text-white"> <div className="h-[18px] w-full max-w-[160px] overflow-hidden truncate whitespace-nowrap text-left text-white">
{date} {date}
</div> </div>
@ -49,7 +52,10 @@ const StudyItem = ({
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
<TooltipContent>{description}</TooltipContent> <TooltipContent>{description}</TooltipContent>
<TooltipTrigger className="w-full"> <TooltipTrigger
className="w-full"
asChild
>
<div className="text-muted-foreground h-[18px] w-full overflow-hidden truncate whitespace-nowrap text-left"> <div className="text-muted-foreground h-[18px] w-full overflow-hidden truncate whitespace-nowrap text-left">
{description} {description}
</div> </div>

View File

@ -50,7 +50,7 @@ function ToolButton(props: ToolButtonProps) {
className, className,
} = props; } = props;
const { buttonSizeClass, iconSizeClass } = sizeClasses[size]; const { buttonSizeClass, iconSizeClass } = sizeClasses[size] || sizeClasses.default;
const buttonClasses = cn( const buttonClasses = cn(
baseClasses, baseClasses,

View File

@ -52,7 +52,7 @@ import { ThumbnailList } from './ThumbnailList';
import { PanelSection } from './PanelSection'; import { PanelSection } from './PanelSection';
import { DisplaySetMessageListTooltip } from './DisplaySetMessageListTooltip'; import { DisplaySetMessageListTooltip } from './DisplaySetMessageListTooltip';
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './Tooltip'; import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './Tooltip';
import { ToolboxUI, Toolbox } from './OHIFToolbox'; import { ToolboxUI } from './OHIFToolbox';
import Numeric from './Numeric'; import Numeric from './Numeric';
import { InputDialog, PresetDialog } from './OHIFDialogs'; import { InputDialog, PresetDialog } from './OHIFDialogs';
import { AboutModal, ImageModal, UserPreferencesModal } from './OHIFModals'; import { AboutModal, ImageModal, UserPreferencesModal } from './OHIFModals';
@ -105,6 +105,7 @@ import {
ToolButtonListItem, ToolButtonListItem,
ToolButtonListDivider, ToolButtonListDivider,
} from './ToolButton'; } from './ToolButton';
import { ToolSettings } from './OHIFToolSettings';
export { export {
Numeric, Numeric,
@ -173,7 +174,6 @@ export {
PanelSection, PanelSection,
DisplaySetMessageListTooltip, DisplaySetMessageListTooltip,
ToolboxUI, ToolboxUI,
Toolbox,
DropdownMenu, DropdownMenu,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
@ -236,4 +236,5 @@ export {
ImageModal, ImageModal,
UserPreferencesModal, UserPreferencesModal,
FooterAction, FooterAction,
ToolSettings,
}; };

View File

@ -1,109 +0,0 @@
import React, { createContext, useContext, useReducer } from 'react';
export const initialState = {};
export const toolboxReducer = (state, action) => {
const { toolbarSectionId } = action.payload;
if (!state[toolbarSectionId]) {
state[toolbarSectionId] = { activeTool: null, toolOptions: {}, selectedEvent: false };
}
switch (action.type) {
case 'SET_ACTIVE_TOOL':
return {
...state,
[toolbarSectionId]: {
...state[toolbarSectionId],
activeTool: action.payload.activeTool,
selectedEvent: true,
},
};
case 'UPDATE_TOOL_OPTION':
const { toolName, optionName, value } = action.payload;
return {
...state,
[toolbarSectionId]: {
...state[toolbarSectionId],
selectedEvent: false,
toolOptions: {
...state[toolbarSectionId].toolOptions,
[toolName]: state[toolbarSectionId].toolOptions[toolName].map(option =>
option.id === optionName ? { ...option, value } : option
),
},
},
};
case 'INITIALIZE_TOOL_OPTIONS':
// Initialize tool options for each toolbarSectionId
return {
...state,
selectedEvent: false,
[action.toolbarSectionId]: {
...state[action.toolbarSectionId],
toolOptions: action.payload,
},
};
default:
return state;
}
};
const ToolboxContext = createContext();
export const ToolboxProvider = ({ children }) => {
const [state, dispatch] = useReducer(toolboxReducer, initialState);
const handleToolSelect = (toolbarSectionId, toolName) => {
dispatch({
type: 'SET_ACTIVE_TOOL',
payload: { toolbarSectionId, activeTool: toolName },
});
};
const handleToolOptionChange = (toolbarSectionId, toolName, optionName, newValue) => {
dispatch({
type: 'UPDATE_TOOL_OPTION',
payload: { toolbarSectionId, toolName, optionName, value: newValue },
});
};
const initializeToolOptions = (toolbarSectionId, toolOptions) => {
dispatch({
type: 'INITIALIZE_TOOL_OPTIONS',
toolbarSectionId,
payload: toolOptions,
});
};
const api = { handleToolSelect, handleToolOptionChange, initializeToolOptions };
const value = { state, api };
return <ToolboxContext.Provider value={value}>{children}</ToolboxContext.Provider>;
};
/**
* Custom hook for accessing toolbox state and actions for a specific toolbar section.
* You can use this hook to access the state and actions for a specific toolbar section (
* defined by the toolbarSectionId) in your custom toolbar components. This hook
* helps to manage the state and actions for the tools and their options in the toolbar.
*/
export const useToolbox = toolbarSectionId => {
const context = useContext(ToolboxContext);
if (context === undefined) {
throw new Error('useToolbox must be used within a ToolboxProvider');
}
const { state, api } = context;
return {
state: state[toolbarSectionId] || { activeTool: null, toolOptions: {} },
api: {
handleToolSelect: toolName => api.handleToolSelect(toolbarSectionId, toolName),
handleToolOptionChange: (toolName, optionName, value) =>
api.handleToolOptionChange(toolbarSectionId, toolName, optionName, value),
initializeToolOptions: toolOptions =>
api.initializeToolOptions(toolbarSectionId, toolOptions),
},
};
};

View File

@ -1,13 +1,11 @@
import NotificationProvider, { useNotification } from './NotificationProvider'; import NotificationProvider, { useNotification } from './NotificationProvider';
import { ViewportGridContext, ViewportGridProvider, useViewportGrid } from './ViewportGridProvider'; import { ViewportGridContext, ViewportGridProvider, useViewportGrid } from './ViewportGridProvider';
import { ToolboxProvider, useToolbox } from './ToolboxContext';
import { ModalProvider, useModal } from './ModalProvider'; import { ModalProvider, useModal } from './ModalProvider';
import { DialogProvider, useDialog } from './DialogProvider'; import { DialogProvider, useDialog } from './DialogProvider';
import ManagedDialog from './ManagedDialog'; import ManagedDialog from './ManagedDialog';
export { useNotification, NotificationProvider }; export { useNotification, NotificationProvider };
export { ViewportGridContext, ViewportGridProvider, useViewportGrid }; export { ViewportGridContext, ViewportGridProvider, useViewportGrid };
export { ToolboxProvider, useToolbox };
export { ModalProvider, useModal }; export { ModalProvider, useModal };
export { DialogProvider, useDialog }; export { DialogProvider, useDialog };
export { ManagedDialog }; export { ManagedDialog };

View File

@ -33,6 +33,7 @@ import {
PanelSection, PanelSection,
DisplaySetMessageListTooltip, DisplaySetMessageListTooltip,
ToolboxUI, ToolboxUI,
ToolSettings,
DoubleSlider, DoubleSlider,
Label, Label,
Slider, Slider,
@ -98,7 +99,6 @@ import {
ToolButtonListDropDown, ToolButtonListDropDown,
ToolButtonListItem, ToolButtonListItem,
ToolButtonListDivider, ToolButtonListDivider,
Toolbox,
Numeric, Numeric,
InputDialog, InputDialog,
PresetDialog, PresetDialog,
@ -113,8 +113,6 @@ import { DataRow } from './components/DataRow';
import { import {
useNotification, useNotification,
NotificationProvider, NotificationProvider,
useToolbox,
ToolboxProvider,
useModal, useModal,
ModalProvider, ModalProvider,
DialogProvider, DialogProvider,
@ -232,9 +230,6 @@ export {
ToolButtonListDropDown, ToolButtonListDropDown,
ToolButtonListItem, ToolButtonListItem,
ToolButtonListDivider, ToolButtonListDivider,
ToolboxProvider,
Toolbox,
useToolbox,
utils, utils,
Numeric, Numeric,
AboutModal, AboutModal,
@ -249,4 +244,5 @@ export {
DialogProvider, DialogProvider,
useDialog, useDialog,
ManagedDialog, ManagedDialog,
ToolSettings,
}; };

View File

@ -13,6 +13,6 @@ test('should reset the image to its original state', async ({ page }) => {
await page.getByTestId('MoreTools-split-button-secondary').click(); await page.getByTestId('MoreTools-split-button-secondary').click();
await page.getByTestId('invert').click(); await page.getByTestId('invert').click();
await page.getByTestId('MoreTools-split-button-secondary').click(); await page.getByTestId('MoreTools-split-button-secondary').click();
await page.getByTestId('Reset').click(); await page.getByText('Reset View').click();
await checkForScreenshot(page, page, screenShotPaths.reset.resetDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.reset.resetDisplayedCorrectly);
}); });

187
yarn.lock
View File

@ -1393,10 +1393,10 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cornerstonejs/adapters@^3.0.1": "@cornerstonejs/adapters@^3.0.4":
version "3.0.1" version "3.0.4"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.0.1.tgz#596e302d7155ee64b7c20bcfedc57ce6ca10d5fb" resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.0.4.tgz#3aea5f65e95fbb067885eb51ebcae2cacd673465"
integrity sha512-Ym6s4cTpMkojap5QZ8MXZ8hDbs2TMMN/ypqU7wKMwAY2LGoltoeGw0Z/7LL+iostbaSw3fVs+TZzJSKhKw8Bug== integrity sha512-TJtW0biDf0wgi+XxQFqvKXT/Cf8ueyUruohH9n/RJsWiyE8z1/LOBxOqCZNkToGGs1EbO51T/PMPIUWordW43w==
dependencies: dependencies:
"@babel/runtime-corejs2" "^7.17.8" "@babel/runtime-corejs2" "^7.17.8"
buffer "^6.0.3" buffer "^6.0.3"
@ -1429,19 +1429,19 @@
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0" resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg== integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
"@cornerstonejs/core@^3.0.1": "@cornerstonejs/core@^3.0.4":
version "3.0.1" version "3.0.4"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.0.1.tgz#8a938013c148d752d2c4f4eba7bd63048a6c814a" resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.0.4.tgz#a75e0e86cc3ac4cd19355c30f8f531ebe279ae36"
integrity sha512-K+cOoN/TzJIPRRoaWWNkhJ+2CdkBBX8BA64DG0aBVOthlwJPRqK2YQE6PTTVRx18mjMA4to/m3de0emFNKrTzQ== integrity sha512-6rMiqhBjyz3wsalPOAFjThfPUJnWVOs7Xqe3xLFvqd1qIFW1q42bqlQ7ak2DRMUMtC1nLURk6YIMhkWxK+t+7A==
dependencies: dependencies:
"@kitware/vtk.js" "32.9.0" "@kitware/vtk.js" "32.9.0"
comlink "^4.4.1" comlink "^4.4.1"
gl-matrix "^3.4.3" gl-matrix "^3.4.3"
"@cornerstonejs/dicom-image-loader@^3.0.1": "@cornerstonejs/dicom-image-loader@^3.0.4":
version "3.0.1" version "3.0.4"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.0.1.tgz#e18f716620ab176d88c25b9efd25fc0983bce180" resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.0.4.tgz#0c553e1b01fc90d956d216d72686f6dcda9400f7"
integrity sha512-t4bI6rVOt4J8YOHRR9Mh9ClInajc98qqI6fwsByiXmtc2Y4INrZ1uaRspFnOJBosbr+EwsJBV7iT2yt6Jk6CjA== integrity sha512-w0NoutE7+/C2FeT1YHo0cnMJpZ17ARH0qwyGwWockFwuJkUVjRcU5ZW1sT0ABNOPUHqZse/K/YmH2IX+ppaScQ==
dependencies: dependencies:
"@cornerstonejs/codec-charls" "^1.2.3" "@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2" "@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
@ -1459,10 +1459,10 @@
dependencies: dependencies:
"@icr/polyseg-wasm" "0.4.0" "@icr/polyseg-wasm" "0.4.0"
"@cornerstonejs/tools@^3.0.1": "@cornerstonejs/tools@^3.0.4":
version "3.0.1" version "3.0.4"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.0.1.tgz#4d707992283235d79532f35e60e8c763b174ccef" resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.0.4.tgz#413031f55dd7aca30168bfd13fe52911ba81f795"
integrity sha512-fRjoXJ46mdpYc5JZ/FUn0g1vCK4LohnNjgZl+O72HK2reMdc5/dQeCueJCT+tBMay04sJMUHm0tAke2DN10FBQ== integrity sha512-gkenXhOS6lYYmQhKDwayojeObTU/KiG9k+JEC/hoOBhRdoFZljyXfQtnljKFHL+G9rkdbNKxXq/a3+C9Bn7EVg==
dependencies: dependencies:
"@types/offscreencanvas" "2019.7.3" "@types/offscreencanvas" "2019.7.3"
comlink "^4.4.1" comlink "^4.4.1"
@ -1574,10 +1574,10 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016" resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016"
integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==
"@cypress/request@^3.0.6": "@cypress/request@^3.0.7":
version "3.0.7" version "3.0.8"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.7.tgz#6a74a4da98d9e5ae9121d6e2d9c14780c9b5cf1a" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.8.tgz#992f1f42ba03ebb14fa5d97290abe9d015ed0815"
integrity sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg== integrity sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==
dependencies: dependencies:
aws-sign2 "~0.7.0" aws-sign2 "~0.7.0"
aws4 "^1.8.0" aws4 "^1.8.0"
@ -1592,7 +1592,7 @@
json-stringify-safe "~5.0.1" json-stringify-safe "~5.0.1"
mime-types "~2.1.19" mime-types "~2.1.19"
performance-now "^2.1.0" performance-now "^2.1.0"
qs "6.13.1" qs "6.14.0"
safe-buffer "^5.1.2" safe-buffer "^5.1.2"
tough-cookie "^5.0.0" tough-cookie "^5.0.0"
tunnel-agent "^0.6.0" tunnel-agent "^0.6.0"
@ -7322,6 +7322,14 @@ cachedir@^2.3.0:
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d"
integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ== integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
@ -7333,6 +7341,14 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bin
get-intrinsic "^1.2.4" get-intrinsic "^1.2.4"
set-function-length "^1.2.1" set-function-length "^1.2.1"
call-bound@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
dependencies:
call-bind-apply-helpers "^1.0.2"
get-intrinsic "^1.3.0"
caller-callsite@^2.0.0: caller-callsite@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
@ -7571,7 +7587,7 @@ ci-info@^3.2.0, ci-info@^3.6.1:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
ci-info@^4.0.0: ci-info@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83"
integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==
@ -8513,12 +8529,12 @@ cypress-file-upload@^5.0.8:
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1" resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1"
integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g== integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==
cypress@^14.0.0: cypress@^14.1.0:
version "14.0.0" version "14.1.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-14.0.0.tgz#a71cb0a243a0bfeb97b6973ab9c5291ca5288e93" resolved "https://registry.yarnpkg.com/cypress/-/cypress-14.1.0.tgz#b2dbe7bbc529dc0c93ffd4e0e9fa59763afba0b8"
integrity sha512-kEGqQr23so5IpKeg/dp6GVi7RlHx1NmW66o2a2Q4wk9gRaAblLZQSiZJuDI8UMC4LlG5OJ7Q6joAiqTrfRNbTw== integrity sha512-pPPj8Uu9NwjaaiXAEcjYZZmgsq6v9Zs1Nw6a+zRF+ANgYSNhH4S32SjFRsvMcuOHR/8dp4GBJhBPqIPSs+TxaA==
dependencies: dependencies:
"@cypress/request" "^3.0.6" "@cypress/request" "^3.0.7"
"@cypress/xvfb" "^1.2.4" "@cypress/xvfb" "^1.2.4"
"@types/sinonjs__fake-timers" "8.1.1" "@types/sinonjs__fake-timers" "8.1.1"
"@types/sizzle" "^2.3.2" "@types/sizzle" "^2.3.2"
@ -8529,7 +8545,7 @@ cypress@^14.0.0:
cachedir "^2.3.0" cachedir "^2.3.0"
chalk "^4.1.0" chalk "^4.1.0"
check-more-types "^2.24.0" check-more-types "^2.24.0"
ci-info "^4.0.0" ci-info "^4.1.0"
cli-cursor "^3.1.0" cli-cursor "^3.1.0"
cli-table3 "~0.6.1" cli-table3 "~0.6.1"
commander "^6.2.1" commander "^6.2.1"
@ -9507,6 +9523,15 @@ dotenv@~16.3.1:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.2.tgz#3cb611ce5a63002dbabf7c281bc331f69d28f03f" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.2.tgz#3cb611ce5a63002dbabf7c281bc331f69d28f03f"
integrity sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ== integrity sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==
dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-errors "^1.3.0"
gopd "^1.2.0"
duplexer@^0.1.1, duplexer@^0.1.2, duplexer@~0.1.1: duplexer@^0.1.1, duplexer@^0.1.2, duplexer@~0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@ -9762,6 +9787,11 @@ es-define-property@^1.0.0:
dependencies: dependencies:
get-intrinsic "^1.2.4" get-intrinsic "^1.2.4"
es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.2.1, es-errors@^1.3.0: es-errors@^1.2.1, es-errors@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
@ -9814,6 +9844,13 @@ es-object-atoms@^1.0.0:
dependencies: dependencies:
es-errors "^1.3.0" es-errors "^1.3.0"
es-object-atoms@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
dependencies:
es-errors "^1.3.0"
es-set-tostringtag@^2.0.1, es-set-tostringtag@^2.0.3: es-set-tostringtag@^2.0.1, es-set-tostringtag@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777"
@ -11103,6 +11140,22 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@
has-symbols "^1.0.3" has-symbols "^1.0.3"
hasown "^2.0.0" hasown "^2.0.0"
get-intrinsic@^1.2.5, get-intrinsic@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
es-define-property "^1.0.1"
es-errors "^1.3.0"
es-object-atoms "^1.1.1"
function-bind "^1.1.2"
get-proto "^1.0.1"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
math-intrinsics "^1.1.0"
get-nonce@^1.0.0: get-nonce@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
@ -11138,6 +11191,14 @@ get-port@5.1.1, get-port@^5.1.1:
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
get-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
get-stdin@^7.0.0: get-stdin@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6"
@ -11451,6 +11512,11 @@ gopd@^1.0.1:
dependencies: dependencies:
get-intrinsic "^1.1.3" get-intrinsic "^1.1.3"
gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
got@^12.1.0: got@^12.1.0:
version "12.6.1" version "12.6.1"
resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549" resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549"
@ -11581,6 +11647,11 @@ has-symbols@^1.0.2, has-symbols@^1.0.3:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: has-tostringtag@^1.0.0, has-tostringtag@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
@ -14289,6 +14360,11 @@ material-colors@^1.2.1:
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
mathjs@^11.2: mathjs@^11.2:
version "11.12.0" version "11.12.0"
resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-11.12.0.tgz#e933e5941930d44763ddfc5bfb08b90059449b2c" resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-11.12.0.tgz#e933e5941930d44763ddfc5bfb08b90059449b2c"
@ -15651,6 +15727,11 @@ object-inspect@^1.13.1:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==
object-inspect@^1.13.3:
version "1.13.4"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
object-is@^1.1.2, object-is@^1.1.5: object-is@^1.1.2, object-is@^1.1.5:
version "1.1.6" version "1.1.6"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07"
@ -17353,12 +17434,12 @@ qs@6.13.0, qs@^6.10.0, qs@^6.12.3:
dependencies: dependencies:
side-channel "^1.0.6" side-channel "^1.0.6"
qs@6.13.1: qs@6.14.0:
version "6.13.1" version "6.14.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930"
integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
dependencies: dependencies:
side-channel "^1.0.6" side-channel "^1.1.0"
query-string@^4.1.0: query-string@^4.1.0:
version "4.3.4" version "4.3.4"
@ -18790,6 +18871,35 @@ shx@^0.3.3:
minimist "^1.2.3" minimist "^1.2.3"
shelljs "^0.8.5" shelljs "^0.8.5"
side-channel-list@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
dependencies:
es-errors "^1.3.0"
object-inspect "^1.13.3"
side-channel-map@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
dependencies:
call-bound "^1.0.2"
es-errors "^1.3.0"
get-intrinsic "^1.2.5"
object-inspect "^1.13.3"
side-channel-weakmap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
dependencies:
call-bound "^1.0.2"
es-errors "^1.3.0"
get-intrinsic "^1.2.5"
object-inspect "^1.13.3"
side-channel-map "^1.0.1"
side-channel@^1.0.4, side-channel@^1.0.6: side-channel@^1.0.4, side-channel@^1.0.6:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
@ -18800,6 +18910,17 @@ side-channel@^1.0.4, side-channel@^1.0.6:
get-intrinsic "^1.2.4" get-intrinsic "^1.2.4"
object-inspect "^1.13.1" object-inspect "^1.13.1"
side-channel@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
dependencies:
es-errors "^1.3.0"
object-inspect "^1.13.3"
side-channel-list "^1.0.0"
side-channel-map "^1.0.1"
side-channel-weakmap "^1.0.2"
signal-exit@3.0.7, signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: signal-exit@3.0.7, signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
version "3.0.7" version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"