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
node_modules
.cursor/
# Output
build

314
bun.lock
View File

@ -26,13 +26,13 @@
"optionalDependencies": {
"@percy/cypress": "^3.1.1",
"@playwright/test": "^1.48.0",
"cypress": "^14.0.0",
"cypress": "^14.1.0",
"cypress-file-upload": "^5.0.8",
},
},
"addOns/externals/devDependencies": {
"name": "@externals/devDependencies",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@kitware/vtk.js": "32.1.1",
@ -120,20 +120,20 @@
},
"addOns/externals/dicom-microscopy-viewer": {
"name": "@externals/dicom-microscopy-viewer",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"dicom-microscopy-viewer": "^0.46.1",
},
},
"extensions/cornerstone": {
"name": "@ohif/extension-cornerstone",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1",
"@cornerstonejs/core": "^3.0.1",
"@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/polymorphic-segmentation": "^3.0.1",
"@cornerstonejs/tools": "^3.0.1",
"@cornerstonejs/tools": "^3.0.4",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.1.1",
"html2canvas": "^1.4.1",
@ -150,9 +150,9 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.0.1",
"@ohif/core": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@cornerstonejs/dicom-image-loader": "^3.0.4",
"@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@ -164,19 +164,19 @@
},
"extensions/cornerstone-dicom-pmap": {
"name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1",
"@cornerstonejs/core": "^3.0.1",
"@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.4",
"@kitware/vtk.js": "32.1.1",
"react-color": "^2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/i18n": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.115",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -187,16 +187,16 @@
},
"extensions/cornerstone-dicom-rt": {
"name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"react-color": "^2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/i18n": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.115",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -207,19 +207,19 @@
},
"extensions/cornerstone-dicom-seg": {
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1",
"@cornerstonejs/core": "^3.0.1",
"@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.4",
"@kitware/vtk.js": "32.1.1",
"react-color": "^2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/i18n": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.115",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -230,19 +230,19 @@
},
"extensions/cornerstone-dicom-sr": {
"name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.0.1",
"@cornerstonejs/core": "^3.0.1",
"@cornerstonejs/tools": "^3.0.1",
"@cornerstonejs/adapters": "^3.0.4",
"@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.4",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-measurement-tracking": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-measurement-tracking": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -252,19 +252,19 @@
},
"extensions/cornerstone-dynamic-volume": {
"name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/core": "^3.0.1",
"@cornerstonejs/tools": "^3.0.1",
"@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.4",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/i18n": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@ -274,7 +274,7 @@
},
"extensions/default": {
"name": "@ohif/extension-default",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/calculate-suv": "^1.1.0",
@ -282,8 +282,8 @@
"lodash.uniqby": "^4.7.0",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/i18n": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.115",
"dcmjs": "*",
"dicomweb-client": "^0.10.4",
"prop-types": "^15.6.2",
@ -297,7 +297,7 @@
},
"extensions/dicom-microscopy": {
"name": "@ohif/extension-dicom-microscopy",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/codec-charls": "^1.2.3",
@ -308,10 +308,10 @@
"mathjs": "^12.4.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/i18n": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -322,14 +322,14 @@
},
"extensions/dicom-pdf": {
"name": "@ohif/extension-dicom-pdf",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -339,14 +339,14 @@
},
"extensions/dicom-video": {
"name": "@ohif/extension-dicom-video",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -356,20 +356,20 @@
},
"extensions/measurement-tracking": {
"name": "@ohif/extension-measurement-tracking",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@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": "^4.10.0",
},
"peerDependencies": {
"@cornerstonejs/core": "^3.0.1",
"@cornerstonejs/tools": "^3.0.1",
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.4",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"classnames": "^2.3.2",
"dcmjs": "*",
"lodash.debounce": "^4.0.8",
@ -382,14 +382,14 @@
},
"extensions/test-extension": {
"name": "@ohif/extension-test",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"dcmjs": "0.38.0",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -399,14 +399,14 @@
},
"extensions/tmtv": {
"name": "@ohif/extension-tmtv",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -416,7 +416,7 @@
},
"modes/basic-dev-mode": {
"name": "@ohif/mode-basic-dev-mode",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -426,17 +426,17 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113",
"@ohif/extension-dicom-video": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.115",
},
},
"modes/basic-test-mode": {
"name": "@ohif/mode-test",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -446,19 +446,19 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113",
"@ohif/extension-dicom-video": "3.10.0-beta.113",
"@ohif/extension-measurement-tracking": "3.10.0-beta.113",
"@ohif/extension-test": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.115",
"@ohif/extension-measurement-tracking": "3.10.0-beta.115",
"@ohif/extension-test": "3.10.0-beta.115",
},
},
"modes/longitudinal": {
"name": "@ohif/mode-longitudinal",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -468,32 +468,32 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113",
"@ohif/extension-dicom-video": "3.10.0-beta.113",
"@ohif/extension-measurement-tracking": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.115",
"@ohif/extension-measurement-tracking": "3.10.0-beta.115",
},
},
"modes/microscopy": {
"name": "@ohif/mode-microscopy",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.115",
},
},
"modes/preclinical-4d": {
"name": "@ohif/mode-preclinical-4d",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
},
@ -502,17 +502,17 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dynamic-volume": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/extension-tmtv": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dynamic-volume": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-tmtv": "3.10.0-beta.115",
},
},
"modes/segmentation": {
"name": "@ohif/mode-segmentation",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -544,19 +544,19 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113",
"@ohif/extension-dicom-video": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.115",
},
},
"modes/tmtv": {
"name": "@ohif/mode-tmtv",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -566,43 +566,43 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113",
"@ohif/extension-dicom-video": "3.10.0-beta.113",
"@ohif/extension-measurement-tracking": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.115",
"@ohif/extension-measurement-tracking": "3.10.0-beta.115",
},
},
"platform/app": {
"name": "@ohif/app",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/codec-charls": "^1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@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",
"@ohif/core": "3.10.0-beta.113",
"@ohif/extension-cornerstone": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.113",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.113",
"@ohif/extension-default": "3.10.0-beta.113",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.113",
"@ohif/extension-dicom-pdf": "3.10.0-beta.113",
"@ohif/extension-dicom-video": "3.10.0-beta.113",
"@ohif/extension-test": "3.10.0-beta.113",
"@ohif/i18n": "3.10.0-beta.113",
"@ohif/mode-basic-dev-mode": "3.10.0-beta.113",
"@ohif/mode-longitudinal": "3.10.0-beta.113",
"@ohif/mode-microscopy": "3.10.0-beta.113",
"@ohif/mode-test": "3.10.0-beta.113",
"@ohif/ui": "3.10.0-beta.113",
"@ohif/ui-next": "3.10.0-beta.113",
"@ohif/core": "3.10.0-beta.115",
"@ohif/extension-cornerstone": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.115",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.115",
"@ohif/extension-default": "3.10.0-beta.115",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.115",
"@ohif/extension-dicom-pdf": "3.10.0-beta.115",
"@ohif/extension-dicom-video": "3.10.0-beta.115",
"@ohif/extension-test": "3.10.0-beta.115",
"@ohif/i18n": "3.10.0-beta.115",
"@ohif/mode-basic-dev-mode": "3.10.0-beta.115",
"@ohif/mode-longitudinal": "3.10.0-beta.115",
"@ohif/mode-microscopy": "3.10.0-beta.115",
"@ohif/mode-test": "3.10.0-beta.115",
"@ohif/ui": "3.10.0-beta.115",
"@ohif/ui-next": "3.10.0-beta.115",
"@svgr/webpack": "^8.1.0",
"@types/react": "^18.3.3",
"classnames": "^2.3.2",
@ -643,7 +643,7 @@
},
"platform/cli": {
"name": "@ohif/cli",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"bin": {
"ohif-cli": "src/index.js"
},
@ -667,7 +667,7 @@
},
"platform/core": {
"name": "@ohif/core",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"dcmjs": "*",
@ -692,15 +692,15 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.0.1",
"@ohif/ui": "3.10.0-beta.113",
"@cornerstonejs/dicom-image-loader": "^3.0.4",
"@ohif/ui": "3.10.0-beta.115",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21",
},
},
"platform/i18n": {
"name": "@ohif/i18n",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next-locize-backend": "^2.0.0",
@ -725,7 +725,7 @@
},
"platform/ui": {
"name": "@ohif/ui",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@testing-library/react": "^13.1.0",
"browser-detect": "^0.2.28",
@ -785,7 +785,7 @@
},
"platform/ui-next": {
"name": "@ohif/ui-next",
"version": "3.10.0-beta.113",
"version": "3.10.0-beta.115",
"dependencies": {
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
@ -1092,7 +1092,7 @@
"@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=="],
@ -1104,13 +1104,13 @@
"@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/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=="],
@ -2638,7 +2638,7 @@
"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=="],

View File

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

View File

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

View File

@ -329,9 +329,19 @@ const commandsModule = ({
},
setThresholdRange: ({
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);
toolNames?.forEach(toolName => {
toolGroup.setToolConfiguration(toolName, {
@ -340,7 +350,7 @@ const commandsModule = ({
},
});
});
});
}
},
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -87,61 +87,10 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
return {
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,
};
},
},
{
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',
evaluate: () => {

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ function ToolbarSplitButtonWithServices({
onInteraction({
groupId,
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
*/
const setMinMaxWidth = (elem, width?) => {
if (!elem) {
return;
}
elem.style.minWidth = width === undefined ? '' : `${width}px`;
elem.style.maxWidth = elem.style.minWidth;
};

View File

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

View File

@ -60,27 +60,6 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
name: 'ohif.progressDropdown',
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',
evaluate: () => {

View File

@ -34,6 +34,7 @@ import promptLabelAnnotation from './utils/promptLabelAnnotation';
import usePatientInfo from './hooks/usePatientInfo';
import { PanelStudyBrowserHeader } from './Panels/StudyBrowser/PanelStudyBrowserHeader';
import * as utils from './utils';
import { Toolbox } from './utils';
import MoreDropdownMenu from './Components/MoreDropdownMenu';
import requestDisplaySetCreationForStudy from './Panels/requestDisplaySetCreationForStudy';
const defaultExtension: Types.Extensions.Extension = {
@ -97,6 +98,7 @@ export {
usePatientInfo,
PanelStudyBrowserHeader,
utils,
Toolbox,
MoreDropdownMenu,
requestDisplaySetCreationForStudy,
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,
}: {
uiDialogService: AppTypes.UIDialogService;
defaultValue?: string;
title?: string;
placeholder?: string;
submitOnEnter?: boolean;
}) {
const dialogId = 'dialog-enter-annotation';

View File

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

View File

@ -32,8 +32,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@cornerstonejs/core": "^3.0.1",
"@cornerstonejs/tools": "^3.0.1",
"@cornerstonejs/core": "^3.0.4",
"@cornerstonejs/tools": "^3.0.4",
"@ohif/core": "3.10.0-beta.116",
"@ohif/extension-cornerstone-dicom-sr": "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 ROIThresholdConfiguration, {
ROI_STAT,
} from './PanelROIThresholdSegmentation/ROIThresholdConfiguration';
import * as cs3dTools from '@cornerstonejs/tools';
import { useSystem } from '@ohif/core';
import { useSegmentations } from '@ohif/extension-cornerstone';
const LOWER_CT_THRESHOLD_DEFAULT = -1024;
const UPPER_CT_THRESHOLD_DEFAULT = 1024;
@ -40,9 +42,10 @@ function reducer(state, action) {
}
}
function RectangleROIOptions({ servicesManager, commandsManager }: withAppTypes) {
const { segmentationService } = servicesManager.services;
const [selectedSegmentationId, setSelectedSegmentationId] = useState(null);
function RectangleROIOptions() {
const { commandsManager } = useSystem();
const segmentations = useSegmentations();
const activeSegmentation = segmentations[0];
const runCommand = useCallback(
(commandName, commandOptions = {}) => {
@ -61,58 +64,20 @@ function RectangleROIOptions({ servicesManager, commandsManager }: withAppTypes)
});
const handleROIThresholding = useCallback(() => {
const segmentationId = selectedSegmentationId;
if (!activeSegmentation) {
return;
}
const segmentationId = activeSegmentation.segmentationId;
const activeSegmentIndex =
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', {
segmentationId,
config,
segmentIndex: activeSegmentIndex,
});
}, [selectedSegmentationId, 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();
});
};
}, []);
}, [activeSegmentation, config]);
return (
<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}
runCommand={runCommand}
/>
{selectedSegmentationId !== null && (
{activeSegmentation && (
<Button
className="mt-2 !h-[26px] !w-[75px]"
onClick={handleROIThresholding}

View File

@ -102,7 +102,7 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
// corrected PT vs the non-attenuation correct PT)
let ptDisplaySet = null;
for (const [viewportId, viewportDetails] of viewportMatchDetails) {
for (const [, viewportDetails] of viewportMatchDetails) {
const { displaySetsInfo } = viewportDetails;
const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) =>
displaySetService.getDisplaySetByUID(displaySetInstanceUID)
@ -199,15 +199,21 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
const { representationData } = segmentation;
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 ctVolumeId = `${volumeLoaderScheme}:${ctDisplaySet.displaySetInstanceUID}`; // VolumeId with loader id + volume id
const ctDisplaySet = displaySetService.getDisplaySetByUID(
ctDisplaySetMatch.displaySetInstanceUID
);
const ptDisplaySet = displaySetService.getDisplaySetByUID(
ptDisplaySetMatch.displaySetInstanceUID
);
const { volumeId: segVolumeId } = representationData[
SegmentationRepresentations.Labelmap
] as csTools.Types.LabelmapToolOperationDataVolume;
const { referencedVolumeId } = cs.cache.getVolume(segVolumeId);
const labelmapVolume = cs.cache.getVolume(segVolumeId);
const annotationUIDs = _getAnnotationsSelectedByToolNames(ROI_THRESHOLD_MANUAL_TOOL_IDS);
@ -220,71 +226,40 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
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(
annotationUIDs,
[referencedVolume, ctReferencedVolume],
ptDisplaySet,
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(
annotationUIDs,
labelmapVolume,
[
{ volume: referencedVolume, lower: ptLower, upper: ptUpper },
{ volume: ctReferencedVolume, lower: ctLower, upper: ctUpper },
{ volume: ptVolume, lower: ptLower, upper: ptUpper },
{ volume: ctVolume, lower: ctLower, upper: ctUpper },
],
{ overwrite: true, segmentIndex }
{ overwrite: true, segmentIndex, segmentationId }
);
},
calculateSuvPeak: async ({ segmentationId, segmentIndex }) => {

View File

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

View File

@ -1,10 +1,10 @@
import RectangleROIOptions from './Panels/RectangleROIOptions';
export default function getToolbarModule({ commandsManager, servicesManager }) {
export default function getToolbarModule() {
return [
{
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';
function getRoiStats(referencedVolume, annotations) {
// roiStats
const { imageData } = referencedVolume;
const values = imageData.getPointData().getScalars().getData();
function getRoiStats(displaySet, annotations) {
const { imageIds } = displaySet;
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
const { fn, baseValue } = _getStrategyFn('max');
@ -11,25 +19,25 @@ function getRoiStats(referencedVolume, annotations) {
const boundsIJK = csTools.utilities.rectangleROITool.getBoundsIJKFromRectangleAnnotations(
annotations,
referencedVolume
volume
);
const [[iMin, iMax], [jMin, jMax], [kMin, kMax]] = boundsIJK;
for (let i = iMin; i <= iMax; i++) {
for (let j = jMin; j <= jMax; j++) {
for (let k = kMin; k <= kMax; k++) {
const offset = imageData.computeOffsetIndex([i, j, k]);
value = fn(values[offset], value);
}
// Use the voxelManager's forEach method to iterate over the bounds
voxelManager.forEach(
({ value: voxelValue }) => {
value = fn(voxelValue, value);
},
{
boundsIJK,
}
}
);
return value;
}
function getThresholdValues(
annotationUIDs,
referencedVolumes,
ptDisplaySet,
config
): { ptLower: number; ptUpper: number; ctLower: number; ctUpper: number } {
if (config.strategy === 'range') {
@ -46,7 +54,7 @@ function getThresholdValues(
csTools.annotation.state.getAnnotation(annotationUID)
);
const ptValue = getRoiStats(referencedVolumes[0], annotations);
const ptValue = getRoiStats(ptDisplaySet, annotations);
return {
ctLower: -Infinity,

View File

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

View File

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

View File

@ -1,7 +1,6 @@
import toolbarButtons from './toolbarButtons';
import { id } from './id';
import initToolGroups from './initToolGroups';
import moreTools from './moreTools';
import i18n from 'i18next';
// Allow this mode by excluding non-imaging modalities such as SR, SEG
@ -53,7 +52,6 @@ const dicomPmap = {
};
const extensionDependencies = {
// Can derive the versions at least process.env.from npm_package_version
'@ohif/extension-default': '^3.0.0',
'@ohif/extension-cornerstone': '^3.0.0',
'@ohif/extension-measurement-tracking': '^3.0.0',
@ -67,8 +65,6 @@ const extensionDependencies = {
function modeFactory() {
return {
// TODO: We're using this as a route segment
// We should not be.
id,
routeName: 'basic-test',
displayName: i18n.t('Modes:Basic Test Mode'),
@ -89,11 +85,12 @@ function modeFactory() {
'@ohif/extension-test.customizationModule.custom-context-menu',
]);
toolbarService.addButtons([...toolbarButtons, ...moreTools]);
toolbarService.addButtons(toolbarButtons);
console.debug('toolbarButtons', toolbarButtons);
toolbarService.createButtonSection('primary', [
'MeasurementTools',
'Zoom',
'WindowLevel',
'WindowLevelGroup',
'Pan',
'Capture',
'Layout',
@ -102,6 +99,48 @@ function modeFactory() {
'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(
{
'ohif.hotkeyBindings': {
@ -154,22 +193,14 @@ function modeFactory() {
routes: [
{
path: 'basic-test',
/*init: ({ servicesManager, extensionManager }) => {
//defaultViewerRouteInit
},*/
layoutTemplate: () => {
return {
id: ohif.layout,
props: {
// Use the first two for an untracked view
// leftPanels: [ohif.thumbnailList],
// rightPanels: [dicomSeg.panel, ohif.measurements],
leftPanels: [tracked.thumbnailList],
leftPanelResizable: true,
// Can use cornerstone.measurements for all measurements
rightPanels: [cornerstone.panel, tracked.measurements, cornerstone.measurements],
rightPanelResizable: true,
// rightPanelClosed: true, // optional prop to start with collapse panels
viewports: [
{
namespace: tracked.viewport,
@ -206,12 +237,7 @@ function modeFactory() {
},
],
extensions: extensionDependencies,
// Default protocol gets self-registered by default in the init
hangingProtocol: 'default',
// Order is important in sop class handlers when two handlers both use
// the same sop class under different situations. In that case, the more
// general handler needs to come last. For this case, the dicomvideo must
// come first to remove video transfer syntax before ohif uses images
sopClassHandlers: [
dicomvideo.sopClassHandler,
dicomSeg.sopClassHandler,
@ -221,8 +247,6 @@ function modeFactory() {
dicomsr.sopClassHandler,
],
hotkeys: {
// Don't store the hotkeys for basic-test-mode under the same key
// because they get customized by tests
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
// 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 { EVENTS } from '@cornerstonejs/core';
import { ViewportGridService } from '@ohif/core';
import { defaults } from '@ohif/core';
const { windowLevelPresets } = defaults;
const { createButton } = ToolbarService;
/**
*
@ -18,18 +15,21 @@ const { createButton } = ToolbarService;
*/
function _createWwwcPreset(preset, title, subtitle) {
return {
id: preset.toString(),
title,
subtitle,
commands: [
{
commandName: 'setWindowLevel',
commandOptions: {
...windowLevelPresets[preset],
id: title,
uiType: 'ohif.toolButton',
props: {
title,
subtitle,
commands: [
{
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[] = [
{
id: 'MeasurementTools',
uiType: 'ohif.toolButtonList',
props: {
buttonSection: 'measurementSection',
groupId: 'MeasurementTools',
// group evaluate to determine which item should move to the top
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({
id: 'Length',
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'Bidirectional',
icon: 'tool-bidirectional',
label: 'Bidirectional',
tooltip: 'Bidirectional Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'ArrowAnnotate',
icon: 'tool-annotate',
label: 'Annotation',
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: '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: 'MoreTools',
uiType: 'ohif.toolButtonList',
props: {
buttonSection: 'moreToolsSection',
groupId: 'MoreTools',
},
},
{
id: 'WindowLevelGroup',
uiType: 'ohif.toolButtonList',
props: {
groupId: 'WindowLevelGroup',
buttonSection: 'windowLevelSection',
},
},
// tool defs
_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'),
{
id: 'WindowLevel',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-window-level',
label: 'Window Level',
tooltip: 'Window Level',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
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: '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: {
icon: 'tool-zoom',
label: 'Zoom',
tooltip: 'Zoom',
commands: setToolActiveToolbar,
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',
uiType: 'ohif.toolButton',
@ -175,6 +196,7 @@ const toolbarButtons: Button[] = [
type: 'tool',
icon: 'tool-move',
label: 'Pan',
tooltip: 'Pan',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
@ -185,24 +207,24 @@ const toolbarButtons: Button[] = [
props: {
icon: 'icon-mpr',
label: 'MPR',
commands: [
{
commandName: 'toggleHangingProtocol',
commandOptions: {
protocolId: 'mpr',
},
tooltip: 'MPR',
commands: {
commandName: 'toggleHangingProtocol',
commandOptions: {
protocolId: 'mpr',
},
],
},
evaluate: 'evaluate.mpr',
},
},
{
id: 'TrackBallRotate',
type: 'ohif.toolButton',
uiType: 'ohif.toolButton',
props: {
type: 'tool',
icon: 'tool-3d-rotate',
label: '3D Rotate',
tooltip: '3D Rotate',
commands: setToolActiveToolbar,
},
},
@ -212,6 +234,7 @@ const toolbarButtons: Button[] = [
props: {
icon: 'tool-capture',
label: 'Capture',
tooltip: 'Capture',
commands: 'showDownloadViewportModal',
evaluate: [
'evaluate.action',
@ -239,6 +262,7 @@ const toolbarButtons: Button[] = [
type: 'tool',
icon: 'tool-crosshair',
label: 'Crosshairs',
tooltip: 'Crosshairs',
commands: {
commandName: 'setToolActiveToolbar',
commandOptions: {
@ -248,6 +272,236 @@ const toolbarButtons: Button[] = [
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 { id } from './id';
import initToolGroups from './initToolGroups';
import toolbarButtons from './toolbarButtons';
import moreTools from './moreTools';
// Allow this mode by excluding non-imaging modalities such as SR, SEG
// Also, SM is not a simple imaging modalities, so exclude it.
@ -84,15 +82,14 @@ function modeFactory({ modeConfiguration }) {
* Lifecycle hooks
*/
onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) {
const { measurementService, toolbarService, toolGroupService, customizationService } =
servicesManager.services;
const { measurementService, toolbarService, toolGroupService } = servicesManager.services;
measurementService.clearMeasurements();
// Init Default and SR ToolGroups
initToolGroups(extensionManager, toolGroupService, commandsManager);
toolbarService.addButtons([...toolbarButtons, ...moreTools]);
toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', [
'MeasurementTools',
'Zoom',
@ -105,6 +102,39 @@ function modeFactory({ modeConfiguration }) {
'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.
// // Do not force activation so as to respect the state the user may have left the UI in.
// _activatePanelTriggersSubscriptions = [
@ -250,4 +280,4 @@ const 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';
const { createButton } = ToolbarService;
import { EVENTS } from '@cornerstonejs/core';
import { ViewportGridService } from '@ohif/core';
const ReferenceLinesListeners = [
{
commandName: 'setSourceViewportForReferenceLinesTool',
context: 'CORNERSTONE',
},
];
export const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar',
@ -13,107 +18,403 @@ export const setToolActiveToolbar = {
};
const toolbarButtons: Button[] = [
// sections
{
id: 'MeasurementTools',
uiType: 'ohif.toolButtonList',
props: {
buttonSection: 'measurementSection',
groupId: 'MeasurementTools',
// group evaluate to determine which item should move to the top
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({
id: 'Length',
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'Bidirectional',
icon: 'tool-bidirectional',
label: 'Bidirectional',
tooltip: 'Bidirectional Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
id: 'ArrowAnnotate',
icon: 'tool-annotate',
label: 'Annotation',
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: 'MoreTools',
uiType: 'ohif.toolButtonList',
props: {
buttonSection: 'moreToolsSection',
groupId: 'MoreTools',
},
},
// tool defs
{
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',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
},
],
},
},
{
id: 'Zoom',
id: 'flipHorizontal',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-zoom',
label: 'Zoom',
icon: 'tool-flip-horizontal',
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,
evaluate: 'evaluate.cornerstoneTool',
},
@ -135,7 +436,6 @@ const toolbarButtons: Button[] = [
],
},
},
// Pan...
{
id: 'Pan',
uiType: 'ohif.toolButton',
@ -147,6 +447,17 @@ const toolbarButtons: Button[] = [
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'Zoom',
uiType: 'ohif.toolButton',
props: {
type: 'tool',
icon: 'tool-zoom',
label: 'Zoom',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
},
{
id: 'TrackballRotate',
uiType: 'ohif.toolButton',

View File

@ -38,8 +38,6 @@ const extensionDependencies = {
function modeFactory({ modeConfiguration }) {
return {
// TODO: We're using this as a route segment
// We should not be.
id,
routeName: 'microscopy',
displayName: i18n.t('Modes:Microscopy'),
@ -47,11 +45,21 @@ function modeFactory({ modeConfiguration }) {
/**
* Lifecycle hooks
*/
onModeEnter: ({ servicesManager, extensionManager, commandsManager }: withAppTypes) => {
onModeEnter: ({ servicesManager }: withAppTypes) => {
const { toolbarService } = servicesManager.services;
toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', ['MeasurementTools', 'dragPan', 'TagBrowser']);
toolbarService.createButtonSection('measurementSection', [
'line',
'point',
'polygon',
'circle',
'box',
'freehandpolygon',
'freehandline',
]);
},
onModeExit: ({ servicesManager }: withAppTypes) => {
@ -79,9 +87,6 @@ function modeFactory({ modeConfiguration }) {
routes: [
{
path: 'microscopy',
/*init: ({ servicesManager, extensionManager }) => {
//defaultViewerRouteInit
},*/
layoutTemplate: ({ location, servicesManager }) => {
return {
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',
};
const defaultButtons = {
buttonSection: 'primary',
buttons: ['MeasurementTools', 'Zoom', 'WindowLevel', 'Crosshairs', 'Pan'],
};
const defaultButtons = [
{
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]];
@ -66,13 +87,7 @@ function getWorkflowSettings({ servicesManager }) {
rightPanelClosed: false,
},
},
toolbarButtons: [
defaultButtons,
{
buttonSection: 'dynamic-toolbox',
buttons: ['BrushTools', 'RectangleROIStartEndThreshold'],
},
],
toolbarButtons: [...defaultButtons, ...ROIThresholdToolbox],
hangingProtocol: {
protocolId: 'default4D',
stageId: 'roiQuantification',

View File

@ -1,9 +1,7 @@
import { id } from './id';
import { hotkeys } from '@ohif/core';
import initWorkflowSteps from './initWorkflowSteps';
import initToolGroups from './initToolGroups';
import toolbarButtons from './toolbarButtons';
import segmentationButtons from './segmentationButtons';
const extensionDependencies = {
'@ohif/extension-default': '3.7.0-beta.76',
@ -56,7 +54,8 @@ function modeFactory({ modeConfiguration }) {
measurementService.clearMeasurements();
initToolGroups({ toolNames, Enums, toolGroupService, commandsManager, servicesManager });
toolbarService.addButtons([...toolbarButtons, ...segmentationButtons]);
toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('secondary', ['ProgressDropdown']);
// 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';
const { createButton } = ToolbarService;
const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar',
commandOptions: {
@ -16,53 +13,67 @@ const toolbarButtons = [
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: [
{
id: 'Length',
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
{
id: 'Bidirectional',
icon: 'tool-bidirectional',
label: 'Bidirectional',
tooltip: 'Bidirectional Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
{
id: 'ArrowAnnotate',
icon: 'tool-annotate',
label: 'Annotation',
tooltip: 'Arrow Annotate',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
{
id: 'EllipticalROI',
icon: 'tool-ellipse',
label: 'Ellipse',
tooltip: 'Ellipse ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
},
],
buttonSection: 'measurementSection',
},
},
{
id: 'BrushTools',
uiType: 'ohif.toolBoxButtonGroup',
props: {
groupId: 'BrushTools',
buttonSection: 'brushToolsSection',
},
},
{
id: 'SegmentationTools',
uiType: 'ohif.toolBoxButton',
props: {
groupId: 'SegmentationTools',
buttonSection: 'segmentationToolboxToolsSection',
},
},
{
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',
},
},
{
@ -109,7 +120,7 @@ const toolbarButtons = [
},
{
id: 'Capture',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-capture',
label: 'Capture',
@ -130,6 +141,7 @@ const toolbarButtons = [
rows: 3,
columns: 4,
evaluate: 'evaluate.action',
commands: 'setViewportGridLayout',
},
},
{
@ -149,7 +161,7 @@ const toolbarButtons = [
},
{
id: 'RectangleROIStartEndThreshold',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toolBoxButton',
props: {
icon: 'tool-create-threshold',
label: 'Rectangle ROI Threshold',
@ -161,6 +173,163 @@ const toolbarButtons = [
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;

View File

@ -1,6 +1,5 @@
import { id } from './id';
import toolbarButtons from './toolbarButtons';
import segmentationButtons from './segmentationButtons';
import initToolGroups from './initToolGroups';
const ohif = {
@ -57,7 +56,6 @@ function modeFactory({ modeConfiguration }) {
initToolGroups(extensionManager, toolGroupService, commandsManager);
toolbarService.addButtons(toolbarButtons);
toolbarService.addButtons(segmentationButtons);
toolbarService.createButtonSection('primary', [
'WindowLevel',
@ -69,7 +67,26 @@ function modeFactory({ modeConfiguration }) {
'Crosshairs',
'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) => {
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.RectangleScissors },
{ 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 { ToolbarService, ViewportGridService } from '@ohif/core';
const { createButton } = ToolbarService;
const ReferenceLinesListeners: RunCommand = [
{
commandName: 'setSourceViewportForReferenceLinesTool',
context: 'CORNERSTONE',
},
];
export const setToolActiveToolbar = {
const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar',
commandOptions: {
toolGroupIds: ['default', 'mpr', 'SRToolGroup', 'volume3d'],
@ -18,6 +8,41 @@ export const setToolActiveToolbar = {
};
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',
uiType: 'ohif.toolButton',
@ -107,150 +132,345 @@ const toolbarButtons: Button[] = [
},
},
{
id: 'MoreTools',
uiType: 'ohif.toolButtonList',
id: 'Reset',
uiType: 'ohif.toolButton',
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',
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',
{
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: [
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',
{
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,
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'] },
},
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: 'Cine',
icon: 'tool-cine',
label: 'Cine',
tooltip: 'Cine',
commands: 'toggleCine',
evaluate: [
'evaluate.cine',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['volume3d'],
},
},
{
name: 'Shape',
type: 'radio',
id: 'brush-mode',
value: 'CircularBrush',
values: [
{ value: 'CircularBrush', label: 'Circle' },
{ value: 'SphereBrush', label: 'Sphere' },
],
}),
createButton({
id: 'Magnify',
icon: 'tool-magnify',
label: 'Zoom-in',
tooltip: 'Zoom-in',
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'],
},
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' },
],
}),
createButton({
id: 'WindowLevelRegion',
icon: 'icon-tool-window-region',
label: 'Window Level Region',
tooltip: 'Window Level Region',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
commands: 'setToolActiveToolbar',
},
],
},
},
{
id: 'Threshold',
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 toolbarButtons from './toolbarButtons.js';
import { classes } from '@ohif/core';
import toolbarButtons from './toolbarButtons';
import { id } from './id.js';
import initToolGroups from './initToolGroups.js';
import setCrosshairsConfiguration from './utils/setCrosshairsConfiguration.js';
@ -96,11 +96,21 @@ function modeFactory({ modeConfiguration }) {
'Crosshairs',
'Pan',
]);
toolbarService.createButtonSection('ROIThresholdToolbox', [
toolbarService.createButtonSection('measurementSection', [
'Length',
'Bidirectional',
'ArrowAnnotate',
'EllipticalROI',
]);
toolbarService.createButtonSection('ROIThresholdToolbox', ['SegmentationTools']);
toolbarService.createButtonSection('segmentationToolboxToolsSection', [
'RectangleROIStartEndThreshold',
'BrushTools',
]);
toolbarService.createButtonSection('brushToolsSection', ['Brush', 'Eraser', 'Threshold']);
customizationService.setCustomizations({
'panelSegmentation.tableMode': {
$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": {
"@percy/cypress": "^3.1.1",
"@playwright/test": "^1.48.0",
"cypress": "^14.0.0",
"cypress": "^14.1.0",
"cypress-file-upload": "^5.0.8"
},
"devDependencies": {

View File

@ -1,108 +1,95 @@
describe('OHIF Download Snapshot File', () => {
beforeEach(() => {
cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.expectMinimumThumbnails(3);
cy.openDownloadImageModal();
});
// describe('OHIF Download Snapshot File', () => {
// beforeEach(() => {
// cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
// cy.expectMinimumThumbnails(3);
// cy.openDownloadImageModal();
// });
it('checks displayed information for Desktop experience', function () {
// Set Desktop resolution
// cy.viewport(1750, 720);
// Visual comparison
// cy.screenshot('Download Image Modal - Desktop experience');
//Check if all elements are displayed
// it('checks displayed information for Desktop experience', function () {
// // Set Desktop resolution
// // cy.viewport(1750, 720);
// // Visual comparison
// // cy.screenshot('Download Image Modal - Desktop experience');
// //Check if all elements are displayed
// TODO: need to add this attribute to the modal
cy.get('[data-cy=modal-header]')
.as('downloadImageModal')
.should('contain.text', 'Download High Quality Image');
// // Check image preview
// cy.get('[data-cy="image-preview"]').should('contain.text', 'Image preview');
// Check input fields
// TODO: select2
// cy.get('[data-cy="file-type"]')
// .select('png')
// .should('have.value', 'png')
// .select('jpg')
// .should('have.value', 'jpg');
// //TODO: This is a canvas now, not an img with src
// // cy.get('[data-cy="viewport-preview-img"]')
// // .should('have.attr', 'src')
// // .and('include', 'data:image');
// Check image preview
cy.get('[data-cy="image-preview"]').should('contain.text', 'Image preview');
// // Check buttons
// 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="viewport-preview-img"]')
// .should('have.attr', 'src')
// .and('include', 'data:image');
// cy.get('[data-cy="cancel-btn"]').click();
// });
// Check buttons
cy.get('[data-cy="cancel-btn"]').scrollIntoView().should('be.visible');
cy.get('[data-cy="download-btn"]').scrollIntoView().should('be.visible');
// /*it('cancel changes on download modal', function() {
// //Change Image Width, Filename and File Type
// 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() {
//Change Image Width, Filename and File Type
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');
});*/
// // //Click on Download button
// // cy.get('[data-cy="download-btn"]')
// // .scrollIntoView()
// // .click();
// // //Check error message
// // });
// 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('checks if "Show Annotations" checkbox will display annotations', function() {
// // Close modal that is initially opened
// cy.get('[data-cy="close-button"]').click();
// //Click on Download button
// cy.get('[data-cy="download-btn"]')
// .scrollIntoView()
// .click();
// //Check error message
// });
// // Add measurements in the viewport
// cy.addLengthMeasurement();
// cy.addAngleMeasurement();
/*it('checks if "Show Annotations" checkbox will display annotations', function() {
// Close modal that is initially opened
cy.get('[data-cy="close-button"]').click();
// Add measurements in the viewport
cy.addLengthMeasurement();
cy.addAngleMeasurement();
// Open Modal
cy.openDownloadImageModal();
// Select "Show Annotations" option
cy.get('[data-cy="show-annotations"]').check();
// Check image preview
cy.get('[data-cy="image-preview"]').scrollIntoView();
//Compare classes that exists on Image Preview with Annotations and Without Annotation
cy.get('[data-cy="modal-content"]')
.find('canvas')
.should('have.class', 'magnifyTool'); //Class "MagnifyTool" exists with annotations displayed on Image preview
// 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');
});*/
});
// // Open Modal
// cy.openDownloadImageModal();
// // Select "Show Annotations" option
// cy.get('[data-cy="show-annotations"]').check();
// // Check image preview
// cy.get('[data-cy="image-preview"]').scrollIntoView();
// //Compare classes that exists on Image Preview with Annotations and Without Annotation
// cy.get('[data-cy="modal-content"]')
// .find('canvas')
// .should('have.class', 'magnifyTool'); //Class "MagnifyTool" exists with annotations displayed on Image preview
// // 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="Zoom"]').as('zoomBtn');
cy.get('[data-cy="WindowLevel-split-button-primary"]').as('wwwcBtnPrimary');
cy.get('[data-cy="WindowLevel-split-button-secondary"]').as('wwwcBtnSecondary');
cy.get('[data-cy="WindowLevelGroup-split-button-primary"]').as('wwwcBtnPrimary');
cy.get('[data-cy="WindowLevelGroup-split-button-secondary"]').as('wwwcBtnSecondary');
cy.get('[data-cy="Pan"]').as('panBtn');
cy.get('[data-cy="MeasurementTools-split-button-primary"]').as('measurementToolsBtnPrimary');
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-openjpeg": "^1.2.4",
"@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",
"@ohif/core": "3.10.0-beta.116",
"@ohif/extension-cornerstone": "3.10.0-beta.116",

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { hotkeys } from '@ohif/core';
import { initToolGroups, toolbarButtons } from '@ohif/mode-longitudinal';
import { id } from './id';
@ -50,14 +49,47 @@ function modeFactory({ modeConfiguration }) {
toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', [
'MeasurementTools',
'measurementSection',
'Zoom',
'WindowLevel',
'Pan',
'Capture',
'Layout',
'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) => {

View File

@ -37,7 +37,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@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",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21"

View File

@ -1,6 +1,8 @@
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 { EVENTS } = toolbarService;
@ -11,15 +13,54 @@ export function useToolbar({ servicesManager, buttonSection = 'primary' }: withA
// Callback function for handling toolbar interactions
const onInteraction = useCallback(
args => {
args.event?.stopPropagation?.();
const viewportId = viewportGridService.getActiveViewportId();
const refreshProps = {
viewportId,
};
toolbarService.recordInteraction(args, {
refreshProps,
});
const refreshProps = { viewportId };
const buttonProps = toolbarService.getButtonProps(args.itemId);
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

View File

@ -134,7 +134,7 @@ export default class ToolbarService extends PubSubService {
buttons.forEach(button => {
if (replace || !this.state.buttons[button.id]) {
if (!button.props) {
button.props = {};
button.props = {} as ButtonProps;
}
this.state.buttons[button.id] = button;
@ -173,12 +173,16 @@ export default class ToolbarService extends PubSubService {
? interaction.commands
: [interaction.commands];
commands = commands.filter(Boolean);
if (!commands?.length) {
this.refreshToolbarState({
...options?.refreshProps,
itemId,
interaction,
});
return;
}
const commandOptions = { ...options, ...interaction };
@ -228,10 +232,6 @@ export default class ToolbarService extends PubSubService {
*/
public refreshToolbarState(refreshProps) {
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 evaluateButtonProps = (button, props, refreshProps) => {
@ -252,59 +252,50 @@ export default class ToolbarService extends PubSubService {
}
};
const refreshedButtons = Object.values(buttons).reduce((acc, button: Button) => {
const isNested = (button.props as NestedButtonProps)?.groupId;
Object.values(buttons).forEach(button => {
const hasSection = (button.props as NestedButtonProps)?.buttonSection;
if (!isNested) {
if (!hasSection) {
this.handleEvaluate(button.props);
const buttonProps = button.props as ButtonProps;
const updatedProps = evaluateButtonProps(button, buttonProps, refreshProps);
acc[button.id] = {
buttons[button.id] = {
...button,
props: updatedProps,
};
} else {
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 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,
primary: groupEvaluated?.primary ?? buttonProps.primary,
disabled: groupEvaluated?.disabled ?? buttonProps.disabled,
disabledText: groupEvaluated?.disabledText ?? buttonProps.disabledText,
};
const { primary, items } = buttonProps;
const toolButtonIds = this.state.buttonSections[buttonProps.buttonSection];
// primary and items evaluate functions
let updatedPrimary;
if (primary) {
updatedPrimary = evaluateButtonProps(primary, primary, refreshProps);
if (!toolButtonIds) {
return;
}
const updatedItems = items.map(item => evaluateButtonProps(item, item, refreshProps));
buttonProps = {
...buttonProps,
primary: updatedPrimary,
items: updatedItems,
};
acc[button.id] = {
...button,
props: buttonProps,
};
toolButtonIds.forEach(buttonId => {
const button = buttons[buttonId];
const updatedProps = evaluateButtonProps(button, button.props, refreshProps);
buttons[buttonId] = {
...button,
props: updatedProps,
};
});
}
});
return acc;
}, {});
this.setButtons(refreshedButtons);
this.setButtons(buttons);
return this.state;
}
@ -349,17 +340,6 @@ export default class ToolbarService extends PubSubService {
* @returns The button properties.
*/
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
// we are checking based on the ids, the nested objects are higher priority
// 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.
* @param button - The button object.
@ -448,41 +434,119 @@ export default class ToolbarService extends PubSubService {
* @param {*} metadata
* @param {*} props - Props set by the Viewer layer
*/
_mapButtonToDisplay(btn, props) {
_mapButtonToDisplay(btn: Button, props: Record<string, unknown>) {
if (!btn) {
return;
}
const { id, uiType, component } = btn;
const { groupId } = btn.props;
const { id, uiType } = btn;
const { groupId } = btn.props as NestedButtonProps;
const buttonTypes = this._getButtonUITypes();
const buttonType = buttonTypes[uiType];
if (!buttonType && !component) {
if (!btn.component) {
btn.component = buttonType.defaultComponent;
}
if (!buttonType) {
return;
}
!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 {
id,
Component: component || buttonType.defaultComponent,
componentProps: Object.assign({}, btn.props, props),
Component: btn.component,
componentProps: Object.assign({ id }, btn.props, props),
};
}
handleEvaluateNested = props => {
const { primary, items } = props;
// handle group evaluate function
this.handleEvaluate(props);
const { buttonSection } = props;
// primary and items evaluate functions
if (primary) {
this.handleEvaluate(primary);
if (!buttonSection) {
return;
}
items.forEach(item => this.handleEvaluate(item));
const toolbarButtons = this.getButtonSection(buttonSection);
if (!toolbarButtons?.length) {
return;
}
toolbarButtons.forEach(button => {
this.handleEvaluate(button.componentProps);
});
};
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
const buttonTypes = this._getButtonUITypes();
const optionComponent = buttonTypes[options]?.defaultComponent;
props.options = {
optionComponent,
};
props.options = optionComponent;
}
if (typeof evaluate === 'function') {

View File

@ -1,4 +1,5 @@
import type { RunCommand } from '../../types/Command';
import React from 'react';
export type EvaluatePublic =
| string
@ -17,6 +18,20 @@ export type EvaluateObject = {
[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 = {
id: string;
icon: string;
@ -27,27 +42,15 @@ export type ButtonProps = {
className?: string;
evaluate?: EvaluatePublic;
listeners?: Record<string, RunCommand>;
};
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;
options?: ButtonOptions[];
buttonSection?: string;
};
export type Button = {
id: string;
props: ButtonProps | NestedButtonProps;
props: ButtonProps;
// button ui type (e.g. 'ohif.splitButton', 'ohif.radioGroup')
// extensions can provide custom components for these types
uiType: string;
component?: React.ComponentType<any>;
};

View File

@ -27,7 +27,7 @@ export const EVENTS = {
sections: [
{
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.
## 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
Previously, the segmentation toolbox was not using an `evaluator` property. This is now taken into account
### evaluators in Toolbox
```js
// old
{
@ -84,7 +187,6 @@ Previously, the segmentation toolbox was not using an `evaluator` property. This
uiType: 'ohif.buttonGroup',
props: {
groupId: 'BrushTools',
items: []
}
}
@ -95,7 +197,237 @@ Previously, the segmentation toolbox was not using an `evaluator` property. This
props: {
groupId: 'BrushTools',
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;
value: string;
values: RadioValue[];
commands?: (val: string) => void;
onChange?: (val: string) => void;
}
interface RowSegmentedControlProps {
option: RadioOption;
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) => {
if (option.commands) {
option.commands(newVal);
if (onChange) {
onChange(newVal);
}
};

View File

@ -56,7 +56,7 @@ const renderRangeSetting = option => {
maxValue={option.max}
step={option.step}
value={option.value}
onChange={value => option.commands?.(value)}
onChange={value => option.onChange?.(value)}
allowNumberEdit={true}
inputClassName="ml-1 w-4/5 cursor-pointer"
/>
@ -70,6 +70,7 @@ const renderRadioSetting = option => {
<RowSegmentedControl
key={option.id}
option={option}
onChange={option.onChange}
/>
);
};
@ -79,7 +80,7 @@ function renderDoubleRangeSetting(option) {
<RowDoubleRange
key={option.id}
values={option.value}
onChange={option.commands}
onChange={option.onChange}
minValue={option.min}
maxValue={option.max}
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 { PanelSection } from '../../components';
@ -6,52 +6,39 @@ import { ToolSettings } from '../OHIFToolSettings';
const ItemsPerRow = 4;
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
interface ToolbarButton {
id: string;
Component: React.ComponentType<{
id: string;
onInteraction: (details: { itemId: string }) => void;
size: string;
}>;
componentProps: {
isActive?: boolean;
buttonSection?: string;
options?: unknown;
};
}
/**
* Just refactoring from the toolbox component to make it more readable
*/
function ToolboxUI(props: withAppTypes) {
interface ToolboxProps {
toolbarButtons: ToolbarButton[];
numRows: number;
title?: string;
useCollapsedPanel?: boolean;
onInteraction?: (details: { itemId: string }) => void;
activeToolOptions?: unknown;
}
function ToolboxUI(props: ToolboxProps) {
const {
toolbarButtons,
handleToolSelect,
toolboxState,
toolbarButtons = [],
numRows,
servicesManager,
title,
useCollapsedPanel = true,
onInteraction,
activeToolOptions,
} = 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 = () => {
return (
<>
@ -67,12 +54,9 @@ function ToolboxUI(props: withAppTypes) {
const toolClasses = `ml-1 ${isLastRow ? '' : 'mb-2'}`;
const onInteraction = ({ itemId, id, commands }) => {
const idToUse = itemId || id;
handleToolSelect(idToUse);
props.onInteraction({
const handleInteraction = ({ itemId }: { itemId: string }) => {
onInteraction?.({
itemId,
commands,
});
};
@ -85,10 +69,8 @@ function ToolboxUI(props: withAppTypes) {
>
<Component
{...componentProps}
{...props}
id={id}
servicesManager={servicesManager}
onInteraction={onInteraction}
onInteraction={handleInteraction}
size="toolbox"
/>
</div>

View File

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

View File

@ -41,7 +41,10 @@ const StudyItem = ({
<div className="flex min-w-0 flex-col items-start text-[13px]">
<Tooltip>
<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">
{date}
</div>
@ -49,7 +52,10 @@ const StudyItem = ({
</Tooltip>
<Tooltip>
<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">
{description}
</div>

View File

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

View File

@ -52,7 +52,7 @@ import { ThumbnailList } from './ThumbnailList';
import { PanelSection } from './PanelSection';
import { DisplaySetMessageListTooltip } from './DisplaySetMessageListTooltip';
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './Tooltip';
import { ToolboxUI, Toolbox } from './OHIFToolbox';
import { ToolboxUI } from './OHIFToolbox';
import Numeric from './Numeric';
import { InputDialog, PresetDialog } from './OHIFDialogs';
import { AboutModal, ImageModal, UserPreferencesModal } from './OHIFModals';
@ -105,6 +105,7 @@ import {
ToolButtonListItem,
ToolButtonListDivider,
} from './ToolButton';
import { ToolSettings } from './OHIFToolSettings';
export {
Numeric,
@ -173,7 +174,6 @@ export {
PanelSection,
DisplaySetMessageListTooltip,
ToolboxUI,
Toolbox,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
@ -236,4 +236,5 @@ export {
ImageModal,
UserPreferencesModal,
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 { ViewportGridContext, ViewportGridProvider, useViewportGrid } from './ViewportGridProvider';
import { ToolboxProvider, useToolbox } from './ToolboxContext';
import { ModalProvider, useModal } from './ModalProvider';
import { DialogProvider, useDialog } from './DialogProvider';
import ManagedDialog from './ManagedDialog';
export { useNotification, NotificationProvider };
export { ViewportGridContext, ViewportGridProvider, useViewportGrid };
export { ToolboxProvider, useToolbox };
export { ModalProvider, useModal };
export { DialogProvider, useDialog };
export { ManagedDialog };

View File

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

187
yarn.lock
View File

@ -1393,10 +1393,10 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cornerstonejs/adapters@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.0.1.tgz#596e302d7155ee64b7c20bcfedc57ce6ca10d5fb"
integrity sha512-Ym6s4cTpMkojap5QZ8MXZ8hDbs2TMMN/ypqU7wKMwAY2LGoltoeGw0Z/7LL+iostbaSw3fVs+TZzJSKhKw8Bug==
"@cornerstonejs/adapters@^3.0.4":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.0.4.tgz#3aea5f65e95fbb067885eb51ebcae2cacd673465"
integrity sha512-TJtW0biDf0wgi+XxQFqvKXT/Cf8ueyUruohH9n/RJsWiyE8z1/LOBxOqCZNkToGGs1EbO51T/PMPIUWordW43w==
dependencies:
"@babel/runtime-corejs2" "^7.17.8"
buffer "^6.0.3"
@ -1429,19 +1429,19 @@
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
"@cornerstonejs/core@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.0.1.tgz#8a938013c148d752d2c4f4eba7bd63048a6c814a"
integrity sha512-K+cOoN/TzJIPRRoaWWNkhJ+2CdkBBX8BA64DG0aBVOthlwJPRqK2YQE6PTTVRx18mjMA4to/m3de0emFNKrTzQ==
"@cornerstonejs/core@^3.0.4":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.0.4.tgz#a75e0e86cc3ac4cd19355c30f8f531ebe279ae36"
integrity sha512-6rMiqhBjyz3wsalPOAFjThfPUJnWVOs7Xqe3xLFvqd1qIFW1q42bqlQ7ak2DRMUMtC1nLURk6YIMhkWxK+t+7A==
dependencies:
"@kitware/vtk.js" "32.9.0"
comlink "^4.4.1"
gl-matrix "^3.4.3"
"@cornerstonejs/dicom-image-loader@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.0.1.tgz#e18f716620ab176d88c25b9efd25fc0983bce180"
integrity sha512-t4bI6rVOt4J8YOHRR9Mh9ClInajc98qqI6fwsByiXmtc2Y4INrZ1uaRspFnOJBosbr+EwsJBV7iT2yt6Jk6CjA==
"@cornerstonejs/dicom-image-loader@^3.0.4":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.0.4.tgz#0c553e1b01fc90d956d216d72686f6dcda9400f7"
integrity sha512-w0NoutE7+/C2FeT1YHo0cnMJpZ17ARH0qwyGwWockFwuJkUVjRcU5ZW1sT0ABNOPUHqZse/K/YmH2IX+ppaScQ==
dependencies:
"@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
@ -1459,10 +1459,10 @@
dependencies:
"@icr/polyseg-wasm" "0.4.0"
"@cornerstonejs/tools@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.0.1.tgz#4d707992283235d79532f35e60e8c763b174ccef"
integrity sha512-fRjoXJ46mdpYc5JZ/FUn0g1vCK4LohnNjgZl+O72HK2reMdc5/dQeCueJCT+tBMay04sJMUHm0tAke2DN10FBQ==
"@cornerstonejs/tools@^3.0.4":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.0.4.tgz#413031f55dd7aca30168bfd13fe52911ba81f795"
integrity sha512-gkenXhOS6lYYmQhKDwayojeObTU/KiG9k+JEC/hoOBhRdoFZljyXfQtnljKFHL+G9rkdbNKxXq/a3+C9Bn7EVg==
dependencies:
"@types/offscreencanvas" "2019.7.3"
comlink "^4.4.1"
@ -1574,10 +1574,10 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016"
integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==
"@cypress/request@^3.0.6":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.7.tgz#6a74a4da98d9e5ae9121d6e2d9c14780c9b5cf1a"
integrity sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==
"@cypress/request@^3.0.7":
version "3.0.8"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.8.tgz#992f1f42ba03ebb14fa5d97290abe9d015ed0815"
integrity sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@ -1592,7 +1592,7 @@
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
performance-now "^2.1.0"
qs "6.13.1"
qs "6.14.0"
safe-buffer "^5.1.2"
tough-cookie "^5.0.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"
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:
version "1.0.7"
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"
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:
version "2.0.0"
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"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
ci-info@^4.0.0:
ci-info@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83"
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"
integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==
cypress@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-14.0.0.tgz#a71cb0a243a0bfeb97b6973ab9c5291ca5288e93"
integrity sha512-kEGqQr23so5IpKeg/dp6GVi7RlHx1NmW66o2a2Q4wk9gRaAblLZQSiZJuDI8UMC4LlG5OJ7Q6joAiqTrfRNbTw==
cypress@^14.1.0:
version "14.1.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-14.1.0.tgz#b2dbe7bbc529dc0c93ffd4e0e9fa59763afba0b8"
integrity sha512-pPPj8Uu9NwjaaiXAEcjYZZmgsq6v9Zs1Nw6a+zRF+ANgYSNhH4S32SjFRsvMcuOHR/8dp4GBJhBPqIPSs+TxaA==
dependencies:
"@cypress/request" "^3.0.6"
"@cypress/request" "^3.0.7"
"@cypress/xvfb" "^1.2.4"
"@types/sinonjs__fake-timers" "8.1.1"
"@types/sizzle" "^2.3.2"
@ -8529,7 +8545,7 @@ cypress@^14.0.0:
cachedir "^2.3.0"
chalk "^4.1.0"
check-more-types "^2.24.0"
ci-info "^4.0.0"
ci-info "^4.1.0"
cli-cursor "^3.1.0"
cli-table3 "~0.6.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"
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:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@ -9762,6 +9787,11 @@ es-define-property@^1.0.0:
dependencies:
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:
version "1.3.0"
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:
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:
version "2.0.3"
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"
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:
version "1.0.1"
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"
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:
version "7.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6"
@ -11451,6 +11512,11 @@ gopd@^1.0.1:
dependencies:
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:
version "12.6.1"
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"
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:
version "1.0.2"
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"
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:
version "11.12.0"
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"
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:
version "1.1.6"
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:
side-channel "^1.0.6"
qs@6.13.1:
version "6.13.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e"
integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==
qs@6.14.0:
version "6.14.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930"
integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
dependencies:
side-channel "^1.0.6"
side-channel "^1.1.0"
query-string@^4.1.0:
version "4.3.4"
@ -18790,6 +18871,35 @@ shx@^0.3.3:
minimist "^1.2.3"
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:
version "1.0.6"
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"
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:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"