feat(toolbox): Refactor Toolbar and Toolbox to enable sections (#4825)
This commit is contained in:
parent
86c12246ac
commit
cc5cdfb081
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
# Packages
|
||||
node_modules
|
||||
.cursor/
|
||||
|
||||
# Output
|
||||
build
|
||||
|
||||
314
bun.lock
314
bun.lock
@ -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=="],
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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 = ({
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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: () => {
|
||||
|
||||
@ -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[]>([]);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -23,7 +23,7 @@ function ToolbarSplitButtonWithServices({
|
||||
onInteraction({
|
||||
groupId,
|
||||
itemId: item.id,
|
||||
commands: item.commands,
|
||||
item,
|
||||
});
|
||||
},
|
||||
})),
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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: () => {
|
||||
|
||||
@ -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,
|
||||
|
||||
122
extensions/default/src/utils/Toolbox.tsx
Normal file
122
extensions/default/src/utils/Toolbox.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export { addIcon } from './addIcon';
|
||||
export * from './Toolbox';
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 }) => {
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -96,7 +96,7 @@ function modeFactory({ modeConfiguration }) {
|
||||
|
||||
toolbarService.addButtons(toolbarButtons);
|
||||
toolbarService.createButtonSection('primary', [
|
||||
'MeasurementTools',
|
||||
'measurementSection',
|
||||
'Zoom',
|
||||
'WindowLevel',
|
||||
'Pan',
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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;
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
141
modes/microscopy/src/toolbarButtons.ts
Normal file
141
modes/microscopy/src/toolbarButtons.ts
Normal 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;
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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;
|
||||
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
300
modes/tmtv/src/toolbarButtons.ts
Normal file
300
modes/tmtv/src/toolbarButtons.ts
Normal 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;
|
||||
@ -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": {
|
||||
|
||||
@ -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');
|
||||
// });*/
|
||||
// });
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 }],
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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') {
|
||||
|
||||
@ -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>;
|
||||
};
|
||||
|
||||
@ -27,7 +27,7 @@ export const EVENTS = {
|
||||
sections: [
|
||||
{
|
||||
key: 'primary',
|
||||
buttons: [ 'MeasurementTools', 'Zoom', ... ],
|
||||
buttons: [ 'measurementSection', 'Zoom', ... ],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -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}
|
||||
+ />
|
||||
```
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
@ -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>
|
||||
|
||||
@ -1,3 +1 @@
|
||||
import { ToolboxUI } from './ToolboxUI';
|
||||
import Toolbox from './Toolbox';
|
||||
export { ToolboxUI, Toolbox };
|
||||
export * from './ToolboxUI';
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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),
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -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 };
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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
187
yarn.lock
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user