fix: jump to measurement forever (#4867)

This commit is contained in:
Alireza 2025-03-28 10:53:04 -04:00 committed by GitHub
parent eafa2efda5
commit d7bd36544a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 731 additions and 448 deletions

318
bun.lock
View File

@ -33,7 +33,7 @@
},
"addOns/externals/devDependencies": {
"name": "@externals/devDependencies",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@kitware/vtk.js": "32.12.0",
@ -121,21 +121,21 @@
},
"addOns/externals/dicom-microscopy-viewer": {
"name": "@externals/dicom-microscopy-viewer",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"dicom-microscopy-viewer": "^0.46.1",
},
},
"extensions/cornerstone": {
"name": "@ohif/extension-cornerstone",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.7.16",
"@cornerstonejs/core": "^3.7.16",
"@cornerstonejs/labelmap-interpolation": "^3.7.16",
"@cornerstonejs/polymorphic-segmentation": "^3.7.16",
"@cornerstonejs/tools": "^3.7.16",
"@cornerstonejs/adapters": "^3.7.17",
"@cornerstonejs/core": "^3.7.17",
"@cornerstonejs/labelmap-interpolation": "^3.7.17",
"@cornerstonejs/polymorphic-segmentation": "^3.7.17",
"@cornerstonejs/tools": "^3.7.17",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "^1.4.1",
@ -152,9 +152,9 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.7.16",
"@ohif/core": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@cornerstonejs/dicom-image-loader": "^3.7.17",
"@ohif/core": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"dcmjs": "*",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@ -166,19 +166,19 @@
},
"extensions/cornerstone-dicom-pmap": {
"name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.7.16",
"@cornerstonejs/core": "^3.7.16",
"@cornerstonejs/adapters": "^3.7.17",
"@cornerstonejs/core": "^3.7.17",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/i18n": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/i18n": "3.10.0-beta.140",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -189,16 +189,16 @@
},
"extensions/cornerstone-dicom-rt": {
"name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"react-color": "^2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/i18n": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/i18n": "3.10.0-beta.140",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -209,19 +209,19 @@
},
"extensions/cornerstone-dicom-seg": {
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.7.16",
"@cornerstonejs/core": "^3.7.16",
"@cornerstonejs/adapters": "^3.7.17",
"@cornerstonejs/core": "^3.7.17",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/i18n": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/i18n": "3.10.0-beta.140",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -232,19 +232,19 @@
},
"extensions/cornerstone-dicom-sr": {
"name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.7.16",
"@cornerstonejs/core": "^3.7.16",
"@cornerstonejs/tools": "^3.7.16",
"@cornerstonejs/adapters": "^3.7.17",
"@cornerstonejs/core": "^3.7.17",
"@cornerstonejs/tools": "^3.7.17",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-measurement-tracking": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-measurement-tracking": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -254,19 +254,19 @@
},
"extensions/cornerstone-dynamic-volume": {
"name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/core": "^3.7.16",
"@cornerstonejs/tools": "^3.7.16",
"@cornerstonejs/core": "^3.7.17",
"@cornerstonejs/tools": "^3.7.17",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/i18n": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/i18n": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"dcmjs": "*",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@ -276,7 +276,7 @@
},
"extensions/default": {
"name": "@ohif/extension-default",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/calculate-suv": "^1.1.0",
@ -284,8 +284,8 @@
"lodash.uniqby": "^4.7.0",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/i18n": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/i18n": "3.10.0-beta.140",
"dcmjs": "*",
"dicomweb-client": "^0.10.4",
"prop-types": "^15.6.2",
@ -299,7 +299,7 @@
},
"extensions/dicom-microscopy": {
"name": "@ohif/extension-dicom-microscopy",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/codec-charls": "^1.2.3",
@ -310,10 +310,10 @@
"mathjs": "^12.4.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/i18n": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/i18n": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -324,14 +324,14 @@
},
"extensions/dicom-pdf": {
"name": "@ohif/extension-dicom-pdf",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -341,14 +341,14 @@
},
"extensions/dicom-video": {
"name": "@ohif/extension-dicom-video",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -358,20 +358,20 @@
},
"extensions/measurement-tracking": {
"name": "@ohif/extension-measurement-tracking",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.140",
"@xstate/react": "^3.2.2",
"xstate": "^4.10.0",
},
"peerDependencies": {
"@cornerstonejs/core": "^3.7.16",
"@cornerstonejs/tools": "^3.7.16",
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@cornerstonejs/core": "^3.7.17",
"@cornerstonejs/tools": "^3.7.17",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"classnames": "^2.3.2",
"dcmjs": "*",
"lodash.debounce": "^4.0.8",
@ -384,14 +384,14 @@
},
"extensions/test-extension": {
"name": "@ohif/extension-test",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"dcmjs": "0.38.0",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -401,14 +401,14 @@
},
"extensions/tmtv": {
"name": "@ohif/extension-tmtv",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@ -418,7 +418,7 @@
},
"modes/basic-dev-mode": {
"name": "@ohif/mode-basic-dev-mode",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -428,17 +428,17 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/extension-dicom-pdf": "3.10.0-beta.139",
"@ohif/extension-dicom-video": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/extension-dicom-pdf": "3.10.0-beta.140",
"@ohif/extension-dicom-video": "3.10.0-beta.140",
},
},
"modes/basic-test-mode": {
"name": "@ohif/mode-test",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -448,19 +448,19 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/extension-dicom-pdf": "3.10.0-beta.139",
"@ohif/extension-dicom-video": "3.10.0-beta.139",
"@ohif/extension-measurement-tracking": "3.10.0-beta.139",
"@ohif/extension-test": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/extension-dicom-pdf": "3.10.0-beta.140",
"@ohif/extension-dicom-video": "3.10.0-beta.140",
"@ohif/extension-measurement-tracking": "3.10.0-beta.140",
"@ohif/extension-test": "3.10.0-beta.140",
},
},
"modes/longitudinal": {
"name": "@ohif/mode-longitudinal",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -470,32 +470,32 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/extension-dicom-pdf": "3.10.0-beta.139",
"@ohif/extension-dicom-video": "3.10.0-beta.139",
"@ohif/extension-measurement-tracking": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/extension-dicom-pdf": "3.10.0-beta.140",
"@ohif/extension-dicom-video": "3.10.0-beta.140",
"@ohif/extension-measurement-tracking": "3.10.0-beta.140",
},
},
"modes/microscopy": {
"name": "@ohif/mode-microscopy",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.140",
},
},
"modes/preclinical-4d": {
"name": "@ohif/mode-preclinical-4d",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
},
@ -504,17 +504,17 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dynamic-volume": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/extension-tmtv": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dynamic-volume": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/extension-tmtv": "3.10.0-beta.140",
},
},
"modes/segmentation": {
"name": "@ohif/mode-segmentation",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -546,19 +546,19 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/extension-dicom-pdf": "3.10.0-beta.139",
"@ohif/extension-dicom-video": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/extension-dicom-pdf": "3.10.0-beta.140",
"@ohif/extension-dicom-video": "3.10.0-beta.140",
},
},
"modes/tmtv": {
"name": "@ohif/mode-tmtv",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next": "^17.0.3",
@ -568,43 +568,43 @@
"webpack-merge": "^5.7.3",
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/extension-dicom-pdf": "3.10.0-beta.139",
"@ohif/extension-dicom-video": "3.10.0-beta.139",
"@ohif/extension-measurement-tracking": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/extension-dicom-pdf": "3.10.0-beta.140",
"@ohif/extension-dicom-video": "3.10.0-beta.140",
"@ohif/extension-measurement-tracking": "3.10.0-beta.140",
},
},
"platform/app": {
"name": "@ohif/app",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"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.7.16",
"@cornerstonejs/dicom-image-loader": "^3.7.17",
"@emotion/serialize": "^1.1.3",
"@ohif/core": "3.10.0-beta.139",
"@ohif/extension-cornerstone": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.139",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.139",
"@ohif/extension-default": "3.10.0-beta.139",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.139",
"@ohif/extension-dicom-pdf": "3.10.0-beta.139",
"@ohif/extension-dicom-video": "3.10.0-beta.139",
"@ohif/extension-test": "3.10.0-beta.139",
"@ohif/i18n": "3.10.0-beta.139",
"@ohif/mode-basic-dev-mode": "3.10.0-beta.139",
"@ohif/mode-longitudinal": "3.10.0-beta.139",
"@ohif/mode-microscopy": "3.10.0-beta.139",
"@ohif/mode-test": "3.10.0-beta.139",
"@ohif/ui": "3.10.0-beta.139",
"@ohif/ui-next": "3.10.0-beta.139",
"@ohif/core": "3.10.0-beta.140",
"@ohif/extension-cornerstone": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-rt": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-seg": "3.10.0-beta.140",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.140",
"@ohif/extension-default": "3.10.0-beta.140",
"@ohif/extension-dicom-microscopy": "3.10.0-beta.140",
"@ohif/extension-dicom-pdf": "3.10.0-beta.140",
"@ohif/extension-dicom-video": "3.10.0-beta.140",
"@ohif/extension-test": "3.10.0-beta.140",
"@ohif/i18n": "3.10.0-beta.140",
"@ohif/mode-basic-dev-mode": "3.10.0-beta.140",
"@ohif/mode-longitudinal": "3.10.0-beta.140",
"@ohif/mode-microscopy": "3.10.0-beta.140",
"@ohif/mode-test": "3.10.0-beta.140",
"@ohif/ui": "3.10.0-beta.140",
"@ohif/ui-next": "3.10.0-beta.140",
"@svgr/webpack": "^8.1.0",
"@types/react": "^18.3.3",
"classnames": "^2.3.2",
@ -645,7 +645,7 @@
},
"platform/cli": {
"name": "@ohif/cli",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"bin": {
"ohif-cli": "src/index.js",
},
@ -669,7 +669,7 @@
},
"platform/core": {
"name": "@ohif/core",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"dcmjs": "*",
@ -694,15 +694,15 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.7.16",
"@ohif/ui": "3.10.0-beta.139",
"@cornerstonejs/dicom-image-loader": "^3.7.17",
"@ohif/ui": "3.10.0-beta.140",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21",
},
},
"platform/i18n": {
"name": "@ohif/i18n",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@babel/runtime": "^7.20.13",
"i18next-locize-backend": "^2.0.0",
@ -727,7 +727,7 @@
},
"platform/ui": {
"name": "@ohif/ui",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@testing-library/react": "^13.1.0",
"browser-detect": "^0.2.28",
@ -787,7 +787,7 @@
},
"platform/ui-next": {
"name": "@ohif/ui-next",
"version": "3.10.0-beta.139",
"version": "3.10.0-beta.140",
"dependencies": {
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
@ -1095,7 +1095,7 @@
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.7.16", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "ndarray": "^1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.16", "@cornerstonejs/tools": "^3.7.16" } }, "sha512-gMLRTZzhIGOwUT9rufpv4uq5iHTJgg2x6GW++eOi/6bEDY2wOiH2peRM8W1EfAoa3cK+lLLoAqEARlVMJq//tg=="],
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.7.17", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "ndarray": "^1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.17", "@cornerstonejs/tools": "^3.7.17" } }, "sha512-K1tz4y5YRgen+z8Yva5mzO3iwn1UZRoqf0wX8VjU1zszx/F4m19DSNcR9H5BJvlGM09DZjpJ42QXK2o9pIvnTg=="],
"@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="],
@ -1107,15 +1107,15 @@
"@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="],
"@cornerstonejs/core": ["@cornerstonejs/core@3.7.16", "", { "dependencies": { "@kitware/vtk.js": "32.12.1", "comlink": "^4.4.1", "gl-matrix": "^3.4.3", "loglevel": "^1.9.2" } }, "sha512-5rc371p1P+hk1rUzpHTByW+7Mp6LyahqWJLJwt/55a4BEiEtbhWyTSAz/XKWcOd5bgq7huAsyHKCmMcWrHx3pg=="],
"@cornerstonejs/core": ["@cornerstonejs/core@3.7.17", "", { "dependencies": { "@kitware/vtk.js": "32.12.1", "comlink": "^4.4.1", "gl-matrix": "^3.4.3", "loglevel": "^1.9.2" } }, "sha512-2Z3FqrGJept9k1dmlGeMBPon1MDR/P7CkcTTevpKTlYiT3D7bqQ+Q5xdssLzWRD86et0PkRSJGktBM163WCgGw=="],
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.7.16", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.5", "comlink": "^4.4.1", "dicom-parser": "^1.8.9", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.16" } }, "sha512-8O3cd9L54s3AFE3DZUMKOUvuuE9MkYuWZ8NgCPtBE1Qky6d4KNGKdljzwmpj8JJ7y7yGLW1rI0KqQc9DCf4//Q=="],
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.7.17", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.5", "comlink": "^4.4.1", "dicom-parser": "^1.8.9", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.17" } }, "sha512-LKHQ7x7S47TtF8M4ZmR4S+afbSsAT+yVMkKD81VU3A8oyuX7hT4+cUO4JlGGck3BxG4FyjgNSGT+T3YxAH1SHw=="],
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@3.7.16", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.16", "@cornerstonejs/tools": "^3.7.16", "@kitware/vtk.js": "32.12.1" } }, "sha512-zoL7arbPywNxUksKkkorcICpIwAgcpYbBBEFIGXt4gSS7NPC7cRU12PU1mj3Cgy+59nO2QIohoPkv0Kj/AwXmA=="],
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@3.7.17", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.17", "@cornerstonejs/tools": "^3.7.17", "@kitware/vtk.js": "32.12.1" } }, "sha512-K8R11wqv4TFljMArbx+4JbMtsINeMandp0mD8zyrj1rWXA+RY49QcNa7ob6KBnV26xJqXMT0VyJ0k/Ow53TiLQ=="],
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.7.16", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.16", "@cornerstonejs/tools": "^3.7.16", "@kitware/vtk.js": "32.12.1" } }, "sha512-HW7h9xRWRDKmaYpv4KWqSYcFz4M2EbMnYNaP+JgnLRws/1eoVPM5YYTsWgyINOM8fdR9o6tMmtMdjcioEGsZPg=="],
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.7.17", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.17", "@cornerstonejs/tools": "^3.7.17", "@kitware/vtk.js": "32.12.1" } }, "sha512-ZI0643arqYbkrjadDSOsGTO2JS1OUtJkF8qEnjjG8MlxqQ7g6sPnTM/atrYRc9ZFoJxFL4kz7D0gOjcBrarYeQ=="],
"@cornerstonejs/tools": ["@cornerstonejs/tools@3.7.16", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.16", "@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-hmAAmUiEr8z920btZtkT7jqYgZzQqLT82FijfYZVd591nddR25hb77OXEJ+PPAQbvktwPSUZx0kMBoUuM5VpWw=="],
"@cornerstonejs/tools": ["@cornerstonejs/tools@3.7.17", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.7.17", "@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-ZDMboShGzMDkqnzEDE7Y9UTJgHqGkVQt8l6AL5dCyA/IIlWEXXdxVe1weotJxgjMqaPETiwJ5EtN/3D4+tf1Rg=="],
"@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=="],

View File

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

View File

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

View File

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

View File

@ -77,28 +77,4 @@ export default function init({
SRRectangleROI: dashedLine,
global: {},
});
measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
({ viewportId, measurement, isConsumed }) => {
if (isConsumed) {
return;
}
try {
const currentViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const { viewPlaneNormal } = currentViewport.getCamera();
const referencedImageId = csToolsUtils.getClosestImageIdForStackViewport(
currentViewport as StackViewport,
measurement.points[0],
viewPlaneNormal
);
const imageIndex = (currentViewport as StackViewport)
.getImageIds()
.indexOf(referencedImageId);
csUtils.jumpToSlice(currentViewport.element, { imageIndex });
} catch (error) {
console.warn('Unable to jump to image based on measurement coordinate', error);
}
}
);
}

View File

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

View File

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

View File

@ -23,12 +23,6 @@ import { WITH_NAVIGATION } from '../services/ViewportService/CornerstoneViewport
const STACK = 'stack';
/**
* Caches the jump to measurement operation, so that if display set is shown,
* it can jump to the measurement.
*/
let cacheJumpToMeasurementEvent;
// Todo: This should be done with expose of internal API similar to react-vtkjs-viewport
// Then we don't need to worry about the re-renders if the props change.
const OHIFCornerstoneViewport = React.memo(
@ -99,6 +93,7 @@ const OHIFCornerstoneViewport = React.memo(
cornerstoneCacheService,
viewportActionCornersService,
customizationService,
measurementService,
} = servicesManager.services;
const [viewportDialogState] = useViewportDialog();
@ -263,14 +258,6 @@ const OHIFCornerstoneViewport = React.memo(
const presentations = getViewportPresentations(viewportId, viewportOptions);
let measurement;
if (cacheJumpToMeasurementEvent?.viewportId === viewportId) {
measurement = cacheJumpToMeasurementEvent.measurement;
// Delete the position presentation so that viewport navigates direct
presentations.positionPresentation = null;
cacheJumpToMeasurementEvent = null;
}
// Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
// Used for segmentation hydration right now, since the logic to decide whether
// a viewport needs to render a segmentation lives inside the CornerstoneViewportService
@ -288,10 +275,6 @@ const OHIFCornerstoneViewport = React.memo(
displaySetOptions,
presentations
);
if (measurement) {
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
}
};
loadViewportData();
@ -312,16 +295,13 @@ const OHIFCornerstoneViewport = React.memo(
return;
}
const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(
elementRef,
viewportId,
servicesManager
const { unsubscribe } = measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
event => handleJumpToMeasurement(event, elementRef, viewportId, cornerstoneViewportService)
);
_checkForCachedJumpToMeasurementEvents(elementRef, viewportId, displaySets, servicesManager);
return () => {
unsubscribeFromJumpToMeasurementEvents();
unsubscribe();
};
}, [displaySets, elementRef, viewportId, isJumpToMeasurementDisabled, servicesManager]);
@ -427,99 +407,52 @@ const OHIFCornerstoneViewport = React.memo(
areEqual
);
function _subscribeToJumpToMeasurementEvents(elementRef, viewportId, servicesManager) {
const { measurementService, cornerstoneViewportService } = servicesManager.services;
// Helper function to handle jumping to measurements
function handleJumpToMeasurement(event, elementRef, viewportId, cornerstoneViewportService) {
const { measurement, isConsumed } = event;
if (!measurement || isConsumed) {
return;
}
const { unsubscribe } = measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
props => {
cacheJumpToMeasurementEvent = props;
const { viewportId: jumpId, measurement, isConsumed } = props;
if (!measurement || isConsumed) {
return;
}
if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) {
// Decide on which viewport should handle this
cacheJumpToMeasurementEvent.cornerstoneViewport =
cornerstoneViewportService.getViewportIdToJump(jumpId, {
displaySetInstanceUID: measurement.displaySetInstanceUID,
...measurement.metadata,
referencedImageId:
measurement.referencedImageId || measurement.metadata?.referencedImageId,
});
}
if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
return;
}
_jumpToMeasurement(measurement, elementRef, viewportId, servicesManager);
}
const enabledElement = getEnabledElement(elementRef.current);
if (!enabledElement) {
return;
}
const viewport = enabledElement.viewport as csTypes.IStackViewport | csTypes.IVolumeViewport;
const { metadata, displaySetInstanceUID } = measurement;
const viewportDisplaySets = cornerstoneViewportService.getViewportDisplaySets(viewportId);
const showingDisplaySet = viewportDisplaySets.find(
ds => ds.displaySetInstanceUID === displaySetInstanceUID
);
return unsubscribe;
}
let metadataToUse = metadata;
// if it is not showing the displaySet we need to remove the FOR from the metadata
if (!showingDisplaySet) {
metadataToUse = {
...metadata,
FrameOfReferenceUID: undefined,
};
}
// Check if there is a queued jumpToMeasurement event
function _checkForCachedJumpToMeasurementEvents(
elementRef,
viewportId,
displaySets,
servicesManager
) {
if (!cacheJumpToMeasurementEvent) {
return;
}
if (cacheJumpToMeasurementEvent.isConsumed) {
cacheJumpToMeasurementEvent = null;
return;
}
const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID);
if (!displaysUIDs?.length) {
// Todo: make it work with cases where we want to define FOR based measurements too
if (!viewport.isReferenceViewable(metadataToUse, WITH_NAVIGATION)) {
return;
}
// Jump to measurement if the measurement exists
const { measurement } = cacheJumpToMeasurementEvent;
if (measurement && elementRef) {
if (displaysUIDs.includes(measurement?.displaySetInstanceUID)) {
_jumpToMeasurement(measurement, elementRef, viewportId, servicesManager);
}
try {
viewport.setViewReference(metadata);
viewport.render();
} catch (e) {
console.warn('Unable to apply', metadata, e);
}
}
function _jumpToMeasurement(measurement, targetElementRef, viewportId, servicesManager) {
const { viewportGridService } = servicesManager.services;
const targetElement = targetElementRef.current;
// Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
// to set it properly
// setCornerstoneMeasurementActive(measurement);
viewportGridService.setActiveViewportId(viewportId);
const enabledElement = getEnabledElement(targetElement);
if (enabledElement) {
// See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
const viewport = enabledElement.viewport as csTypes.IStackViewport | csTypes.IVolumeViewport;
const { metadata } = measurement;
if (!viewport.isReferenceViewable(metadata, WITH_NAVIGATION)) {
console.log("Reference isn't viewable, postponing until updated");
return;
}
try {
viewport.setViewReference(metadata);
} catch (e) {
console.warn('Unable to apply', metadata, e);
}
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
// Jump to measurement consumed, remove.
cacheJumpToMeasurementEvent?.consume?.();
cacheJumpToMeasurementEvent = null;
}
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
event?.consume?.();
}
function _rehydrateSynchronizers(viewportId: string, syncGroupService: any) {

View File

@ -215,6 +215,7 @@ function commandsModule({
viewportId,
displaySetInstanceUID,
referencedImageId,
options,
}) => {
const presentations = cornerstoneViewportService.getPresentations(viewportId);
const { positionPresentationStore, setPositionPresentation, getPositionPresentationId } =
@ -226,16 +227,18 @@ function commandsModule({
([key, value]) => key.includes(displaySetInstanceUID) && value.viewportId === viewportId
)?.[0];
if (previousReferencedDisplaySetStoreKey) {
const presentationData = referencedImageId
? {
...presentations.positionPresentation,
viewReference: {
referencedImageId,
},
}
: presentations.positionPresentation;
// Create presentation data with referencedImageId and options if provided
const presentationData = referencedImageId
? {
...presentations.positionPresentation,
viewReference: {
referencedImageId,
...options,
},
}
: presentations.positionPresentation;
if (previousReferencedDisplaySetStoreKey) {
setPositionPresentation(previousReferencedDisplaySetStoreKey, presentationData);
return;
}
@ -243,13 +246,12 @@ function commandsModule({
// if not found means we have not visited that referencedDisplaySetInstanceUID before
// so we need to grab the positionPresentationId directly from the store,
// Todo: this is really hacky, we should have a better way for this
const positionPresentationId = getPositionPresentationId({
displaySetInstanceUIDs: [displaySetInstanceUID],
viewportId,
});
setPositionPresentation(positionPresentationId, presentations.positionPresentation);
setPositionPresentation(positionPresentationId, presentationData);
},
getNearbyToolData({ nearbyToolData, element, canvasCoordinates }) {
return nearbyToolData ?? cstUtils.getAnnotationNearPoint(element, canvasCoordinates);

View File

@ -173,9 +173,6 @@ const cornerstoneExtension: Types.Extensions.Extension = {
getHangingProtocolModule,
getViewportModule({ servicesManager, commandsManager }) {
const ExtendedOHIFCornerstoneViewport = props => {
// const onNewImageHandler = jumpData => {
// commandsManager.runCommand('jumpToImage', jumpData);
// };
const { toolbarService } = servicesManager.services;
return (
@ -192,6 +189,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
{
name: 'cornerstone',
component: ExtendedOHIFCornerstoneViewport,
isReferenceViewable: props => utils.isReferenceViewable({ ...props, servicesManager }),
},
];
},

View File

@ -92,6 +92,7 @@ export default async function init({
hangingProtocolService,
viewportGridService,
segmentationService,
measurementService,
} = servicesManager.services;
window.services = servicesManager.services;
@ -177,6 +178,17 @@ export default async function init({
initCineService(servicesManager);
initStudyPrefetcherService(servicesManager);
[
measurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
measurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
].forEach(event => {
measurementService.subscribe(event, evt => {
const { measurement } = evt;
const { uid: annotationUID } = measurement;
cornerstoneTools.annotation.selection.setAnnotationSelected(annotationUID, true);
});
});
segmentationService.subscribeDebounced(
segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
async ({ segmentationId }) => {

View File

@ -38,7 +38,7 @@ const EVENTS = {
VIEWPORT_VOLUMES_CHANGED: 'event::cornerstoneViewportService:viewportVolumesChanged',
};
export const WITH_NAVIGATION = { withNavigation: true, withOrientation: true };
export const WITH_NAVIGATION = { withNavigation: true, withOrientation: false };
/**
* Handles cornerstone viewport logic including enabling, disabling, and
@ -851,14 +851,18 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
viewport.setProperties(properties, volumeId);
});
viewport.render();
this.setPresentations(viewport.id, presentations, viewportInfo);
const imageIndex = this._getInitialImageIndexForViewport(viewportInfo);
if (!presentations.positionPresentation) {
const imageIndex = this._getInitialImageIndexForViewport(viewportInfo);
if (imageIndex !== undefined) {
csUtils.jumpToSlice(viewport.element, {
imageIndex,
});
if (imageIndex !== undefined) {
csUtils.jumpToSlice(viewport.element, {
imageIndex,
});
}
}
viewport.render();
@ -1177,6 +1181,20 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
}
});
}
/**
* Gets the display sets for a given viewport
* @param viewportId - The ID of the viewport to get display sets for
* @returns Array of display sets for the viewport
*/
public getViewportDisplaySets(viewportId: string): OhifTypes.DisplaySet[] {
const { displaySetService } = this.servicesManager.services;
const displaySetInstanceUIDs = this.viewportsDisplaySets.get(viewportId) || [];
return displaySetInstanceUIDs
.map(uid => displaySetService.getDisplaySetByUID(uid))
.filter(Boolean);
}
}
export default CornerstoneViewportService;

View File

@ -1,7 +1,9 @@
import { handleSegmentChange } from './segmentUtils';
import { isReferenceViewable } from './isReferenceViewable';
const utils = {
handleSegmentChange,
isReferenceViewable,
};
export default utils;

View File

@ -0,0 +1,98 @@
import { vec3 } from 'gl-matrix';
import { Enums } from '@cornerstonejs/core';
import OrientationAxis = Enums.OrientationAxis;
export const isReferenceViewable = ({
viewportId,
reference,
viewportOptions,
servicesManager,
}) => {
const { cornerstoneViewportService, displaySetService } = servicesManager.services;
if (!viewportOptions) {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
// we can make a customization for this to allow
const isViewable = viewport.isReferenceViewable(reference, {
withNavigation: true,
});
return isViewable;
}
if (viewportOptions.viewportType === 'stack') {
// we only need the viewport to include the referenced imageId
const displaySet = displaySetService.getDisplaySetByUID(reference.displaySetInstanceUID);
const imageIds = displaySet.instances.map(instance => instance.imageId);
return imageIds.includes(reference.referencedImageId);
}
// for the volume viewports, we need to check orientation
const { orientation } = viewportOptions;
// Todo: handle hanging protocols that have acquisition orientation
const closestOrientation = getClosestOrientationFromIOP(
displaySetService,
reference.displaySetInstanceUID
);
return closestOrientation === orientation;
};
/**
* Get the plane (orientation) to which the ImageOrientationPatient is most closely aligned
*
* @param displaySetService
* @param displaySetInstanceUID
* @returns orientation
*/
export default function getClosestOrientationFromIOP(
displaySetService,
displaySetInstanceUID
): OrientationAxis {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
const imageOrientationPatient = displaySet.instances[0].ImageOrientationPatient as Array<number>;
// ImageOrientationPatient must be an array of length 6.
if (imageOrientationPatient?.length !== 6) {
return;
}
// Take cross product to get vector coming "out" of image plane
const rowCosineVec = vec3.fromValues(
imageOrientationPatient[0],
imageOrientationPatient[1],
imageOrientationPatient[2]
);
const colCosineVec = vec3.fromValues(
imageOrientationPatient[3],
imageOrientationPatient[4],
imageOrientationPatient[5]
);
const scanAxisNormal = vec3.cross(vec3.create(), rowCosineVec, colCosineVec);
// Define the reference vectors for axial, coronal, and sagittal planes
const unitVectors = {
[OrientationAxis.AXIAL]: vec3.fromValues(0, 0, 1),
[OrientationAxis.CORONAL]: vec3.fromValues(0, 1, 0),
[OrientationAxis.SAGITTAL]: vec3.fromValues(1, 0, 0),
};
// Compute dot products for each reference plane
// Because all vectors are normalized, dot product is bounded between -1 and 1
let maxDot = 0;
let maxOrientation: string = '';
for (const [k, v] of Object.entries(unitVectors)) {
// Absolute value of dot product because we only care about alignment with the axis
// For example, dot product of -1 for a given axis means perfect alignment
// but the image is pointing in the "opposite" direction
const res = Math.abs(vec3.dot(scanAxisNormal, v));
if (res > maxDot) {
maxDot = res;
maxOrientation = k;
}
}
return maxOrientation as OrientationAxis;
}

View File

@ -82,7 +82,7 @@ function ViewerLayout({
);
}
return { entry, content: entry.component };
return { entry };
};
useEffect(() => {
@ -107,6 +107,7 @@ function ViewerLayout({
return {
component: entry.component,
isReferenceViewable: entry.isReferenceViewable,
displaySetsToDisplay: viewportComponent.displaySetsToDisplay,
};
};

View File

@ -14,6 +14,7 @@ const getViewportModule = ({
{
name: 'chartViewport',
component: LineChartViewport,
isReferenceViewable: () => false,
},
];
};

View File

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

View File

@ -268,12 +268,20 @@ function TrackedMeasurementsContextProvider(
displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID &&
displaySet.isRehydratable === true
) {
console.log('sending event...', trackedMeasurements);
sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', {
const params = {
displaySetInstanceUID: displaySet.displaySetInstanceUID,
SeriesInstanceUID: displaySet.SeriesInstanceUID,
viewportId: activeViewportId,
});
};
// Check if we should bypass the confirmation prompt
const disableConfirmationPrompts = appConfig?.disableConfirmationPrompts;
if (disableConfirmationPrompts) {
sendTrackedMeasurementsEvent('HYDRATE_SR', params);
} else {
sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', params);
}
}
}
};
@ -284,6 +292,7 @@ function TrackedMeasurementsContextProvider(
sendTrackedMeasurementsEvent,
servicesManager.services,
viewports,
appConfig,
]);
useEffect(() => {

View File

@ -57,7 +57,6 @@ const machineConfiguration = {
actions: ['setPreviousState'],
},
],
// Unused? We may only do PROMPT_HYDRATE_SR now?
SET_TRACKED_SERIES: [
{
target: 'tracking',

View File

@ -1,4 +1,5 @@
import React from 'react';
import { utils } from '@ohif/extension-cornerstone';
const Component = React.lazy(() => {
return import(/* webpackPrefetch: true */ './viewports/TrackedCornerstoneViewport');
@ -28,6 +29,7 @@ function getViewportModule({ servicesManager, commandsManager, extensionManager
{
name: 'cornerstone-tracked',
component: ExtendedOHIFCornerstoneTrackingViewport,
isReferenceViewable: props => utils.isReferenceViewable({ ...props, servicesManager }),
},
];
}

View File

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

View File

@ -1,14 +1,13 @@
import React, { useEffect, useCallback, useRef } from 'react';
import React, { useEffect, useCallback, useRef, useMemo } from 'react';
import { useResizeDetector } from 'react-resize-detector';
import { Types, MeasurementService } from '@ohif/core';
import { ViewportGrid, ViewportPane } from '@ohif/ui-next';
import { useViewportGrid } from '@ohif/ui-next';
import EmptyViewport from './EmptyViewport';
import classNames from 'classnames';
import { useAppConfig } from '@state';
function ViewerViewportGrid(props: withAppTypes) {
const { servicesManager, viewportComponents = [], dataSource } = props;
const { servicesManager, viewportComponents = [], dataSource, commandsManager } = props;
const [viewportGrid, viewportGridService] = useViewportGrid();
const [appConfig] = useAppConfig();
@ -167,17 +166,51 @@ function ViewerViewportGrid(props: withAppTypes) {
useEffect(() => {
const { unsubscribe } = measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
({ viewportId, measurement, isConsumed }) => {
event => {
const { viewportId, measurement, isConsumed } = event;
if (isConsumed) {
return;
}
// This occurs when no viewport has elected to consume the event
// so we need to change layouts into a layout which can consume
// the event.
const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement;
const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement;
const { viewports } = viewportGridService.getState();
// Check if any viewport can display this measurement
let canAnyViewportDisplayMeasurement = false;
viewports.forEach((viewport, id) => {
const displaySetInstanceUIDs = viewport.displaySetInstanceUIDs || [];
const viewportHasDisplaySet = displaySetInstanceUIDs.includes(
referencedDisplaySetInstanceUID
);
// Extract metadata and prepare reference
const { FrameOfReferenceUID, ...metadataRest } = measurement.metadata;
const reference = {
...(viewportHasDisplaySet ? measurement.metadata : metadataRest),
displaySetInstanceUID: referencedDisplaySetInstanceUID,
};
// Check if viewport can display the reference
if (
viewport.isReferenceViewable?.({
viewportId: id,
reference,
})
) {
canAnyViewportDisplayMeasurement = true;
}
});
if (canAnyViewportDisplayMeasurement) {
// Let the viewports handle the jump
return;
}
// Need to change layouts since no viewport consumed the event
const updatedViewports = _getUpdatedViewports(viewportId, referencedDisplaySetInstanceUID);
if (!updatedViewports[0]) {
if (!updatedViewports?.[0]) {
console.warn(
'ViewportGrid::Unable to navigate to viewport containing',
referencedDisplaySetInstanceUID
@ -185,24 +218,35 @@ function ViewerViewportGrid(props: withAppTypes) {
return;
}
// Arbitrarily assign the viewport to element 0
// TODO - this should perform a search to find the most suitable viewport.
updatedViewports[0] = { ...updatedViewports[0] };
const [viewport] = updatedViewports;
// Find the viewport that can display the measurement
const viewport = updatedViewports.find(viewport => {
const gridViewport = viewportGridService.getViewportState(viewport.viewportId);
return gridViewport.isReferenceViewable?.({
viewportId: viewport.viewportId,
reference: {
...measurement.metadata,
displaySetInstanceUID: referencedDisplaySetInstanceUID,
},
viewportOptions: gridViewport.viewportOptions || {},
});
});
// Copy the viewport options to prevent modifying the internal data
viewport.viewportOptions = {
...viewport.viewportOptions,
orientation: 'acquisition',
// The preferred way to jump to the measurement view is to set the
// view reference, as this can hold information such as the orientation
// or zoom level required to display an annotation. The metadata attribute
// of the measurement is a viewReference, so use it to show the measurement.
// Longer term this should clear the view reference data
viewReference: measurement.metadata,
viewportType: measurement.metadata.volumeId ? 'volume' : null,
};
if (!viewport) {
console.warn('No suitable viewport found for displaying measurement');
return;
}
// Update stored position presentation
commandsManager.run('updateStoredPositionPresentation', {
viewportId: viewport.viewportId,
displaySetInstanceUID: referencedDisplaySetInstanceUID,
referencedImageId: measurement.referencedImageId,
options: {
...measurement.metadata,
},
});
event.consume();
viewportGridService.setDisplaySetsForViewports(updatedViewports);
}
);
@ -210,7 +254,7 @@ function ViewerViewportGrid(props: withAppTypes) {
return () => {
unsubscribe();
};
}, [viewports]);
}, [viewports, _getUpdatedViewports]);
const onDropHandler = (viewportId, { displaySetInstanceUID }) => {
const customOnDropHandler = customizationService.getCustomization('customOnDropHandler');
@ -229,6 +273,9 @@ function ViewerViewportGrid(props: withAppTypes) {
});
};
// Store previous isReferenceViewable values to avoid infinite loops
const prevReferenceViewableMap = useRef(new Map());
const getViewportPanes = useCallback(() => {
const viewportPanes = [];
@ -260,12 +307,27 @@ function ViewerViewportGrid(props: withAppTypes) {
return !displaySet?.unsupported;
});
const ViewportComponent = _getViewportComponent(
const { component: ViewportComponent, isReferenceViewable } = _getViewportComponent(
displaySets,
viewportComponents,
uiNotificationService
);
// Only update isReferenceViewable if it's changed to avoid render loops
// We need to handle both function and non-function values
if (viewportId) {
const prevValue = prevReferenceViewableMap.current.get(viewportId);
const isFunction = typeof isReferenceViewable === 'function';
const isSameFunction = isFunction && typeof prevValue === 'function';
// For non-functions, compare directly. For functions, we treat them as always different
// (this is conservative but safe)
if (!isSameFunction && prevValue !== isReferenceViewable) {
viewportGridService.setIsReferenceViewable(viewportId, isReferenceViewable);
prevReferenceViewableMap.current.set(viewportId, isReferenceViewable);
}
}
// look inside displaySets to see if they need reRendering
const displaySetsNeedsRerendering = displaySets.some(displaySet => {
return displaySet.needsRerendering;
@ -381,7 +443,7 @@ function ViewerViewportGrid(props: withAppTypes) {
function _getViewportComponent(displaySets, viewportComponents, uiNotificationService) {
if (!displaySets || !displaySets.length) {
return EmptyViewport;
return { component: EmptyViewport, isReferenceViewable: () => false };
}
// Todo: Do we have a viewport that has two different SOPClassHandlerIds?
@ -395,8 +457,8 @@ function _getViewportComponent(displaySets, viewportComponents, uiNotificationSe
throw new Error('displaySetsToDisplay is null');
}
if (viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId)) {
const { component } = viewportComponents[i];
return component;
const { component, isReferenceViewable } = viewportComponents[i];
return { component, isReferenceViewable };
}
}
@ -407,7 +469,7 @@ function _getViewportComponent(displaySets, viewportComponents, uiNotificationSe
type: 'error',
});
return EmptyViewport;
return { component: EmptyViewport, isReferenceViewable: () => false };
}
export default ViewerViewportGrid;

View File

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

View File

@ -670,8 +670,11 @@ class MeasurementService extends PubSubService {
measurement,
});
this._broadcastEvent(EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, consumableEvent);
// Important: we should broadcast the layout event first, since
// in the layout there might be a viewport that we can match and choose
// and jump in it before we decide on changing the orientation of different viewports
this._broadcastEvent(EVENTS.JUMP_TO_MEASUREMENT_LAYOUT, consumableEvent);
this._broadcastEvent(EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, consumableEvent);
}
_getSourceUID(name, version) {

View File

@ -38,6 +38,10 @@ class ViewportGridService extends PubSubService {
this.presentationIdProviders.set(id, provider);
}
public setIsReferenceViewable(viewportId: string, isReferenceViewable: boolean): void {
this.serviceImplementation._setIsReferenceViewable(viewportId, isReferenceViewable);
}
public getPresentationId(id: string, viewportId: string): string | null {
const state = this.getState();
const viewport = state.viewports.get(viewportId);
@ -93,7 +97,12 @@ class ViewportGridService extends PubSubService {
set: setImplementation,
getNumViewportPanes: getNumViewportPanesImplementation,
setViewportIsReady: setViewportIsReadyImplementation,
setIsReferenceViewable: setIsReferenceViewableImplementation,
getViewportState: getViewportStateImplementation,
}): void {
if (getViewportStateImplementation) {
this.serviceImplementation._getViewportState = getViewportStateImplementation;
}
if (getStateImplementation) {
this.serviceImplementation._getState = getStateImplementation;
}
@ -123,6 +132,9 @@ class ViewportGridService extends PubSubService {
if (setViewportIsReadyImplementation) {
this.serviceImplementation._setViewportIsReady = setViewportIsReadyImplementation;
}
if (setIsReferenceViewableImplementation) {
this.serviceImplementation._setIsReferenceViewable = setIsReferenceViewableImplementation;
}
}
public publishViewportsReady() {
@ -148,8 +160,7 @@ class ViewportGridService extends PubSubService {
}
public getViewportState(viewportId: string) {
const state = this.getState();
return state.viewports.get(viewportId);
return this.serviceImplementation._getViewportState(viewportId);
}
public setViewportIsReady(viewportId, callback) {

View File

@ -45,6 +45,10 @@ export interface ViewportGridState {
activeViewportId: string | null;
layout: Layout;
isHangingProtocolLayout: boolean;
isReferenceViewable: (props: {
viewportId: string;
reference: Record<string, unknown>;
}) => boolean;
viewports: GridViewports;
}

View File

@ -0,0 +1,54 @@
import { vec3 } from 'gl-matrix';
/**
* Get the plane (orientation) to which the ImageOrientationPatient is most closely aligned
*
* @param displaySetService
* @param displaySetInstanceUID
* @returns orientation
*/
export default function getClosestOrientationFromIOP(displaySetService, displaySetInstanceUID) {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
const imageOrientationPatient = displaySet.instances[0].ImageOrientationPatient as Array<number>;
// ImageOrientationPatient must be an array of length 6.
if (imageOrientationPatient?.length !== 6) {
return;
}
// Take cross product to get vector coming "out" of image plane
const rowCosineVec = vec3.fromValues(
imageOrientationPatient[0],
imageOrientationPatient[1],
imageOrientationPatient[2]
);
const colCosineVec = vec3.fromValues(
imageOrientationPatient[3],
imageOrientationPatient[4],
imageOrientationPatient[5]
);
const scanAxisNormal = vec3.cross(vec3.create(), rowCosineVec, colCosineVec);
// Define the reference vectors for axial, coronal, and sagittal planes
const unitVectors = {
axial: vec3.fromValues(0, 0, 1),
coronal: vec3.fromValues(0, 1, 0),
sagittal: vec3.fromValues(1, 0, 0),
};
// Compute dot products for each reference plane
// Because all vectors are normalized, dot product is bounded between -1 and 1
let maxDot = 0;
let maxOrientation: string = '';
for (const [k, v] of Object.entries(unitVectors)) {
// Absolute value of dot product because we only care about alignment with the axis
// For example, dot product of -1 for a given axis means perfect alignment
// but the image is pointing in the "opposite" direction
const res = Math.abs(vec3.dot(scanAxisNormal, v));
if (res > maxDot) {
maxDot = res;
maxOrientation = k;
}
}
return maxOrientation;
}

View File

@ -1,53 +0,0 @@
import * as utils from './index';
describe('Top level exports', () => {
test('should export the modules ', () => {
const expectedExports = [
'guid',
'ObjectPath',
'absoluteUrl',
'seriesSortCriteria',
'sortBy',
'sortStudy',
'sortBySeriesDate',
'sortStudyInstances',
'sortStudySeries',
'sortingCriteria',
'splitComma',
'getSplitParam',
'isLowPriorityModality',
'writeScript',
'debounce',
'downloadCSVReport',
'imageIdToURI',
'roundNumber',
'b64toBlob',
'sopClassDictionary',
'createStudyBrowserTabs',
'formatDate',
'formatTime',
'formatPN',
'generateAcceptHeader',
'isEqualWithin',
//'loadAndCacheDerivedDisplaySets',
'isDisplaySetReconstructable',
'isImage',
'urlUtil',
'makeDeferred',
'makeCancelable',
'hotkeys',
'Queue',
'isDicomUid',
'resolveObjectPath',
'hierarchicalListUtils',
'progressTrackingUtils',
'uuidv4',
'addAccessors',
'MeasurementFilters',
].sort();
const exports = Object.keys(utils.default).sort();
expect(exports).toEqual(expectedExports);
});
});

View File

@ -40,7 +40,7 @@ import { splitComma, getSplitParam } from './splitComma';
import { createStudyBrowserTabs } from './createStudyBrowserTabs';
import { sopClassDictionary } from './sopClassDictionary';
import * as MeasurementFilters from './measurementFilters';
import getClosestOrientationFromIOP from './getClosestOrientationFromIOP';
// Commented out unused functionality.
// Need to implement new mechanism for derived displaySets using the displaySetManager.
@ -86,6 +86,7 @@ const utils = {
generateAcceptHeader,
createStudyBrowserTabs,
MeasurementFilters,
getClosestOrientationFromIOP,
};
export {
@ -120,6 +121,7 @@ export {
generateAcceptHeader,
createStudyBrowserTabs,
MeasurementFilters,
getClosestOrientationFromIOP,
};
export default utils;

View File

@ -141,6 +141,19 @@ interface ViewportGridProviderProps {
export function ViewportGridProvider({ children, service }: ViewportGridProviderProps) {
const viewportGridReducer = (state: AppTypes.ViewportGrid.State, action) => {
switch (action.type) {
case 'SET_IS_REFERENCE_VIEWABLE': {
const { viewportId, isReferenceViewable } = action.payload;
const viewports = new Map(state.viewports);
const viewport = viewports.get(viewportId);
if (!viewport) {
return;
}
viewports.set(viewportId, {
...viewport,
isReferenceViewable,
});
return { ...state, viewports };
}
case 'SET_ACTIVE_VIEWPORT_ID': {
return { ...state, ...{ activeViewportId: action.payload } };
}
@ -378,6 +391,13 @@ export function ViewportGridProvider({ children, service }: ViewportGridProvider
[dispatch]
);
const setIsReferenceViewable = useCallback(
(viewportId, isReferenceViewable) => {
dispatch({ type: 'SET_IS_REFERENCE_VIEWABLE', payload: { viewportId, isReferenceViewable } });
},
[dispatch]
);
const setDisplaySetsForViewports = useCallback(
viewports =>
dispatch({
@ -449,6 +469,14 @@ export function ViewportGridProvider({ children, service }: ViewportGridProvider
[dispatch]
);
const getViewportState = useCallback(
viewportId => {
const { viewports } = viewportGridState;
return viewports.get(viewportId);
},
[viewportGridState]
);
const getNumViewportPanes = useCallback(() => {
const { layout, viewports } = viewportGridState;
const { numRows, numCols } = layout;
@ -466,12 +494,14 @@ export function ViewportGridProvider({ children, service }: ViewportGridProvider
getState,
setActiveViewportId,
setDisplaySetsForViewports,
setIsReferenceViewable,
setLayout,
reset,
onModeExit: reset,
set,
getNumViewportPanes,
setViewportIsReady,
getViewportState,
getGridViewportsReady,
});
}
@ -480,12 +510,14 @@ export function ViewportGridProvider({ children, service }: ViewportGridProvider
service,
setActiveViewportId,
setDisplaySetsForViewports,
setIsReferenceViewable,
setLayout,
reset,
set,
getNumViewportPanes,
setViewportIsReady,
getGridViewportsReady,
getViewportState,
]);
// run many of the calls through the service itself since we want to publish events
@ -494,7 +526,10 @@ export function ViewportGridProvider({ children, service }: ViewportGridProvider
setActiveViewportId: index => service.setActiveViewportId(index),
setDisplaySetsForViewport: props => service.setDisplaySetsForViewports([props]),
setDisplaySetsForViewports: props => service.setDisplaySetsForViewports(props),
setIsReferenceViewable: (viewportId, isReferenceViewable) =>
service.setIsReferenceViewable(viewportId, isReferenceViewable),
setLayout: layout => service.setLayout(layout),
getViewportState: viewportId => service.getViewportState(viewportId),
reset: () => service.reset(),
set: gridLayoutState => service.setState(gridLayoutState), // run it through the service itself since we want to publish events
getNumViewportPanes,

View File

@ -0,0 +1,104 @@
import { test } from '@playwright/test';
import { visitStudy, checkForScreenshot, screenShotPaths, simulateClicksOnElement } from './utils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.6.1.4.1.25403.345050719074.3824.20170125095258.1';
const mode = 'viewer';
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should hydrate in MPR correctly', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('trackedMeasurements-btn').click();
// get the div that has Body 4.0 Lung I and double click it
await page.locator(':text("S:7")').first().dblclick();
await page.evaluate(() => {
// Access cornerstone directly from the window object
const cornerstone = window.cornerstone;
if (!cornerstone) {
return;
}
const enabledElements = cornerstone.getEnabledElements();
if (enabledElements.length === 0) {
return;
}
const viewport = enabledElements[0].viewport;
if (viewport) {
viewport.setImageIdIndex(20);
viewport.render();
}
});
await page.getByTestId('MeasurementTools-split-button-secondary').click();
await page.getByTestId('Bidirectional').click();
const locator = page.getByTestId('viewport-pane').locator('canvas');
await simulateClicksOnElement({
locator,
points: [
{
x: 405,
y: 277,
},
{
x: 515,
y: 339,
},
],
});
// wait 2 seconds
await page.waitForTimeout(2000);
await page.getByTestId('prompt-begin-tracking-yes-btn').click();
// scroll away
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.initialDraw);
// use mouse wheel to scroll away
await page.mouse.wheel(0, 100);
// wait 2 seconds
await page.waitForTimeout(2000);
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.scrollAway);
await page.getByTestId('data-row').first().click();
await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.jumpToMeasurementStack);
await page.getByTestId('Layout').click();
await page.locator('div').filter({ hasText: /^MPR$/ }).first().click();
// wait 2 seconds
await page.waitForTimeout(2000);
// jump in viewport again
await page.getByTestId('data-row').first().click();
await page.waitForTimeout(3000);
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.jumpInMPR);
await page.locator(':text("S:3")').first().dblclick();
await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.changeSeriesInMPR);
await page.getByTestId('data-row').first().click();
await page.waitForTimeout(5000);
await checkForScreenshot(
page,
page,
screenShotPaths.jumpToMeasurementMPR.jumpToMeasurementAfterSeriesChange
);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

@ -102,6 +102,16 @@ const screenShotPaths = {
tmtvRendering: {
tmtvDisplayedCorrectly: 'tmtvDisplayedCorrectly.png',
},
jumpToMeasurementMPR: {
initialDraw: 'jumpToMeasurementMPR-initialDraw.png',
scrollAway: 'jumpToMeasurementMPR-scrollAway.png',
jumpToMeasurementStack: 'jumpToMeasurementMPR-jumpToMeasurementStack.png',
goToMPR: 'jumpToMeasurementMPR-goToMPR.png',
jumpInMPR: 'jumpToMeasurementMPR-jumpInMPR.png',
changeSeriesInMPR: 'jumpToMeasurementMPR-changeSeriesInMPR.png',
jumpToMeasurementAfterSeriesChange:
'jumpToMeasurementMPR-jumpToMeasurementAfterSeriesChange.png',
},
};
export { screenShotPaths };

View File

@ -1393,10 +1393,10 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cornerstonejs/adapters@^3.7.16":
version "3.7.16"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.7.16.tgz#4ac66230ffba5b527abf4ba58c8b10ef46643c45"
integrity sha512-gMLRTZzhIGOwUT9rufpv4uq5iHTJgg2x6GW++eOi/6bEDY2wOiH2peRM8W1EfAoa3cK+lLLoAqEARlVMJq//tg==
"@cornerstonejs/adapters@^3.7.17":
version "3.7.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.7.17.tgz#7e0a84d4010c4757e58c0d350c5604de865d8c56"
integrity sha512-K1tz4y5YRgen+z8Yva5mzO3iwn1UZRoqf0wX8VjU1zszx/F4m19DSNcR9H5BJvlGM09DZjpJ42QXK2o9pIvnTg==
dependencies:
"@babel/runtime-corejs2" "^7.17.8"
buffer "^6.0.3"
@ -1429,20 +1429,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.7.16":
version "3.7.16"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.7.16.tgz#7b4a1ab6a726482ddc380eee15299ec1f9461ad5"
integrity sha512-5rc371p1P+hk1rUzpHTByW+7Mp6LyahqWJLJwt/55a4BEiEtbhWyTSAz/XKWcOd5bgq7huAsyHKCmMcWrHx3pg==
"@cornerstonejs/core@^3.7.17":
version "3.7.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.7.17.tgz#4a7986aa8bb7a68fbff597d3906e96e6b6483fc2"
integrity sha512-2Z3FqrGJept9k1dmlGeMBPon1MDR/P7CkcTTevpKTlYiT3D7bqQ+Q5xdssLzWRD86et0PkRSJGktBM163WCgGw==
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.7.16":
version "3.7.16"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.7.16.tgz#e0ed7621f81b5a3dd38f3e899ac9b7ba4a2a4ea9"
integrity sha512-8O3cd9L54s3AFE3DZUMKOUvuuE9MkYuWZ8NgCPtBE1Qky6d4KNGKdljzwmpj8JJ7y7yGLW1rI0KqQc9DCf4//Q==
"@cornerstonejs/dicom-image-loader@^3.7.17":
version "3.7.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.7.17.tgz#b14ba0cf64ab8a454ac086cd1ed6f2481359d533"
integrity sha512-LKHQ7x7S47TtF8M4ZmR4S+afbSsAT+yVMkKD81VU3A8oyuX7hT4+cUO4JlGGck3BxG4FyjgNSGT+T3YxAH1SHw==
dependencies:
"@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
@ -1454,25 +1454,25 @@
pako "^2.0.4"
uuid "^9.0.0"
"@cornerstonejs/labelmap-interpolation@^3.7.16":
version "3.7.16"
resolved "https://registry.yarnpkg.com/@cornerstonejs/labelmap-interpolation/-/labelmap-interpolation-3.7.16.tgz#fffda0b2da7cbb21ecfed8e19dd72371e5bc73fb"
integrity sha512-zoL7arbPywNxUksKkkorcICpIwAgcpYbBBEFIGXt4gSS7NPC7cRU12PU1mj3Cgy+59nO2QIohoPkv0Kj/AwXmA==
"@cornerstonejs/labelmap-interpolation@^3.7.17":
version "3.7.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/labelmap-interpolation/-/labelmap-interpolation-3.7.17.tgz#fb57706c19c77cbc8192d81d9f2d6e086be637ae"
integrity sha512-K8R11wqv4TFljMArbx+4JbMtsINeMandp0mD8zyrj1rWXA+RY49QcNa7ob6KBnV26xJqXMT0VyJ0k/Ow53TiLQ==
dependencies:
"@itk-wasm/morphological-contour-interpolation" "1.1.0"
itk-wasm "1.0.0-b.165"
"@cornerstonejs/polymorphic-segmentation@^3.7.16":
version "3.7.16"
resolved "https://registry.yarnpkg.com/@cornerstonejs/polymorphic-segmentation/-/polymorphic-segmentation-3.7.16.tgz#75c6b564178e3057075f46e5fa66a1fff3e2c3d5"
integrity sha512-HW7h9xRWRDKmaYpv4KWqSYcFz4M2EbMnYNaP+JgnLRws/1eoVPM5YYTsWgyINOM8fdR9o6tMmtMdjcioEGsZPg==
"@cornerstonejs/polymorphic-segmentation@^3.7.17":
version "3.7.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/polymorphic-segmentation/-/polymorphic-segmentation-3.7.17.tgz#f6b4f4530996d974d59fff97ec29bd34a7a8cb16"
integrity sha512-ZI0643arqYbkrjadDSOsGTO2JS1OUtJkF8qEnjjG8MlxqQ7g6sPnTM/atrYRc9ZFoJxFL4kz7D0gOjcBrarYeQ==
dependencies:
"@icr/polyseg-wasm" "0.4.0"
"@cornerstonejs/tools@^3.7.16":
version "3.7.16"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.7.16.tgz#53fd8b8437a63bcfedcb776d9edb0ad7cac414ab"
integrity sha512-hmAAmUiEr8z920btZtkT7jqYgZzQqLT82FijfYZVd591nddR25hb77OXEJ+PPAQbvktwPSUZx0kMBoUuM5VpWw==
"@cornerstonejs/tools@^3.7.17":
version "3.7.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.7.17.tgz#dc758ec14e0016bf76d04f54d3e91bd824ebdc7a"
integrity sha512-ZDMboShGzMDkqnzEDE7Y9UTJgHqGkVQt8l6AL5dCyA/IIlWEXXdxVe1weotJxgjMqaPETiwJ5EtN/3D4+tf1Rg==
dependencies:
"@types/offscreencanvas" "2019.7.3"
comlink "^4.4.1"