feat(image capture): Enhance SEG and RTSS Support with Dependency Updates and Bug Fixes (#4924)
276
bun.lock
@ -33,7 +33,7 @@
|
||||
},
|
||||
"addOns/externals/devDependencies": {
|
||||
"name": "@externals/devDependencies",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
@ -121,22 +121,22 @@
|
||||
},
|
||||
"addOns/externals/dicom-microscopy-viewer": {
|
||||
"name": "@externals/dicom-microscopy-viewer",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"dicom-microscopy-viewer": "^0.46.1",
|
||||
},
|
||||
},
|
||||
"extensions/cornerstone": {
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/ai": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.8.4",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/ai": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.8.5",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
@ -153,9 +153,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.8.4",
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.5",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -167,19 +167,19 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-pmap": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-pmap",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/i18n": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/i18n": "3.10.0-beta.147",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -190,16 +190,16 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-rt": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"react-color": "^2.19.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/i18n": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/i18n": "3.10.0-beta.147",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -210,19 +210,19 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-seg": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/i18n": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/i18n": "3.10.0-beta.147",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -233,19 +233,19 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-sr": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-sr",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -255,19 +255,19 @@
|
||||
},
|
||||
"extensions/cornerstone-dynamic-volume": {
|
||||
"name": "@ohif/extension-cornerstone-dynamic-volume",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/i18n": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/i18n": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -277,7 +277,7 @@
|
||||
},
|
||||
"extensions/default": {
|
||||
"name": "@ohif/extension-default",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/calculate-suv": "^1.1.0",
|
||||
@ -285,8 +285,8 @@
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/i18n": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/i18n": "3.10.0-beta.147",
|
||||
"dcmjs": "*",
|
||||
"dicomweb-client": "^0.10.4",
|
||||
"prop-types": "^15.6.2",
|
||||
@ -300,7 +300,7 @@
|
||||
},
|
||||
"extensions/dicom-microscopy": {
|
||||
"name": "@ohif/extension-dicom-microscopy",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
@ -311,10 +311,10 @@
|
||||
"mathjs": "^12.4.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/i18n": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/i18n": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -325,14 +325,14 @@
|
||||
},
|
||||
"extensions/dicom-pdf": {
|
||||
"name": "@ohif/extension-dicom-pdf",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -342,14 +342,14 @@
|
||||
},
|
||||
"extensions/dicom-video": {
|
||||
"name": "@ohif/extension-dicom-video",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -359,20 +359,20 @@
|
||||
},
|
||||
"extensions/measurement-tracking": {
|
||||
"name": "@ohif/extension-measurement-tracking",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"@xstate/react": "^3.2.2",
|
||||
"xstate": "^4.10.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"classnames": "^2.3.2",
|
||||
"dcmjs": "*",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -385,14 +385,14 @@
|
||||
},
|
||||
"extensions/test-extension": {
|
||||
"name": "@ohif/extension-test",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"dcmjs": "0.38.0",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -402,7 +402,7 @@
|
||||
},
|
||||
"extensions/tmtv": {
|
||||
"name": "@ohif/extension-tmtv",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
@ -419,7 +419,7 @@
|
||||
},
|
||||
"modes/basic-dev-mode": {
|
||||
"name": "@ohif/mode-basic-dev-mode",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -429,17 +429,17 @@
|
||||
"webpack-merge": "^5.7.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-video": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-video": "3.10.0-beta.147",
|
||||
},
|
||||
},
|
||||
"modes/basic-test-mode": {
|
||||
"name": "@ohif/mode-test",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -449,19 +449,19 @@
|
||||
"webpack-merge": "^5.7.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-video": "3.10.0-beta.144",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0-beta.144",
|
||||
"@ohif/extension-test": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-video": "3.10.0-beta.147",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0-beta.147",
|
||||
"@ohif/extension-test": "3.10.0-beta.147",
|
||||
},
|
||||
},
|
||||
"modes/longitudinal": {
|
||||
"name": "@ohif/mode-longitudinal",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -471,32 +471,32 @@
|
||||
"webpack-merge": "^5.7.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-video": "3.10.0-beta.144",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-video": "3.10.0-beta.147",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0-beta.147",
|
||||
},
|
||||
},
|
||||
"modes/microscopy": {
|
||||
"name": "@ohif/mode-microscopy",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-microscopy": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-microscopy": "3.10.0-beta.147",
|
||||
},
|
||||
},
|
||||
"modes/preclinical-4d": {
|
||||
"name": "@ohif/mode-preclinical-4d",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
},
|
||||
@ -515,7 +515,7 @@
|
||||
},
|
||||
"modes/segmentation": {
|
||||
"name": "@ohif/mode-segmentation",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -559,7 +559,7 @@
|
||||
},
|
||||
"modes/tmtv": {
|
||||
"name": "@ohif/mode-tmtv",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -580,32 +580,32 @@
|
||||
},
|
||||
"platform/app": {
|
||||
"name": "@ohif/app",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"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.8.4",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.5",
|
||||
"@emotion/serialize": "^1.1.3",
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.144",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.144",
|
||||
"@ohif/extension-default": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-microscopy": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0-beta.144",
|
||||
"@ohif/extension-dicom-video": "3.10.0-beta.144",
|
||||
"@ohif/extension-test": "3.10.0-beta.144",
|
||||
"@ohif/i18n": "3.10.0-beta.144",
|
||||
"@ohif/mode-basic-dev-mode": "3.10.0-beta.144",
|
||||
"@ohif/mode-longitudinal": "3.10.0-beta.144",
|
||||
"@ohif/mode-microscopy": "3.10.0-beta.144",
|
||||
"@ohif/mode-test": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@ohif/ui-next": "3.10.0-beta.144",
|
||||
"@ohif/core": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.147",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.147",
|
||||
"@ohif/extension-default": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-microscopy": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0-beta.147",
|
||||
"@ohif/extension-dicom-video": "3.10.0-beta.147",
|
||||
"@ohif/extension-test": "3.10.0-beta.147",
|
||||
"@ohif/i18n": "3.10.0-beta.147",
|
||||
"@ohif/mode-basic-dev-mode": "3.10.0-beta.147",
|
||||
"@ohif/mode-longitudinal": "3.10.0-beta.147",
|
||||
"@ohif/mode-microscopy": "3.10.0-beta.147",
|
||||
"@ohif/mode-test": "3.10.0-beta.147",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"@ohif/ui-next": "3.10.0-beta.147",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"classnames": "^2.3.2",
|
||||
@ -646,7 +646,7 @@
|
||||
},
|
||||
"platform/cli": {
|
||||
"name": "@ohif/cli",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"bin": {
|
||||
"ohif-cli": "src/index.js",
|
||||
},
|
||||
@ -670,7 +670,7 @@
|
||||
},
|
||||
"platform/core": {
|
||||
"name": "@ohif/core",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"dcmjs": "*",
|
||||
@ -695,15 +695,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.8.4",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.5",
|
||||
"@ohif/ui": "3.10.0-beta.147",
|
||||
"cornerstone-math": "0.1.9",
|
||||
"dicom-parser": "^1.8.21",
|
||||
},
|
||||
},
|
||||
"platform/i18n": {
|
||||
"name": "@ohif/i18n",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next-locize-backend": "^2.0.0",
|
||||
@ -728,7 +728,7 @@
|
||||
},
|
||||
"platform/ui": {
|
||||
"name": "@ohif/ui",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@testing-library/react": "^13.1.0",
|
||||
"browser-detect": "^0.2.28",
|
||||
@ -788,7 +788,7 @@
|
||||
},
|
||||
"platform/ui-next": {
|
||||
"name": "@ohif/ui-next",
|
||||
"version": "3.10.0-beta.144",
|
||||
"version": "3.10.0-beta.147",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
@ -1099,9 +1099,9 @@
|
||||
|
||||
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
|
||||
|
||||
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.8.4", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "ndarray": "^1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.4", "@cornerstonejs/tools": "^3.8.4" } }, "sha512-TFTvNZQBW54rEcyjDfyHZYYvIPHVGZoUCtjW3isHfpoXkjHz2cliwC55e0kcNJ0zbyI6H41c4VsYH8h2MTYOdA=="],
|
||||
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.8.5", "", { "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.8.5", "@cornerstonejs/tools": "^3.8.5" } }, "sha512-dQlKKSCbV6V3QpivDdZZiI7N792l1VqsOj8H+e8iiarpy6MvwiEvck2u50/GBhto9D/U9QvRkZVs9TH0ewUs5w=="],
|
||||
|
||||
"@cornerstonejs/ai": ["@cornerstonejs/ai@3.8.4", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "lodash.clonedeep": "^4.5.0", "ndarray": "^1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.4", "@cornerstonejs/tools": "^3.8.4" } }, "sha512-scc2srdL1BqNTSFEro4y7UlNBsPtBVSF31eT4SC1psxZQcr/iyaCKuOQg/hglk1yobFkdIKvjRAvvqvYmGduQw=="],
|
||||
"@cornerstonejs/ai": ["@cornerstonejs/ai@3.8.5", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "lodash.clonedeep": "^4.5.0", "ndarray": "^1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.5", "@cornerstonejs/tools": "^3.8.5" } }, "sha512-9QR7PmTtkeXZOjDFh9WulLJGnraWLaM/g/qNB0ZaPKDMo1PLT7/tHn1F6wsyT2cmpEb3/G5towdxtEvhvlpm/w=="],
|
||||
|
||||
"@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="],
|
||||
|
||||
@ -1113,15 +1113,15 @@
|
||||
|
||||
"@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="],
|
||||
|
||||
"@cornerstonejs/core": ["@cornerstonejs/core@3.8.4", "", { "dependencies": { "@kitware/vtk.js": "32.12.1", "comlink": "^4.4.1", "gl-matrix": "^3.4.3", "loglevel": "^1.9.2" } }, "sha512-8spQvERZRn4I/0EhzTNZ5ruPBrVn5F4msb/q0ttBtiVEa9JaVwoWh3yEcO4q5NpK7t1BhpfSPMKwU98Dy02ugQ=="],
|
||||
"@cornerstonejs/core": ["@cornerstonejs/core@3.8.5", "", { "dependencies": { "@kitware/vtk.js": "32.12.1", "comlink": "^4.4.1", "gl-matrix": "^3.4.3", "loglevel": "^1.9.2" } }, "sha512-x3LZwZUqhVldChpwtLqNwUWx8eXq9q1ndwrx5lVpnX4ZUT9qQIAro01Ih8DPiXlLb1NXbN3sG4e/x/TxuQ+5bw=="],
|
||||
|
||||
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.8.4", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.5", "comlink": "^4.4.1", "dicom-parser": "^1.8.9", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.4" } }, "sha512-N2D4U8JDhen+dCY1HrLOoK8W3CD2AAf/Cd+GiJMEGjtrihIuQ+Ha+hZTVQzaDMc7v2Ruvx2DpJ7i9HUJXoyWHw=="],
|
||||
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.8.5", "", { "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.8.5" } }, "sha512-yuLLU2sbtFWueNKVbeYBddPoJnfhrUQ+rRnXJb+y9iczwZMQLy4tYyk3Q5INo+/nIdwCVeZg7P9qORZERsBivw=="],
|
||||
|
||||
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@3.8.4", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.4", "@cornerstonejs/tools": "^3.8.4", "@kitware/vtk.js": "32.12.1" } }, "sha512-KKDTeVJg1s0trplkaHglBm0foQ3XL9BhsTfatFCTzgIOCT0wolrKbr+xAKIeZuBQifiFazDWpYaeBHRbkWEEMA=="],
|
||||
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@3.8.5", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.5", "@cornerstonejs/tools": "^3.8.5", "@kitware/vtk.js": "32.12.1" } }, "sha512-u80OTJE8eju+tZ+S/N1b/HTIw2mPjEE2o5u3T/7/w90FX7OaxJJiMYjPPfnSZWXjE2HhRu0gEAKHOU3dr8pL4w=="],
|
||||
|
||||
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.8.4", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.4", "@cornerstonejs/tools": "^3.8.4", "@kitware/vtk.js": "32.12.1" } }, "sha512-+3o280NOniUkG9GcWTeTi6x3qy8Rm7WpYFycWpxXIufJzVudJi0zAR+EIWDq/uvTQ3DH1SrBDe4wzYNqD+yirw=="],
|
||||
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.8.5", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.5", "@cornerstonejs/tools": "^3.8.5", "@kitware/vtk.js": "32.12.1" } }, "sha512-6L9a9f36CnUIuGsydA8kkkC0z1Gjid4KfclCQoYYaxcsLWLDpba5usFERmxCLIm9VyBsA3POqesIGDqWTgJmFg=="],
|
||||
|
||||
"@cornerstonejs/tools": ["@cornerstonejs/tools@3.8.4", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.4", "@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-yr8RLtnxGh9BsPOFpX+zwT++UGJDqWMo7noWGzxkuISDriLr+HGZI8gL910Mw62obaF8r9qpeOSvSOzEHN3nSw=="],
|
||||
"@cornerstonejs/tools": ["@cornerstonejs/tools@3.8.5", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.8.5", "@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-aTaziRHJakCCxvbHhbrSkEuiamURGakcK83KXGJPjSdStWC/2cVpkCzPxc8V7FZBIstsvo5pgciXSpNc6Yt7Og=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@ -305,9 +305,6 @@ const commandsModule = ({
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
loadSegmentationDisplaySetsForViewport: {
|
||||
commandFn: actions.loadSegmentationDisplaySetsForViewport,
|
||||
},
|
||||
loadSegmentationsForViewport: {
|
||||
commandFn: actions.loadSegmentationsForViewport,
|
||||
},
|
||||
|
||||
@ -47,7 +47,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
// States
|
||||
let selectedSegmentObjectIndex: number = 0;
|
||||
const selectedSegmentObjectIndex: number = 0;
|
||||
const { setPositionPresentation } = usePositionPresentationStore();
|
||||
|
||||
// Hydration means that the SEG is opened and segments are loaded into the
|
||||
@ -160,7 +160,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
displaySetInstanceUID: referencedDisplaySet.displaySetInstanceUID,
|
||||
});
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
|
||||
@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.8.4",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.5",
|
||||
"@ohif/core": "3.10.0-beta.148",
|
||||
"@ohif/ui": "3.10.0-beta.148",
|
||||
"dcmjs": "*",
|
||||
@ -51,12 +51,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/ai": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.8.4",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/ai": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.8.5",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@ -35,6 +35,7 @@ import { toolNames } from './initCornerstoneTools';
|
||||
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
||||
import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats';
|
||||
import { generateSegmentationCSVReport } from './utils/generateSegmentationCSVReport';
|
||||
import { getUpdatedViewportsForSegmentation } from './utils/hydrationUtils';
|
||||
|
||||
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
||||
const toggleSyncFunctions = {
|
||||
@ -495,7 +496,6 @@ function commandsModule({
|
||||
|
||||
downloadCSVSegmentationReport: ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
const cachedStats = segmentation.cachedStats;
|
||||
|
||||
const { representationData } = segmentation;
|
||||
const { Labelmap } = representationData;
|
||||
@ -1021,6 +1021,10 @@ function commandsModule({
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
if (!toolGroup.hasTool(toolName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevConfig = toolGroup?.getToolConfiguration(toolName);
|
||||
toolGroup?.setToolConfiguration(
|
||||
toolName,
|
||||
@ -1539,7 +1543,8 @@ function commandsModule({
|
||||
DefaultHistoryMemo.redo();
|
||||
},
|
||||
toggleSegmentPreviewEdit: ({ toggle }) => {
|
||||
const labelmapTools = getLabelmapTools({ toolGroupService });
|
||||
let labelmapTools = getLabelmapTools({ toolGroupService });
|
||||
labelmapTools = labelmapTools.filter(tool => !tool.toolName.includes('Eraser'));
|
||||
labelmapTools.forEach(tool => {
|
||||
tool.configuration = {
|
||||
...tool.configuration,
|
||||
@ -1562,7 +1567,8 @@ function commandsModule({
|
||||
});
|
||||
},
|
||||
toggleUseCenterSegmentIndex: ({ toggle }) => {
|
||||
const labelmapTools = getLabelmapTools({ toolGroupService });
|
||||
let labelmapTools = getLabelmapTools({ toolGroupService });
|
||||
labelmapTools = labelmapTools.filter(tool => !tool.toolName.includes('Eraser'));
|
||||
labelmapTools.forEach(tool => {
|
||||
tool.configuration = {
|
||||
...tool.configuration,
|
||||
@ -1707,6 +1713,20 @@ function commandsModule({
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation(activeViewportId);
|
||||
segmentationService.addSegment(activeSegmentation.segmentationId);
|
||||
},
|
||||
loadSegmentationDisplaySetsForViewport: ({ viewportId, displaySetInstanceUIDs }) => {
|
||||
const updatedViewports = getUpdatedViewportsForSegmentation({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
displaySetInstanceUIDs,
|
||||
});
|
||||
|
||||
updatedViewports.forEach(viewport => {
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId: viewport.viewportId,
|
||||
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@ -1985,6 +2005,7 @@ function commandsModule({
|
||||
increaseBrushSize: actions.increaseBrushSize,
|
||||
decreaseBrushSize: actions.decreaseBrushSize,
|
||||
addNewSegment: actions.addNewSegment,
|
||||
loadSegmentationDisplaySetsForViewport: actions.loadSegmentationDisplaySetsForViewport,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -303,7 +303,7 @@ function DicomUploadProgress({
|
||||
<span
|
||||
className={classNames(
|
||||
NO_WRAP_ELLIPSIS_CLASS_NAMES,
|
||||
'text-primary-active hover:text-primary-light active:text-aqua-pale ml-auto cursor-pointer'
|
||||
'text-primary hover:text-primary-light active:text-aqua-pale ml-auto cursor-pointer'
|
||||
)}
|
||||
onClick={cancelAllUploads}
|
||||
>
|
||||
|
||||
@ -89,7 +89,7 @@ const DicomUploadProgressItem = memo(
|
||||
)}
|
||||
<div className="ml-auto flex cursor-pointer">
|
||||
<Icons.Close
|
||||
className="text-primary-active self-center"
|
||||
className="text-primary self-center"
|
||||
onClick={cancelUpload}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -82,6 +82,7 @@ const stackRetrieveOptions = {
|
||||
},
|
||||
};
|
||||
|
||||
const unsubscriptions = [];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ -142,6 +143,8 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
useToggleOneUpViewportGridStore.getState().clearToggleOneUpViewportGridStore();
|
||||
useSegmentationPresentationStore.getState().clearSegmentationPresentationStore();
|
||||
segmentationService.removeAllSegmentations();
|
||||
|
||||
unsubscriptions.forEach(unsubscribe => unsubscribe());
|
||||
},
|
||||
|
||||
/**
|
||||
@ -149,7 +152,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
*
|
||||
* @param configuration.csToolsConfig - Passed directly to `initCornerstoneTools`
|
||||
*/
|
||||
preRegistration: function (props: Types.Extensions.ExtensionParams): Promise<void> {
|
||||
preRegistration: async function (props: Types.Extensions.ExtensionParams): Promise<void> {
|
||||
const { servicesManager, serviceProvidersManager } = props;
|
||||
servicesManager.registerService(CornerstoneViewportService.REGISTRATION);
|
||||
servicesManager.registerService(ToolGroupService.REGISTRATION);
|
||||
@ -167,7 +170,13 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
const { syncGroupService } = servicesManager.services;
|
||||
syncGroupService.registerCustomSynchronizer('frameview', createFrameViewSynchronizer);
|
||||
|
||||
return init.call(this, props);
|
||||
const initResult = await init.call(this, props);
|
||||
|
||||
unsubscriptions.push(...initResult.unsubscriptions);
|
||||
|
||||
return {
|
||||
...initResult,
|
||||
};
|
||||
},
|
||||
getToolbarModule,
|
||||
getHangingProtocolModule,
|
||||
|
||||
@ -193,13 +193,14 @@ export default async function init({
|
||||
});
|
||||
|
||||
// Setup segmentation event handlers
|
||||
setupSegmentationDataModifiedHandler({
|
||||
segmentationService,
|
||||
customizationService,
|
||||
commandsManager,
|
||||
});
|
||||
const { unsubscribe: unsubscribeSegmentationDataModifiedHandler } =
|
||||
setupSegmentationDataModifiedHandler({
|
||||
segmentationService,
|
||||
customizationService,
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
setupSegmentationModifiedHandler({
|
||||
const { unsubscribe: unsubscribeSegmentationModifiedHandler } = setupSegmentationModifiedHandler({
|
||||
segmentationService,
|
||||
});
|
||||
|
||||
@ -299,6 +300,13 @@ export default async function init({
|
||||
|
||||
// Call this function when initializing
|
||||
initializeWebWorkerProgressHandler(servicesManager.services.uiNotificationService);
|
||||
|
||||
const unsubscriptions = [
|
||||
unsubscribeSegmentationDataModifiedHandler,
|
||||
unsubscribeSegmentationModifiedHandler,
|
||||
];
|
||||
|
||||
return { unsubscriptions };
|
||||
}
|
||||
|
||||
function initializeWebWorkerProgressHandler(uiNotificationService) {
|
||||
|
||||
@ -2,12 +2,10 @@ import React from 'react';
|
||||
import { SegmentationTable } from '@ohif/ui-next';
|
||||
import { useActiveViewportSegmentationRepresentations } from '../hooks/useActiveViewportSegmentationRepresentations';
|
||||
import { metaData } from '@cornerstonejs/core';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
export default function PanelSegmentation({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
children,
|
||||
}: withAppTypes) {
|
||||
export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
const { commandsManager, servicesManager } = useSystem();
|
||||
const { customizationService, displaySetService } = servicesManager.services;
|
||||
|
||||
const { segmentationsWithRepresentations, disabled } =
|
||||
|
||||
@ -427,6 +427,8 @@ class SegmentationService extends PubSubService {
|
||||
...metaData.get('instance', image.referencedImageId),
|
||||
}));
|
||||
|
||||
segDisplaySet.imageIds = derivedImageIds;
|
||||
|
||||
// We should parse the segmentation as separate slices to support overlapping segments.
|
||||
// This parsing should occur in the CornerstoneJS library adapters.
|
||||
// For now, we use the volume returned from the library and chop it here.
|
||||
@ -513,7 +515,7 @@ class SegmentationService extends PubSubService {
|
||||
type: LABELMAP,
|
||||
data: {
|
||||
imageIds: derivedImageIds,
|
||||
referencedVolumeId: this._getVolumeIdForDisplaySet(referencedDisplaySet),
|
||||
// referencedVolumeId: this._getVolumeIdForDisplaySet(referencedDisplaySet),
|
||||
referencedImageIds: imageIds as string[],
|
||||
},
|
||||
},
|
||||
@ -566,9 +568,10 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
const referencedImageIds = referencedDisplaySet.imageIds;
|
||||
// find the first image id that contains a referenced SOP instance UID
|
||||
const firstSegmentedSliceImageId = referencedImageIds.find(imageId =>
|
||||
referencedImageIdsWithGeometry.some(referencedId => imageId.includes(referencedId))
|
||||
);
|
||||
const firstSegmentedSliceImageId =
|
||||
referencedImageIds?.find(imageId =>
|
||||
referencedImageIdsWithGeometry.some(referencedId => imageId.includes(referencedId))
|
||||
) || null;
|
||||
|
||||
rtDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId;
|
||||
// Map ROI contours to RT Struct Data
|
||||
|
||||
@ -742,6 +742,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// If you call loadStudyMetadata and it's not in the DicomMetadataStore cache, it should fire
|
||||
// a request through the data source?
|
||||
// (This call may or may not create sub-requests for series metadata)
|
||||
const { displaySetService } = this.servicesManager.services;
|
||||
const volumeInputArray = [];
|
||||
const displaySetOptionsArray = viewportInfo.getDisplaySetOptions();
|
||||
const { hangingProtocolService } = this.servicesManager.services;
|
||||
@ -750,7 +751,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const displaySetInstanceUIDs = [];
|
||||
|
||||
for (const [index, data] of viewportData.data.entries()) {
|
||||
const { volume, imageIds, displaySetInstanceUID } = data;
|
||||
const { imageIds, displaySetInstanceUID } = data;
|
||||
let volume = data.volume;
|
||||
|
||||
if (!volume) {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
volume = csToolsUtils.getOrCreateImageVolume(displaySet.images.map(image => image.imageId));
|
||||
}
|
||||
|
||||
displaySetInstanceUIDs.push(displaySetInstanceUID);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import html2canvas from 'html2canvas';
|
||||
import { getEnabledElement, StackViewport, BaseVolumeViewport } from '@cornerstonejs/core';
|
||||
import { ToolGroupManager } from '@cornerstonejs/tools';
|
||||
import { ToolGroupManager, segmentation, Enums } from '@cornerstonejs/tools';
|
||||
import { getEnabledElement as OHIFgetEnabledElement } from '../state';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
@ -103,6 +103,9 @@ const CornerstoneViewportDownloadForm = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const segmentationRepresentations =
|
||||
segmentation.state.getViewportSegmentationRepresentations(activeViewportId);
|
||||
|
||||
const { viewport } = activeViewportEnabledElement;
|
||||
const downloadViewport = renderingEngine.getViewport(VIEWPORT_ID);
|
||||
|
||||
@ -113,20 +116,48 @@ const CornerstoneViewportDownloadForm = ({
|
||||
|
||||
await downloadViewport.setStack([imageId]);
|
||||
downloadViewport.setProperties(properties);
|
||||
|
||||
return {
|
||||
width: Math.min(width || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
height: Math.min(height || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
};
|
||||
} else if (downloadViewport instanceof BaseVolumeViewport) {
|
||||
const volumeIds = viewport.getAllVolumeIds();
|
||||
downloadViewport.setVolumes([{ volumeId: volumeIds[0] }]);
|
||||
|
||||
return {
|
||||
width: Math.min(width || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
height: Math.min(height || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
};
|
||||
}
|
||||
|
||||
if (segmentationRepresentations.length > 0) {
|
||||
segmentationRepresentations.forEach(segRepresentation => {
|
||||
const { segmentationId, colorLUTIndex, type } = segRepresentation;
|
||||
if (type === Enums.SegmentationRepresentations.Labelmap) {
|
||||
segmentation.addLabelmapRepresentationToViewportMap({
|
||||
[downloadViewport.id]: [
|
||||
{
|
||||
segmentationId,
|
||||
type: Enums.SegmentationRepresentations.Labelmap,
|
||||
config: {
|
||||
colorLUTOrIndex: colorLUTIndex,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (type === Enums.SegmentationRepresentations.Contour) {
|
||||
segmentation.addContourRepresentationToViewportMap({
|
||||
[downloadViewport.id]: [
|
||||
{
|
||||
segmentationId,
|
||||
type: Enums.SegmentationRepresentations.Contour,
|
||||
config: {
|
||||
colorLUTOrIndex: colorLUTIndex,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
width: Math.min(width || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
height: Math.min(height || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading image:', error);
|
||||
}
|
||||
@ -167,6 +198,10 @@ const CornerstoneViewportDownloadForm = ({
|
||||
setTimeout(() => {
|
||||
handleLoadImage(viewportDimensions.width, viewportDimensions.height);
|
||||
handleToggleAnnotations(showAnnotations);
|
||||
// we need a resize here to make suer annotations world to canvas
|
||||
// are properly calculated
|
||||
renderingEngine.resize();
|
||||
renderingEngine.render();
|
||||
}, 100);
|
||||
}
|
||||
}, [viewportDimensions, showAnnotations]);
|
||||
|
||||
31
extensions/cornerstone/src/utils/hydrationUtils.ts
Normal file
@ -0,0 +1,31 @@
|
||||
function getUpdatedViewportsForSegmentation({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
displaySetInstanceUIDs,
|
||||
}: withAppTypes) {
|
||||
const { hangingProtocolService, viewportGridService } = servicesManager.services;
|
||||
|
||||
const { isHangingProtocolLayout } = viewportGridService.getState();
|
||||
|
||||
const viewport = getTargetViewport({ viewportId, viewportGridService });
|
||||
const targetViewportId = viewport.viewportOptions.viewportId;
|
||||
|
||||
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
targetViewportId,
|
||||
displaySetInstanceUIDs[0],
|
||||
isHangingProtocolLayout
|
||||
);
|
||||
|
||||
return updatedViewports.filter(v => v.viewportOptions?.viewportType !== 'volume3d');
|
||||
}
|
||||
|
||||
const getTargetViewport = ({ viewportId, viewportGridService }) => {
|
||||
const { viewports, activeViewportId } = viewportGridService.getState();
|
||||
const targetViewportId = viewportId || activeViewportId;
|
||||
|
||||
const viewport = viewports.get(targetViewportId);
|
||||
|
||||
return viewport;
|
||||
};
|
||||
|
||||
export { getUpdatedViewportsForSegmentation };
|
||||
@ -9,10 +9,15 @@ export function setupSegmentationDataModifiedHandler({
|
||||
customizationService,
|
||||
commandsManager,
|
||||
}) {
|
||||
segmentationService.subscribeDebounced(
|
||||
const { unsubscribe } = segmentationService.subscribeDebounced(
|
||||
segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
|
||||
async ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
if (!segmentation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const readableText = customizationService.getCustomization('panelSegmentation.readableText');
|
||||
|
||||
// Check for segments with bidirectional measurements and update them
|
||||
@ -46,17 +51,23 @@ export function setupSegmentationDataModifiedHandler({
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
||||
return { unsubscribe };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the handler for segmentation modification events
|
||||
*/
|
||||
export function setupSegmentationModifiedHandler({ segmentationService }) {
|
||||
segmentationService.subscribe(
|
||||
const { unsubscribe } = segmentationService.subscribe(
|
||||
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
|
||||
async ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
if (!segmentation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const annotationState = cornerstoneTools.annotation.state.getAllAnnotations();
|
||||
const bidirectionalAnnotations = annotationState.filter(
|
||||
annotation =>
|
||||
@ -90,4 +101,6 @@ export function setupSegmentationModifiedHandler({ segmentationService }) {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return { unsubscribe };
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ function DataSourceConfigurationModalComponent({
|
||||
: 'border border-dashed border-secondary-light';
|
||||
|
||||
const getSelectedItemTextClasses = itemIndex =>
|
||||
itemIndex <= selectedItems.length ? 'text-primary-light' : 'text-primary-active';
|
||||
itemIndex <= selectedItems.length ? 'text-primary-light' : 'text-primary';
|
||||
|
||||
const getErrorComponent = (): ReactElement => {
|
||||
return (
|
||||
|
||||
@ -20,7 +20,7 @@ function PanelStudyBrowserHeader({
|
||||
<div className={'flex h-[24px] w-full select-none justify-center self-center text-[14px]'}>
|
||||
<div className="flex w-full items-center gap-[10px]">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="text-primary-active flex items-center space-x-1">
|
||||
<div className="text-primary flex items-center space-x-1">
|
||||
{actionIcons.map((icon: actionIcon, index) =>
|
||||
React.createElement(Icons[icon.iconName] || Icons.MissingIcon, {
|
||||
key: index,
|
||||
|
||||
@ -44,9 +44,9 @@ function HeaderPatientInfo({ servicesManager, appConfig }: withAppTypes) {
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
{isMixedPatients ? (
|
||||
<Icons.MultiplePatients className="text-primary-active" />
|
||||
<Icons.MultiplePatients className="text-primary" />
|
||||
) : (
|
||||
<Icons.Patient className="text-primary-active" />
|
||||
<Icons.Patient className="text-primary" />
|
||||
)}
|
||||
<div className="flex flex-col justify-center">
|
||||
{expanded ? (
|
||||
@ -61,12 +61,12 @@ function HeaderPatientInfo({ servicesManager, appConfig }: withAppTypes) {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-primary-active self-center text-[13px]">
|
||||
<div className="text-primary self-center text-[13px]">
|
||||
{isMixedPatients ? 'Multiple Patients' : 'Patient'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Icons.ArrowLeft className={`text-primary-active ${expanded ? 'rotate-180' : ''}`} />
|
||||
<Icons.ArrowLeft className={`text-primary ${expanded ? 'rotate-180' : ''}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
|
||||
)
|
||||
}
|
||||
UndoRedo={
|
||||
<div className="text-primary-active flex cursor-pointer items-center">
|
||||
<div className="text-primary flex cursor-pointer items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hover:bg-primary-dark"
|
||||
|
||||
@ -85,7 +85,7 @@ export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; t
|
||||
{CustomConfigComponent && (
|
||||
<div className="ml-auto mr-2">
|
||||
<Icons.Settings
|
||||
className="text-primary-active h-4 w-4"
|
||||
className="text-primary h-4 w-4"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setShowConfig(!showConfig);
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"@ohif/core": "3.10.0-beta.148",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.148",
|
||||
"@ohif/extension-default": "3.10.0-beta.148",
|
||||
|
||||
@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DicomMetadataStore } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Input, Label, PanelSection, Separator } from '@ohif/ui-next';
|
||||
import { Button, Input, Label, PanelSection } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
const DEFAULT_MEATADATA = {
|
||||
PatientWeight: null,
|
||||
@ -37,7 +38,10 @@ const InputRow = ({ children, className, ...props }) => {
|
||||
|
||||
// InputRow sub-components
|
||||
InputRow.Label = ({ children, unit, className, ...props }) => (
|
||||
<Label className={`min-w-32 flex-shrink-0 ${className || ''}`} {...props}>
|
||||
<Label
|
||||
className={`min-w-32 flex-shrink-0 ${className || ''}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{unit && <span className="text-muted-foreground"> {unit}</span>}
|
||||
</Label>
|
||||
@ -54,7 +58,8 @@ InputRow.Input = ({ className, ...props }) => (
|
||||
InputRow.Label.displayName = 'InputRow.Label';
|
||||
InputRow.Input.displayName = 'InputRow.Input';
|
||||
|
||||
export default function PanelPetSUV({ servicesManager, commandsManager }: withAppTypes) {
|
||||
export default function PanelPetSUV({ servicesManager }: withAppTypes) {
|
||||
const { commandsManager } = useSystem();
|
||||
const { t } = useTranslation('PanelSUV');
|
||||
const { displaySetService, toolGroupService, toolbarService, hangingProtocolService } =
|
||||
servicesManager.services;
|
||||
@ -192,7 +197,9 @@ export default function PanelPetSUV({ servicesManager, commandsManager }: withAp
|
||||
<InputRow>
|
||||
<InputRow.Label unit="bq">{t('Total Dose')}</InputRow.Label>
|
||||
<InputRow.Input
|
||||
value={metadata.RadiopharmaceuticalInformationSequence.RadionuclideTotalDose || ''}
|
||||
value={
|
||||
metadata.RadiopharmaceuticalInformationSequence.RadionuclideTotalDose || ''
|
||||
}
|
||||
onChange={e => {
|
||||
handleMetadataChange({
|
||||
RadiopharmaceuticalInformationSequence: {
|
||||
@ -206,7 +213,9 @@ export default function PanelPetSUV({ servicesManager, commandsManager }: withAp
|
||||
<InputRow>
|
||||
<InputRow.Label unit="s">{t('Half Life')}</InputRow.Label>
|
||||
<InputRow.Input
|
||||
value={metadata.RadiopharmaceuticalInformationSequence.RadionuclideHalfLife || ''}
|
||||
value={
|
||||
metadata.RadiopharmaceuticalInformationSequence.RadionuclideHalfLife || ''
|
||||
}
|
||||
onChange={e => {
|
||||
handleMetadataChange({
|
||||
RadiopharmaceuticalInformationSequence: {
|
||||
@ -221,7 +230,8 @@ export default function PanelPetSUV({ servicesManager, commandsManager }: withAp
|
||||
<InputRow.Label unit="s">{t('Injection Time')}</InputRow.Label>
|
||||
<InputRow.Input
|
||||
value={
|
||||
metadata.RadiopharmaceuticalInformationSequence.RadiopharmaceuticalStartTime || ''
|
||||
metadata.RadiopharmaceuticalInformationSequence
|
||||
.RadiopharmaceuticalStartTime || ''
|
||||
}
|
||||
onChange={e => {
|
||||
handleMetadataChange({
|
||||
@ -269,4 +279,4 @@ PanelPetSUV.propTypes = {
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,11 +2,10 @@ import React, { useEffect } from 'react';
|
||||
import { useActiveViewportSegmentationRepresentations } from '@ohif/extension-cornerstone';
|
||||
import { handleROIThresholding } from '../../utils/handleROIThresholding';
|
||||
import { debounce } from '@ohif/core/src/utils';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
export default function PanelRoiThresholdSegmentation({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
}: withAppTypes) {
|
||||
export default function PanelRoiThresholdSegmentation() {
|
||||
const { commandsManager, servicesManager } = useSystem();
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const { segmentationsWithRepresentations: segmentationsInfo } =
|
||||
useActiveViewportSegmentationRepresentations({ servicesManager });
|
||||
|
||||
@ -1,62 +1,13 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
PanelSegmentation,
|
||||
useActiveViewportSegmentationRepresentations,
|
||||
} from '@ohif/extension-cornerstone';
|
||||
import { Button, Icons } from '@ohif/ui-next';
|
||||
import { PanelSegmentation } from '@ohif/extension-cornerstone';
|
||||
import PanelROIThresholdExport from './PanelROIThresholdSegmentation/PanelROIThresholdExport';
|
||||
|
||||
export default function PanelTMTV({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
configuration,
|
||||
}: withAppTypes) {
|
||||
export default function PanelTMTV({ configuration }: withAppTypes) {
|
||||
return (
|
||||
<>
|
||||
<PanelSegmentation
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
extensionManager={extensionManager}
|
||||
configuration={configuration}
|
||||
>
|
||||
<ExportCSV
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
<PanelSegmentation configuration={configuration}>
|
||||
<PanelROIThresholdExport />
|
||||
</PanelSegmentation>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const ExportCSV = ({ servicesManager, commandsManager }: withAppTypes) => {
|
||||
const { segmentationsWithRepresentations: representations } =
|
||||
useActiveViewportSegmentationRepresentations({ servicesManager });
|
||||
|
||||
const tmtv = representations[0]?.segmentation.cachedStats?.tmtv;
|
||||
|
||||
const segmentations = representations.map(representation => representation.segmentation);
|
||||
|
||||
if (!segmentations.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-8 w-full items-center rounded pr-0.5">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="pl-1.5"
|
||||
onClick={() => {
|
||||
commandsManager.runCommand('exportTMTVReportCSV', {
|
||||
segmentations,
|
||||
tmtv,
|
||||
config: {},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icons.Download />
|
||||
<span className="pl-1">CSV</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,7 +8,6 @@ import createAndDownloadTMTVReport from './utils/createAndDownloadTMTVReport';
|
||||
|
||||
import dicomRTAnnotationExport from './utils/dicomRTAnnotationExport/RTStructureSet';
|
||||
|
||||
import { getWebWorkerManager } from '@cornerstonejs/core';
|
||||
import { Enums } from '@cornerstonejs/tools';
|
||||
import { utils } from '@ohif/core';
|
||||
|
||||
@ -22,32 +21,6 @@ const ROI_THRESHOLD_MANUAL_TOOL_IDS = [
|
||||
'CircleROIStartEndThreshold',
|
||||
];
|
||||
|
||||
const workerManager = getWebWorkerManager();
|
||||
|
||||
// Register the task
|
||||
|
||||
function getVolumesFromSegmentation(segmentationId) {
|
||||
const csSegmentation = csTools.segmentation.state.getSegmentation(segmentationId);
|
||||
const labelmapData = csSegmentation.representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
] as csTools.Types.LabelmapToolOperationDataVolume;
|
||||
|
||||
const { volumeId, referencedVolumeId } = labelmapData;
|
||||
const labelmapVolume = cs.cache.getVolume(volumeId);
|
||||
const referencedVolume = cs.cache.getVolume(referencedVolumeId);
|
||||
|
||||
return { labelmapVolume, referencedVolume };
|
||||
}
|
||||
|
||||
function getLabelmapVolumeFromSegmentation(segmentation) {
|
||||
const { representationData } = segmentation;
|
||||
const { volumeId } = representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
] as csTools.Types.LabelmapToolOperationDataVolume;
|
||||
|
||||
return cs.cache.getVolume(volumeId);
|
||||
}
|
||||
|
||||
const commandsModule = ({ servicesManager, commandsManager, extensionManager }: withAppTypes) => {
|
||||
const {
|
||||
viewportGridService,
|
||||
@ -265,8 +238,14 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
|
||||
segmentations,
|
||||
});
|
||||
|
||||
let total_tlg = 0;
|
||||
for (const segmentationId in segReport) {
|
||||
const report = segReport[segmentationId];
|
||||
const tlg = report['namedStats_lesionGlycolysis'];
|
||||
total_tlg += tlg.value;
|
||||
}
|
||||
const additionalReportRows = [
|
||||
{ key: 'Total Lesion Glycolysis', value: { tlg: tlg.toFixed(4) } },
|
||||
{ key: 'Total Lesion Glycolysis', value: { tlg: total_tlg.toFixed(4) } },
|
||||
{ key: 'Threshold Configuration', value: { ...config } },
|
||||
];
|
||||
|
||||
@ -382,9 +361,8 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
|
||||
continue;
|
||||
}
|
||||
|
||||
const referencedVolumeId = labelmapVolume.referencedVolumeId;
|
||||
|
||||
const referencedVolume = cs.cache.getVolume(referencedVolumeId);
|
||||
const referencedVolume =
|
||||
csTools.utilities.segmentation.getReferenceVolumeForSegmentationVolume(segmentationId);
|
||||
|
||||
if (!referencedVolume) {
|
||||
report[id] = segReport;
|
||||
@ -476,9 +454,6 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
|
||||
thresholdSegmentationByRectangleROITool: {
|
||||
commandFn: actions.thresholdSegmentationByRectangleROITool,
|
||||
},
|
||||
getTotalLesionGlycolysis: {
|
||||
commandFn: actions.getTotalLesionGlycolysis,
|
||||
},
|
||||
calculateTMTV: {
|
||||
commandFn: actions.calculateTMTV,
|
||||
},
|
||||
|
||||
@ -5,13 +5,7 @@ import PanelTMTV from './Panels/PanelTMTV';
|
||||
|
||||
function getPanelModule({ commandsManager, extensionManager, servicesManager }) {
|
||||
const wrappedPanelPetSuv = () => {
|
||||
return (
|
||||
<PanelPetSUV
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
/>
|
||||
);
|
||||
return <PanelPetSUV />;
|
||||
};
|
||||
|
||||
const wrappedROIThresholdToolbox = () => {
|
||||
@ -24,12 +18,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
};
|
||||
|
||||
const wrappedROIThresholdExport = () => {
|
||||
return (
|
||||
<PanelROIThresholdExport
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
);
|
||||
return <PanelROIThresholdExport />;
|
||||
};
|
||||
|
||||
const wrappedPanelTMTV = () => {
|
||||
@ -43,10 +32,6 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
<PanelROIThresholdExport
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,10 +3,12 @@ import type { Button } from '@ohif/core/types';
|
||||
import { EVENTS } from '@cornerstonejs/core';
|
||||
import { ViewportGridService } from '@ohif/core';
|
||||
|
||||
const ReferenceLinesListeners = [
|
||||
const callbacks = (toolName: string) => [
|
||||
{
|
||||
commandName: 'setSourceViewportForReferenceLinesTool',
|
||||
context: 'CORNERSTONE',
|
||||
commandName: 'setViewportForToolConfiguration',
|
||||
commandOptions: {
|
||||
toolName,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -118,8 +120,8 @@ const toolbarButtons: Button[] = [
|
||||
tooltip: 'Show Reference Lines',
|
||||
commands: 'toggleEnabledDisabledToolbar',
|
||||
listeners: {
|
||||
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners,
|
||||
[ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners,
|
||||
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: callbacks('ReferenceLinesTool'),
|
||||
[ViewportGridService.EVENTS.VIEWPORTS_READY]: callbacks('ReferenceLinesTool'),
|
||||
},
|
||||
evaluate: [
|
||||
'evaluate.cornerstoneTool.toggle',
|
||||
|
||||
@ -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.8.4",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.5",
|
||||
"@emotion/serialize": "^1.1.3",
|
||||
"@ohif/core": "3.10.0-beta.148",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.148",
|
||||
|
||||
@ -15,7 +15,7 @@ function Debug() {
|
||||
/>
|
||||
<div className="space-y-2 pt-4 text-center">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<p className="text-primary-active mt-4 text-xl font-semibold">Debug Information</p>
|
||||
<p className="text-primary mt-4 text-xl font-semibold">Debug Information</p>
|
||||
<div className="mt-4 flex items-center space-x-2">
|
||||
<p className="text-md text-white">Cross Origin Isolated (COOP/COEP)</p>
|
||||
<Icons.ByName
|
||||
|
||||
@ -343,7 +343,7 @@ function WorkList({
|
||||
<>
|
||||
<Icons.GroupLayers
|
||||
className={classnames('mr-2 inline-flex w-4', {
|
||||
'text-primary-active': isExpanded,
|
||||
'text-primary': isExpanded,
|
||||
'text-secondary-light': !isExpanded,
|
||||
})}
|
||||
/>
|
||||
|
||||
@ -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.8.4",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.5",
|
||||
"@ohif/ui": "3.10.0-beta.148",
|
||||
"cornerstone-math": "0.1.9",
|
||||
"dicom-parser": "^1.8.21"
|
||||
|
||||
503
platform/docs/src/pages/patterns/patterns-segmentation-raw.tsx
Normal file
@ -0,0 +1,503 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { DataRow } from '../../../../ui-next/src/components/DataRow';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import {
|
||||
Select,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from '../../../../ui-next/src/components/Select';
|
||||
import { Icons } from '../../../../ui-next/src/components/Icons';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuPortal,
|
||||
} from '../../../../ui-next/src/components/DropdownMenu';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '../../../../ui-next/src/components/Accordion';
|
||||
import { Slider } from '../../../../ui-next/src/components/Slider';
|
||||
import { Switch } from '../../../../ui-next/src/components/Switch';
|
||||
import { Label } from '../../../../ui-next/src/components/Label';
|
||||
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';
|
||||
import { DataItem, ListGroup } from '../../../../ui-next/assets/data';
|
||||
export default function SegmentationPanel() {
|
||||
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
|
||||
const [selectedTab, setSelectedTab] = useState<string>('Fill & Outline');
|
||||
const handleAction = (id: string, action: string) => {
|
||||
console.log(`Action "${action}" triggered for item with id: ${id}`);
|
||||
// Implement actual action logic here
|
||||
};
|
||||
|
||||
// Handle row selection
|
||||
const handleRowSelect = (id: string) => {
|
||||
setSelectedRowId(prevSelectedId => (prevSelectedId === id ? null : id));
|
||||
};
|
||||
|
||||
const organSegmentationGroup = dataList.find(
|
||||
(listGroup: any) => listGroup.type === 'Organ Segmentation'
|
||||
) as unknown as ListGroup;
|
||||
|
||||
if (!organSegmentationGroup) {
|
||||
return <div className="text-red-500">Organ Segmentation data not found.</div>;
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<div
|
||||
className="h-2.5 w-2.5 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: item.colorHex }}
|
||||
></div>
|
||||
<h3 className="text-muted-foreground break-words text-lg font-semibold">{item.title}</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid X</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidX.value}</span>{' '}
|
||||
<span className="">{stats.centroidX.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid Y</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidY.value}</span>{' '}
|
||||
<span className="">{stats.centroidY.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid Z</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidZ.value}</span>{' '}
|
||||
<span className="">{stats.centroidZ.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Frame Duration</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.frameDuration.value}</span>{' '}
|
||||
<span className="">{stats.frameDuration.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Kurtosis</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.kurtosis.value}</span>{' '}
|
||||
<span className="">{stats.kurtosis.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Max</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.max.value}</span>{' '}
|
||||
<span className="">{stats.max.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Max Slice</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.maxSlice.value}</span>{' '}
|
||||
<span className="">{stats.maxSlice.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Mean</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.mean.value}</span>{' '}
|
||||
<span className="">{stats.mean.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Median</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.median.value}</span>{' '}
|
||||
<span className="">{stats.median.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Min</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.min.value}</span>{' '}
|
||||
<span className="">{stats.min.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Regions</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.regions.value}</span>{' '}
|
||||
<span className="">{stats.regions.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Skewness</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.skewness.value}</span>{' '}
|
||||
<span className="">{stats.skewness.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Sphere Diameter</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.sphereDiameter.value}</span>{' '}
|
||||
<span className="">{stats.sphereDiameter.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Standard Deviation</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.standardDeviation.value}</span>{' '}
|
||||
<span className="">{stats.standardDeviation.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">SUV Peak</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.suvPeak.value}</span>{' '}
|
||||
<span className="">{stats.suvPeak.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Total</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.total.value}</span>{' '}
|
||||
<span className="">{stats.total.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Glycolysis</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.glycolysis.value}</span>{' '}
|
||||
<span className="">{stats.glycolysis.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Volume</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.volume.value}</span>{' '}
|
||||
<span className="">{stats.volume.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Voxel Count</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.voxelCount.value}</span>{' '}
|
||||
<span className="">{stats.voxelCount.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-auto flex min-h-screen w-full justify-center bg-black py-12">
|
||||
<div className="w-64 space-y-0">
|
||||
<TooltipProvider>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
defaultValue={['segmentation-tools', 'segmentation-list']}
|
||||
>
|
||||
{/* Segmentation Tools */}
|
||||
<AccordionItem value="segmentation-tools">
|
||||
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
|
||||
<span>Segmentation Tools</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Segmentation List */}
|
||||
<AccordionItem value="segmentation-list">
|
||||
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
|
||||
<span>Segmentation List</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="mb-0">
|
||||
{/* Header Controls */}
|
||||
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.More className="h-6 w-6" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem>
|
||||
<Icons.Add className="text-foreground" />
|
||||
<span className="pl-2">Create New Segmentation</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Manage Current Segmentation</DropdownMenuLabel>
|
||||
<DropdownMenuItem>
|
||||
<Icons.Series className="text-foreground" />
|
||||
<span className="pl-2">Remove from Viewport</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span className="pl-2">Rename</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">Export & Download</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
|
||||
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
|
||||
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Icons.Delete className="text-red-600" />
|
||||
<span className="pl-2 text-red-600">Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Select>
|
||||
<SelectTrigger className="w-full overflow-hidden">
|
||||
<SelectValue placeholder="Segmentation 1" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="seg1">Segmentation 1</SelectItem>
|
||||
<SelectItem value="seg2">Segmentation 2</SelectItem>
|
||||
<SelectItem value="seg3">Segmentation Long Name 123</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.Info className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Appearance Settings */}
|
||||
<AccordionItem value="segmentation-display">
|
||||
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
|
||||
<div className="flex space-x-2">
|
||||
<Icons.Controls className="text-primary" />
|
||||
<span className="text-primary pr-1">Appearance Settings</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-1.5 pt-0.5 pb-3">
|
||||
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
|
||||
{/* Display Label with Selected Tab */}
|
||||
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
|
||||
{/* Tabs Controls */}
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
onValueChange={setSelectedTab}
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="Fill & Outline">
|
||||
<Icons.DisplayFillAndOutline className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Outline Only">
|
||||
<Icons.DisplayOutlineOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Fill Only">
|
||||
<Icons.DisplayFillOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
{/* Opacity Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Opacity
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[85]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="85"
|
||||
/>
|
||||
</div>
|
||||
{/* Border Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Border
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[10]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="2"
|
||||
/>
|
||||
</div>
|
||||
{/* Sync Changes Switch */}
|
||||
<div className="my-2 flex items-center pl-1 pb-1">
|
||||
<Switch defaultChecked />
|
||||
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Sync changes in all viewports
|
||||
</Label>
|
||||
</div>
|
||||
<div className="border-input w-full border"></div>
|
||||
{/* Display Inactive Segmentations Switch */}
|
||||
<div className="my-2 flex items-center pl-1">
|
||||
<Switch defaultChecked />
|
||||
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Display inactive segmentations
|
||||
</Label>
|
||||
</div>
|
||||
{/* Additional Opacity Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Opacity
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[65]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="65"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
{/* Action Buttons */}
|
||||
<div className="my-px flex h-9 w-full items-center justify-between rounded pl-0.5 pr-7">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="pr pl-0.5"
|
||||
>
|
||||
<Icons.Add />
|
||||
Add Segment
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<Icons.Hide className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Data Rows */}
|
||||
<div className="space-y-px">
|
||||
{organSegmentationGroup.items.map((item, index) => {
|
||||
const compositeId = `${organSegmentationGroup.type}-${item.id}-panel`; // Ensure unique composite ID
|
||||
return (
|
||||
<HoverCard
|
||||
key={`hover-${compositeId}`}
|
||||
openDelay={300}
|
||||
closeDelay={200}
|
||||
// open={true}
|
||||
>
|
||||
<HoverCardTrigger asChild>
|
||||
<div>
|
||||
<DataRow
|
||||
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
|
||||
number={index + 1}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
colorHex={item.colorHex}
|
||||
details={item.details || { primary: [], secondary: [] }}
|
||||
actionOptions={
|
||||
actionOptionsMap[organSegmentationGroup.type] || ['Action']
|
||||
}
|
||||
onAction={(action: string) => 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}
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="w-72 border"
|
||||
>
|
||||
{renderStatisticsPanel(item)}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -2,538 +2,72 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { DataRow } from '../../../../ui-next/src/components/DataRow';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import {
|
||||
Select,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from '../../../../ui-next/src/components/Select';
|
||||
import { Icons } from '../../../../ui-next/src/components/Icons';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuPortal,
|
||||
} from '../../../../ui-next/src/components/DropdownMenu';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '../../../../ui-next/src/components/Accordion';
|
||||
import { Slider } from '../../../../ui-next/src/components/Slider';
|
||||
import { Switch } from '../../../../ui-next/src/components/Switch';
|
||||
import { Label } from '../../../../ui-next/src/components/Label';
|
||||
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 { panelSegmentationData } from '../../../../ui-next/assets/data';
|
||||
import { SegmentationTable } from '../../../../ui-next/src/components/SegmentationTable';
|
||||
import { TooltipProvider } from '../../../../ui-next/src/components/Tooltip';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardTrigger,
|
||||
HoverCardContent,
|
||||
} from '../../../../ui-next/src/components/HoverCard';
|
||||
|
||||
interface DataItem {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
optionalField?: string;
|
||||
colorHex?: 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 {
|
||||
type: string;
|
||||
items: DataItem[];
|
||||
}
|
||||
|
||||
export default function SegmentationPanel() {
|
||||
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
|
||||
const [selectedTab, setSelectedTab] = useState<string>('Fill & Outline');
|
||||
const handleAction = (id: string, action: string) => {
|
||||
console.log(`Action "${action}" triggered for item with id: ${id}`);
|
||||
// Implement actual action logic here
|
||||
const tableProps = panelSegmentationData;
|
||||
tableProps.mode = 'expanded';
|
||||
|
||||
const renderSegments = () => {
|
||||
return (
|
||||
<SegmentationTable.Segments>
|
||||
<SegmentationTable.SegmentStatistics.Header></SegmentationTable.SegmentStatistics.Header>
|
||||
<SegmentationTable.SegmentStatistics.Body />
|
||||
</SegmentationTable.Segments>
|
||||
);
|
||||
};
|
||||
|
||||
// Handle row selection
|
||||
const handleRowSelect = (id: string) => {
|
||||
setSelectedRowId(prevSelectedId => (prevSelectedId === id ? null : id));
|
||||
};
|
||||
|
||||
const organSegmentationGroup = dataList.find(
|
||||
(listGroup: any) => listGroup.type === 'Organ Segmentation'
|
||||
) as unknown as ListGroup;
|
||||
|
||||
if (!organSegmentationGroup) {
|
||||
return <div className="text-red-500">Organ Segmentation data not found.</div>;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Render content based on mode
|
||||
const renderModeContent = () => {
|
||||
if (tableProps.mode === 'collapsed') {
|
||||
return (
|
||||
<SegmentationTable.Collapsed>
|
||||
<SegmentationTable.Collapsed.Header>
|
||||
<SegmentationTable.Collapsed.DropdownMenu>
|
||||
{/* <CustomDropdownMenuContent /> */}
|
||||
</SegmentationTable.Collapsed.DropdownMenu>
|
||||
<SegmentationTable.Collapsed.Selector />
|
||||
<SegmentationTable.Collapsed.Info />
|
||||
</SegmentationTable.Collapsed.Header>
|
||||
<SegmentationTable.Collapsed.Content>
|
||||
<SegmentationTable.AddSegmentRow />
|
||||
{renderSegments()}
|
||||
</SegmentationTable.Collapsed.Content>
|
||||
</SegmentationTable.Collapsed>
|
||||
);
|
||||
}
|
||||
|
||||
const stats = item.statistics;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<div
|
||||
className="h-2.5 w-2.5 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: item.colorHex }}
|
||||
></div>
|
||||
<h3 className="text-muted-foreground break-words text-lg font-semibold">{item.title}</h3>
|
||||
</div>
|
||||
<>
|
||||
<SegmentationTable.Expanded>
|
||||
<SegmentationTable.Expanded.Header>
|
||||
<SegmentationTable.Expanded.DropdownMenu>
|
||||
{/* <CustomDropdownMenuContent /> */}
|
||||
</SegmentationTable.Expanded.DropdownMenu>
|
||||
<SegmentationTable.Expanded.Label />
|
||||
<SegmentationTable.Expanded.Info />
|
||||
</SegmentationTable.Expanded.Header>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid X</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidX.value}</span>{' '}
|
||||
<span className="">{stats.centroidX.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid Y</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidY.value}</span>{' '}
|
||||
<span className="">{stats.centroidY.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid Z</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidZ.value}</span>{' '}
|
||||
<span className="">{stats.centroidZ.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Frame Duration</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.frameDuration.value}</span>{' '}
|
||||
<span className="">{stats.frameDuration.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Kurtosis</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.kurtosis.value}</span>{' '}
|
||||
<span className="">{stats.kurtosis.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Max</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.max.value}</span>{' '}
|
||||
<span className="">{stats.max.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Max Slice</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.maxSlice.value}</span>{' '}
|
||||
<span className="">{stats.maxSlice.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Mean</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.mean.value}</span>{' '}
|
||||
<span className="">{stats.mean.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Median</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.median.value}</span>{' '}
|
||||
<span className="">{stats.median.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Min</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.min.value}</span>{' '}
|
||||
<span className="">{stats.min.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Regions</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.regions.value}</span>{' '}
|
||||
<span className="">{stats.regions.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Skewness</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.skewness.value}</span>{' '}
|
||||
<span className="">{stats.skewness.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Sphere Diameter</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.sphereDiameter.value}</span>{' '}
|
||||
<span className="">{stats.sphereDiameter.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Standard Deviation</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.standardDeviation.value}</span>{' '}
|
||||
<span className="">{stats.standardDeviation.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">SUV Peak</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.suvPeak.value}</span>{' '}
|
||||
<span className="">{stats.suvPeak.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Total</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.total.value}</span>{' '}
|
||||
<span className="">{stats.total.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Glycolysis</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.glycolysis.value}</span>{' '}
|
||||
<span className="">{stats.glycolysis.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Volume</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.volume.value}</span>{' '}
|
||||
<span className="">{stats.volume.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Voxel Count</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.voxelCount.value}</span>{' '}
|
||||
<span className="">{stats.voxelCount.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SegmentationTable.Expanded.Content>
|
||||
<SegmentationTable.AddSegmentRow />
|
||||
{renderSegments()}
|
||||
</SegmentationTable.Expanded.Content>
|
||||
</SegmentationTable.Expanded>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-auto flex min-h-screen w-full justify-center bg-black py-12">
|
||||
<div className="w-64 space-y-0">
|
||||
<TooltipProvider>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
defaultValue={['segmentation-tools', 'segmentation-list']}
|
||||
>
|
||||
{/* Segmentation Tools */}
|
||||
<AccordionItem value="segmentation-tools">
|
||||
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
|
||||
<span>Segmentation Tools</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Segmentation List */}
|
||||
<AccordionItem value="segmentation-list">
|
||||
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
|
||||
<span>Segmentation List</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="mb-0">
|
||||
{/* Header Controls */}
|
||||
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.More className="h-6 w-6" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem>
|
||||
<Icons.Add className="text-foreground" />
|
||||
<span className="pl-2">Create New Segmentation</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Manage Current Segmentation</DropdownMenuLabel>
|
||||
<DropdownMenuItem>
|
||||
<Icons.Series className="text-foreground" />
|
||||
<span className="pl-2">Remove from Viewport</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span className="pl-2">Rename</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">Export & Download</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
|
||||
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
|
||||
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Icons.Delete className="text-red-600" />
|
||||
<span className="pl-2 text-red-600">Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Select>
|
||||
<SelectTrigger className="w-full overflow-hidden">
|
||||
<SelectValue placeholder="Segmentation 1" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="seg1">Segmentation 1</SelectItem>
|
||||
<SelectItem value="seg2">Segmentation 2</SelectItem>
|
||||
<SelectItem value="seg3">Segmentation Long Name 123</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.Info className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Appearance Settings */}
|
||||
<AccordionItem value="segmentation-display">
|
||||
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
|
||||
<div className="flex space-x-2">
|
||||
<Icons.Controls className="text-primary" />
|
||||
<span className="text-primary pr-1">Appearance Settings</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-1.5 pt-0.5 pb-3">
|
||||
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
|
||||
{/* Display Label with Selected Tab */}
|
||||
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
|
||||
{/* Tabs Controls */}
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
onValueChange={setSelectedTab}
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="Fill & Outline">
|
||||
<Icons.DisplayFillAndOutline className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Outline Only">
|
||||
<Icons.DisplayOutlineOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Fill Only">
|
||||
<Icons.DisplayFillOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
{/* Opacity Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Opacity
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[85]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="85"
|
||||
/>
|
||||
</div>
|
||||
{/* Border Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Border
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[10]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="2"
|
||||
/>
|
||||
</div>
|
||||
{/* Sync Changes Switch */}
|
||||
<div className="my-2 flex items-center pl-1 pb-1">
|
||||
<Switch defaultChecked />
|
||||
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Sync changes in all viewports
|
||||
</Label>
|
||||
</div>
|
||||
<div className="border-input w-full border"></div>
|
||||
{/* Display Inactive Segmentations Switch */}
|
||||
<div className="my-2 flex items-center pl-1">
|
||||
<Switch defaultChecked />
|
||||
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Display inactive segmentations
|
||||
</Label>
|
||||
</div>
|
||||
{/* Additional Opacity Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Opacity
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[65]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="65"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
{/* Action Buttons */}
|
||||
<div className="my-px flex h-9 w-full items-center justify-between rounded pl-0.5 pr-7">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="pr pl-0.5"
|
||||
>
|
||||
<Icons.Add />
|
||||
Add Segment
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<Icons.Hide className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Data Rows */}
|
||||
<div className="space-y-px">
|
||||
{organSegmentationGroup.items.map((item, index) => {
|
||||
const compositeId = `${organSegmentationGroup.type}-${item.id}-panel`; // Ensure unique composite ID
|
||||
return (
|
||||
<HoverCard
|
||||
key={`hover-${compositeId}`}
|
||||
openDelay={300}
|
||||
closeDelay={200}
|
||||
// open={true}
|
||||
>
|
||||
<HoverCardTrigger asChild>
|
||||
<div>
|
||||
<DataRow
|
||||
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
|
||||
number={index + 1}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
colorHex={item.colorHex}
|
||||
details={item.details || { primary: [], secondary: [] }}
|
||||
actionOptions={
|
||||
actionOptionsMap[organSegmentationGroup.type] || ['Action']
|
||||
}
|
||||
onAction={(action: string) => 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}
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="w-72 border"
|
||||
>
|
||||
{renderStatisticsPanel(item)}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<div className="px-auto flex min-h-screen w-full justify-center border-2 bg-black py-64">
|
||||
<TooltipProvider>
|
||||
<SegmentationTable {...tableProps}>
|
||||
<SegmentationTable.Config />
|
||||
<SegmentationTable.AddSegmentationRow />
|
||||
{renderModeContent()}
|
||||
</SegmentationTable>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,39 @@
|
||||
interface DataItem {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
optionalField?: string;
|
||||
colorHex?: string;
|
||||
details?: { primary: string[]; secondary: string[] };
|
||||
series?: string;
|
||||
statistics?: {
|
||||
centroidX: { value: number; unit: string };
|
||||
centroidY: { value: number; unit: string };
|
||||
centroidZ: { value: number; unit: string };
|
||||
frameDuration: { value: number; unit: string };
|
||||
kurtosis: { value: number; unit: string };
|
||||
max: { value: number; unit: string };
|
||||
maxSlice: { value: number; unit: string };
|
||||
mean: { value: number; unit: string };
|
||||
median: { value: number; unit: string };
|
||||
min: { value: number; unit: string };
|
||||
regions: { value: number; unit: string };
|
||||
skewness: { value: number; unit: string };
|
||||
sphereDiameter: { value: number; unit: string };
|
||||
standardDeviation: { value: number; unit: string };
|
||||
suvPeak: { value: number; unit: string };
|
||||
total: { value: number; unit: string };
|
||||
glycolysis: { value: number; unit: string };
|
||||
volume: { value: number; unit: string };
|
||||
voxelCount: { value: number; unit: string };
|
||||
};
|
||||
}
|
||||
|
||||
interface ListGroup {
|
||||
type: string;
|
||||
items: DataItem[];
|
||||
}
|
||||
|
||||
const actionOptionsMap: { [key: string]: string[] } = {
|
||||
Measurement: ['Rename', 'Lock', 'Delete'],
|
||||
Segmentation: ['Rename', 'Lock', 'Export', 'Delete'],
|
||||
@ -547,4 +583,146 @@ const dataList = [
|
||||
},
|
||||
];
|
||||
|
||||
export { actionOptionsMap, dataList };
|
||||
const segmentationData = {
|
||||
representation: {
|
||||
id: '791aaa59-1df6-60f8-3c28-6d870e6a4585-Labelmap-default',
|
||||
segmentationId: '791aaa59-1df6-60f8-3c28-6d870e6a4585',
|
||||
label: 'Segmentation',
|
||||
active: true,
|
||||
type: 'Labelmap',
|
||||
visible: true,
|
||||
segments: {
|
||||
'1': {
|
||||
color: [255, 0, 0, 255],
|
||||
segmentIndex: 1,
|
||||
opacity: 255,
|
||||
visible: true,
|
||||
},
|
||||
'2': {
|
||||
color: [0, 255, 0, 255],
|
||||
segmentIndex: 2,
|
||||
opacity: 255,
|
||||
visible: true,
|
||||
},
|
||||
'3': {
|
||||
color: [255, 255, 0, 255],
|
||||
segmentIndex: 3,
|
||||
opacity: 255,
|
||||
visible: true,
|
||||
},
|
||||
'4': {
|
||||
color: [0, 255, 255, 255],
|
||||
segmentIndex: 4,
|
||||
opacity: 255,
|
||||
visible: true,
|
||||
},
|
||||
},
|
||||
viewportId: 'default',
|
||||
colorLUTIndex: 0,
|
||||
config: {},
|
||||
},
|
||||
segmentation: {
|
||||
segmentationId: '791aaa59-1df6-60f8-3c28-6d870e6a4585',
|
||||
label: 'Segmentation',
|
||||
cachedStats: {},
|
||||
segments: {
|
||||
'1': {
|
||||
segmentIndex: 1,
|
||||
label: 'Liver',
|
||||
locked: false,
|
||||
cachedStats: {
|
||||
category: 'Anatomical Structure',
|
||||
type: 'Liver',
|
||||
algorithmType: 'SEMIAUTOMATIC',
|
||||
algorithmName: 'Amira',
|
||||
},
|
||||
active: true,
|
||||
},
|
||||
'2': {
|
||||
segmentIndex: 2,
|
||||
label: 'Mass',
|
||||
locked: false,
|
||||
cachedStats: {
|
||||
category: 'Morphologically Altered Structure',
|
||||
type: 'Mass',
|
||||
algorithmType: 'SEMIAUTOMATIC',
|
||||
algorithmName: 'Amira',
|
||||
},
|
||||
active: false,
|
||||
},
|
||||
'3': {
|
||||
segmentIndex: 3,
|
||||
label: 'Portal vein',
|
||||
locked: false,
|
||||
cachedStats: {
|
||||
category: 'Anatomical Structure',
|
||||
type: 'Portal vein',
|
||||
algorithmType: 'SEMIAUTOMATIC',
|
||||
algorithmName: 'Amira',
|
||||
},
|
||||
active: false,
|
||||
},
|
||||
'4': {
|
||||
segmentIndex: 4,
|
||||
label: 'Abdominal aorta',
|
||||
locked: false,
|
||||
cachedStats: {
|
||||
category: 'Anatomical Structure',
|
||||
type: 'Abdominal aorta',
|
||||
algorithmType: 'SEMIAUTOMATIC',
|
||||
algorithmName: 'Amira',
|
||||
},
|
||||
active: false,
|
||||
},
|
||||
},
|
||||
representationData: {
|
||||
Labelmap: {
|
||||
imageIds: [],
|
||||
referencedImageIds: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const getSegmentationData = ({
|
||||
active = false,
|
||||
segmentationId = '791aaa59-1df6-60f8-3c28-6d870e6a4585',
|
||||
}) => {
|
||||
return {
|
||||
segmentation: {
|
||||
...segmentationData.segmentation,
|
||||
active,
|
||||
segmentationId,
|
||||
},
|
||||
representation: {
|
||||
...segmentationData.representation,
|
||||
active,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const panelSegmentationData = {
|
||||
disabled: false,
|
||||
data: [
|
||||
getSegmentationData({ active: true }),
|
||||
getSegmentationData({ active: false, segmentationId: 'babas' }),
|
||||
],
|
||||
mode: 'collapsed',
|
||||
title: 'Segmentations',
|
||||
exportOptions: [
|
||||
{
|
||||
segmentationId: '791aaa59-1df6-60f8-3c28-6d870e6a4585',
|
||||
isExportable: true,
|
||||
},
|
||||
{
|
||||
segmentationId: '791aaa59-1df6-60f8-3c28-6d870e6a4585',
|
||||
isExportable: true,
|
||||
},
|
||||
],
|
||||
disableEditing: false,
|
||||
showAddSegment: true,
|
||||
renderInactiveSegmentations: true,
|
||||
};
|
||||
|
||||
export { actionOptionsMap, dataList, panelSegmentationData };
|
||||
export type { DataItem, ListGroup };
|
||||
|
||||
@ -65,7 +65,7 @@ function Header({
|
||||
onClick={onClickReturn}
|
||||
data-cy="return-to-work-list"
|
||||
>
|
||||
{isReturnEnabled && <Icons.ArrowLeft className="text-primary-active w-8" />}
|
||||
{isReturnEnabled && <Icons.ArrowLeft className="text-primary w-8" />}
|
||||
<div className="ml-1">
|
||||
{WhiteLabeling?.createLogoComponentFn?.(React, props) || <Icons.OHIFLogo />}
|
||||
</div>
|
||||
@ -86,7 +86,7 @@ function Header({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-primary-active hover:bg-primary-dark mt-2 h-full w-full"
|
||||
className="text-primary hover:bg-primary-dark mt-2 h-full w-full"
|
||||
>
|
||||
<Icons.GearSettings />
|
||||
</Button>
|
||||
|
||||
@ -27,7 +27,7 @@ export const AddSegmentationRow: React.FC<{ children?: React.ReactNode }> = ({
|
||||
onClick={() => !disabled && onSegmentationAdd('')}
|
||||
>
|
||||
{children}
|
||||
<div className="text-primary-active group-hover:bg-secondary-dark flex items-center rounded-[4px] pl-1 group-hover:cursor-pointer">
|
||||
<div className="text-primary group-hover:bg-secondary-dark flex items-center rounded-[4px] pl-1 group-hover:cursor-pointer">
|
||||
<div className="grid h-[28px] w-[28px] place-items-center">
|
||||
{disabled ? <Icons.Info /> : <Icons.Add />}
|
||||
</div>
|
||||
|
||||
@ -17,7 +17,7 @@ const SegmentationExpandedHeader = ({ children }: { children: React.ReactNode })
|
||||
|
||||
return (
|
||||
<PanelSection.Header
|
||||
className={`border-input border-t-2 bg-transparent pl-1 ${isActive ? 'border-primary' : ''} py-0`}
|
||||
className={`bg-muted my-0 rounded-none border-l-[2px] pl-0 ${isActive ? 'border-primary/70' : 'border-primary/35'}`}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onSegmentationClick(segmentation.segmentationId);
|
||||
@ -59,7 +59,7 @@ const SegmentationExpandedInfo = () => {
|
||||
const { segmentation } = useSegmentationExpanded('SegmentationExpandedInfo');
|
||||
|
||||
return (
|
||||
<div className="ml-auto mr-1">
|
||||
<div className="ml-auto mr-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
@ -79,8 +79,11 @@ const SegmentationExpandedInfo = () => {
|
||||
|
||||
// Content component
|
||||
const SegmentationExpandedContent = ({ children }: { children: React.ReactNode }) => {
|
||||
const { isActive } = useSegmentationExpanded('SegmentationExpandedContent');
|
||||
return (
|
||||
<PanelSection.Content className="py-0">
|
||||
<PanelSection.Content
|
||||
className={`border-l-[2px] py-0 pb-6 pl-[8px] ${isActive ? 'border-primary/70' : 'border-primary/35'}`}
|
||||
>
|
||||
<div className="segmentation-expanded-section">{children}</div>
|
||||
</PanelSection.Content>
|
||||
);
|
||||
@ -97,14 +100,14 @@ const SegmentationExpandedRoot = ({ children }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-0">
|
||||
<div className={`space-y-0 pl-0.5`}>
|
||||
{data.map(segmentationInfo => {
|
||||
const isActive = segmentationInfo.segmentation.segmentationId === activeSegmentationId;
|
||||
|
||||
return (
|
||||
<PanelSection
|
||||
key={segmentationInfo.segmentation.segmentationId}
|
||||
className="mb-0"
|
||||
className=""
|
||||
>
|
||||
<SegmentationExpandedProvider
|
||||
segmentation={segmentationInfo.segmentation}
|
||||
|
||||
@ -65,42 +65,42 @@ export const SegmentationSegments = ({ children = null }: { children?: React.Rea
|
||||
const { locked, active, label, displayText } = segmentFromSegmentation;
|
||||
const cssColor = `rgb(${color[0]},${color[1]},${color[2]})`;
|
||||
|
||||
return (
|
||||
const hasStats = segmentFromSegmentation.cachedStats?.namedStats;
|
||||
const DataRowComponent = (
|
||||
<DataRow
|
||||
key={segmentIndex}
|
||||
number={segmentIndex}
|
||||
title={label}
|
||||
details={displayText}
|
||||
description={displayText}
|
||||
colorHex={cssColor}
|
||||
isSelected={active}
|
||||
isVisible={visible}
|
||||
isLocked={locked}
|
||||
disableEditing={disableEditing}
|
||||
className={!isActiveSegmentation ? 'opacity-80' : ''}
|
||||
onColor={() => onSegmentColorClick(segmentation.segmentationId, segmentIndex)}
|
||||
onToggleVisibility={() =>
|
||||
onToggleSegmentVisibility(
|
||||
segmentation.segmentationId,
|
||||
segmentIndex,
|
||||
representation.type
|
||||
)
|
||||
}
|
||||
onToggleLocked={() => onToggleSegmentLock(segmentation.segmentationId, segmentIndex)}
|
||||
onSelect={() => onSegmentClick(segmentation.segmentationId, segmentIndex)}
|
||||
onRename={() => onSegmentEdit(segmentation.segmentationId, segmentIndex)}
|
||||
onDelete={() => onSegmentDelete(segmentation.segmentationId, segmentIndex)}
|
||||
/>
|
||||
);
|
||||
|
||||
return hasStats ? (
|
||||
<HoverCard
|
||||
key={`hover-${segmentIndex}`}
|
||||
openDelay={300}
|
||||
>
|
||||
<HoverCardTrigger asChild>
|
||||
{/* The div here is not random, it is needed for making it work with the hover card */}
|
||||
<div>
|
||||
<DataRow
|
||||
key={segmentIndex}
|
||||
number={segmentIndex}
|
||||
title={label}
|
||||
details={displayText}
|
||||
description={displayText}
|
||||
colorHex={cssColor}
|
||||
isSelected={active}
|
||||
isVisible={visible}
|
||||
isLocked={locked}
|
||||
disableEditing={disableEditing}
|
||||
className={!isActiveSegmentation ? 'opacity-80' : ''}
|
||||
onColor={() => onSegmentColorClick(segmentation.segmentationId, segmentIndex)}
|
||||
onToggleVisibility={() =>
|
||||
onToggleSegmentVisibility(
|
||||
segmentation.segmentationId,
|
||||
segmentIndex,
|
||||
representation.type
|
||||
)
|
||||
}
|
||||
onToggleLocked={() =>
|
||||
onToggleSegmentLock(segmentation.segmentationId, segmentIndex)
|
||||
}
|
||||
onSelect={() => onSegmentClick(segmentation.segmentationId, segmentIndex)}
|
||||
onRename={() => onSegmentEdit(segmentation.segmentationId, segmentIndex)}
|
||||
onDelete={() => onSegmentDelete(segmentation.segmentationId, segmentIndex)}
|
||||
/>
|
||||
</div>
|
||||
<div>{DataRowComponent}</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side="left"
|
||||
@ -126,6 +126,8 @@ export const SegmentationSegments = ({ children = null }: { children?: React.Rea
|
||||
</SegmentStatistics>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
) : (
|
||||
DataRowComponent
|
||||
);
|
||||
})}
|
||||
</ScrollArea>
|
||||
|
||||
@ -112,7 +112,7 @@ export const SegmentationTableRoot = (props: SegmentationTableProps) => {
|
||||
{hasConfigComponent && (
|
||||
<div className="ml-auto mr-2">
|
||||
<Icons.Settings
|
||||
className="text-primary-active h-4 w-4"
|
||||
className="text-primary h-4 w-4"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
toggleShowConfig();
|
||||
|
||||
@ -61,13 +61,13 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="fill-and-outline">
|
||||
<Icons.FillAndOutline className="text-primary-active" />
|
||||
<Icons.FillAndOutline className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="outline">
|
||||
<Icons.OutlineOnly className="text-primary-active" />
|
||||
<Icons.OutlineOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="fill">
|
||||
<Icons.FillOnly className="text-primary-active" />
|
||||
<Icons.FillOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
@ -112,7 +112,7 @@ const getTabClassNames = (
|
||||
isTabDisabled: boolean
|
||||
) =>
|
||||
classnames('h-[28px] mb-[2px] cursor-pointer text-white bg-black', {
|
||||
'hover:text-primary-active': !isActiveTab && !isTabDisabled,
|
||||
'hover:text-primary': !isActiveTab && !isTabDisabled,
|
||||
'rounded-l': tabIndex % numColumns === 0,
|
||||
'rounded-r': (tabIndex + 1) % numColumns === 0 || tabIndex === numTabs - 1,
|
||||
});
|
||||
@ -294,7 +294,7 @@ const SidePanel = ({
|
||||
data-cy={`side-panel-header-${side}`}
|
||||
>
|
||||
<Icons.NavigationPanelReveal
|
||||
className={classnames('text-primary-active', side === 'left' && 'rotate-180 transform')}
|
||||
className={classnames('text-primary', side === 'left' && 'rotate-180 transform')}
|
||||
/>
|
||||
</div>
|
||||
<div className={classnames('mt-3 flex flex-col space-y-3')}>
|
||||
@ -304,14 +304,14 @@ const SidePanel = ({
|
||||
<div
|
||||
id={`${childComponent.name}-btn`}
|
||||
data-cy={`${childComponent.name}-btn`}
|
||||
className="text-primary-active hover:cursor-pointer"
|
||||
className="text-primary hover:cursor-pointer"
|
||||
onClick={() => {
|
||||
return childComponent.disabled ? null : updateActiveTabIndex(index, true);
|
||||
}}
|
||||
>
|
||||
{React.createElement(Icons[childComponent.iconName] || Icons.MissingIcon, {
|
||||
className: classnames({
|
||||
'text-primary-active': true,
|
||||
'text-primary': true,
|
||||
'ohif-disabled': childComponent.disabled,
|
||||
}),
|
||||
style: {
|
||||
@ -352,7 +352,7 @@ const SidePanel = ({
|
||||
data-cy={`side-panel-header-${side}`}
|
||||
>
|
||||
{React.createElement(Icons[openStateIconName[side]] || Icons.MissingIcon, {
|
||||
className: 'text-primary-active',
|
||||
className: 'text-primary',
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
@ -365,7 +365,7 @@ const SidePanel = ({
|
||||
<>
|
||||
{getCloseIcon()}
|
||||
<div className={classnames('flex grow justify-center')}>
|
||||
<div className={classnames('bg-primary-dark text-primary-active flex flex-wrap')}>
|
||||
<div className={classnames('bg-primary-dark text-primary flex flex-wrap')}>
|
||||
{tabs.map((tab, tabIndex) => {
|
||||
const { disabled } = tab;
|
||||
return (
|
||||
@ -401,7 +401,7 @@ const SidePanel = ({
|
||||
>
|
||||
{React.createElement(Icons[tab.iconName] || Icons.MissingIcon, {
|
||||
className: classnames({
|
||||
'text-primary-active': true,
|
||||
'text-primary': true,
|
||||
'ohif-disabled': disabled,
|
||||
}),
|
||||
style: {
|
||||
@ -429,7 +429,7 @@ const SidePanel = ({
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'text-primary-active flex grow cursor-pointer select-none justify-center self-center text-[13px]'
|
||||
'text-primary flex grow cursor-pointer select-none justify-center self-center text-[13px]'
|
||||
)}
|
||||
data-cy={`${tabs[0].name}-btn`}
|
||||
onClick={() => updatePanelOpen(!panelOpen)}
|
||||
|
||||
@ -111,7 +111,7 @@ const Thumbnail = ({
|
||||
<TooltipContent side="right">
|
||||
<div className="flex flex-1 flex-row">
|
||||
<div className="flex-2 flex items-center justify-center pr-4">
|
||||
<Icons.InfoLink className="text-primary-active" />
|
||||
<Icons.InfoLink className="text-primary" />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<span>
|
||||
@ -226,7 +226,7 @@ const Thumbnail = ({
|
||||
<TooltipContent side="right">
|
||||
<div className="flex flex-1 flex-row">
|
||||
<div className="flex-2 flex items-center justify-center pr-4">
|
||||
<Icons.InfoLink className="text-primary-active" />
|
||||
<Icons.InfoLink className="text-primary" />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<span>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
const getToggledClassName = isToggled => {
|
||||
return isToggled
|
||||
? '!text-primary-active'
|
||||
? '!text-primary'
|
||||
: '!text-common-bright hover:!bg-primary-dark hover:text-primary-light';
|
||||
};
|
||||
|
||||
|
||||
42
tests/SEGHydrationFromMPR.spec.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { test } from '@playwright/test';
|
||||
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046';
|
||||
const mode = 'viewer';
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
});
|
||||
|
||||
test('should properly display MPR for MR', async ({ page }) => {
|
||||
await page.getByTestId('side-panel-header-right').click();
|
||||
|
||||
await page.getByTestId('Layout').click();
|
||||
await page.getByTestId('MPR').click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprBeforeSEG);
|
||||
|
||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSEG);
|
||||
|
||||
await page.getByTestId('yes-hydrate-btn').click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSegHydrated);
|
||||
|
||||
await page.getByTestId('Layout').click();
|
||||
await page.getByTestId('Axial Primary').click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
screenShotPaths.segHydrationFromMPR.mprAfterSegHydratedAfterLayoutChange
|
||||
);
|
||||
});
|
||||
@ -12,7 +12,7 @@ test('should properly display MPR for MR', async ({ page }) => {
|
||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||
|
||||
await page.getByTestId('yes-hydrate-btn').click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationMPR.segPostHydration);
|
||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationThenMPR.segPostHydration);
|
||||
|
||||
await page.getByTestId('Layout').click();
|
||||
await page.getByTestId('Axial Primary').click();
|
||||
@ -20,6 +20,6 @@ test('should properly display MPR for MR', async ({ page }) => {
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
screenShotPaths.segHydrationMPR.segPostHydrationMPRAxialPrimary
|
||||
screenShotPaths.segHydrationThenMPR.segPostHydrationMPRAxialPrimary
|
||||
);
|
||||
});
|
||||
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 223 KiB |
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 301 KiB |
@ -79,10 +79,16 @@ const screenShotPaths = {
|
||||
segPreHydration: 'segPreHydration.png',
|
||||
segJumpToSegment: 'segJumpToSegment.png',
|
||||
},
|
||||
segHydrationMPR: {
|
||||
segHydrationThenMPR: {
|
||||
segPostHydration: 'segPostHydration.png',
|
||||
segPostHydrationMPRAxialPrimary: 'segPostHydrationMPRAxialPrimary.png',
|
||||
},
|
||||
segHydrationFromMPR: {
|
||||
mprBeforeSEG: 'mprBeforeSEG.png',
|
||||
mprAfterSEG: 'mprAfterSEG.png',
|
||||
mprAfterSegHydrated: 'mprAfterSegHydrated.png',
|
||||
mprAfterSegHydratedAfterLayoutChange: 'mprAfterSegHydratedAfterLayoutChange.png',
|
||||
},
|
||||
rtHydration: {
|
||||
rtPostHydration: 'rtPostHydration.png',
|
||||
rtPreHydration: 'rtPreHydration.png',
|
||||
|
||||
56
yarn.lock
@ -1393,10 +1393,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
||||
|
||||
"@cornerstonejs/adapters@^3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.8.4.tgz#14e22bef3abaf14cde52ecf8b05b43a687b0dece"
|
||||
integrity sha512-TFTvNZQBW54rEcyjDfyHZYYvIPHVGZoUCtjW3isHfpoXkjHz2cliwC55e0kcNJ0zbyI6H41c4VsYH8h2MTYOdA==
|
||||
"@cornerstonejs/adapters@^3.8.5":
|
||||
version "3.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.8.5.tgz#d516a8fba29d200b47d77fe0ef586ed8962506de"
|
||||
integrity sha512-dQlKKSCbV6V3QpivDdZZiI7N792l1VqsOj8H+e8iiarpy6MvwiEvck2u50/GBhto9D/U9QvRkZVs9TH0ewUs5w==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.17.8"
|
||||
buffer "^6.0.3"
|
||||
@ -1404,10 +1404,10 @@
|
||||
gl-matrix "^3.4.3"
|
||||
ndarray "^1.0.19"
|
||||
|
||||
"@cornerstonejs/ai@^3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/ai/-/ai-3.8.4.tgz#c998cdfbd1cae01e3ea9ed61810c657d91112f75"
|
||||
integrity sha512-scc2srdL1BqNTSFEro4y7UlNBsPtBVSF31eT4SC1psxZQcr/iyaCKuOQg/hglk1yobFkdIKvjRAvvqvYmGduQw==
|
||||
"@cornerstonejs/ai@^3.8.5":
|
||||
version "3.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/ai/-/ai-3.8.5.tgz#8f6be57b7bc660afacd52b34ce56a71c696419ed"
|
||||
integrity sha512-9QR7PmTtkeXZOjDFh9WulLJGnraWLaM/g/qNB0ZaPKDMo1PLT7/tHn1F6wsyT2cmpEb3/G5towdxtEvhvlpm/w==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.17.8"
|
||||
buffer "^6.0.3"
|
||||
@ -1443,20 +1443,20 @@
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
|
||||
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
|
||||
|
||||
"@cornerstonejs/core@^3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.8.4.tgz#bbc6863cb3f2e78f5f4c7a1a5a9287c091287228"
|
||||
integrity sha512-8spQvERZRn4I/0EhzTNZ5ruPBrVn5F4msb/q0ttBtiVEa9JaVwoWh3yEcO4q5NpK7t1BhpfSPMKwU98Dy02ugQ==
|
||||
"@cornerstonejs/core@^3.8.5":
|
||||
version "3.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.8.5.tgz#577bd32f0a3da36f895bf89c821b02a4cf97e225"
|
||||
integrity sha512-x3LZwZUqhVldChpwtLqNwUWx8eXq9q1ndwrx5lVpnX4ZUT9qQIAro01Ih8DPiXlLb1NXbN3sG4e/x/TxuQ+5bw==
|
||||
dependencies:
|
||||
"@kitware/vtk.js" "32.12.1"
|
||||
comlink "^4.4.1"
|
||||
gl-matrix "^3.4.3"
|
||||
loglevel "^1.9.2"
|
||||
|
||||
"@cornerstonejs/dicom-image-loader@^3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.8.4.tgz#c4f7af80a40a1f70deff37ddfc50447cc42422e7"
|
||||
integrity sha512-N2D4U8JDhen+dCY1HrLOoK8W3CD2AAf/Cd+GiJMEGjtrihIuQ+Ha+hZTVQzaDMc7v2Ruvx2DpJ7i9HUJXoyWHw==
|
||||
"@cornerstonejs/dicom-image-loader@^3.8.5":
|
||||
version "3.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.8.5.tgz#963a3174af3603e4beb096d448671442a0d5f10a"
|
||||
integrity sha512-yuLLU2sbtFWueNKVbeYBddPoJnfhrUQ+rRnXJb+y9iczwZMQLy4tYyk3Q5INo+/nIdwCVeZg7P9qORZERsBivw==
|
||||
dependencies:
|
||||
"@cornerstonejs/codec-charls" "^1.2.3"
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
||||
@ -1468,25 +1468,25 @@
|
||||
pako "^2.0.4"
|
||||
uuid "^9.0.0"
|
||||
|
||||
"@cornerstonejs/labelmap-interpolation@^3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/labelmap-interpolation/-/labelmap-interpolation-3.8.4.tgz#5c18761a7bbb8e3e76bfbf22b2205c1c5e8bee6e"
|
||||
integrity sha512-KKDTeVJg1s0trplkaHglBm0foQ3XL9BhsTfatFCTzgIOCT0wolrKbr+xAKIeZuBQifiFazDWpYaeBHRbkWEEMA==
|
||||
"@cornerstonejs/labelmap-interpolation@^3.8.5":
|
||||
version "3.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/labelmap-interpolation/-/labelmap-interpolation-3.8.5.tgz#19e8142362d5b83540bcb764306cd6bdf277c9ce"
|
||||
integrity sha512-u80OTJE8eju+tZ+S/N1b/HTIw2mPjEE2o5u3T/7/w90FX7OaxJJiMYjPPfnSZWXjE2HhRu0gEAKHOU3dr8pL4w==
|
||||
dependencies:
|
||||
"@itk-wasm/morphological-contour-interpolation" "1.1.0"
|
||||
itk-wasm "1.0.0-b.165"
|
||||
|
||||
"@cornerstonejs/polymorphic-segmentation@^3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/polymorphic-segmentation/-/polymorphic-segmentation-3.8.4.tgz#5c7578eedd4e7ac78250f06ccecb3950ddf5ba28"
|
||||
integrity sha512-+3o280NOniUkG9GcWTeTi6x3qy8Rm7WpYFycWpxXIufJzVudJi0zAR+EIWDq/uvTQ3DH1SrBDe4wzYNqD+yirw==
|
||||
"@cornerstonejs/polymorphic-segmentation@^3.8.5":
|
||||
version "3.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/polymorphic-segmentation/-/polymorphic-segmentation-3.8.5.tgz#5289f1b9d7f8fdbf3164330c4d30dd03fffa16be"
|
||||
integrity sha512-6L9a9f36CnUIuGsydA8kkkC0z1Gjid4KfclCQoYYaxcsLWLDpba5usFERmxCLIm9VyBsA3POqesIGDqWTgJmFg==
|
||||
dependencies:
|
||||
"@icr/polyseg-wasm" "0.4.0"
|
||||
|
||||
"@cornerstonejs/tools@^3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.8.4.tgz#4676ba9d7fcae22f236bab2314118b5d06cc973e"
|
||||
integrity sha512-yr8RLtnxGh9BsPOFpX+zwT++UGJDqWMo7noWGzxkuISDriLr+HGZI8gL910Mw62obaF8r9qpeOSvSOzEHN3nSw==
|
||||
"@cornerstonejs/tools@^3.8.5":
|
||||
version "3.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.8.5.tgz#15fe20fa051dc12084141515c2a715a4f0a9a858"
|
||||
integrity sha512-aTaziRHJakCCxvbHhbrSkEuiamURGakcK83KXGJPjSdStWC/2cVpkCzPxc8V7FZBIstsvo5pgciXSpNc6Yt7Og==
|
||||
dependencies:
|
||||
"@types/offscreencanvas" "2019.7.3"
|
||||
comlink "^4.4.1"
|
||||
|
||||