diff --git a/bun.lock b/bun.lock
index c26f809ba..a5532c046 100644
--- a/bun.lock
+++ b/bun.lock
@@ -33,7 +33,7 @@
},
"addOns/externals/devDependencies": {
"name": "@externals/devDependencies",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@kitware/vtk.js": "32.12.0",
@@ -121,20 +121,21 @@
},
"addOns/externals/dicom-microscopy-viewer": {
"name": "@externals/dicom-microscopy-viewer",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"dicom-microscopy-viewer": "^0.46.1",
},
},
"extensions/cornerstone": {
"name": "@ohif/extension-cornerstone",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^3.5.0",
- "@cornerstonejs/core": "^3.5.0",
- "@cornerstonejs/polymorphic-segmentation": "^3.5.0",
- "@cornerstonejs/tools": "^3.5.0",
+ "@cornerstonejs/adapters": "^3.7.0",
+ "@cornerstonejs/core": "^3.7.0",
+ "@cornerstonejs/labelmap-interpolation": "^3.7.0",
+ "@cornerstonejs/polymorphic-segmentation": "^3.7.0",
+ "@cornerstonejs/tools": "^3.7.0",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "^1.4.1",
@@ -151,9 +152,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.5.0",
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@cornerstonejs/dicom-image-loader": "^3.7.0",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"dcmjs": "*",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@@ -165,19 +166,19 @@
},
"extensions/cornerstone-dicom-pmap": {
"name": "@ohif/extension-cornerstone-dicom-pmap",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^3.5.0",
- "@cornerstonejs/core": "^3.5.0",
+ "@cornerstonejs/adapters": "^3.7.0",
+ "@cornerstonejs/core": "^3.7.0",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/i18n": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/i18n": "3.10.0-beta.125",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -188,16 +189,16 @@
},
"extensions/cornerstone-dicom-rt": {
"name": "@ohif/extension-cornerstone-dicom-rt",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"react-color": "^2.19.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/i18n": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/i18n": "3.10.0-beta.125",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -208,19 +209,19 @@
},
"extensions/cornerstone-dicom-seg": {
"name": "@ohif/extension-cornerstone-dicom-seg",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^3.5.0",
- "@cornerstonejs/core": "^3.5.0",
+ "@cornerstonejs/adapters": "^3.7.0",
+ "@cornerstonejs/core": "^3.7.0",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/i18n": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/i18n": "3.10.0-beta.125",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -231,19 +232,19 @@
},
"extensions/cornerstone-dicom-sr": {
"name": "@ohif/extension-cornerstone-dicom-sr",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^3.5.0",
- "@cornerstonejs/core": "^3.5.0",
- "@cornerstonejs/tools": "^3.5.0",
+ "@cornerstonejs/adapters": "^3.7.0",
+ "@cornerstonejs/core": "^3.7.0",
+ "@cornerstonejs/tools": "^3.7.0",
"classnames": "^2.3.2",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-measurement-tracking": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-measurement-tracking": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@@ -253,19 +254,19 @@
},
"extensions/cornerstone-dynamic-volume": {
"name": "@ohif/extension-cornerstone-dynamic-volume",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/core": "^3.5.0",
- "@cornerstonejs/tools": "^3.5.0",
+ "@cornerstonejs/core": "^3.7.0",
+ "@cornerstonejs/tools": "^3.7.0",
"classnames": "^2.3.2",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/i18n": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/i18n": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"dcmjs": "*",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@@ -275,7 +276,7 @@
},
"extensions/default": {
"name": "@ohif/extension-default",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/calculate-suv": "^1.1.0",
@@ -283,8 +284,8 @@
"lodash.uniqby": "^4.7.0",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/i18n": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/i18n": "3.10.0-beta.125",
"dcmjs": "*",
"dicomweb-client": "^0.10.4",
"prop-types": "^15.6.2",
@@ -298,7 +299,7 @@
},
"extensions/dicom-microscopy": {
"name": "@ohif/extension-dicom-microscopy",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/codec-charls": "^1.2.3",
@@ -309,10 +310,10 @@
"mathjs": "^12.4.2",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/i18n": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/i18n": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -323,14 +324,14 @@
},
"extensions/dicom-pdf": {
"name": "@ohif/extension-dicom-pdf",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@@ -340,14 +341,14 @@
},
"extensions/dicom-video": {
"name": "@ohif/extension-dicom-video",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@@ -357,20 +358,20 @@
},
"extensions/measurement-tracking": {
"name": "@ohif/extension-measurement-tracking",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@ohif/ui": "3.10.0-beta.121",
+ "@ohif/ui": "3.10.0-beta.125",
"@xstate/react": "^3.2.2",
"xstate": "^4.10.0",
},
"peerDependencies": {
- "@cornerstonejs/core": "^3.5.0",
- "@cornerstonejs/tools": "^3.5.0",
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@cornerstonejs/core": "^3.7.0",
+ "@cornerstonejs/tools": "^3.7.0",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"classnames": "^2.3.2",
"dcmjs": "*",
"lodash.debounce": "^4.0.8",
@@ -383,14 +384,14 @@
},
"extensions/test-extension": {
"name": "@ohif/extension-test",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"dcmjs": "0.38.0",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@@ -400,14 +401,14 @@
},
"extensions/tmtv": {
"name": "@ohif/extension-tmtv",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@@ -417,7 +418,7 @@
},
"modes/basic-dev-mode": {
"name": "@ohif/mode-basic-dev-mode",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@@ -427,17 +428,17 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/extension-dicom-pdf": "3.10.0-beta.121",
- "@ohif/extension-dicom-video": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/extension-dicom-pdf": "3.10.0-beta.125",
+ "@ohif/extension-dicom-video": "3.10.0-beta.125",
},
},
"modes/basic-test-mode": {
"name": "@ohif/mode-test",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@@ -447,19 +448,19 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/extension-dicom-pdf": "3.10.0-beta.121",
- "@ohif/extension-dicom-video": "3.10.0-beta.121",
- "@ohif/extension-measurement-tracking": "3.10.0-beta.121",
- "@ohif/extension-test": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/extension-dicom-pdf": "3.10.0-beta.125",
+ "@ohif/extension-dicom-video": "3.10.0-beta.125",
+ "@ohif/extension-measurement-tracking": "3.10.0-beta.125",
+ "@ohif/extension-test": "3.10.0-beta.125",
},
},
"modes/longitudinal": {
"name": "@ohif/mode-longitudinal",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@@ -469,32 +470,32 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/extension-dicom-pdf": "3.10.0-beta.121",
- "@ohif/extension-dicom-video": "3.10.0-beta.121",
- "@ohif/extension-measurement-tracking": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/extension-dicom-pdf": "3.10.0-beta.125",
+ "@ohif/extension-dicom-video": "3.10.0-beta.125",
+ "@ohif/extension-measurement-tracking": "3.10.0-beta.125",
},
},
"modes/microscopy": {
"name": "@ohif/mode-microscopy",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-dicom-microscopy": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-dicom-microscopy": "3.10.0-beta.125",
},
},
"modes/preclinical-4d": {
"name": "@ohif/mode-preclinical-4d",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
},
@@ -503,17 +504,17 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dynamic-volume": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/extension-tmtv": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dynamic-volume": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/extension-tmtv": "3.10.0-beta.125",
},
},
"modes/segmentation": {
"name": "@ohif/mode-segmentation",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@@ -545,19 +546,19 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/extension-dicom-pdf": "3.10.0-beta.121",
- "@ohif/extension-dicom-video": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/extension-dicom-pdf": "3.10.0-beta.125",
+ "@ohif/extension-dicom-video": "3.10.0-beta.125",
},
},
"modes/tmtv": {
"name": "@ohif/mode-tmtv",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@@ -567,43 +568,43 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/extension-dicom-pdf": "3.10.0-beta.121",
- "@ohif/extension-dicom-video": "3.10.0-beta.121",
- "@ohif/extension-measurement-tracking": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/extension-dicom-pdf": "3.10.0-beta.125",
+ "@ohif/extension-dicom-video": "3.10.0-beta.125",
+ "@ohif/extension-measurement-tracking": "3.10.0-beta.125",
},
},
"platform/app": {
"name": "@ohif/app",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"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.5.0",
+ "@cornerstonejs/dicom-image-loader": "^3.7.0",
"@emotion/serialize": "^1.1.3",
- "@ohif/core": "3.10.0-beta.121",
- "@ohif/extension-cornerstone": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.121",
- "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.121",
- "@ohif/extension-default": "3.10.0-beta.121",
- "@ohif/extension-dicom-microscopy": "3.10.0-beta.121",
- "@ohif/extension-dicom-pdf": "3.10.0-beta.121",
- "@ohif/extension-dicom-video": "3.10.0-beta.121",
- "@ohif/extension-test": "3.10.0-beta.121",
- "@ohif/i18n": "3.10.0-beta.121",
- "@ohif/mode-basic-dev-mode": "3.10.0-beta.121",
- "@ohif/mode-longitudinal": "3.10.0-beta.121",
- "@ohif/mode-microscopy": "3.10.0-beta.121",
- "@ohif/mode-test": "3.10.0-beta.121",
- "@ohif/ui": "3.10.0-beta.121",
- "@ohif/ui-next": "3.10.0-beta.121",
+ "@ohif/core": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.125",
+ "@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.125",
+ "@ohif/extension-default": "3.10.0-beta.125",
+ "@ohif/extension-dicom-microscopy": "3.10.0-beta.125",
+ "@ohif/extension-dicom-pdf": "3.10.0-beta.125",
+ "@ohif/extension-dicom-video": "3.10.0-beta.125",
+ "@ohif/extension-test": "3.10.0-beta.125",
+ "@ohif/i18n": "3.10.0-beta.125",
+ "@ohif/mode-basic-dev-mode": "3.10.0-beta.125",
+ "@ohif/mode-longitudinal": "3.10.0-beta.125",
+ "@ohif/mode-microscopy": "3.10.0-beta.125",
+ "@ohif/mode-test": "3.10.0-beta.125",
+ "@ohif/ui": "3.10.0-beta.125",
+ "@ohif/ui-next": "3.10.0-beta.125",
"@svgr/webpack": "^8.1.0",
"@types/react": "^18.3.3",
"classnames": "^2.3.2",
@@ -644,7 +645,7 @@
},
"platform/cli": {
"name": "@ohif/cli",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"bin": {
"ohif-cli": "src/index.js",
},
@@ -668,7 +669,7 @@
},
"platform/core": {
"name": "@ohif/core",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"dcmjs": "*",
@@ -693,15 +694,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.5.0",
- "@ohif/ui": "3.10.0-beta.121",
+ "@cornerstonejs/dicom-image-loader": "^3.7.0",
+ "@ohif/ui": "3.10.0-beta.125",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21",
},
},
"platform/i18n": {
"name": "@ohif/i18n",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next-locize-backend": "^2.0.0",
@@ -726,7 +727,7 @@
},
"platform/ui": {
"name": "@ohif/ui",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@testing-library/react": "^13.1.0",
"browser-detect": "^0.2.28",
@@ -786,13 +787,14 @@
},
"platform/ui-next": {
"name": "@ohif/ui-next",
- "version": "3.10.0-beta.121",
+ "version": "3.10.0-beta.125",
"dependencies": {
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
+ "@radix-ui/react-hover-card": "^1.1.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.0.7",
@@ -1093,7 +1095,7 @@
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
- "@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.5.0", "", { "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.5.0", "@cornerstonejs/tools": "^3.5.0" } }, "sha512-xkixGbCDS2wwAHmggx1mW0ewEjWGntbE5L1svdkmRpGBvTtEkQJbNkNG6dUiHXWZ9mICxwahyiMc7YVDdH3tfA=="],
+ "@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.7.0", "", { "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.7.0", "@cornerstonejs/tools": "^3.7.0" } }, "sha512-7ROFACNjT4W8dukUoKwggHhl+tjg0rdDpYDq8rM6b0c0gO2MgYKbU55t9MoT682yIuebt81jCsAbFSRGVzf45Q=="],
"@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="],
@@ -1105,13 +1107,15 @@
"@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="],
- "@cornerstonejs/core": ["@cornerstonejs/core@3.5.0", "", { "dependencies": { "@kitware/vtk.js": "32.12.0", "comlink": "^4.4.1", "gl-matrix": "^3.4.3", "loglevel": "^1.9.2" } }, "sha512-Ai4dyxZameDz+cp3nBST6KfK1WK/Ic5R6DoTFrBlph4DuK7IUradAx2N9uEydhUJTuF6606mF3VeaAdPG9Hfig=="],
+ "@cornerstonejs/core": ["@cornerstonejs/core@3.7.0", "", { "dependencies": { "@kitware/vtk.js": "32.12.1", "comlink": "^4.4.1", "gl-matrix": "^3.4.3", "loglevel": "^1.9.2" } }, "sha512-eRCovUYO3JvFV3Dzuwc6EWn9bpm+fvp/x6SXvr3MwiO+LRw1o5bmZfeh0+m4+5xzvQJPbjNYiXPPEpLYGQIOqQ=="],
- "@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.5.0", "", { "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.5.0", "dicom-parser": "^1.8.9" } }, "sha512-Jel/an3IHe68sebtRLYB3/aJ1Dggw8D11cWBiBUqzXfxGTlg1SUd3KnvU5OrgNJ+ZpNUEdwCrR3OoiU65hL6UA=="],
+ "@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.7.0", "", { "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", "dicom-parser": "^1.8.9", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.0" } }, "sha512-MSv5A7ZcAp7uVm1ofrTaQppPm1DyOAXN0BdX8a2/fMZ2Ud4OmEtk0aoUlaJ52/hQtUvfdCqfYObBos2oBIMQ1w=="],
- "@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.5.0", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.5.0", "@cornerstonejs/tools": "^3.5.0", "@kitware/vtk.js": "32.12.0" } }, "sha512-S2zqXNKlFURPCq99fPj5CAnBO7chuVk7bIRWmBOBYQL21TnL4rzyvY+Td5pvHBal4sZ8uTs6PW72rrwGCospAQ=="],
+ "@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@3.7.0", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.0", "@cornerstonejs/tools": "^3.7.0", "@kitware/vtk.js": "32.12.1" } }, "sha512-/1l1XsKFYeov+hrMKindJ4n6l4H2zcXFwcO+wtqE4kmfoFhXRN6RSlIXbCRzEZtFCmh0hafg7SUUcXxr0QoClQ=="],
- "@cornerstonejs/tools": ["@cornerstonejs/tools@3.5.0", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.5.0", "@kitware/vtk.js": "32.12.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-wyT7runGpPvY9dMJOUBAtxDBSqabk6Ks/SNjbTGXAIUemAYuonP+33pN4zK/4n/zFfa0gpKFP7LFc461xFK16g=="],
+ "@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.7.0", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.0", "@cornerstonejs/tools": "^3.7.0", "@kitware/vtk.js": "32.12.1" } }, "sha512-MYb8aXE2EwfsyPmhvhTmNdTkCq5pIlMJuVRmT6GuQ4nf2dIhx5AeZ+Ya/TRaNNumV2Fx9/9PetI8+Z8XJLAILw=="],
+
+ "@cornerstonejs/tools": ["@cornerstonejs/tools@3.7.0", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.0", "@kitware/vtk.js": "32.12.1", "@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-72lbmaAm/dnOANAUNc0OK+9aL2pFklaWBLR+kOKn8OeSh1i9RRTSEaP8zWzStaSNAcdhwQ+JOQzcMR40Eo8oNQ=="],
"@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=="],
@@ -1539,7 +1543,7 @@
"@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collapsible": "1.1.2", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-b1oh54x4DMCdGsB4/7ahiSrViXxaBwRPotiZNnYXjLha9vfuURSAZErki6qjDoSIV0eXx5v57XnTGVtGwnfp2g=="],
- "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="],
+ "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw=="],
@@ -1565,6 +1569,8 @@
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA=="],
+ "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ=="],
+
"@radix-ui/react-icons": ["@radix-ui/react-icons@1.3.2", "", { "peerDependencies": { "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } }, "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g=="],
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="],
@@ -1575,7 +1581,7 @@
"@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YXkTAftOIW2Bt3qKH8vYr6n9gCkVrvyvfiTObVjoHVTHnNj26rmvO87IKa3VgtgCjb8FAQ6qOjNViwl+9iIzlg=="],
- "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw=="],
+ "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="],
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw=="],
@@ -1593,7 +1599,7 @@
"@radix-ui/react-slider": ["@radix-ui/react-slider@1.2.2", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA=="],
- "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
"@radix-ui/react-switch": ["@radix-ui/react-switch@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g=="],
@@ -3555,7 +3561,7 @@
"iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
- "itk-wasm": ["itk-wasm@1.0.0-b.173", "", { "dependencies": { "@itk-wasm/dam": "^1.1.1", "@thewtex/zstddec": "^0.2.0", "@types/emscripten": "^1.39.10", "axios": "^1.6.2", "chalk": "^5.3.0", "comlink": "^4.4.1", "commander": "^11.1.0", "fs-extra": "^11.2.0", "glob": "^8.1.0", "markdown-table": "^3.0.3", "mime-types": "^2.1.35", "wasm-feature-detect": "^1.6.1" }, "bin": { "itk-wasm": "src/itk-wasm-cli.js" } }, "sha512-SV2lfZ1mClWuSK/noaZgGj9jhroY4MZu19ci9pIucuyhoGdXrVSmWlPH/JYMDi9RP3BogmQwe9wfFc3X1dcEPg=="],
+ "itk-wasm": ["itk-wasm@1.0.0-b.165", "", { "dependencies": { "@itk-wasm/dam": "^1.1.1", "@thewtex/zstddec": "^0.2.0", "@types/emscripten": "^1.39.10", "axios": "^1.6.2", "comlink": "^4.4.1", "commander": "^11.1.0", "fs-extra": "^11.2.0", "glob": "^8.1.0", "markdown-table": "^3.0.3", "mime-types": "^2.1.35", "wasm-feature-detect": "^1.6.1" }, "bin": { "itk-wasm": "src/itk-wasm-cli.js" } }, "sha512-Rq0+AL2BuRqcFh9r3h69pB3nt84tw/gTxnI6GjIOYw5Bfah9qO+C8hRJpD8aWNBa5o3wNHRAMVlp0pt/L4K8gg=="],
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
@@ -5381,6 +5387,14 @@
"@cornerstonejs/adapters/dcmjs": ["dcmjs@0.29.13", "", { "dependencies": { "@babel/runtime-corejs3": "^7.22.5", "adm-zip": "^0.5.10", "gl-matrix": "^3.1.0", "lodash.clonedeep": "^4.5.0", "loglevelnext": "^3.0.1", "ndarray": "^1.0.19", "pako": "^2.0.4" } }, "sha512-Bf9tKzJNWqk4kbV210N5TLEHDqaZvO3S+MH9vezFAU8WKcG4cR6z4/II3TQVqhLI185eNUL+lhfPCVH1Uu2yTA=="],
+ "@cornerstonejs/core/@kitware/vtk.js": ["@kitware/vtk.js@32.12.1", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-IF8WK7WDu84035ws3HZXz8wys8O81mmeFayYFbdBEfKjgZ4k0EGCK25YCGc6Q47Zxq1iutjs3Zs2xAgGjTgGQg=="],
+
+ "@cornerstonejs/labelmap-interpolation/@kitware/vtk.js": ["@kitware/vtk.js@32.12.1", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-IF8WK7WDu84035ws3HZXz8wys8O81mmeFayYFbdBEfKjgZ4k0EGCK25YCGc6Q47Zxq1iutjs3Zs2xAgGjTgGQg=="],
+
+ "@cornerstonejs/polymorphic-segmentation/@kitware/vtk.js": ["@kitware/vtk.js@32.12.1", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-IF8WK7WDu84035ws3HZXz8wys8O81mmeFayYFbdBEfKjgZ4k0EGCK25YCGc6Q47Zxq1iutjs3Zs2xAgGjTgGQg=="],
+
+ "@cornerstonejs/tools/@kitware/vtk.js": ["@kitware/vtk.js@32.12.1", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-IF8WK7WDu84035ws3HZXz8wys8O81mmeFayYFbdBEfKjgZ4k0EGCK25YCGc6Q47Zxq1iutjs3Zs2xAgGjTgGQg=="],
+
"@cypress/request/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@cypress/xvfb/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@@ -5411,6 +5425,8 @@
"@itk-wasm/dam/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
+ "@itk-wasm/morphological-contour-interpolation/itk-wasm": ["itk-wasm@1.0.0-b.173", "", { "dependencies": { "@itk-wasm/dam": "^1.1.1", "@thewtex/zstddec": "^0.2.0", "@types/emscripten": "^1.39.10", "axios": "^1.6.2", "chalk": "^5.3.0", "comlink": "^4.4.1", "commander": "^11.1.0", "fs-extra": "^11.2.0", "glob": "^8.1.0", "markdown-table": "^3.0.3", "mime-types": "^2.1.35", "wasm-feature-detect": "^1.6.1" }, "bin": { "itk-wasm": "src/itk-wasm-cli.js" } }, "sha512-SV2lfZ1mClWuSK/noaZgGj9jhroY4MZu19ci9pIucuyhoGdXrVSmWlPH/JYMDi9RP3BogmQwe9wfFc3X1dcEPg=="],
+
"@jest/console/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"@jest/console/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
@@ -5527,6 +5543,38 @@
"@ohif/ui/postcss-loader": ["postcss-loader@7.3.4", "", { "dependencies": { "cosmiconfig": "^8.3.5", "jiti": "^1.20.0", "semver": "^7.5.4" }, "peerDependencies": { "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" } }, "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A=="],
+ "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
+
+ "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+
+ "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+
+ "@radix-ui/react-hover-card/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="],
+
+ "@radix-ui/react-hover-card/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="],
+
+ "@radix-ui/react-hover-card/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
+
+ "@radix-ui/react-menu/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw=="],
+
+ "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+
+ "@radix-ui/react-popover/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw=="],
+
+ "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+
+ "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
+
+ "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+
+ "@radix-ui/react-select/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw=="],
+
+ "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+
+ "@radix-ui/react-tooltip/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw=="],
+
+ "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+
"@rollup/plugin-node-resolve/deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"@rollup/plugin-replace/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
@@ -5985,8 +6033,6 @@
"istanbul-lib-source-maps/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
- "itk-wasm/axios": ["axios@1.7.9", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="],
-
"itk-wasm/fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
"itk-wasm/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="],
@@ -6629,6 +6675,14 @@
"@babel/register/make-dir/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
+ "@cornerstonejs/core/@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
+
+ "@cornerstonejs/labelmap-interpolation/@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
+
+ "@cornerstonejs/polymorphic-segmentation/@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
+
+ "@cornerstonejs/tools/@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
+
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
@@ -6645,6 +6699,12 @@
"@itk-wasm/dam/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+ "@itk-wasm/morphological-contour-interpolation/itk-wasm/axios": ["axios@1.7.9", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="],
+
+ "@itk-wasm/morphological-contour-interpolation/itk-wasm/fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
+
+ "@itk-wasm/morphological-contour-interpolation/itk-wasm/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="],
+
"@jest/console/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"@jest/core/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -6719,6 +6779,14 @@
"@ohif/ui/postcss-loader/cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="],
+ "@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="],
+
+ "@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="],
+
+ "@radix-ui/react-select/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="],
+
+ "@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="],
+
"@storybook/builder-webpack5/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@storybook/builder-webpack5/babel-loader/find-cache-dir": ["find-cache-dir@4.0.0", "", { "dependencies": { "common-path-prefix": "^3.0.0", "pkg-dir": "^7.0.0" } }, "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg=="],
@@ -7051,8 +7119,6 @@
"ipfs-unixfs-exporter/p-queue/p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="],
- "itk-wasm/axios/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
-
"itk-wasm/glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
"jake/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -7465,6 +7531,10 @@
"@itk-wasm/dam/tar/fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+ "@itk-wasm/morphological-contour-interpolation/itk-wasm/axios/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
+
+ "@itk-wasm/morphological-contour-interpolation/itk-wasm/glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+
"@lerna/child-process/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"@lerna/create/cosmiconfig/parse-json/lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
@@ -7729,6 +7799,8 @@
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
+ "@itk-wasm/morphological-contour-interpolation/itk-wasm/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
+
"@ohif/ui/babel-loader/find-cache-dir/pkg-dir/find-up": ["find-up@6.3.0", "", { "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" } }, "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw=="],
"@ohif/ui/postcss-loader/cosmiconfig/parse-json/lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
diff --git a/extensions/cornerstone-dicom-pmap/package.json b/extensions/cornerstone-dicom-pmap/package.json
index 69b5835bb..c4a2be5f3 100644
--- a/extensions/cornerstone-dicom-pmap/package.json
+++ b/extensions/cornerstone-dicom-pmap/package.json
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^3.5.0",
- "@cornerstonejs/core": "^3.5.0",
+ "@cornerstonejs/adapters": "^3.7.0",
+ "@cornerstonejs/core": "^3.7.0",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3"
}
diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json
index c14f7bf17..1126c98ff 100644
--- a/extensions/cornerstone-dicom-seg/package.json
+++ b/extensions/cornerstone-dicom-seg/package.json
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^3.5.0",
- "@cornerstonejs/core": "^3.5.0",
+ "@cornerstonejs/adapters": "^3.7.0",
+ "@cornerstonejs/core": "^3.7.0",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3"
}
diff --git a/extensions/cornerstone-dicom-seg/src/getToolbarModule.ts b/extensions/cornerstone-dicom-seg/src/getToolbarModule.ts
index fdd32c1c7..681a5fd95 100644
--- a/extensions/cornerstone-dicom-seg/src/getToolbarModule.ts
+++ b/extensions/cornerstone-dicom-seg/src/getToolbarModule.ts
@@ -42,6 +42,13 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
};
}
+ if (!toolNames) {
+ return {
+ disabled: false,
+ // isActive: true,
+ };
+ }
+
const toolName = toolbarService.getToolNameForButton(button);
if (!toolGroup.hasTool(toolName) && !toolNames) {
diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json
index f5a4a0752..88b321f6b 100644
--- a/extensions/cornerstone-dicom-sr/package.json
+++ b/extensions/cornerstone-dicom-sr/package.json
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^3.5.0",
- "@cornerstonejs/core": "^3.5.0",
- "@cornerstonejs/tools": "^3.5.0",
+ "@cornerstonejs/adapters": "^3.7.0",
+ "@cornerstonejs/core": "^3.7.0",
+ "@cornerstonejs/tools": "^3.7.0",
"classnames": "^2.3.2"
}
}
diff --git a/extensions/cornerstone-dynamic-volume/package.json b/extensions/cornerstone-dynamic-volume/package.json
index f7de52eb4..e7bf2fbb9 100644
--- a/extensions/cornerstone-dynamic-volume/package.json
+++ b/extensions/cornerstone-dynamic-volume/package.json
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/core": "^3.5.0",
- "@cornerstonejs/tools": "^3.5.0",
+ "@cornerstonejs/core": "^3.7.0",
+ "@cornerstonejs/tools": "^3.7.0",
"classnames": "^2.3.2"
}
}
diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json
index bfe325d5d..03b5db339 100644
--- a/extensions/cornerstone/package.json
+++ b/extensions/cornerstone/package.json
@@ -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.5.0",
+ "@cornerstonejs/dicom-image-loader": "^3.7.0",
"@ohif/core": "3.10.0-beta.125",
"@ohif/ui": "3.10.0-beta.125",
"dcmjs": "*",
@@ -51,10 +51,11 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^3.5.0",
- "@cornerstonejs/core": "^3.5.0",
- "@cornerstonejs/polymorphic-segmentation": "^3.5.0",
- "@cornerstonejs/tools": "^3.5.0",
+ "@cornerstonejs/adapters": "^3.7.0",
+ "@cornerstonejs/core": "^3.7.0",
+ "@cornerstonejs/polymorphic-segmentation": "^3.7.0",
+ "@cornerstonejs/labelmap-interpolation": "^3.7.0",
+ "@cornerstonejs/tools": "^3.7.0",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "^1.4.1",
diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts
index df6d9b51c..849335130 100644
--- a/extensions/cornerstone/src/commandsModule.ts
+++ b/extensions/cornerstone/src/commandsModule.ts
@@ -4,6 +4,7 @@ import {
VolumeViewport,
utilities as csUtils,
Types as CoreTypes,
+ cache,
BaseVolumeViewport,
} from '@cornerstonejs/core';
import {
@@ -14,6 +15,7 @@ import {
annotation,
} from '@cornerstonejs/tools';
import * as cornerstoneTools from '@cornerstonejs/tools';
+import * as labelmapInterpolation from '@cornerstonejs/labelmap-interpolation';
import { Types as OhifTypes, utils } from '@ohif/core';
import i18n from '@ohif/i18n';
@@ -31,6 +33,8 @@ import toggleVOISliceSync from './utils/toggleVOISliceSync';
import { usePositionPresentationStore, useSegmentationPresentationStore } from './stores';
import { toolNames } from './initCornerstoneTools';
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
+import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats';
+import { generateSegmentationCSVReport } from './utils/generateSegmentationCSVReport';
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
const toggleSyncFunctions = {
imageSlice: toggleImageSliceSync,
@@ -39,7 +43,6 @@ const toggleSyncFunctions = {
function commandsModule({
servicesManager,
- extensionManager,
commandsManager,
}: OhifTypes.Extensions.ExtensionParams): OhifTypes.Extensions.CommandsModule {
const {
@@ -54,7 +57,9 @@ function commandsModule({
colorbarService,
hangingProtocolService,
syncGroupService,
- } = servicesManager.services;
+ segmentationService,
+ displaySetService,
+ } = servicesManager.services as AppTypes.Services;
const { measurementServiceSource } = this;
@@ -67,7 +72,75 @@ function commandsModule({
return toolGroupService.getToolGroupForViewport(viewport.id);
}
+ function _getActiveSegmentationInfo() {
+ const viewportId = viewportGridService.getActiveViewportId();
+ const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
+ const segmentationId = activeSegmentation?.segmentationId;
+ const activeSegmentIndex = segmentationService.getActiveSegment(viewportId).segmentIndex;
+
+ return {
+ segmentationId,
+ segmentIndex: activeSegmentIndex,
+ };
+ }
+
const actions = {
+ runSegmentBidirectional: async ({ segmentationId, segmentIndex } = {}) => {
+ // Get active segmentation if not specified
+ const targetSegmentation =
+ segmentationId && segmentIndex
+ ? { segmentationId, segmentIndex }
+ : _getActiveSegmentationInfo();
+
+ const { segmentationId: targetId, segmentIndex: targetIndex } = targetSegmentation;
+
+ // Get bidirectional measurement data
+ const bidirectionalData = await cstUtils.segmentation.getSegmentLargestBidirectional({
+ segmentationId: targetId,
+ segmentIndices: [targetIndex],
+ });
+
+ const activeViewportId = viewportGridService.getActiveViewportId();
+
+ // Process each bidirectional measurement
+ bidirectionalData.forEach(measurement => {
+ const { segmentIndex, majorAxis, minorAxis } = measurement;
+
+ // Create annotation
+ const annotation = cornerstoneTools.SegmentBidirectionalTool.hydrate(
+ activeViewportId,
+ [majorAxis, minorAxis],
+ {
+ segmentIndex,
+ segmentationId: targetId,
+ }
+ );
+
+ // Update segmentation stats
+ const updatedSegmentation = updateSegmentBidirectionalStats({
+ segmentationId: targetId,
+ segmentIndex: targetIndex,
+ bidirectionalData: measurement,
+ segmentationService,
+ annotation,
+ });
+
+ // Save changes if needed
+ if (updatedSegmentation) {
+ segmentationService.addOrUpdateSegmentation({
+ segmentationId: targetId,
+ segments: updatedSegmentation.segments,
+ });
+ }
+ });
+ },
+ interpolateLabelmap: () => {
+ const { segmentationId, segmentIndex } = _getActiveSegmentationInfo();
+ labelmapInterpolation.interpolate({
+ segmentationId,
+ segmentIndex,
+ });
+ },
/**
* Generates the selector props for the context menu, specific to
* the cornerstone viewport, and then runs the context menu.
@@ -373,6 +446,44 @@ function commandsModule({
utils.downloadCSVReport(measurementService.getMeasurements(measurementFilter));
},
+ downloadCSVSegmentationReport: ({ segmentationId }) => {
+ const segmentation = segmentationService.getSegmentation(segmentationId);
+ const cachedStats = segmentation.cachedStats;
+
+ const { representationData } = segmentation;
+ const { Labelmap } = representationData;
+ const { referencedImageIds } = Labelmap;
+
+ const firstImageId = referencedImageIds[0];
+
+ // find displaySet for firstImageId
+ const displaySet = displaySetService
+ .getActiveDisplaySets()
+ .find(ds => ds.imageIds?.some(i => i === firstImageId));
+
+ const {
+ SeriesNumber,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ SeriesDate,
+ SeriesTime,
+ SeriesDescription,
+ } = displaySet;
+
+ const additionalInfo = {
+ reference: {
+ SeriesNumber,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ SeriesDate,
+ SeriesTime,
+ SeriesDescription,
+ },
+ };
+
+ generateSegmentationCSVReport(segmentation, additionalInfo);
+ },
+
// Retrieve value commands
getActiveViewportEnabledElement: _getActiveViewportEnabledElement,
@@ -1094,7 +1205,12 @@ function commandsModule({
* @param props.segmentIndex - The index of the segment to activate
*/
setActiveSegmentAndCenterCommand: ({ segmentationId, segmentIndex }) => {
- const { segmentationService } = servicesManager.services;
+ const { segmentationService, viewportGridService } = servicesManager.services;
+ // set both active segmentation and active segment
+ segmentationService.setActiveSegmentation(
+ viewportGridService.getActiveViewportId(),
+ segmentationId
+ );
segmentationService.setActiveSegment(segmentationId, segmentIndex);
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex);
},
@@ -1636,6 +1752,9 @@ function commandsModule({
},
undo: actions.undo,
redo: actions.redo,
+ interpolateLabelmap: actions.interpolateLabelmap,
+ runSegmentBidirectional: actions.runSegmentBidirectional,
+ downloadCSVSegmentationReport: actions.downloadCSVSegmentationReport,
};
return {
diff --git a/extensions/cornerstone/src/customizations/CustomDropdownMenuContent.tsx b/extensions/cornerstone/src/customizations/CustomDropdownMenuContent.tsx
new file mode 100644
index 000000000..56cad0f24
--- /dev/null
+++ b/extensions/cornerstone/src/customizations/CustomDropdownMenuContent.tsx
@@ -0,0 +1,127 @@
+import React from 'react';
+import {
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuPortal,
+ DropdownMenuSeparator,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+ Icons,
+ useSegmentationTableContext,
+ useSegmentationExpanded,
+} from '@ohif/ui-next';
+import { useTranslation } from 'react-i18next';
+import { useSystem } from '@ohif/core/src';
+
+/**
+ * Custom dropdown menu component for segmentation panel that uses context for data
+ */
+export const CustomDropdownMenuContent = () => {
+ const { commandsManager } = useSystem();
+ const { t } = useTranslation('SegmentationTable');
+ const {
+ onSegmentationAdd,
+ onSegmentationRemoveFromViewport,
+ onSegmentationEdit,
+ onSegmentationDelete,
+ exportOptions,
+ activeSegmentation,
+ activeSegmentationId,
+ } = useSegmentationTableContext('CustomDropdownMenu');
+
+ // Try to get segmentation data from expanded context first, fall back to table context
+ let segmentation;
+ let segmentationId;
+ let allowExport = false;
+
+ try {
+ // Try to get from expanded context
+ const context = useSegmentationExpanded();
+ segmentation = context.segmentation;
+ segmentationId = segmentation.segmentationId;
+ } catch (e) {
+ // If not in expanded context, fallback to active segmentation from table context
+ segmentation = activeSegmentation;
+ segmentationId = activeSegmentationId;
+ }
+
+ // Determine if export is allowed for this segmentation
+ if (exportOptions && segmentationId) {
+ const exportOption = exportOptions.find(opt => opt.segmentationId === segmentationId);
+ allowExport = exportOption?.isExportable || false;
+ }
+
+ if (!segmentation || !segmentationId) {
+ return null;
+ }
+
+ const actions = {
+ storeSegmentation: async segmentationId => {
+ commandsManager.run({
+ commandName: 'storeSegmentation',
+ commandOptions: { segmentationId },
+ context: 'CORNERSTONE',
+ });
+ },
+ onSegmentationDownloadRTSS: segmentationId => {
+ commandsManager.run('downloadRTSS', { segmentationId });
+ },
+ onSegmentationDownload: segmentationId => {
+ commandsManager.run('downloadSegmentation', { segmentationId });
+ },
+ downloadCSVSegmentationReport: segmentationId => {
+ commandsManager.run('downloadCSVSegmentationReport', { segmentationId });
+ },
+ };
+
+ return (
+
+ onSegmentationAdd(segmentationId)}>
+
+ {t('Create New Segmentation')}
+
+
+ {t('Manage Current Segmentation')}
+ onSegmentationRemoveFromViewport(segmentationId)}>
+
+ {t('Remove from Viewport')}
+
+ onSegmentationEdit(segmentationId)}>
+
+ {t('Rename')}
+
+
+
+
+ {t('Export & Download')}
+
+
+
+ actions.downloadCSVSegmentationReport(segmentationId)}>
+ {t('Download CSV Report')}
+
+ actions.storeSegmentation(segmentationId)}
+ disabled={!allowExport}
+ >
+ {t('Export DICOM SEG')}
+
+ actions.onSegmentationDownload(segmentationId)}
+ disabled={!allowExport}
+ >
+ {t('Download DICOM SEG')}
+
+
+
+
+
+ onSegmentationDelete(segmentationId)}>
+
+ {t('Delete')}
+
+
+ );
+};
diff --git a/extensions/cornerstone/src/customizations/CustomSegmentStatisticsHeader.tsx b/extensions/cornerstone/src/customizations/CustomSegmentStatisticsHeader.tsx
new file mode 100644
index 000000000..9c089e26d
--- /dev/null
+++ b/extensions/cornerstone/src/customizations/CustomSegmentStatisticsHeader.tsx
@@ -0,0 +1,138 @@
+import * as cornerstoneTools from '@cornerstonejs/tools';
+import React from 'react';
+import { Separator, Button, Tooltip, TooltipTrigger, TooltipContent, Icons } from '@ohif/ui-next';
+import { useTranslation } from 'react-i18next';
+import { roundNumber } from '@ohif/core/src/utils';
+import { useSystem } from '@ohif/core/src';
+
+interface CustomSegmentStatisticsHeaderProps {
+ segmentationId: string;
+ segmentIndex: number;
+}
+
+/**
+ * Custom header component for segment statistics
+ */
+export const CustomSegmentStatisticsHeader = ({
+ segmentationId,
+ segmentIndex,
+}: CustomSegmentStatisticsHeaderProps) => {
+ const { servicesManager, commandsManager } = useSystem();
+ const { segmentationService } = servicesManager.services;
+ const { t } = useTranslation('SegmentationTable');
+
+ const segmentation = segmentationService.getSegmentation(segmentationId);
+ const segment = segmentation.segments[segmentIndex];
+ const cachedStats = segment.cachedStats;
+ const namedStats = cachedStats.namedStats;
+
+ if (!namedStats) {
+ return null;
+ }
+
+ const bidirectional = namedStats.bidirectional;
+
+ if (!bidirectional) {
+ return (
+
+
+
+
+
+
+ {t('Add bidirectional measurement')}
+
+
+
+
+ );
+ }
+
+ const { value, unit } = bidirectional;
+ const maxMajor = value.maxMajor;
+ const maxMinor = value.maxMinor;
+
+ const max = Math.max(maxMajor, maxMinor);
+ const min = Math.min(maxMajor, maxMinor);
+
+ const isVisible = cornerstoneTools.annotation.visibility.isAnnotationVisible(
+ bidirectional.annotationUID
+ );
+
+ return (
+
+
+
+
+ L: {roundNumber(max)} {unit}
+
+
+ W: {roundNumber(min)} {unit}
+
+
+
+
+
+
+
+ {t('Toggle visibility')}
+
+
+
+
+
+ {t('Jump to measurement')}
+
+
+
+
+
+ );
+};
diff --git a/extensions/cornerstone/src/customizations/segmentationPanelCustomization.tsx b/extensions/cornerstone/src/customizations/segmentationPanelCustomization.tsx
index d0649f57f..23236f701 100644
--- a/extensions/cornerstone/src/customizations/segmentationPanelCustomization.tsx
+++ b/extensions/cornerstone/src/customizations/segmentationPanelCustomization.tsx
@@ -1,77 +1,10 @@
-import React from 'react';
-import {
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuPortal,
- DropdownMenuSeparator,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- Icons,
-} from '@ohif/ui-next';
+import { CustomDropdownMenuContent } from './CustomDropdownMenuContent';
+import { CustomSegmentStatisticsHeader } from './CustomSegmentStatisticsHeader';
export default function getSegmentationPanelCustomization({ commandsManager, servicesManager }) {
return {
- 'panelSegmentation.customDropdownMenuContent': ({
- activeSegmentation,
- onSegmentationAdd,
- onSegmentationRemoveFromViewport,
- onSegmentationEdit,
- onSegmentationDelete,
- allowExport,
- storeSegmentation,
- onSegmentationDownload,
- onSegmentationDownloadRTSS,
- t,
- }) => (
-
- onSegmentationAdd(activeSegmentation.segmentationId)}>
-
- {t('Create New Segmentation')}
-
-
- {t('Manage Current Segmentation')}
- onSegmentationRemoveFromViewport(activeSegmentation.segmentationId)}
- >
-
- {t('Remove from Viewport')}
-
- onSegmentationEdit(activeSegmentation.segmentationId)}>
-
- {t('Rename')}
-
-
-
-
- {t('Export & Download')}
-
-
-
- storeSegmentation(activeSegmentation.segmentationId)}
- >
- {t('Export DICOM SEG')}
-
- onSegmentationDownload(activeSegmentation.segmentationId)}
- >
- {t('Download DICOM SEG')}
-
-
-
-
-
- onSegmentationDelete(activeSegmentation.segmentationId)}>
-
- {t('Delete')}
-
-
- ),
+ 'panelSegmentation.customDropdownMenuContent': CustomDropdownMenuContent,
+ 'panelSegmentation.customSegmentStatisticsHeader': CustomSegmentStatisticsHeader,
'panelSegmentation.disableEditing': false,
'panelSegmentation.showAddSegment': true,
'panelSegmentation.onSegmentationAdd': () => {
@@ -81,15 +14,21 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
},
'panelSegmentation.tableMode': 'collapsed',
'panelSegmentation.readableText': {
- lesionStats: 'Lesion Statistics',
- minValue: 'Minimum Value',
- maxValue: 'Maximum Value',
- meanValue: 'Mean Value',
- volume: 'Volume (ml)',
- suvPeak: 'SUV Peak',
- suvMax: 'Maximum SUV',
- suvMaxIJK: 'SUV Max IJK',
- lesionGlyoclysisStats: 'Lesion Glycolysis',
+ // the values will appear in this order
+ min: 'Min Value',
+ minLPS: 'Min Coord',
+ max: 'Max Value',
+ maxLPS: 'Max Coord',
+ mean: 'Mean Value',
+ stdDev: 'Standard Deviation',
+ count: 'Voxel Count',
+ median: 'Median',
+ skewness: 'Skewness',
+ kurtosis: 'Kurtosis',
+ peakValue: 'Peak Value',
+ peakLPS: 'Peak Coord',
+ volume: 'Volume',
+ lesionGlycolysis: 'Lesion Glycolysis',
},
};
}
diff --git a/extensions/cornerstone/src/getToolbarModule.tsx b/extensions/cornerstone/src/getToolbarModule.tsx
index 95fac6c71..7279bbfee 100644
--- a/extensions/cornerstone/src/getToolbarModule.tsx
+++ b/extensions/cornerstone/src/getToolbarModule.tsx
@@ -190,16 +190,16 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
},
},
{
- name: 'evaluate.mpr',
+ name: 'evaluate.displaySetIsReconstructable',
evaluate: ({ viewportId, disabledText = 'Selected viewport is not reconstructable' }) => {
- const { protocol } = hangingProtocolService.getActiveProtocol();
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
- const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
-
- if (!displaySetUIDs?.length) {
+ if (!viewport) {
return;
}
+ const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
+
const displaySets = displaySetUIDs.map(displaySetService.getDisplaySetByUID);
const areReconstructable = displaySets.every(displaySet => {
@@ -210,11 +210,8 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
return getDisabledState(disabledText);
}
- const isMpr = protocol?.id === 'mpr';
-
return {
disabled: false,
- className: utils.getToggledClassName(isMpr),
};
},
},
diff --git a/extensions/cornerstone/src/init.tsx b/extensions/cornerstone/src/init.tsx
index 565844169..706dfe536 100644
--- a/extensions/cornerstone/src/init.tsx
+++ b/extensions/cornerstone/src/init.tsx
@@ -39,6 +39,7 @@ import { useLutPresentationStore } from './stores/useLutPresentationStore';
import { usePositionPresentationStore } from './stores/usePositionPresentationStore';
import { useSegmentationPresentationStore } from './stores/useSegmentationPresentationStore';
import { imageRetrieveMetadataProvider } from '@cornerstonejs/core/utilities';
+import { updateSegmentationStats } from './utils/updateSegmentationStats';
const { registerColormap } = csUtilities.colormap;
@@ -90,6 +91,7 @@ export default async function init({
cornerstoneViewportService,
hangingProtocolService,
viewportGridService,
+ segmentationService,
} = servicesManager.services;
window.services = servicesManager.services;
@@ -150,8 +152,6 @@ export default async function init({
);
});
- // ... existing code ...
-
// add metadata providers
metaData.addProvider(
csUtilities.calibratedPixelSpacingMetadataProvider.get.bind(
@@ -177,6 +177,45 @@ export default async function init({
initCineService(servicesManager);
initStudyPrefetcherService(servicesManager);
+ segmentationService.subscribeDebounced(
+ segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
+ async ({ segmentationId }) => {
+ const segmentation = segmentationService.getSegmentation(segmentationId);
+ const readableText = customizationService.getCustomization('panelSegmentation.readableText');
+ const updatedSegmentation = await updateSegmentationStats({
+ segmentation,
+ segmentationId,
+ readableText,
+ });
+
+ if (updatedSegmentation) {
+ segmentationService.addOrUpdateSegmentation({
+ segmentationId,
+ segments: updatedSegmentation.segments,
+ });
+ }
+
+ // Check for segments with bidirectional measurements and update them
+ if (segmentation) {
+ const segmentIndices = Object.keys(segmentation.segments)
+ .map(index => parseInt(index))
+ .filter(index => index > 0);
+
+ for (const segmentIndex of segmentIndices) {
+ const segment = segmentation.segments[segmentIndex];
+ if (segment?.cachedStats?.namedStats?.bidirectional) {
+ // Run the command to update the bidirectional measurement
+ commandsManager.runCommand('runSegmentBidirectional', {
+ segmentationId,
+ segmentIndex,
+ });
+ }
+ }
+ }
+ },
+ 1000
+ );
+
// When a custom image load is performed, update the relevant viewports
hangingProtocolService.subscribe(
hangingProtocolService.EVENTS.CUSTOM_IMAGE_LOAD_PERFORMED,
@@ -278,35 +317,35 @@ export default async function init({
function initializeWebWorkerProgressHandler(uiNotificationService) {
const activeToasts = new Map();
- eventTarget.addEventListener(EVENTS.WEB_WORKER_PROGRESS, ({ detail }) => {
- const { progress, type, id } = detail;
+ // eventTarget.addEventListener(EVENTS.WEB_WORKER_PROGRESS, ({ detail }) => {
+ // const { progress, type, id } = detail;
- const cacheKey = `${type}-${id}`;
- if (progress === 0 && !activeToasts.has(cacheKey)) {
- const progressPromise = new Promise((resolve, reject) => {
- activeToasts.set(cacheKey, { resolve, reject });
- });
+ // const cacheKey = `${type}-${id}`;
+ // if (progress === 0 && !activeToasts.has(cacheKey)) {
+ // const progressPromise = new Promise((resolve, reject) => {
+ // activeToasts.set(cacheKey, { resolve, reject });
+ // });
- uiNotificationService.show({
- id: cacheKey,
- title: `${type}`,
- message: `${type}: ${progress}%`,
- autoClose: false,
- promise: progressPromise,
- promiseMessages: {
- loading: `Computing...`,
- success: `Completed successfully`,
- error: 'Web Worker failed',
- },
- });
- } else {
- if (progress === 100) {
- const { resolve } = activeToasts.get(cacheKey);
- resolve({ progress, type });
- activeToasts.delete(cacheKey);
- }
- }
- });
+ // uiNotificationService.show({
+ // id: cacheKey,
+ // title: `${type}`,
+ // message: `${type}: ${progress}%`,
+ // autoClose: false,
+ // promise: progressPromise,
+ // promiseMessages: {
+ // loading: `Computing...`,
+ // success: `Completed successfully`,
+ // error: 'Web Worker failed',
+ // },
+ // });
+ // } else {
+ // if (progress === 100) {
+ // const { resolve } = activeToasts.get(cacheKey);
+ // resolve({ progress, type });
+ // activeToasts.delete(cacheKey);
+ // }
+ // }
+ // });
}
/**
diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js
index 423bbf9d9..cd632559e 100644
--- a/extensions/cornerstone/src/initCornerstoneTools.js
+++ b/extensions/cornerstone/src/initCornerstoneTools.js
@@ -1,6 +1,7 @@
import {
PanTool,
WindowLevelTool,
+ SegmentBidirectionalTool,
StackScrollTool,
VolumeRotateTool,
ZoomTool,
@@ -52,8 +53,14 @@ export default function initCornerstoneTools(configuration = {}) {
addons: {
polySeg,
},
+ computeWorker: {
+ autoTerminateOnIdle: {
+ enabled: false,
+ },
+ },
});
addTool(PanTool);
+ addTool(SegmentBidirectionalTool);
addTool(WindowLevelTool);
addTool(StackScrollTool);
addTool(VolumeRotateTool);
@@ -142,6 +149,7 @@ const toolNames = {
OrientationMarker: OrientationMarkerTool.toolName,
WindowLevelRegion: WindowLevelRegionTool.toolName,
PlanarFreehandContourSegmentation: PlanarFreehandContourSegmentationTool.toolName,
+ SegmentBidirectional: SegmentBidirectionalTool.toolName,
};
export { toolNames };
diff --git a/extensions/cornerstone/src/initMeasurementService.ts b/extensions/cornerstone/src/initMeasurementService.ts
index f0d863363..156f1e705 100644
--- a/extensions/cornerstone/src/initMeasurementService.ts
+++ b/extensions/cornerstone/src/initMeasurementService.ts
@@ -34,6 +34,7 @@ const initMeasurementService = (
LivewireContour,
Probe,
UltrasoundDirectional,
+ SegmentBidirectional,
} = measurementServiceMappingsFactory(
measurementService,
displaySetService,
@@ -172,6 +173,14 @@ const initMeasurementService = (
UltrasoundDirectional.toMeasurement
);
+ measurementService.addMapping(
+ csTools3DVer1MeasurementSource,
+ 'SegmentBidirectional',
+ SegmentBidirectional.matchingCriteria,
+ SegmentBidirectional.toAnnotation,
+ SegmentBidirectional.toMeasurement
+ );
+
return csTools3DVer1MeasurementSource;
};
diff --git a/extensions/cornerstone/src/panels/PanelSegmentation.tsx b/extensions/cornerstone/src/panels/PanelSegmentation.tsx
index bb5def037..cee8e5485 100644
--- a/extensions/cornerstone/src/panels/PanelSegmentation.tsx
+++ b/extensions/cornerstone/src/panels/PanelSegmentation.tsx
@@ -15,217 +15,191 @@ export default function PanelSegmentation({
servicesManager,
});
- const handlers = {
- onSegmentationClick: (segmentationId: string) => {
- commandsManager.run('setActiveSegmentation', { segmentationId });
- },
-
- onSegmentAdd: segmentationId => {
- commandsManager.run('addSegment', { segmentationId });
- },
-
- onSegmentClick: (segmentationId, segmentIndex) => {
- commandsManager.run('setActiveSegmentAndCenter', { segmentationId, segmentIndex });
- },
-
- onSegmentEdit: (segmentationId, segmentIndex) => {
- commandsManager.run('editSegmentLabel', { segmentationId, segmentIndex });
- },
-
- onSegmentationEdit: segmentationId => {
- commandsManager.run('editSegmentationLabel', { segmentationId });
- },
-
- onSegmentColorClick: (segmentationId, segmentIndex) => {
- commandsManager.run('editSegmentColor', { segmentationId, segmentIndex });
- },
-
- onSegmentDelete: (segmentationId, segmentIndex) => {
- commandsManager.run('deleteSegment', { segmentationId, segmentIndex });
- },
-
- onToggleSegmentVisibility: (segmentationId, segmentIndex, type) => {
- commandsManager.run('toggleSegmentVisibility', { segmentationId, segmentIndex, type });
- },
-
- onToggleSegmentLock: (segmentationId, segmentIndex) => {
- commandsManager.run('toggleSegmentLock', { segmentationId, segmentIndex });
- },
-
- onToggleSegmentationRepresentationVisibility: (segmentationId, type) => {
- commandsManager.run('toggleSegmentationVisibility', { segmentationId, type });
- },
-
- onSegmentationDownload: segmentationId => {
- commandsManager.run('downloadSegmentation', { segmentationId });
- },
-
- storeSegmentation: async segmentationId => {
- commandsManager.run({
- commandName: 'storeSegmentation',
- commandOptions: { segmentationId },
- context: 'CORNERSTONE',
- });
- },
-
- onSegmentationDownloadRTSS: segmentationId => {
- commandsManager.run('downloadRTSS', { segmentationId });
- },
-
- setStyle: (segmentationId, type, key, value) => {
- commandsManager.run('setSegmentationStyle', { segmentationId, type, key, value });
- },
-
- toggleRenderInactiveSegmentations: () => {
- commandsManager.run('toggleRenderInactiveSegmentations');
- },
-
- onSegmentationRemoveFromViewport: segmentationId => {
- commandsManager.run('removeSegmentationFromViewport', { segmentationId });
- },
-
- onSegmentationDelete: segmentationId => {
- commandsManager.run('deleteSegmentation', { segmentationId });
- },
-
- setFillAlpha: ({ type }, value) => {
- commandsManager.run('setFillAlpha', { type, value });
- },
-
- setOutlineWidth: ({ type }, value) => {
- commandsManager.run('setOutlineWidth', { type, value });
- },
-
- setRenderFill: ({ type }, value) => {
- commandsManager.run('setRenderFill', { type, value });
- },
-
- setRenderOutline: ({ type }, value) => {
- commandsManager.run('setRenderOutline', { type, value });
- },
-
- setFillAlphaInactive: ({ type }, value) => {
- commandsManager.run('setFillAlphaInactive', { type, value });
- },
-
- getRenderInactiveSegmentations: () => {
- return commandsManager.run('getRenderInactiveSegmentations');
- },
- };
-
+ // Extract customization options
const segmentationTableMode = customizationService.getCustomization(
'panelSegmentation.tableMode'
- );
-
- // custom onSegmentationAdd if provided
+ ) as unknown as string;
const onSegmentationAdd = customizationService.getCustomization(
'panelSegmentation.onSegmentationAdd'
);
-
const disableEditing = customizationService.getCustomization('panelSegmentation.disableEditing');
const showAddSegment = customizationService.getCustomization('panelSegmentation.showAddSegment');
const CustomDropdownMenuContent = customizationService.getCustomization(
'panelSegmentation.customDropdownMenuContent'
);
+ const CustomSegmentStatisticsHeader = customizationService.getCustomization(
+ 'panelSegmentation.customSegmentStatisticsHeader'
+ );
+
+ // Create handlers object for all command runs
+ const handlers = {
+ onSegmentationClick: (segmentationId: string) => {
+ commandsManager.run('setActiveSegmentation', { segmentationId });
+ },
+ onSegmentAdd: segmentationId => {
+ commandsManager.run('addSegment', { segmentationId });
+ },
+ onSegmentClick: (segmentationId, segmentIndex) => {
+ commandsManager.run('setActiveSegmentAndCenter', { segmentationId, segmentIndex });
+ },
+ onSegmentEdit: (segmentationId, segmentIndex) => {
+ commandsManager.run('editSegmentLabel', { segmentationId, segmentIndex });
+ },
+ onSegmentationEdit: segmentationId => {
+ commandsManager.run('editSegmentationLabel', { segmentationId });
+ },
+ onSegmentColorClick: (segmentationId, segmentIndex) => {
+ commandsManager.run('editSegmentColor', { segmentationId, segmentIndex });
+ },
+ onSegmentDelete: (segmentationId, segmentIndex) => {
+ commandsManager.run('deleteSegment', { segmentationId, segmentIndex });
+ },
+ onToggleSegmentVisibility: (segmentationId, segmentIndex, type) => {
+ commandsManager.run('toggleSegmentVisibility', { segmentationId, segmentIndex, type });
+ },
+ onToggleSegmentLock: (segmentationId, segmentIndex) => {
+ commandsManager.run('toggleSegmentLock', { segmentationId, segmentIndex });
+ },
+ onToggleSegmentationRepresentationVisibility: (segmentationId, type) => {
+ commandsManager.run('toggleSegmentationVisibility', { segmentationId, type });
+ },
+ onSegmentationDownload: segmentationId => {
+ commandsManager.run('downloadSegmentation', { segmentationId });
+ },
+ setStyle: (segmentationId, type, key, value) => {
+ commandsManager.run('setSegmentationStyle', { segmentationId, type, key, value });
+ },
+ toggleRenderInactiveSegmentations: () => {
+ commandsManager.run('toggleRenderInactiveSegmentations');
+ },
+ onSegmentationRemoveFromViewport: segmentationId => {
+ commandsManager.run('removeSegmentationFromViewport', { segmentationId });
+ },
+ onSegmentationDelete: segmentationId => {
+ commandsManager.run('deleteSegmentation', { segmentationId });
+ },
+ setFillAlpha: ({ type }, value) => {
+ commandsManager.run('setFillAlpha', { type, value });
+ },
+ setOutlineWidth: ({ type }, value) => {
+ commandsManager.run('setOutlineWidth', { type, value });
+ },
+ setRenderFill: ({ type }, value) => {
+ commandsManager.run('setRenderFill', { type, value });
+ },
+ setRenderOutline: ({ type }, value) => {
+ commandsManager.run('setRenderOutline', { type, value });
+ },
+ setFillAlphaInactive: ({ type }, value) => {
+ commandsManager.run('setFillAlphaInactive', { type, value });
+ },
+ getRenderInactiveSegmentations: () => {
+ return commandsManager.run('getRenderInactiveSegmentations');
+ },
+ };
+
+ // Generate export options
const exportOptions = segmentationsWithRepresentations.map(({ segmentation }) => {
const { representationData, segmentationId } = segmentation;
const { Labelmap } = representationData;
if (!Labelmap) {
- return {
- segmentationId,
- isExportable: true,
- };
+ return { segmentationId, isExportable: true };
}
const referencedImageIds = Labelmap.referencedImageIds;
const firstImageId = referencedImageIds[0];
-
const instance = metaData.get('instance', firstImageId);
if (!instance) {
- return {
- segmentationId,
- isExportable: false,
- };
+ return { segmentationId, isExportable: false };
}
const SOPInstanceUID = instance.SOPInstanceUID || instance.SopInstanceUID;
const SeriesInstanceUID = instance.SeriesInstanceUID;
-
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
SeriesInstanceUID
);
- const isExportable = displaySet?.isReconstructable;
-
return {
segmentationId,
- isExportable,
+ isExportable: displaySet?.isReconstructable,
};
});
- return (
- <>
-
- {children}
-
-
+ // Common props for SegmentationTable
+ const tableProps = {
+ disabled,
+ data: segmentationsWithRepresentations,
+ mode: segmentationTableMode,
+ title: 'Segmentations',
+ exportOptions,
+ disableEditing,
+ onSegmentationAdd,
+ showAddSegment,
+ renderInactiveSegmentations: handlers.getRenderInactiveSegmentations(),
+ ...handlers,
+ };
- {segmentationTableMode === 'collapsed' ? (
-
-
+ const renderSegments = () => {
+ return (
+
+
+
+
+
+
+ );
+ };
+
+ // Render content based on mode
+ const renderModeContent = () => {
+ if (tableProps.mode === 'collapsed') {
+ return (
+
+
+
-
+
+
+
+
+
-
-
- ) : (
-
-
+ {renderSegments()}
+
+
+ );
+ }
+
+ return (
+ <>
+
+
+
-
- {/* */}
-
-
- )}
-
- >
+
+
+
+
+
+
+
+ {renderSegments()}
+
+
+ >
+ );
+ };
+
+ return (
+
+ {children}
+
+
+ {renderModeContent()}
+
);
}
diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
index 0541e6c7c..8cac0c542 100644
--- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
+++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
@@ -92,6 +92,7 @@ class SegmentationService extends PubSubService {
};
private _segmentationIdToColorLUTIndexMap: Map;
+ private _segmentationGroupStatsMap: Map;
readonly servicesManager: AppTypes.ServicesManager;
highlightIntervalId = null;
readonly EVENTS = EVENTS;
@@ -102,6 +103,8 @@ class SegmentationService extends PubSubService {
this._segmentationIdToColorLUTIndexMap = new Map();
this.servicesManager = servicesManager;
+
+ this._segmentationGroupStatsMap = new Map();
}
public onModeEnter(): void {
@@ -1058,6 +1061,33 @@ class SegmentationService extends PubSubService {
public getRenderInactiveSegmentations(viewportId: string): boolean {
return cstSegmentation.config.style.getRenderInactiveSegmentations(viewportId);
}
+ /**
+ * Sets statistics for a group of segmentations
+ * @param segmentationIds - Array of segmentation IDs that form the group
+ * @param stats - Statistics object containing metrics for the segmentation group
+ *
+ * @remarks
+ * This method stores statistical data for a group of related segmentations.
+ * The stats are stored using a composite key created from the sorted and joined
+ */
+ public setSegmentationGroupStats(segmentationIds: string[], stats: any) {
+ const groupId = this.getGroupId(segmentationIds);
+ this._segmentationGroupStatsMap.set(groupId, stats);
+ }
+
+ /**
+ * Gets statistics for a group of segmentations
+ * @param segmentationIds - Array of segmentation IDs that form the group
+ * @returns The stored statistics object for the segmentation group if found, undefined otherwise
+ */
+ public getSegmentationGroupStats(segmentationIds: string[]) {
+ const groupId = this.getGroupId(segmentationIds);
+ return this._segmentationGroupStatsMap.get(groupId);
+ }
+
+ private getGroupId(segmentationIds: string[]) {
+ return segmentationIds.sort().join(',');
+ }
/**
* Toggles the visibility of a segmentation in the state, and broadcasts the event.
diff --git a/extensions/cornerstone/src/utils/generateSegmentationCSVReport.ts b/extensions/cornerstone/src/utils/generateSegmentationCSVReport.ts
new file mode 100644
index 000000000..023f46df6
--- /dev/null
+++ b/extensions/cornerstone/src/utils/generateSegmentationCSVReport.ts
@@ -0,0 +1,139 @@
+export function generateSegmentationCSVReport(
+ segmentationData,
+ info: {
+ reference: {
+ SeriesNumber: string;
+ SeriesInstanceUID: string;
+ StudyInstanceUID: string;
+ SeriesDate: string;
+ SeriesTime: string;
+ SeriesDescription: string;
+ };
+ }
+) {
+ // Initialize the rows for our CSV
+ const csvRows = [];
+
+ // Add segmentation-level information
+ csvRows.push(['Segmentation ID', segmentationData.segmentationId || '']);
+ csvRows.push(['Segmentation Label', segmentationData.label || '']);
+
+ csvRows.push([]);
+
+ const additionalInfo = info.reference;
+ // Add reference information
+ const referenceKeys = [
+ ['Series Number', additionalInfo.SeriesNumber],
+ ['Series Instance UID', additionalInfo.SeriesInstanceUID],
+ ['Study Instance UID', additionalInfo.StudyInstanceUID],
+ ['Series Date', additionalInfo.SeriesDate],
+ ['Series Time', additionalInfo.SeriesTime],
+ ['Series Description', additionalInfo.SeriesDescription],
+ ];
+
+ referenceKeys.forEach(([key, value]) => {
+ if (value) {
+ csvRows.push([`reference ${key}`, value]);
+ }
+ });
+
+ // Add a blank row for separation
+ csvRows.push([]);
+
+ csvRows.push(['Segments Statistics']);
+
+ // Add segment information in columns
+ if (segmentationData.segments) {
+ // First row: Segment headers
+ const segmentHeaderRow = ['Label'];
+ for (const segmentId in segmentationData.segments) {
+ const segment = segmentationData.segments[segmentId];
+ segmentHeaderRow.push(`${segment.label || ''}`);
+ }
+ csvRows.push(segmentHeaderRow);
+
+ // Add segment properties
+ csvRows.push([
+ 'Segment Index',
+ ...Object.values(segmentationData.segments).map(s => s.segmentIndex || ''),
+ ]);
+ csvRows.push([
+ 'Locked',
+ ...Object.values(segmentationData.segments).map(s => (s.locked ? 'Yes' : 'No')),
+ ]);
+ csvRows.push([
+ 'Active',
+ ...Object.values(segmentationData.segments).map(s => (s.active ? 'Yes' : 'No')),
+ ]);
+
+ // Add segment statistics
+ // First, collect all unique statistics across all segments
+ const allStats = new Set();
+ for (const segment of Object.values(segmentationData.segments)) {
+ if (segment.cachedStats && segment.cachedStats.namedStats) {
+ for (const statKey in segment.cachedStats.namedStats) {
+ const stat = segment.cachedStats.namedStats[statKey];
+ const statLabel = stat.label || stat.name;
+ const statUnit = stat.unit ? ` (${stat.unit})` : '';
+ allStats.add(`${statLabel}${statUnit}`);
+ }
+ }
+ }
+
+ // Then create a row for each statistic
+ for (const statName of allStats) {
+ const statRow = [statName];
+
+ for (const segment of Object.values(segmentationData.segments)) {
+ let statValue = '';
+
+ if (segment.cachedStats && segment.cachedStats.namedStats) {
+ for (const statKey in segment.cachedStats.namedStats) {
+ const stat = segment.cachedStats.namedStats[statKey];
+ const currentStatName = `${stat.label || stat.name}${stat.unit ? ` (${stat.unit})` : ''}`;
+
+ if (currentStatName === statName) {
+ statValue = stat.value !== undefined ? stat.value : '';
+ break;
+ }
+ }
+ }
+
+ statRow.push(statValue);
+ }
+
+ csvRows.push(statRow);
+ }
+ }
+
+ // Convert to CSV string
+ let csvString = '';
+ for (const row of csvRows) {
+ const formattedRow = row.map(cell => {
+ // Handle values that need to be quoted (contain commas, quotes, or newlines)
+ const cellValue = cell !== undefined && cell !== null ? cell.toString() : '';
+ if (cellValue.includes(',') || cellValue.includes('"') || cellValue.includes('\n')) {
+ // Escape quotes and wrap in quotes
+ return '"' + cellValue.replace(/"/g, '""') + '"';
+ }
+ return cellValue;
+ });
+ csvString += formattedRow.join(',') + '\n';
+ }
+
+ // Create a download link and trigger the download
+ const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+
+ link.setAttribute('href', url);
+ link.setAttribute(
+ 'download',
+ `${segmentationData.label || 'Segmentation'}_Report_${new Date().toISOString().split('T')[0]}.csv`
+ );
+ link.style.visibility = 'hidden';
+
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+}
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/SegmentBidirectional.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/SegmentBidirectional.ts
new file mode 100644
index 000000000..312b5e793
--- /dev/null
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/SegmentBidirectional.ts
@@ -0,0 +1,185 @@
+import SUPPORTED_TOOLS from './constants/supportedTools';
+import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
+import { utils } from '@ohif/core';
+import { getDisplayUnit } from './utils';
+import { getIsLocked } from './utils/getIsLocked';
+import { getIsVisible } from './utils/getIsVisible';
+
+const SegmentBidirectional = {
+ toAnnotation: measurement => {},
+ toMeasurement: (
+ csToolsEventDetail,
+ displaySetService,
+ cornerstoneViewportService,
+ getValueTypeFromToolType,
+ customizationService
+ ) => {
+ const { annotation } = csToolsEventDetail;
+ const { metadata, data, annotationUID } = annotation;
+ debugger;
+ const isLocked = getIsLocked(annotationUID);
+ const isVisible = getIsVisible(annotationUID);
+
+ if (!metadata || !data) {
+ console.debug('SegmentBidirectional tool: Missing metadata or data');
+ return null;
+ }
+
+ const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
+ const validToolType = SUPPORTED_TOOLS.includes(toolName);
+
+ if (!validToolType) {
+ throw new Error('Tool not supported');
+ }
+
+ const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes(
+ referencedImageId,
+ displaySetService,
+ annotation
+ );
+
+ let displaySet;
+
+ if (SOPInstanceUID) {
+ displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
+ SOPInstanceUID,
+ SeriesInstanceUID
+ );
+ } else {
+ displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID)[0];
+ }
+
+ const { points, textBox } = data.handles;
+
+ const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
+
+ const displayText = getDisplayText(mappedAnnotations, displaySet);
+ const getReport = () =>
+ _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService);
+
+ return {
+ uid: annotationUID,
+ SOPInstanceUID,
+ FrameOfReferenceUID,
+ points,
+ textBox,
+ isLocked,
+ isVisible,
+ metadata,
+ referenceSeriesUID: SeriesInstanceUID,
+ referenceStudyUID: StudyInstanceUID,
+ referencedImageId,
+ frameNumber: mappedAnnotations[0]?.frameNumber || 1,
+ toolName: metadata.toolName,
+ displaySetInstanceUID: displaySet.displaySetInstanceUID,
+ label: data.label,
+ displayText: displayText,
+ data: data.cachedStats,
+ type: getValueTypeFromToolType(toolName),
+ getReport,
+ };
+ },
+};
+
+function getMappedAnnotations(annotation, displaySetService) {
+ const { metadata, data } = annotation;
+ const { cachedStats } = data;
+ const { referencedImageId } = metadata;
+ const targets = Object.keys(cachedStats);
+
+ if (!targets.length) {
+ return [];
+ }
+
+ const annotations = [];
+ Object.keys(cachedStats).forEach(targetId => {
+ const targetStats = cachedStats[targetId];
+
+ const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = getSOPInstanceAttributes(
+ referencedImageId,
+ displaySetService,
+ annotation
+ );
+
+ const displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID)[0];
+
+ const { SeriesNumber } = displaySet;
+ const { length, width, unit } = targetStats;
+
+ annotations.push({
+ SeriesInstanceUID,
+ SOPInstanceUID,
+ SeriesNumber,
+ frameNumber,
+ unit,
+ length,
+ width,
+ });
+ });
+
+ return annotations;
+}
+
+function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) {
+ const columns = [];
+ const values = [];
+
+ // Add Type
+ columns.push('AnnotationType');
+ values.push('Cornerstone:SegmentBidirectional');
+
+ mappedAnnotations.forEach(annotation => {
+ const { length, width, unit } = annotation;
+ columns.push(`Length`, `Width`, 'Unit');
+ values.push(length, width, unit);
+ });
+
+ if (FrameOfReferenceUID) {
+ columns.push('FrameOfReferenceUID');
+ values.push(FrameOfReferenceUID);
+ }
+
+ if (points) {
+ columns.push('points');
+ values.push(points.map(p => p.join(' ')).join(';'));
+ }
+
+ return {
+ columns,
+ values,
+ };
+}
+
+function getDisplayText(mappedAnnotations, displaySet) {
+ const displayText = {
+ primary: [],
+ secondary: [],
+ };
+
+ if (!mappedAnnotations || !mappedAnnotations.length) {
+ return displayText;
+ }
+
+ // Area is the same for all series
+ const { length, width, unit, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
+ const roundedLength = utils.roundNumber(length, 2);
+ const roundedWidth = utils.roundNumber(width, 2);
+
+ const instance = displaySet.instances.find(image => image.SOPInstanceUID === SOPInstanceUID);
+
+ let InstanceNumber;
+ if (instance) {
+ InstanceNumber = instance.InstanceNumber;
+ }
+
+ const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
+ const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
+
+ displayText.primary.push(`L: ${roundedLength} ${getDisplayUnit(unit)}`);
+ displayText.primary.push(`W: ${roundedWidth} ${getDisplayUnit(unit)}`);
+ displayText.secondary.push(`S: ${SeriesNumber}${instanceText}${frameText}`);
+
+ return displayText;
+}
+
+export default SegmentBidirectional;
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js
index a3f9c832c..6f6c19232 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js
@@ -13,6 +13,7 @@ const supportedTools = [
'LivewireContour',
'UltrasoundDirectionalTool',
'SCOORD3DPoint',
+ 'SegmentBidirectional',
];
export default supportedTools;
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts
index 041c767b6..ff944ab64 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts
@@ -12,6 +12,7 @@ import SplineROI from './SplineROI';
import LivewireContour from './LivewireContour';
import Probe from './Probe';
import UltrasoundDirectional from './UltrasoundDirectional';
+import SegmentBidirectional from './SegmentBidirectional';
const measurementServiceMappingsFactory = (
measurementService: MeasurementService,
@@ -48,6 +49,7 @@ const measurementServiceMappingsFactory = (
LivewireContour: POLYLINE,
Probe: POINT,
UltrasoundDirectional: POLYLINE,
+ SegmentBidirectional: BIDIRECTIONAL,
};
return TOOL_TYPE_TO_VALUE_TYPE[toolType];
@@ -94,6 +96,27 @@ const measurementServiceMappingsFactory = (
},
],
},
+ SegmentBidirectional: {
+ toAnnotation: SegmentBidirectional.toAnnotation,
+ toMeasurement: csToolsAnnotation =>
+ SegmentBidirectional.toMeasurement(
+ csToolsAnnotation,
+ displaySetService,
+ cornerstoneViewportService,
+ _getValueTypeFromToolType,
+ customizationService
+ ),
+ matchingCriteria: [
+ {
+ valueType: MeasurementService.VALUE_TYPES.POLYLINE,
+ points: 2,
+ },
+ {
+ valueType: MeasurementService.VALUE_TYPES.POLYLINE,
+ points: 2,
+ },
+ ],
+ },
EllipticalROI: {
toAnnotation: EllipticalROI.toAnnotation,
toMeasurement: csToolsAnnotation =>
diff --git a/extensions/cornerstone/src/utils/updateSegmentationStats.ts b/extensions/cornerstone/src/utils/updateSegmentationStats.ts
new file mode 100644
index 000000000..936c822b4
--- /dev/null
+++ b/extensions/cornerstone/src/utils/updateSegmentationStats.ts
@@ -0,0 +1,185 @@
+import * as cornerstoneTools from '@cornerstonejs/tools';
+
+interface BidirectionalAxis {
+ length: number;
+ // Add other axis properties as needed
+}
+
+interface BidirectionalData {
+ majorAxis: BidirectionalAxis;
+ minorAxis: BidirectionalAxis;
+}
+
+/**
+ * Updates the statistics for a segmentation by calculating stats for each segment
+ * and storing them in the segment's cachedStats property
+ *
+ * @param segmentation - The segmentation object containing segments to update stats for
+ * @param segmentationId - The ID of the segmentation
+ * @returns The updated segmentation object with new stats, or null if no updates were made
+ */
+export async function updateSegmentationStats({
+ segmentation,
+ segmentationId,
+ readableText,
+}: {
+ segmentation: any;
+ segmentationId: string;
+ readableText: any;
+}): Promise {
+ if (!segmentation) {
+ console.debug('No segmentation found for id:', segmentationId);
+ return null;
+ }
+
+ const segmentIndices = Object.keys(segmentation.segments)
+ .map(index => parseInt(index))
+ .filter(index => index > 0); // Filter out segment 0 which is typically background
+
+ if (segmentIndices.length === 0) {
+ console.debug('No segments found in segmentation:', segmentationId);
+ return null;
+ }
+
+ const stats = await cornerstoneTools.utilities.segmentation.getStatistics({
+ segmentationId,
+ segmentIndices,
+ mode: 'individual',
+ });
+
+ if (!stats) {
+ return null;
+ }
+
+ const updatedSegmentation = { ...segmentation };
+ let hasUpdates = false;
+
+ // Loop through each segment's stats
+ Object.entries(stats).forEach(([segmentIndex, segmentStats]) => {
+ const index = parseInt(segmentIndex);
+
+ if (!updatedSegmentation.segments[index].cachedStats) {
+ updatedSegmentation.segments[index].cachedStats = {};
+ hasUpdates = true;
+ }
+
+ // Get existing namedStats or initialize if not present
+ const namedStats = updatedSegmentation.segments[index].cachedStats.namedStats || {};
+
+ if (segmentStats.array) {
+ segmentStats.array.forEach(stat => {
+ // only gather stats that are in the readableText
+ if (!readableText[stat.name]) {
+ return;
+ }
+
+ if (stat && stat.name) {
+ namedStats[stat.name] = {
+ name: stat.name,
+ label: readableText[stat.name],
+ value: stat.value,
+ unit: stat.unit,
+ order: Object.keys(readableText).indexOf(stat.name),
+ };
+ }
+ });
+
+ if (readableText.volume) {
+ // Add volume if it exists but isn't in the array
+ if (segmentStats.volume && !namedStats.volume) {
+ namedStats.volume = {
+ name: 'volume',
+ label: 'Volume',
+ value: segmentStats.volume.value,
+ unit: segmentStats.volume.unit,
+ order: Object.keys(readableText).indexOf('volume'),
+ };
+ }
+ }
+
+ // Update the segment's cachedStats with namedStats
+ updatedSegmentation.segments[index].cachedStats.namedStats = namedStats;
+ hasUpdates = true;
+ }
+ });
+
+ return hasUpdates ? updatedSegmentation : null;
+}
+
+/**
+ * Updates a segment's statistics with bidirectional measurement data
+ *
+ * @param segmentationId - The ID of the segmentation
+ * @param segmentIndex - The index of the segment to update
+ * @param bidirectionalData - The bidirectional measurement data to add
+ * @param segmentationService - The segmentation service to use for updating the segment
+ * @returns Whether the update was successful
+ */
+export function updateSegmentBidirectionalStats({
+ segmentationId,
+ segmentIndex,
+ bidirectionalData,
+ segmentationService,
+ annotation,
+}: {
+ segmentationId: string;
+ segmentIndex: number;
+ bidirectionalData: BidirectionalData;
+ segmentationService: AppTypes.SegmentationService;
+ annotation: any;
+}) {
+ if (!segmentationId || segmentIndex === undefined || !bidirectionalData) {
+ console.debug('Missing required data for bidirectional stats update');
+ return null;
+ }
+
+ const segmentation = segmentationService.getSegmentation(segmentationId);
+ if (!segmentation || !segmentation.segments[segmentIndex]) {
+ console.debug('Segment not found:', segmentIndex, 'in segmentation:', segmentationId);
+ return null;
+ }
+
+ const updatedSegmentation = { ...segmentation };
+ const segment = updatedSegmentation.segments[segmentIndex];
+
+ if (!segment.cachedStats) {
+ segment.cachedStats = { namedStats: {} };
+ }
+
+ if (!segment.cachedStats.namedStats) {
+ segment.cachedStats.namedStats = {};
+ }
+
+ const { majorAxis, minorAxis, maxMajor, maxMinor } = bidirectionalData;
+ if (!majorAxis || !minorAxis) {
+ console.debug('Missing major or minor axis data');
+ return null;
+ }
+
+ let hasUpdates = false;
+ const namedStats = segment.cachedStats.namedStats;
+
+ // Only calculate and update if we have valid measurements
+ if (maxMajor > 0 && maxMinor > 0) {
+ namedStats.bidirectional = {
+ name: 'bidirectional',
+ label: 'Bidirectional',
+ annotationUID: annotation.annotationUID,
+ value: {
+ maxMajor,
+ maxMinor,
+ majorAxis,
+ minorAxis,
+ },
+ unit: 'mm',
+ };
+
+ hasUpdates = true;
+ }
+
+ if (hasUpdates) {
+ return updatedSegmentation;
+ }
+
+ return null;
+}
diff --git a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx
index 35b32ec9a..fb4c32034 100644
--- a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx
+++ b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx
@@ -1,5 +1,4 @@
import React, { useCallback } from 'react';
-import PropTypes from 'prop-types';
//
import PanelStudyBrowser from './StudyBrowser/PanelStudyBrowser';
import getImageSrcFromImageId from './getImageSrcFromImageId';
diff --git a/extensions/default/src/Toolbar/ToolBoxWrapper.tsx b/extensions/default/src/Toolbar/ToolBoxWrapper.tsx
index 7a292299c..abc51051b 100644
--- a/extensions/default/src/Toolbar/ToolBoxWrapper.tsx
+++ b/extensions/default/src/Toolbar/ToolBoxWrapper.tsx
@@ -26,7 +26,7 @@ export function ToolBoxButtonGroupWrapper({ groupId, buttonSection, ...props })
{...item}
key={item.id}
size="small"
- className={props.disabled && 'text-primary'}
+ className={item.disabled && 'text-foreground/70'}
onInteraction={event => {
onInteraction?.({
event,
@@ -49,7 +49,7 @@ export function ToolBoxButtonWrapper({ onInteraction, options, ...props }) {
{...props}
id={props.id}
size="small"
- className={classNames(props.disabled && 'text-primary')}
+ className={classNames(props.disabled && 'text-foreground/70')}
onInteraction={event => {
onInteraction?.({
event,
diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json
index df54ba8f1..f7622ac56 100644
--- a/extensions/measurement-tracking/package.json
+++ b/extensions/measurement-tracking/package.json
@@ -32,8 +32,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
- "@cornerstonejs/core": "^3.5.0",
- "@cornerstonejs/tools": "^3.5.0",
+ "@cornerstonejs/core": "^3.7.0",
+ "@cornerstonejs/tools": "^3.7.0",
"@ohif/core": "3.10.0-beta.125",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.125",
"@ohif/extension-default": "3.10.0-beta.125",
diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx
index 962384ae1..afadb3e56 100644
--- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx
+++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx
@@ -1,5 +1,4 @@
import React, { useCallback } from 'react';
-import PropTypes from 'prop-types';
//
import PanelStudyBrowserTracking from './PanelStudyBrowserTracking';
import getImageSrcFromImageId from './getImageSrcFromImageId';
diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx
index 315cbef83..9457adba6 100644
--- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx
+++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx
@@ -11,11 +11,9 @@ export default function PanelRoiThresholdSegmentation({
const { segmentationsWithRepresentations: segmentationsInfo } =
useActiveViewportSegmentationRepresentations({ servicesManager });
- useEffect(() => {
- const segmentationIds = segmentationsInfo.map(
- segmentationInfo => segmentationInfo.segmentation.segmentationId
- );
+ const segmentationIds = segmentationsInfo?.map(info => info.segmentation.segmentationId) || [];
+ useEffect(() => {
const initialRun = async () => {
for (const segmentationId of segmentationIds) {
await handleROIThresholding({
@@ -54,10 +52,9 @@ export default function PanelRoiThresholdSegmentation({
}, [commandsManager, segmentationService]);
// Find the first segmentation with a TMTV value since all of them have the same value
- const tmtvSegmentation = segmentationsInfo.find(
- info => info.segmentation.cachedStats?.tmtv !== undefined
- );
- const tmtvValue = tmtvSegmentation?.segmentation.cachedStats?.tmtv;
+ const stats = segmentationService.getSegmentationGroupStats(segmentationIds);
+ const tmtvValue = stats?.tmtv;
+ const tlgValue = stats?.tlg;
return (
@@ -68,6 +65,7 @@ export default function PanelRoiThresholdSegmentation({
{'TMTV:'}
{`${tmtvValue?.toFixed(3)} mL`}
+
{`${tlgValue?.toFixed(3)} mL`}
) : null}
diff --git a/extensions/tmtv/src/commandsModule.ts b/extensions/tmtv/src/commandsModule.ts
index 430111ef7..f8555e802 100644
--- a/extensions/tmtv/src/commandsModule.ts
+++ b/extensions/tmtv/src/commandsModule.ts
@@ -24,20 +24,7 @@ const ROI_THRESHOLD_MANUAL_TOOL_IDS = [
const workerManager = getWebWorkerManager();
-const options = {
- maxWorkerInstances: 1,
- autoTerminateOnIdle: {
- enabled: true,
- idleTimeThreshold: 3000,
- },
-};
-
// Register the task
-const workerFn = () => {
- return new Worker(new URL('./utils/calculateSUVPeakWorker.js', import.meta.url), {
- name: 'suv-peak-worker', // name used by the browser to name the worker
- });
-};
function getVolumesFromSegmentation(segmentationId) {
const csSegmentation = csTools.segmentation.state.getSegmentation(segmentationId);
@@ -262,153 +249,22 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
{ overwrite: true, segmentIndex, segmentationId }
);
},
- calculateSuvPeak: async ({ segmentationId, segmentIndex }) => {
- const segmentation = segmentationService.getSegmentation(segmentationId);
-
- const { representationData } = segmentation;
- const { volumeId, referencedVolumeId } = representationData[
- SegmentationRepresentations.Labelmap
- ] as csTools.Types.LabelmapToolOperationDataVolume;
-
- const labelmap = cs.cache.getVolume(volumeId);
- const referencedVolume = cs.cache.getVolume(referencedVolumeId);
-
- // if we put it in the top, it will appear in other modes
- workerManager.registerWorker('suv-peak-worker', workerFn, options);
-
- const annotationUIDs = _getAnnotationsSelectedByToolNames(ROI_THRESHOLD_MANUAL_TOOL_IDS);
-
- const annotations = annotationUIDs.map(annotationUID =>
- csTools.annotation.state.getAnnotation(annotationUID)
- );
-
- const labelmapProps = {
- dimensions: labelmap.dimensions,
- origin: labelmap.origin,
- direction: labelmap.direction,
- spacing: labelmap.spacing,
- metadata: labelmap.metadata,
- scalarData: labelmap.voxelManager.getCompleteScalarDataArray(),
- };
-
- const referenceVolumeProps = {
- dimensions: referencedVolume.dimensions,
- origin: referencedVolume.origin,
- direction: referencedVolume.direction,
- spacing: referencedVolume.spacing,
- metadata: referencedVolume.metadata,
- scalarData: referencedVolume.voxelManager.getCompleteScalarDataArray(),
- };
-
- // metadata in annotations has enabledElement which is not serializable
- // we need to remove it
- // Todo: we should probably have a sanitization function for this
- const annotationsToSend = annotations.map(annotation => {
- return {
- ...annotation,
- metadata: {
- ...annotation.metadata,
- enabledElement: {
- ...annotation.metadata.enabledElement,
- viewport: null,
- renderingEngine: null,
- element: null,
- },
- },
- };
- });
-
- const suvPeak =
- (await workerManager.executeTask('suv-peak-worker', 'calculateSuvPeak', {
- labelmapProps,
- referenceVolumeProps,
- annotations: annotationsToSend,
- segmentIndex,
- })) || {};
-
- return {
- suvPeak: suvPeak.mean,
- suvMax: suvPeak.max,
- suvMaxIJK: suvPeak.maxIJK,
- suvMaxLPS: suvPeak.maxLPS,
- };
- },
- getLesionStats: ({ segmentationId, segmentIndex = 1 }) => {
- const { labelmapVolume, referencedVolume } = getVolumesFromSegmentation(segmentationId);
- const { voxelManager: segVoxelManager, imageData, spacing } = labelmapVolume;
- const { voxelManager: refVoxelManager } = referencedVolume;
-
- let segmentationMax = -Infinity;
- let segmentationMin = Infinity;
- const segmentationValues = [];
- let voxelCount = 0;
-
- const callback = ({ value, index }) => {
- if (value === segmentIndex) {
- const refValue = refVoxelManager.getAtIndex(index) as number;
- segmentationValues.push(refValue);
- if (refValue > segmentationMax) {
- segmentationMax = refValue;
- }
- if (refValue < segmentationMin) {
- segmentationMin = refValue;
- }
- voxelCount++;
- }
- };
-
- segVoxelManager.forEach(callback, { imageData });
- const mean = segmentationValues.reduce((a, b) => a + b, 0) / voxelCount;
- const stats = {
- minValue: segmentationMin,
- maxValue: segmentationMax,
- meanValue: mean,
- stdValue: Math.sqrt(
- segmentationValues.map(k => (k - mean) ** 2).reduce((acc, curr) => acc + curr, 0) /
- voxelCount
- ),
- volume: voxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3,
- };
-
- return stats;
- },
- calculateLesionGlycolysis: ({ lesionStats }) => {
- const { meanValue, volume } = lesionStats;
-
- return {
- lesionGlyoclysisStats: volume * meanValue,
- };
- },
calculateTMTV: async ({ segmentations }) => {
- const labelmapProps = segmentations.map(segmentation => {
- const labelmap = getLabelmapVolumeFromSegmentation(segmentation);
- return {
- dimensions: labelmap.dimensions,
- spacing: labelmap.spacing,
- scalarData: labelmap.voxelManager.getCompleteScalarDataArray(),
- origin: labelmap.origin,
- direction: labelmap.direction,
- };
+ const segmentationIds = segmentations.map(segmentation => segmentation.segmentationId);
+
+ const stats = await csTools.utilities.segmentation.computeMetabolicStats({
+ segmentationIds,
+ segmentIndex: 1,
});
- if (!labelmapProps.length) {
- return;
- }
-
- const tmtv = await workerManager.executeTask(
- 'suv-peak-worker',
- 'calculateTMTV',
- labelmapProps
- );
-
- return tmtv;
+ segmentationService.setSegmentationGroupStats(segmentationIds, stats);
+ return stats;
},
exportTMTVReportCSV: async ({ segmentations, tmtv, config, options }) => {
const segReport = commandsManager.runCommand('getSegmentationCSVReport', {
segmentations,
});
- const tlg = await actions.getTotalLesionGlycolysis({ segmentations });
const additionalReportRows = [
{ key: 'Total Lesion Glycolysis', value: { tlg: tlg.toFixed(4) } },
{ key: 'Threshold Configuration', value: { ...config } },
@@ -423,35 +279,7 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
createAndDownloadTMTVReport(segReport, additionalReportRows, options);
},
- getTotalLesionGlycolysis: async ({ segmentations }) => {
- const labelmapProps = segmentations.map(segmentation => {
- const labelmap = getLabelmapVolumeFromSegmentation(segmentation);
- return {
- dimensions: labelmap.dimensions,
- spacing: labelmap.spacing,
- scalarData: labelmap.voxelManager.getCompleteScalarDataArray(),
- origin: labelmap.origin,
- direction: labelmap.direction,
- };
- });
- const { referencedVolume: ptVolume } = getVolumesFromSegmentation(
- segmentations[0].segmentationId
- );
-
- const ptVolumeProps = {
- dimensions: ptVolume.dimensions,
- spacing: ptVolume.spacing,
- scalarData: ptVolume.voxelManager.getCompleteScalarDataArray(),
- origin: ptVolume.origin,
- direction: ptVolume.direction,
- };
-
- return await workerManager.executeTask('suv-peak-worker', 'getTotalLesionGlycolysis', {
- labelmapProps,
- referenceVolumeProps: ptVolumeProps,
- });
- },
setStartSliceForROIThresholdTool: () => {
const { viewport } = _getActiveViewportsEnabledElement();
const { focalPoint } = viewport.getCamera();
@@ -651,12 +479,6 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
getTotalLesionGlycolysis: {
commandFn: actions.getTotalLesionGlycolysis,
},
- calculateSuvPeak: {
- commandFn: actions.calculateSuvPeak,
- },
- getLesionStats: {
- commandFn: actions.getLesionStats,
- },
calculateTMTV: {
commandFn: actions.calculateTMTV,
},
diff --git a/extensions/tmtv/src/utils/calculateSUVPeakWorker.js b/extensions/tmtv/src/utils/calculateSUVPeakWorker.js
deleted file mode 100644
index dbfdea32d..000000000
--- a/extensions/tmtv/src/utils/calculateSUVPeakWorker.js
+++ /dev/null
@@ -1,209 +0,0 @@
-import { utilities } from '@cornerstonejs/core';
-import { utilities as cstUtils } from '@cornerstonejs/tools';
-import { vec3 } from 'gl-matrix';
-import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
-import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
-import { expose } from 'comlink';
-
-const createVolume = ({ dimensions, origin, direction, spacing, metadata, scalarData }) => {
- const imageData = vtkImageData.newInstance();
- imageData.setDimensions(dimensions);
- imageData.setOrigin(origin);
- imageData.setDirection(direction);
- imageData.setSpacing(spacing);
-
- const scalarArray = vtkDataArray.newInstance({
- name: 'Pixels',
- numberOfComponents: 1,
- values: scalarData,
- });
-
- imageData.getPointData().setScalars(scalarArray);
-
- imageData.modified();
-
- const voxelManager = utilities.VoxelManager.createScalarVolumeVoxelManager({
- scalarData,
- dimensions,
- numberOfComponents: 1,
- });
- return {
- imageData,
- spacing,
- origin,
- direction,
- metadata,
- voxelManager,
- };
-};
-
-/**
- * This method calculates the SUV peak on a segmented ROI from a reference PET
- * volume. If a rectangle annotation is provided, the peak is calculated within that
- * rectangle. Otherwise, the calculation is performed on the entire volume which
- * will be slower but same result.
- * @param viewport Viewport to use for the calculation
- * @param labelmap Labelmap from which the mask is taken
- * @param referenceVolume PET volume to use for SUV calculation
- * @param toolData [Optional] list of toolData to use for SUV calculation
- * @param segmentIndex The index of the segment to use for masking
- * @returns
- */
-function calculateSuvPeak({ labelmapProps, referenceVolumeProps, annotations, segmentIndex = 1 }) {
- const labelmapInfo = createVolume(labelmapProps);
- const referenceInfo = createVolume(referenceVolumeProps);
-
- if (referenceInfo.metadata.Modality !== 'PT') {
- return;
- }
-
- const { dimensions, imageData: labelmapImageData } = labelmapInfo;
- const { imageData: referenceVolumeImageData } = referenceInfo;
-
- let boundsIJK;
- // Todo: using the first annotation for now
- if (annotations?.length && annotations[0].data?.cachedStats) {
- const { projectionPoints } = annotations[0].data.cachedStats;
- const pointsToUse = [].concat(...projectionPoints); // cannot use flat() because of typescript compiler right now
-
- const rectangleCornersIJK = pointsToUse.map(world => {
- const ijk = vec3.fromValues(0, 0, 0);
- referenceVolumeImageData.worldToIndex(world, ijk);
- return ijk;
- });
-
- boundsIJK = cstUtils.boundingBox.getBoundingBoxAroundShape(rectangleCornersIJK, dimensions);
- }
-
- let max = 0;
- let maxIJK = [0, 0, 0];
- let maxLPS = [0, 0, 0];
-
- const callback = ({ pointIJK, pointLPS }) => {
- const value = labelmapInfo.voxelManager.getAtIJKPoint(pointIJK);
-
- if (value !== segmentIndex) {
- return;
- }
-
- const referenceValue = referenceInfo.voxelManager.getAtIJKPoint(pointIJK);
-
- if (referenceValue > max) {
- max = referenceValue;
- maxIJK = pointIJK;
- maxLPS = pointLPS;
- }
- };
-
- labelmapInfo.voxelManager.forEach(callback, {
- boundsIJK,
- imageData: labelmapImageData,
- isInObject: () => true,
- returnPoints: true,
- });
-
- const direction = labelmapImageData.getDirection().slice(0, 3);
-
- /**
- * 2. Find the bottom and top of the great circle for the second sphere (1cc sphere)
- * V = (4/3)Ï€r3
- */
- const radius = Math.pow(1 / ((4 / 3) * Math.PI), 1 / 3) * 10;
- const diameter = radius * 2;
-
- const secondaryCircleWorld = vec3.create();
- const bottomWorld = vec3.create();
- const topWorld = vec3.create();
- referenceVolumeImageData.indexToWorld(maxIJK, secondaryCircleWorld);
- vec3.scaleAndAdd(bottomWorld, secondaryCircleWorld, direction, -diameter / 2);
- vec3.scaleAndAdd(topWorld, secondaryCircleWorld, direction, diameter / 2);
- const suvPeakCirclePoints = [bottomWorld, topWorld];
-
- /**
- * 3. Find the Mean and Max of the 1cc sphere centered on the suv Max of the previous
- * sphere
- */
- let count = 0;
- let acc = 0;
- const suvPeakMeanCallback = ({ value }) => {
- acc += value;
- count += 1;
- };
-
- cstUtils.pointInSurroundingSphereCallback(
- referenceVolumeImageData,
- suvPeakCirclePoints,
- suvPeakMeanCallback
- );
-
- const mean = acc / count;
-
- return {
- max,
- maxIJK,
- maxLPS,
- mean,
- };
-}
-
-function calculateTMTV(labelmapProps, segmentIndex = 1) {
- const labelmaps = labelmapProps.map(props => createVolume(props));
-
- const mergedLabelmap =
- labelmaps.length === 1
- ? labelmaps[0]
- : cstUtils.segmentation.createMergedLabelmapForIndex(labelmaps);
-
- const { imageData, spacing } = mergedLabelmap;
- const values = imageData.getPointData().getScalars().getData();
-
- // count non-zero values inside the outputData, this would
- // consider the overlapping regions to be only counted once
- const numVoxels = values.reduce((acc, curr) => {
- if (curr > 0) {
- return acc + 1;
- }
- return acc;
- }, 0);
-
- return 1e-3 * numVoxels * spacing[0] * spacing[1] * spacing[2];
-}
-
-function getTotalLesionGlycolysis({ labelmapProps, referenceVolumeProps }) {
- const labelmaps = labelmapProps.map(props => createVolume(props));
-
- const mergedLabelmap =
- labelmaps.length === 1
- ? labelmaps[0]
- : cstUtils.segmentation.createMergedLabelmapForIndex(labelmaps);
-
- // grabbing the first labelmap referenceVolume since it will be the same for all
- const { spacing } = labelmaps[0];
-
- const ptVolume = createVolume(referenceVolumeProps);
-
- let suv = 0;
- let totalLesionVoxelCount = 0;
- const scalarDataLength = mergedLabelmap.voxelManager.getScalarDataLength();
- for (let i = 0; i < scalarDataLength; i++) {
- // if not background
- if (mergedLabelmap.voxelManager.getAtIndex(i) !== 0) {
- suv += ptVolume.voxelManager.getAtIndex(i);
- totalLesionVoxelCount += 1;
- }
- }
-
- // Average SUV for the merged labelmap
- const averageSuv = suv / totalLesionVoxelCount;
-
- // total Lesion Glycolysis [suv * ml]
- return averageSuv * totalLesionVoxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3;
-}
-
-const obj = {
- calculateSuvPeak,
- calculateTMTV,
- getTotalLesionGlycolysis,
-};
-
-expose(obj);
diff --git a/extensions/tmtv/src/utils/calculateTMTV.ts b/extensions/tmtv/src/utils/calculateTMTV.ts
deleted file mode 100644
index 0fcc680ae..000000000
--- a/extensions/tmtv/src/utils/calculateTMTV.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Types } from '@cornerstonejs/core';
-import { utilities } from '@cornerstonejs/tools';
-
-/**
- * Given a list of labelmaps (with the possibility of overlapping regions),
- * and a referenceVolume, it calculates the total metabolic tumor volume (TMTV)
- * by flattening and rasterizing each segment into a single labelmap and summing
- * the total number of volume voxels. It should be noted that for this calculation
- * we do not double count voxels that are part of multiple labelmaps.
- * @param {} labelmaps
- * @param {number} segmentIndex
- * @returns {number} TMTV in ml
- */
-function calculateTMTV(labelmaps: Array, segmentIndex = 1): number {
- const volumeId = 'mergedLabelmap';
-
- const mergedLabelmap = utilities.segmentation.createMergedLabelmapForIndex(
- labelmaps,
- segmentIndex,
- volumeId
- );
-
- const { imageData, spacing, voxelManager } = mergedLabelmap;
-
- // count non-zero values inside the outputData, this would
- // consider the overlapping regions to be only counted once
- let numVoxels = 0;
- const callback = ({ value }) => {
- if (value > 0) {
- numVoxels += 1;
- }
- };
-
- voxelManager.forEach(callback, {
- imageData,
- isInObject: () => true,
- });
-
- return 1e-3 * numVoxels * spacing[0] * spacing[1] * spacing[2];
-}
-
-export default calculateTMTV;
diff --git a/extensions/tmtv/src/utils/handleROIThresholding.ts b/extensions/tmtv/src/utils/handleROIThresholding.ts
index bb5ae0c55..9d001b0c9 100644
--- a/extensions/tmtv/src/utils/handleROIThresholding.ts
+++ b/extensions/tmtv/src/utils/handleROIThresholding.ts
@@ -1,72 +1,12 @@
-import { Segment, Segmentation } from '@cornerstonejs/tools/types';
-import { triggerEvent, eventTarget, Enums } from '@cornerstonejs/core';
-
export const handleROIThresholding = async ({
- segmentationId,
commandsManager,
segmentationService,
}: withAppTypes<{
segmentationId: string;
}>) => {
- const segmentation = segmentationService.getSegmentation(segmentationId);
-
- triggerEvent(eventTarget, Enums.Events.WEB_WORKER_PROGRESS, {
- progress: 0,
- type: 'Calculate Lesion Stats',
- id: segmentationId,
- });
-
- // re-calculating the cached stats for the active segmentation
- const updatedPerSegmentCachedStats = {};
- for (const [segmentIndex, segment] of Object.entries(segmentation.segments)) {
- if (!segment) {
- continue;
- }
-
- const numericSegmentIndex = Number(segmentIndex);
-
- const lesionStats = await commandsManager.run('getLesionStats', {
- segmentationId,
- segmentIndex: numericSegmentIndex,
- });
-
- const suvPeak = await commandsManager.run('calculateSuvPeak', {
- segmentationId,
- segmentIndex: numericSegmentIndex,
- });
-
- const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue;
-
- // update segDetails with the suv peak for the active segmentation
- const cachedStats = {
- lesionStats,
- suvPeak,
- lesionGlyoclysisStats,
- };
-
- const updatedSegment: Segment = {
- ...segment,
- cachedStats: {
- ...segment.cachedStats,
- ...cachedStats,
- },
- };
-
- updatedPerSegmentCachedStats[numericSegmentIndex] = cachedStats;
-
- segmentation.segments[segmentIndex] = updatedSegment;
- }
-
- // all available segmentations
const segmentations = segmentationService.getSegmentations();
const tmtv = await commandsManager.run('calculateTMTV', { segmentations });
- triggerEvent(eventTarget, Enums.Events.WEB_WORKER_PROGRESS, {
- progress: 100,
- type: 'Calculate Lesion Stats',
- id: segmentationId,
- });
-
// add the tmtv to all the segment cachedStats, although it is a global
// value but we don't have any other way to display it for now
// Update all segmentations with the calculated TMTV
@@ -76,22 +16,6 @@ export const handleROIThresholding = async ({
tmtv,
};
- // Update each segment within the segmentation
- Object.keys(segmentation.segments).forEach(segmentIndex => {
- segmentation.segments[segmentIndex].cachedStats = {
- ...segmentation.segments[segmentIndex].cachedStats,
- tmtv,
- };
- });
-
- // Update the segmentation object
- const updatedSegmentation: Segmentation = {
- ...segmentation,
- segments: {
- ...segmentation.segments,
- },
- };
-
- segmentationService.addOrUpdateSegmentation(updatedSegmentation);
+ segmentationService.addOrUpdateSegmentation(segmentation);
});
};
diff --git a/modes/basic-test-mode/src/toolbarButtons.ts b/modes/basic-test-mode/src/toolbarButtons.ts
index 7569e45de..f63af8291 100644
--- a/modes/basic-test-mode/src/toolbarButtons.ts
+++ b/modes/basic-test-mode/src/toolbarButtons.ts
@@ -214,7 +214,7 @@ const toolbarButtons: Button[] = [
protocolId: 'mpr',
},
},
- evaluate: 'evaluate.mpr',
+ evaluate: 'evaluate.displaySetIsReconstructable',
},
},
{
diff --git a/modes/segmentation/src/index.tsx b/modes/segmentation/src/index.tsx
index 212945c49..7b8325d10 100644
--- a/modes/segmentation/src/index.tsx
+++ b/modes/segmentation/src/index.tsx
@@ -12,6 +12,7 @@ const ohif = {
const cornerstone = {
viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone',
panelTool: '@ohif/extension-cornerstone.panelModule.panelSegmentationWithTools',
+ measurements: '@ohif/extension-cornerstone.panelModule.panelMeasurement',
};
const segmentation = {
@@ -19,6 +20,10 @@ const segmentation = {
viewport: '@ohif/extension-cornerstone-dicom-seg.viewportModule.dicom-seg',
};
+const dicomRT = {
+ viewport: '@ohif/extension-cornerstone-dicom-rt.viewportModule.dicom-rt',
+ sopClassHandler: '@ohif/extension-cornerstone-dicom-rt.sopClassHandlerModule.dicom-rt',
+};
/**
* Just two dependencies to be able to render a viewport with panels in order
* to make sure that the mode is working.
@@ -27,6 +32,7 @@ const extensionDependencies = {
'@ohif/extension-default': '^3.0.0',
'@ohif/extension-cornerstone': '^3.0.0',
'@ohif/extension-cornerstone-dicom-seg': '^3.0.0',
+ '@ohif/extension-cornerstone-dicom-rt': '^3.0.0',
};
function modeFactory({ modeConfiguration }) {
@@ -81,7 +87,14 @@ function modeFactory({ modeConfiguration }) {
'TagBrowser',
]);
- toolbarService.createButtonSection('segmentationToolbox', ['SegmentationTools']);
+ toolbarService.createButtonSection('segmentationToolbox', [
+ 'SegmentationUtilities',
+ 'SegmentationTools',
+ ]);
+ toolbarService.createButtonSection('segmentationToolboxUtilitySection', [
+ 'InterpolateLabelmap',
+ 'SegmentBidirectional',
+ ]);
toolbarService.createButtonSection('segmentationToolboxToolsSection', [
'BrushTools',
'Shapes',
@@ -149,7 +162,7 @@ function modeFactory({ modeConfiguration }) {
props: {
leftPanels: [ohif.leftPanel],
leftPanelResizable: true,
- rightPanels: [cornerstone.panelTool],
+ rightPanels: [cornerstone.panelTool, cornerstone.measurements],
rightPanelResizable: true,
// leftPanelClosed: true,
viewports: [
@@ -161,6 +174,10 @@ function modeFactory({ modeConfiguration }) {
namespace: segmentation.viewport,
displaySetsToDisplay: [segmentation.sopClassHandler],
},
+ {
+ namespace: dicomRT.viewport,
+ displaySetsToDisplay: [dicomRT.sopClassHandler],
+ },
],
},
};
@@ -174,7 +191,7 @@ function modeFactory({ modeConfiguration }) {
// The example is used for a grid layout to specify that as a preferred layout
// hangingProtocol: ['@ohif/mnGrid'],
/** SopClassHandlers used by the mode */
- sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler],
+ sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler, dicomRT.sopClassHandler],
};
}
diff --git a/modes/segmentation/src/initToolGroups.ts b/modes/segmentation/src/initToolGroups.ts
index 6335563e2..1f4de6ee4 100644
--- a/modes/segmentation/src/initToolGroups.ts
+++ b/modes/segmentation/src/initToolGroups.ts
@@ -76,6 +76,9 @@ function createTools(utilityModule) {
},
},
},
+ {
+ toolName: toolNames.SegmentBidirectional,
+ },
{
toolName: 'ThresholdSphereBrushDynamic',
parentTool: 'Brush',
diff --git a/modes/segmentation/src/toolbarButtons.ts b/modes/segmentation/src/toolbarButtons.ts
index a9d81b3b8..42afe1b9e 100644
--- a/modes/segmentation/src/toolbarButtons.ts
+++ b/modes/segmentation/src/toolbarButtons.ts
@@ -255,7 +255,7 @@ const toolbarButtons: Button[] = [
{
id: 'Brush',
- uiType: 'ohif.toolButton',
+ uiType: 'ohif.toolBoxButton',
props: {
icon: 'icon-tool-brush',
label: 'Brush',
@@ -292,9 +292,39 @@ const toolbarButtons: Button[] = [
],
},
},
+ {
+ id: 'InterpolateLabelmap',
+ uiType: 'ohif.toolBoxButton',
+ props: {
+ icon: 'icon-tool-interpolation',
+ label: 'Interpolate Labelmap',
+ evaluate: [
+ 'evaluate.cornerstone.segmentation',
+ {
+ name: 'evaluate.displaySetIsReconstructable',
+ disabledText: 'The current viewport cannot handle interpolation.',
+ },
+ ],
+ commands: 'interpolateLabelmap',
+ },
+ },
+ {
+ id: 'SegmentBidirectional',
+ uiType: 'ohif.toolBoxButton',
+ props: {
+ icon: 'icon-tool-bidirectional-segment',
+ label: 'Segment Bidirectional',
+ tooltip: 'Finding the largest bidirectional measurement per lesion',
+ evaluate: {
+ name: 'evaluate.cornerstone.segmentation',
+ disabledText: 'Create new segmentation to enable this tool.',
+ },
+ commands: 'runSegmentBidirectional',
+ },
+ },
{
id: 'Eraser',
- uiType: 'ohif.toolButton',
+ uiType: 'ohif.toolBoxButton',
props: {
icon: 'icon-tool-eraser',
label: 'Eraser',
@@ -332,7 +362,7 @@ const toolbarButtons: Button[] = [
},
{
id: 'Threshold',
- uiType: 'ohif.toolButton',
+ uiType: 'ohif.toolBoxButton',
props: {
icon: 'icon-tool-threshold',
label: 'Threshold Tool',
diff --git a/platform/app/package.json b/platform/app/package.json
index feb81fa63..edf2acf9c 100644
--- a/platform/app/package.json
+++ b/platform/app/package.json
@@ -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.5.0",
+ "@cornerstonejs/dicom-image-loader": "^3.7.0",
"@emotion/serialize": "^1.1.3",
"@ohif/core": "3.10.0-beta.125",
"@ohif/extension-cornerstone": "3.10.0-beta.125",
diff --git a/platform/core/package.json b/platform/core/package.json
index b2a741651..85dc03e50 100644
--- a/platform/core/package.json
+++ b/platform/core/package.json
@@ -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.5.0",
+ "@cornerstonejs/dicom-image-loader": "^3.7.0",
"@ohif/ui": "3.10.0-beta.125",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21"
diff --git a/platform/core/src/services/ToolBarService/ToolbarService.ts b/platform/core/src/services/ToolBarService/ToolbarService.ts
index b38e8efa0..90334c13a 100644
--- a/platform/core/src/services/ToolBarService/ToolbarService.ts
+++ b/platform/core/src/services/ToolBarService/ToolbarService.ts
@@ -585,7 +585,11 @@ export default class ToolbarService extends PubSubService {
});
props.evaluate = args => {
- const results = evaluators.map(evaluator => evaluator(args));
+ const results = evaluators.map(evaluator => evaluator(args)).filter(Boolean);
+
+ // had at least one disabled button, so we need to disable the button
+ const hasDisabledButton = results?.some(result => result.disabled);
+
const mergedResult = results.reduce((acc, result) => {
return {
...acc,
@@ -593,6 +597,10 @@ export default class ToolbarService extends PubSubService {
};
}, {});
+ if (hasDisabledButton) {
+ mergedResult.disabled = true;
+ }
+
return mergedResult;
};
diff --git a/platform/core/src/services/_shared/pubSubServiceInterface.ts b/platform/core/src/services/_shared/pubSubServiceInterface.ts
index a286b9755..59c41e74f 100644
--- a/platform/core/src/services/_shared/pubSubServiceInterface.ts
+++ b/platform/core/src/services/_shared/pubSubServiceInterface.ts
@@ -1,4 +1,5 @@
import guid from '../../utils/guid';
+import debounce from '../../utils/debounce';
/**
* Consumer must implement:
@@ -92,14 +93,14 @@ function _broadcastEvent(eventName, callbackProps) {
/** Export a PubSubService class to be used instead of the individual items */
export class PubSubService {
- EVENTS: any;
- subscribe: (eventName: string, callback: Function) => { unsubscribe: () => any };
- _broadcastEvent: (eventName: string, callbackProps: any) => void;
+ EVENTS: Record;
+ subscribe: (eventName: string, callback: (data: unknown) => void) => { unsubscribe: () => void };
+ _broadcastEvent: (eventName: string, callbackProps: unknown) => void;
_unsubscribe: (eventName: string, listenerId: string) => void;
_isValidEvent: (eventName: string) => boolean;
- listeners: {};
- unsubscriptions: any[];
- constructor(EVENTS) {
+ listeners: Record void }> | undefined>;
+ unsubscriptions: Array<() => void>;
+ constructor(EVENTS: Record) {
this.EVENTS = EVENTS;
this.subscribe = subscribe;
this._broadcastEvent = _broadcastEvent;
@@ -109,6 +110,27 @@ export class PubSubService {
this.unsubscriptions = [];
}
+ /**
+ * Subscribe to updates with debouncing to limit callback execution frequency
+ * @param eventName - The name of the event
+ * @param callback - Events callback
+ * @param wait - Debounce wait time in milliseconds
+ * @param immediate - If true, trigger on the leading edge instead of trailing
+ */
+ subscribeDebounced(
+ eventName: string,
+ callback: (data: unknown) => void,
+ wait = 300,
+ immediate = false
+ ) {
+ if (this._isValidEvent(eventName)) {
+ const debouncedCallback = debounce(callback, wait, immediate);
+ return this.subscribe(eventName, debouncedCallback);
+ } else {
+ throw new Error(`Event ${eventName} not supported.`);
+ }
+ }
+
reset() {
this.unsubscriptions.forEach(unsub => unsub());
this.unsubscriptions = [];
@@ -120,7 +142,9 @@ export class PubSubService {
* Check eventData.isConsumed to see if it is consumed or not.
* @param props - to include in the event
*/
- protected createConsumableEvent(props) {
+ protected createConsumableEvent>(
+ props: T
+ ): T & { isConsumed: boolean; consume: () => void } {
return {
...props,
isConsumed: false,
diff --git a/platform/docs/docs/migration-guide/3p9-to-3p10/UI/Migration-3p10-SegmentationTable.md b/platform/docs/docs/migration-guide/3p9-to-3p10/UI/Migration-3p10-SegmentationTable.md
new file mode 100644
index 000000000..713f44ec3
--- /dev/null
+++ b/platform/docs/docs/migration-guide/3p9-to-3p10/UI/Migration-3p10-SegmentationTable.md
@@ -0,0 +1,83 @@
+---
+title: Segmentation Table
+---
+
+
+# SegmentationTable Migration Guide
+
+This guide will help you migrate your code to use the refactored SegmentationTable component.
+
+## Key Changes
+
+* **Context System Refactoring**: The context system has been completely redesigned with dedicated providers for different aspects of the segmentation UI
+* **Compound Component Pattern**: Components now follow a more structured compound component pattern with clearer parent-child relationships
+* **Header Components Changed**: The `SegmentationTable.SelectorHeader` has been replaced with specialized header components
+* **New Features**: Added segment statistics, hover cards, and better customization options
+* **Better Customization**: Added support for custom dropdown menus and segment statistics headers
+
+## Migration Steps
+
+
+
+### 1. Migrate from SelectorHeader to New Header Components
+
+The `SegmentationTable.SelectorHeader` has been removed. Use the new Collapsed pattern instead:
+
+```diff
+-
+-
+-
+
++
++
++
++
++
++
++
++
++
++ {/* Content here */}
++
++
+```
+
+### 2. Update Component Hierarchy for Expanded View
+
+The expanded view structure has also changed:
+
+```diff
+-
+-
+-
+-
+-
+-
+
++
++
++
++
++
++
++
++
++
++
++
++
++
+```
+
+### 3. Using the New Segment Statistics Component
+
+The new `SegmentStatistics` component provides a way to display segment statistics:
+
+```diff
++
++
++
++
++
++
+```
diff --git a/platform/docs/docs/migration-guide/3p9-to-3p10/UI/Migration-3p10-Tourss.md b/platform/docs/docs/migration-guide/3p9-to-3p10/UI/Migration-3p10-Tours.md
similarity index 100%
rename from platform/docs/docs/migration-guide/3p9-to-3p10/UI/Migration-3p10-Tourss.md
rename to platform/docs/docs/migration-guide/3p9-to-3p10/UI/Migration-3p10-Tours.md
diff --git a/platform/docs/src/pages/patterns/patterns-segmentation.tsx b/platform/docs/src/pages/patterns/patterns-segmentation.tsx
index c78b6c226..3e9681aa5 100644
--- a/platform/docs/src/pages/patterns/patterns-segmentation.tsx
+++ b/platform/docs/src/pages/patterns/patterns-segmentation.tsx
@@ -37,6 +37,11 @@ import { Input } from '../../../../ui-next/src/components/Input';
import { Tabs, TabsList, TabsTrigger } from '../../../../ui-next/src/components/Tabs';
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
import { TooltipProvider } from '../../../../ui-next/src/components/Tooltip';
+import {
+ HoverCard,
+ HoverCardTrigger,
+ HoverCardContent,
+} from '../../../../ui-next/src/components/HoverCard';
interface DataItem {
id: number;
@@ -44,8 +49,29 @@ interface DataItem {
description: string;
optionalField?: string;
colorHex?: string;
- details?: string;
+ details?: { primary: string[]; secondary: string[] };
series?: string;
+ statistics?: {
+ centroidX: string;
+ centroidY: string;
+ centroidZ: string;
+ frameDuration: string;
+ kurtosis: string;
+ max: string;
+ maxSlice: string;
+ mean: string;
+ median: string;
+ min: string;
+ regions: string;
+ skewness: string;
+ sphereDiameter: string;
+ standardDeviation: string;
+ suvPeak: string;
+ total: string;
+ glycolysis: string;
+ volume: string;
+ voxelCount: string;
+ };
}
interface ListGroup {
@@ -67,13 +93,189 @@ export default function SegmentationPanel() {
};
const organSegmentationGroup = dataList.find(
- (listGroup: ListGroup) => listGroup.type === 'Organ Segmentation'
- );
+ (listGroup: any) => listGroup.type === 'Organ Segmentation'
+ ) as unknown as ListGroup;
if (!organSegmentationGroup) {
return Organ Segmentation data not found.
;
}
+ // Create a state to track which item's statistics to show
+
+ // Function to render statistics panel
+ const renderStatisticsPanel = (item: DataItem) => {
+ if (!item.statistics) {
+ return null;
+ }
+
+ const stats = item.statistics;
+ return (
+
+
+
+
+
+
Centroid X
+
+ {stats.centroidX.value}{' '}
+ {stats.centroidX.unit}
+
+
+
+
+
Centroid Y
+
+ {stats.centroidY.value}{' '}
+ {stats.centroidY.unit}
+
+
+
+
+
Centroid Z
+
+ {stats.centroidZ.value}{' '}
+ {stats.centroidZ.unit}
+
+
+
+
+
Frame Duration
+
+ {stats.frameDuration.value}{' '}
+ {stats.frameDuration.unit}
+
+
+
+
+
Kurtosis
+
+ {stats.kurtosis.value}{' '}
+ {stats.kurtosis.unit}
+
+
+
+
+
Max
+
+ {stats.max.value}{' '}
+ {stats.max.unit}
+
+
+
+
+
Max Slice
+
+ {stats.maxSlice.value}{' '}
+ {stats.maxSlice.unit}
+
+
+
+
+
Mean
+
+ {stats.mean.value}{' '}
+ {stats.mean.unit}
+
+
+
+
+
Median
+
+ {stats.median.value}{' '}
+ {stats.median.unit}
+
+
+
+
+
Min
+
+ {stats.min.value}{' '}
+ {stats.min.unit}
+
+
+
+
+
Regions
+
+ {stats.regions.value}{' '}
+ {stats.regions.unit}
+
+
+
+
+
Skewness
+
+ {stats.skewness.value}{' '}
+ {stats.skewness.unit}
+
+
+
+
+
Sphere Diameter
+
+ {stats.sphereDiameter.value}{' '}
+ {stats.sphereDiameter.unit}
+
+
+
+
+
Standard Deviation
+
+ {stats.standardDeviation.value}{' '}
+ {stats.standardDeviation.unit}
+
+
+
+
+
SUV Peak
+
+ {stats.suvPeak.value}{' '}
+ {stats.suvPeak.unit}
+
+
+
+
+
Total
+
+ {stats.total.value}{' '}
+ {stats.total.unit}
+
+
+
+
+
Glycolysis
+
+ {stats.glycolysis.value}{' '}
+ {stats.glycolysis.unit}
+
+
+
+
+
Volume
+
+ {stats.volume.value}{' '}
+ {stats.volume.unit}
+
+
+
+
+
Voxel Count
+
+ {stats.voxelCount.value}{' '}
+ {stats.voxelCount.unit}
+
+
+
+
+ );
+ };
+
return (
@@ -284,20 +486,46 @@ export default function SegmentationPanel() {
{organSegmentationGroup.items.map((item, index) => {
const compositeId = `${organSegmentationGroup.type}-${item.id}-panel`; // Ensure unique composite ID
return (
-
handleAction(compositeId, action)}
- isSelected={selectedRowId === compositeId}
- onSelect={() => handleRowSelect(compositeId)}
- />
+
+
+
+ handleAction(compositeId, action)}
+ isSelected={selectedRowId === compositeId}
+ onSelect={() => handleRowSelect(compositeId)}
+ isVisible={true}
+ isLocked={false}
+ onToggleVisibility={() => console.debug('Toggle visibility')}
+ onToggleLocked={() => console.debug('Toggle locked')}
+ onRename={() => console.debug('Rename')}
+ onDelete={() => console.debug('Delete')}
+ onColor={() => console.debug('Color')}
+ disableEditing={false}
+ />
+
+
+
+ {renderStatisticsPanel(item)}
+
+
);
})}
diff --git a/platform/ui-next/assets/data/index.ts b/platform/ui-next/assets/data/index.ts
index 8d63a4e7e..8eac4ec80 100644
--- a/platform/ui-next/assets/data/index.ts
+++ b/platform/ui-next/assets/data/index.ts
@@ -118,84 +118,378 @@ const dataList = [
title: 'Spleen',
description: 'Description for Spleen.',
colorHex: '#6B8E23',
+ statistics: {
+ centroidX: { value: -3.45, unit: 'cm' },
+ centroidY: { value: 4.78, unit: 'cm' },
+ centroidZ: { value: 118.32, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.22, unit: '' },
+ max: { value: 14.92, unit: 'SUV' },
+ maxSlice: { value: 78, unit: '' },
+ mean: { value: 7.14, unit: 'SUV' },
+ median: { value: 6.85, unit: 'SUV' },
+ min: { value: 3.8, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.76, unit: '' },
+ sphereDiameter: { value: 7.12, unit: 'cm' },
+ standardDeviation: { value: 2.31, unit: 'SUV' },
+ suvPeak: { value: 12.95, unit: 'SUV' },
+ total: { value: 18245.63, unit: 'SUV' },
+ glycolysis: { value: 1245.78, unit: 'SUV ml' },
+ volume: { value: 178.34, unit: 'ml' },
+ voxelCount: { value: 2834, unit: '' },
+ },
},
{
id: 16,
title: 'Kidney',
description: 'Description for Kidney.',
colorHex: '#4682B4',
+ statistics: {
+ centroidX: { value: 2.18, unit: 'cm' },
+ centroidY: { value: 6.32, unit: 'cm' },
+ centroidZ: { value: 125.47, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.18, unit: '' },
+ max: { value: 16.23, unit: 'SUV' },
+ maxSlice: { value: 92, unit: '' },
+ mean: { value: 8.05, unit: 'SUV' },
+ median: { value: 7.92, unit: 'SUV' },
+ min: { value: 4.2, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.91, unit: '' },
+ sphereDiameter: { value: 8.45, unit: 'cm' },
+ standardDeviation: { value: 2.67, unit: 'SUV' },
+ suvPeak: { value: 14.32, unit: 'SUV' },
+ total: { value: 22567.89, unit: 'SUV' },
+ glycolysis: { value: 1532.45, unit: 'SUV ml' },
+ volume: { value: 215.67, unit: 'ml' },
+ voxelCount: { value: 3245, unit: '' },
+ },
},
{
id: 17,
title: 'Kidney very long title name lorem ipsum dolor sit amet segmentation',
description: 'Description for Kidney.',
colorHex: '#9ACD32',
+ statistics: {
+ centroidX: { value: -2.34, unit: 'cm' },
+ centroidY: { value: 5.87, unit: 'cm' },
+ centroidZ: { value: 124.65, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.25, unit: '' },
+ max: { value: 15.45, unit: 'SUV' },
+ maxSlice: { value: 88, unit: '' },
+ mean: { value: 7.82, unit: 'SUV' },
+ median: { value: 7.45, unit: 'SUV' },
+ min: { value: 4.1, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.85, unit: '' },
+ sphereDiameter: { value: 7.95, unit: 'cm' },
+ standardDeviation: { value: 2.54, unit: 'SUV' },
+ suvPeak: { value: 13.78, unit: 'SUV' },
+ total: { value: 21345.67, unit: 'SUV' },
+ glycolysis: { value: 1423.56, unit: 'SUV ml' },
+ volume: { value: 198.45, unit: 'ml' },
+ voxelCount: { value: 3012, unit: '' },
+ },
},
{
id: 18,
title: 'Gallbladder',
description: 'Description for Gallbladder.',
colorHex: '#20B2AA',
+ statistics: {
+ centroidX: { value: -1.87, unit: 'cm' },
+ centroidY: { value: 4.23, unit: 'cm' },
+ centroidZ: { value: 119.78, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.14, unit: '' },
+ max: { value: 12.67, unit: 'SUV' },
+ maxSlice: { value: 72, unit: '' },
+ mean: { value: 6.34, unit: 'SUV' },
+ median: { value: 6.12, unit: 'SUV' },
+ min: { value: 3.5, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.72, unit: '' },
+ sphereDiameter: { value: 5.23, unit: 'cm' },
+ standardDeviation: { value: 2.12, unit: 'SUV' },
+ suvPeak: { value: 11.45, unit: 'SUV' },
+ total: { value: 9876.54, unit: 'SUV' },
+ glycolysis: { value: 876.32, unit: 'SUV ml' },
+ volume: { value: 98.45, unit: 'ml' },
+ voxelCount: { value: 1567, unit: '' },
+ },
},
{
id: 19,
title: 'Esophagus',
description: 'Description for Esophagus.',
colorHex: '#DAA520',
+ statistics: {
+ centroidX: { value: 0.45, unit: 'cm' },
+ centroidY: { value: 7.65, unit: 'cm' },
+ centroidZ: { value: 132.45, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.12, unit: '' },
+ max: { value: 11.34, unit: 'SUV' },
+ maxSlice: { value: 65, unit: '' },
+ mean: { value: 5.87, unit: 'SUV' },
+ median: { value: 5.65, unit: 'SUV' },
+ min: { value: 3.2, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.68, unit: '' },
+ sphereDiameter: { value: 4.56, unit: 'cm' },
+ standardDeviation: { value: 1.98, unit: 'SUV' },
+ suvPeak: { value: 10.23, unit: 'SUV' },
+ total: { value: 7654.32, unit: 'SUV' },
+ glycolysis: { value: 765.43, unit: 'SUV ml' },
+ volume: { value: 87.65, unit: 'ml' },
+ voxelCount: { value: 1345, unit: '' },
+ },
},
{
id: 20,
title: 'Liver',
description: 'Description for Liver.',
colorHex: '#CD5C5C',
+ statistics: {
+ centroidX: { value: -1.23, unit: 'cm' },
+ centroidY: { value: 5.43, unit: 'cm' },
+ centroidZ: { value: 121.34, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.19, unit: '' },
+ max: { value: 17.56, unit: 'SUV' },
+ maxSlice: { value: 95, unit: '' },
+ mean: { value: 8.76, unit: 'SUV' },
+ median: { value: 8.45, unit: 'SUV' },
+ min: { value: 4.5, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.95, unit: '' },
+ sphereDiameter: { value: 9.87, unit: 'cm' },
+ standardDeviation: { value: 2.87, unit: 'SUV' },
+ suvPeak: { value: 15.67, unit: 'SUV' },
+ total: { value: 32456.78, unit: 'SUV' },
+ glycolysis: { value: 2345.67, unit: 'SUV ml' },
+ volume: { value: 345.67, unit: 'ml' },
+ voxelCount: { value: 5678, unit: '' },
+ },
},
{
id: 21,
title: 'Stomach',
description: 'Description for Stomach.',
colorHex: '#778899',
+ statistics: {
+ centroidX: { value: -0.87, unit: 'cm' },
+ centroidY: { value: 4.98, unit: 'cm' },
+ centroidZ: { value: 120.56, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.15, unit: '' },
+ max: { value: 13.45, unit: 'SUV' },
+ maxSlice: { value: 82, unit: '' },
+ mean: { value: 6.78, unit: 'SUV' },
+ median: { value: 6.54, unit: 'SUV' },
+ min: { value: 3.7, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.79, unit: '' },
+ sphereDiameter: { value: 6.78, unit: 'cm' },
+ standardDeviation: { value: 2.23, unit: 'SUV' },
+ suvPeak: { value: 12.34, unit: 'SUV' },
+ total: { value: 15678.9, unit: 'SUV' },
+ glycolysis: { value: 1123.45, unit: 'SUV ml' },
+ volume: { value: 156.78, unit: 'ml' },
+ voxelCount: { value: 2456, unit: '' },
+ },
},
{
id: 22,
title: 'Abdominal aorta',
description: 'Description for Abdominal Aorta.',
colorHex: '#B8860B',
+ statistics: {
+ centroidX: { value: 0.12, unit: 'cm' },
+ centroidY: { value: 6.54, unit: 'cm' },
+ centroidZ: { value: 126.78, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.11, unit: '' },
+ max: { value: 10.23, unit: 'SUV' },
+ maxSlice: { value: 68, unit: '' },
+ mean: { value: 5.43, unit: 'SUV' },
+ median: { value: 5.21, unit: 'SUV' },
+ min: { value: 3.1, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.65, unit: '' },
+ sphereDiameter: { value: 3.45, unit: 'cm' },
+ standardDeviation: { value: 1.87, unit: 'SUV' },
+ suvPeak: { value: 9.45, unit: 'SUV' },
+ total: { value: 5432.1, unit: 'SUV' },
+ glycolysis: { value: 654.32, unit: 'SUV ml' },
+ volume: { value: 67.89, unit: 'ml' },
+ voxelCount: { value: 1123, unit: '' },
+ },
},
{
id: 23,
title: 'Inferior vena cava',
description: 'Description for Inferior Vena Cava.',
colorHex: '#556B2F',
+ statistics: {
+ centroidX: { value: 0.34, unit: 'cm' },
+ centroidY: { value: 6.12, unit: 'cm' },
+ centroidZ: { value: 125.43, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.13, unit: '' },
+ max: { value: 9.87, unit: 'SUV' },
+ maxSlice: { value: 64, unit: '' },
+ mean: { value: 5.12, unit: 'SUV' },
+ median: { value: 4.98, unit: 'SUV' },
+ min: { value: 2.9, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.62, unit: '' },
+ sphereDiameter: { value: 3.21, unit: 'cm' },
+ standardDeviation: { value: 1.76, unit: 'SUV' },
+ suvPeak: { value: 8.98, unit: 'SUV' },
+ total: { value: 4567.89, unit: 'SUV' },
+ glycolysis: { value: 598.76, unit: 'SUV ml' },
+ volume: { value: 59.87, unit: 'ml' },
+ voxelCount: { value: 987, unit: '' },
+ },
},
{
id: 24,
title: 'Portal vein',
description: 'Description for Portal Vein.',
colorHex: '#8B4513',
+ statistics: {
+ centroidX: { value: -0.45, unit: 'cm' },
+ centroidY: { value: 5.67, unit: 'cm' },
+ centroidZ: { value: 123.45, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.14, unit: '' },
+ max: { value: 10.56, unit: 'SUV' },
+ maxSlice: { value: 71, unit: '' },
+ mean: { value: 5.67, unit: 'SUV' },
+ median: { value: 5.45, unit: 'SUV' },
+ min: { value: 3.2, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.69, unit: '' },
+ sphereDiameter: { value: 3.78, unit: 'cm' },
+ standardDeviation: { value: 1.92, unit: 'SUV' },
+ suvPeak: { value: 9.67, unit: 'SUV' },
+ total: { value: 6789.01, unit: 'SUV' },
+ glycolysis: { value: 723.45, unit: 'SUV ml' },
+ volume: { value: 76.54, unit: 'ml' },
+ voxelCount: { value: 1234, unit: '' },
+ },
},
{
id: 25,
title: 'Pancreas',
description: 'Description for Pancreas.',
colorHex: '#2F4F4F',
+ statistics: {
+ centroidX: { value: -1.56, unit: 'cm' },
+ centroidY: { value: 5.23, unit: 'cm' },
+ centroidZ: { value: 120.87, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.17, unit: '' },
+ max: { value: 14.23, unit: 'SUV' },
+ maxSlice: { value: 79, unit: '' },
+ mean: { value: 7.23, unit: 'SUV' },
+ median: { value: 6.98, unit: 'SUV' },
+ min: { value: 3.9, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.81, unit: '' },
+ sphereDiameter: { value: 6.34, unit: 'cm' },
+ standardDeviation: { value: 2.34, unit: 'SUV' },
+ suvPeak: { value: 12.67, unit: 'SUV' },
+ total: { value: 14567.89, unit: 'SUV' },
+ glycolysis: { value: 1087.65, unit: 'SUV ml' },
+ volume: { value: 134.56, unit: 'ml' },
+ voxelCount: { value: 2134, unit: '' },
+ },
},
{
id: 26,
title: 'Adrenal gland',
description: 'Description for Adrenal Gland.',
colorHex: '#708090',
+ statistics: {
+ centroidX: { value: -2.12, unit: 'cm' },
+ centroidY: { value: 5.76, unit: 'cm' },
+ centroidZ: { value: 122.34, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.16, unit: '' },
+ max: { value: 12.34, unit: 'SUV' },
+ maxSlice: { value: 74, unit: '' },
+ mean: { value: 6.45, unit: 'SUV' },
+ median: { value: 6.23, unit: 'SUV' },
+ min: { value: 3.6, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.74, unit: '' },
+ sphereDiameter: { value: 5.67, unit: 'cm' },
+ standardDeviation: { value: 2.21, unit: 'SUV' },
+ suvPeak: { value: 11.23, unit: 'SUV' },
+ total: { value: 10987.65, unit: 'SUV' },
+ glycolysis: { value: 945.67, unit: 'SUV ml' },
+ volume: { value: 112.34, unit: 'ml' },
+ voxelCount: { value: 1789, unit: '' },
+ },
},
{
id: 27,
title: 'Adrenal gland',
description: 'Description for Adrenal Gland.',
colorHex: '#6A5ACD',
+ statistics: {
+ centroidX: { value: 2.34, unit: 'cm' },
+ centroidY: { value: 5.89, unit: 'cm' },
+ centroidZ: { value: 123.56, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.15, unit: '' },
+ max: { value: 11.98, unit: 'SUV' },
+ maxSlice: { value: 73, unit: '' },
+ mean: { value: 6.32, unit: 'SUV' },
+ median: { value: 6.12, unit: 'SUV' },
+ min: { value: 3.5, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.73, unit: '' },
+ sphereDiameter: { value: 5.45, unit: 'cm' },
+ standardDeviation: { value: 2.18, unit: 'SUV' },
+ suvPeak: { value: 10.87, unit: 'SUV' },
+ total: { value: 10456.78, unit: 'SUV' },
+ glycolysis: { value: 923.45, unit: 'SUV ml' },
+ volume: { value: 109.87, unit: 'ml' },
+ voxelCount: { value: 1756, unit: '' },
+ },
},
{
id: 28,
title: 'New Seg Test New Seg Test New Seg Test New Seg Test New Seg Test New Seg Test ',
description: 'Description for New Seg Test.',
colorHex: '#4682B4',
+ statistics: {
+ centroidX: { value: -2.76, unit: 'cm' },
+ centroidY: { value: 4.98, unit: 'cm' },
+ centroidZ: { value: 121.23, unit: 'cm' },
+ frameDuration: { value: 'N/A', unit: 'ms' },
+ kurtosis: { value: -0.21, unit: '' },
+ max: { value: 15.12, unit: 'SUV' },
+ maxSlice: { value: 83, unit: '' },
+ mean: { value: 7.45, unit: 'SUV' },
+ median: { value: 7.12, unit: 'SUV' },
+ min: { value: 4.1, unit: 'SUV' },
+ regions: { value: 1, unit: '' },
+ skewness: { value: 0.82, unit: '' },
+ sphereDiameter: { value: 7.23, unit: 'cm' },
+ standardDeviation: { value: 2.45, unit: 'SUV' },
+ suvPeak: { value: 13.45, unit: 'SUV' },
+ total: { value: 19876.54, unit: 'SUV' },
+ glycolysis: { value: 1345.67, unit: 'SUV ml' },
+ volume: { value: 187.65, unit: 'ml' },
+ voxelCount: { value: 2987, unit: '' },
+ },
},
],
},
diff --git a/platform/ui-next/package.json b/platform/ui-next/package.json
index 27460481d..31453b502 100644
--- a/platform/ui-next/package.json
+++ b/platform/ui-next/package.json
@@ -44,6 +44,7 @@
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
+ "@radix-ui/react-hover-card": "^1.1.6",
"class-variance-authority": "^0.7.0",
"clsx": "*",
"cmdk": "^1.0.0",
diff --git a/platform/ui-next/src/components/DataRow/DataRow.tsx b/platform/ui-next/src/components/DataRow/DataRow.tsx
index b50630e50..7d084e8a8 100644
--- a/platform/ui-next/src/components/DataRow/DataRow.tsx
+++ b/platform/ui-next/src/components/DataRow/DataRow.tsx
@@ -8,6 +8,7 @@ import {
} from '../../components/DropdownMenu';
import { Icons } from '../../components/Icons/Icons';
import { Tooltip, TooltipTrigger, TooltipContent } from '../../components/Tooltip/Tooltip';
+import { cn } from '../../lib/utils';
/**
* DataRow is a complex UI component that displays a selectable, interactive row with hierarchical data.
@@ -77,6 +78,7 @@ interface DataRowProps {
//
colorHex?: string;
onColor: () => void;
+ className?: string;
}
const DataRow: React.FC
= ({
@@ -94,16 +96,19 @@ const DataRow: React.FC = ({
isSelected = false,
isVisible = true,
disableEditing = false,
+ className,
}) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const isTitleLong = title?.length > 25;
const rowRef = useRef(null);
- useEffect(() => {
- if (isSelected && rowRef.current) {
- rowRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
- }
- }, [isSelected]);
+ // useEffect(() => {
+ // if (isSelected && rowRef.current) {
+ // setTimeout(() => {
+ // rowRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ // }, 200);
+ // }
+ // }, [isSelected]);
const handleAction = (action: string, e: React.MouseEvent) => {
e.stopPropagation();
@@ -190,7 +195,7 @@ const DataRow: React.FC = ({
return (
,
+ React.ComponentPropsWithoutRef
+>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
+
+));
+HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
+
+export { HoverCard, HoverCardTrigger, HoverCardContent };
diff --git a/platform/ui-next/src/components/HoverCard/index.ts b/platform/ui-next/src/components/HoverCard/index.ts
new file mode 100644
index 000000000..476d28765
--- /dev/null
+++ b/platform/ui-next/src/components/HoverCard/index.ts
@@ -0,0 +1 @@
+export * from './HoverCard';
diff --git a/platform/ui-next/src/components/Icons/Icons.tsx b/platform/ui-next/src/components/Icons/Icons.tsx
index e311f4779..01c2a01ad 100644
--- a/platform/ui-next/src/components/Icons/Icons.tsx
+++ b/platform/ui-next/src/components/Icons/Icons.tsx
@@ -24,6 +24,7 @@ import IconMPR from './Sources/IconMPR';
import Info from './Sources/Info';
import InfoLink from './Sources/InfoLink';
import InfoSeries from './Sources/InfoSeries';
+import JumpToSlice from './Sources/JumpToSlice';
import ListView from './Sources/ListView';
import LoadingSpinner from './Sources/LoadingSpinner';
import Lock from './Sources/Lock';
@@ -38,6 +39,7 @@ import Pin from './Sources/Pin';
import PinFill from './Sources/PinFill';
import Plus from './Sources/Plus';
import PowerOff from './Sources/PowerOff';
+import Redo from './Sources/Redo';
import Refresh from './Sources/Refresh';
import Rename from './Sources/Rename';
import Series from './Sources/Series';
@@ -195,6 +197,7 @@ import NotificationWarning from './Sources/NotificationWarning';
import ArrowRight from './Sources/ArrowRight';
import ChevronLeft from './Sources/ChevronLeft';
import StatusAlert from './Sources/StatusAlert';
+import Undo from './Sources/Undo';
//
//
type IconProps = React.HTMLAttributes;
@@ -717,6 +720,9 @@ export const Icons = {
'tool-point': (props: IconProps) => ToolCircle(props),
'tool-freehand-line': (props: IconProps) => ToolFreehand(props),
clipboard: (props: IconProps) => Clipboard(props),
+ Undo,
+ Redo,
+ JumpToSlice,
/** Adds an icon to the set of icons */
addIcon: (name: string, icon) => {
diff --git a/platform/ui-next/src/components/Icons/Sources/JumpToSlice.tsx b/platform/ui-next/src/components/Icons/Sources/JumpToSlice.tsx
new file mode 100644
index 000000000..fcd10707d
--- /dev/null
+++ b/platform/ui-next/src/components/Icons/Sources/JumpToSlice.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import type { IconProps } from '../types';
+
+export const JumpToSlice = (props: IconProps) => (
+
+);
+
+export default JumpToSlice;
diff --git a/platform/ui-next/src/components/Icons/Sources/Redo.tsx b/platform/ui-next/src/components/Icons/Sources/Redo.tsx
new file mode 100644
index 000000000..ca0c2d6b7
--- /dev/null
+++ b/platform/ui-next/src/components/Icons/Sources/Redo.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import type { IconProps } from '../types';
+
+export const Redo = (props: IconProps) => (
+
+);
+
+export default Redo;
diff --git a/platform/ui-next/src/components/Icons/Sources/Undo.tsx b/platform/ui-next/src/components/Icons/Sources/Undo.tsx
new file mode 100644
index 000000000..6fc9e1f1a
--- /dev/null
+++ b/platform/ui-next/src/components/Icons/Sources/Undo.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import type { IconProps } from '../types';
+
+export const Undo = (props: IconProps) => (
+
+);
+
+export default Undo;
diff --git a/platform/ui-next/src/components/SegmentationTable/AddSegmentRow.tsx b/platform/ui-next/src/components/SegmentationTable/AddSegmentRow.tsx
index 3da4e253f..c385e3b3a 100644
--- a/platform/ui-next/src/components/SegmentationTable/AddSegmentRow.tsx
+++ b/platform/ui-next/src/components/SegmentationTable/AddSegmentRow.tsx
@@ -1,26 +1,8 @@
import React from 'react';
import { Button, Icons } from '@ohif/ui-next';
-import { useSegmentationTableContext } from './SegmentationTableContext';
+import { useSegmentationTableContext, useSegmentationExpanded } from './contexts';
-/**
- * Props interface for the AddSegmentRow component
- */
-interface AddSegmentRowProps {
- /** Optional child elements to render within the row */
- children?: React.ReactNode;
- /** Optional segmentation object to override the active segmentation */
- segmentation?: unknown;
-}
-
-/**
- * A component that renders a row with controls for adding segments and toggling visibility
- * in the segmentation table.
- *
- * @param props - Component properties
- * @param props.children - Optional child elements to render within the row
- * @param props.segmentation - Optional segmentation object to override the active segmentation
- */
-export const AddSegmentRow: React.FC = ({ children = null, segmentation }) => {
+export const AddSegmentRow: React.FC<{ children?: React.ReactNode }> = ({ children = null }) => {
const {
activeRepresentation,
disableEditing,
@@ -29,18 +11,32 @@ export const AddSegmentRow: React.FC = ({ children = null, s
onToggleSegmentationRepresentationVisibility,
data,
showAddSegment,
- } = useSegmentationTableContext('SegmentationTable');
+ } = useSegmentationTableContext('AddSegmentRow');
- const allSegmentsVisible = Object.values(activeRepresentation?.segments || {}).every(
- segment => segment?.visible !== false
- );
+ // Try to get from expanded context first, then fall back to active segmentation
+ let segmentationId = activeSegmentationId;
+ let representation = activeRepresentation;
- const segmentationIdToUse = segmentation ? segmentation.segmentationId : activeSegmentationId;
+ try {
+ const expandedContext = useSegmentationExpanded('AddSegmentRow');
+ if (expandedContext.isActive) {
+ segmentationId = expandedContext.segmentation.segmentationId;
+ representation = expandedContext.representation;
+ }
+ } catch (e) {
+ // Use the default values from table context
+ }
+ // If no segmentations, don't render
if (!data?.length) {
return null;
}
+ // Check if all segments are visible
+ const allSegmentsVisible = Object.values(representation?.segments || {}).every(
+ segment => segment?.visible !== false
+ );
+
const Icon = allSegmentsVisible ? (
) : (
@@ -50,14 +46,14 @@ export const AddSegmentRow: React.FC = ({ children = null, s
const allowAddSegment = showAddSegment && !disableEditing;
return (
-
+
{allowAddSegment ? (