diff --git a/bun.lock b/bun.lock index 74a3c3084..307c2094d 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], diff --git a/extensions/cornerstone-dicom-pmap/package.json b/extensions/cornerstone-dicom-pmap/package.json index d3c0e6bb2..0233bfd65 100644 --- a/extensions/cornerstone-dicom-pmap/package.json +++ b/extensions/cornerstone-dicom-pmap/package.json @@ -46,8 +46,8 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^3.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" } diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json index b3e085183..62f7b5454 100644 --- a/extensions/cornerstone-dicom-seg/package.json +++ b/extensions/cornerstone-dicom-seg/package.json @@ -46,8 +46,8 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^3.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" } diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index 86107f4a5..e9f50c8cc 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -46,9 +46,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^3.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" } } diff --git a/extensions/cornerstone-dicom-sr/src/init.ts b/extensions/cornerstone-dicom-sr/src/init.ts index c2e4ff315..2f4cb4464 100644 --- a/extensions/cornerstone-dicom-sr/src/init.ts +++ b/extensions/cornerstone-dicom-sr/src/init.ts @@ -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); - } - } - ); } diff --git a/extensions/cornerstone-dynamic-volume/package.json b/extensions/cornerstone-dynamic-volume/package.json index 4b8d592c4..245d0a7c1 100644 --- a/extensions/cornerstone-dynamic-volume/package.json +++ b/extensions/cornerstone-dynamic-volume/package.json @@ -42,8 +42,8 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/core": "^3.7.16", - "@cornerstonejs/tools": "^3.7.16", + "@cornerstonejs/core": "^3.7.17", + "@cornerstonejs/tools": "^3.7.17", "classnames": "^2.3.2" } } diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 86df796e2..734ea86b8 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -38,7 +38,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjph": "^2.4.5", - "@cornerstonejs/dicom-image-loader": "^3.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", diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx index d7570200d..7f0c0279c 100644 --- a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx @@ -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) { diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts index fb6c49635..320a2067a 100644 --- a/extensions/cornerstone/src/commandsModule.ts +++ b/extensions/cornerstone/src/commandsModule.ts @@ -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); diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx index 0d39581d4..ee55b7234 100644 --- a/extensions/cornerstone/src/index.tsx +++ b/extensions/cornerstone/src/index.tsx @@ -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 }), }, ]; }, diff --git a/extensions/cornerstone/src/init.tsx b/extensions/cornerstone/src/init.tsx index 706dfe536..7535b5afe 100644 --- a/extensions/cornerstone/src/init.tsx +++ b/extensions/cornerstone/src/init.tsx @@ -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 }) => { diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts index 82f7fbb03..b63c9a044 100644 --- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -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; diff --git a/extensions/cornerstone/src/utils/index.ts b/extensions/cornerstone/src/utils/index.ts index 4df1f248b..14de717e0 100644 --- a/extensions/cornerstone/src/utils/index.ts +++ b/extensions/cornerstone/src/utils/index.ts @@ -1,7 +1,9 @@ import { handleSegmentChange } from './segmentUtils'; +import { isReferenceViewable } from './isReferenceViewable'; const utils = { handleSegmentChange, + isReferenceViewable, }; export default utils; diff --git a/extensions/cornerstone/src/utils/isReferenceViewable.ts b/extensions/cornerstone/src/utils/isReferenceViewable.ts new file mode 100644 index 000000000..246c7af34 --- /dev/null +++ b/extensions/cornerstone/src/utils/isReferenceViewable.ts @@ -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; + // 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; +} diff --git a/extensions/default/src/ViewerLayout/index.tsx b/extensions/default/src/ViewerLayout/index.tsx index 764690083..360a4f860 100644 --- a/extensions/default/src/ViewerLayout/index.tsx +++ b/extensions/default/src/ViewerLayout/index.tsx @@ -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, }; }; diff --git a/extensions/default/src/getViewportModule.tsx b/extensions/default/src/getViewportModule.tsx index c3ca1efbb..322bbfc45 100644 --- a/extensions/default/src/getViewportModule.tsx +++ b/extensions/default/src/getViewportModule.tsx @@ -14,6 +14,7 @@ const getViewportModule = ({ { name: 'chartViewport', component: LineChartViewport, + isReferenceViewable: () => false, }, ]; }; diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index 676f498f1..241d60e70 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -32,8 +32,8 @@ "start": "yarn run dev" }, "peerDependencies": { - "@cornerstonejs/core": "^3.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", diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx index e69f0917b..73696fdf2 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx @@ -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(() => { diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js index e23de0c62..9a48439ce 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js @@ -57,7 +57,6 @@ const machineConfiguration = { actions: ['setPreviousState'], }, ], - // Unused? We may only do PROMPT_HYDRATE_SR now? SET_TRACKED_SERIES: [ { target: 'tracking', diff --git a/extensions/measurement-tracking/src/getViewportModule.tsx b/extensions/measurement-tracking/src/getViewportModule.tsx index ee5f4a06f..8e5546839 100644 --- a/extensions/measurement-tracking/src/getViewportModule.tsx +++ b/extensions/measurement-tracking/src/getViewportModule.tsx @@ -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 }), }, ]; } diff --git a/platform/app/package.json b/platform/app/package.json index 8043a066e..19393736a 100644 --- a/platform/app/package.json +++ b/platform/app/package.json @@ -54,7 +54,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjph": "^2.4.5", - "@cornerstonejs/dicom-image-loader": "^3.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", diff --git a/platform/app/src/components/ViewportGrid.tsx b/platform/app/src/components/ViewportGrid.tsx index 53f8b2242..58d0ba81b 100644 --- a/platform/app/src/components/ViewportGrid.tsx +++ b/platform/app/src/components/ViewportGrid.tsx @@ -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; diff --git a/platform/core/package.json b/platform/core/package.json index c4edb8b1c..bf5a468e4 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -37,7 +37,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjph": "^2.4.5", - "@cornerstonejs/dicom-image-loader": "^3.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" diff --git a/platform/core/src/services/MeasurementService/MeasurementService.ts b/platform/core/src/services/MeasurementService/MeasurementService.ts index f85b551f6..81d39148b 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.ts +++ b/platform/core/src/services/MeasurementService/MeasurementService.ts @@ -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) { diff --git a/platform/core/src/services/ViewportGridService/ViewportGridService.ts b/platform/core/src/services/ViewportGridService/ViewportGridService.ts index 14ac3307a..ee828b74a 100644 --- a/platform/core/src/services/ViewportGridService/ViewportGridService.ts +++ b/platform/core/src/services/ViewportGridService/ViewportGridService.ts @@ -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) { diff --git a/platform/core/src/types/ViewportGridType.ts b/platform/core/src/types/ViewportGridType.ts index 1079a8bcd..fd1008c05 100644 --- a/platform/core/src/types/ViewportGridType.ts +++ b/platform/core/src/types/ViewportGridType.ts @@ -45,6 +45,10 @@ export interface ViewportGridState { activeViewportId: string | null; layout: Layout; isHangingProtocolLayout: boolean; + isReferenceViewable: (props: { + viewportId: string; + reference: Record; + }) => boolean; viewports: GridViewports; } diff --git a/platform/core/src/utils/getClosestOrientationFromIOP.ts b/platform/core/src/utils/getClosestOrientationFromIOP.ts new file mode 100644 index 000000000..8d4059592 --- /dev/null +++ b/platform/core/src/utils/getClosestOrientationFromIOP.ts @@ -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; + // 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; +} diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js deleted file mode 100644 index 9e7edfd85..000000000 --- a/platform/core/src/utils/index.test.js +++ /dev/null @@ -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); - }); -}); diff --git a/platform/core/src/utils/index.ts b/platform/core/src/utils/index.ts index e2e4d0381..821d2ebe5 100644 --- a/platform/core/src/utils/index.ts +++ b/platform/core/src/utils/index.ts @@ -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; diff --git a/platform/ui-next/src/contextProviders/ViewportGridProvider.tsx b/platform/ui-next/src/contextProviders/ViewportGridProvider.tsx index 1b03b1ebb..bf5d60649 100644 --- a/platform/ui-next/src/contextProviders/ViewportGridProvider.tsx +++ b/platform/ui-next/src/contextProviders/ViewportGridProvider.tsx @@ -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, diff --git a/tests/JumpToMeasurementMPR.spec.ts b/tests/JumpToMeasurementMPR.spec.ts new file mode 100644 index 000000000..21f08c7c6 --- /dev/null +++ b/tests/JumpToMeasurementMPR.spec.ts @@ -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 + ); +}); diff --git a/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-changeSeriesInMPR.png b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-changeSeriesInMPR.png new file mode 100644 index 000000000..d2018de5e Binary files /dev/null and b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-changeSeriesInMPR.png differ diff --git a/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-initialDraw.png b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-initialDraw.png new file mode 100644 index 000000000..29e520301 Binary files /dev/null and b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-initialDraw.png differ diff --git a/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpInMPR.png b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpInMPR.png new file mode 100644 index 000000000..49fb5e836 Binary files /dev/null and b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpInMPR.png differ diff --git a/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpToMeasurementAfterSeriesChange.png b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpToMeasurementAfterSeriesChange.png new file mode 100644 index 000000000..49fb5e836 Binary files /dev/null and b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpToMeasurementAfterSeriesChange.png differ diff --git a/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpToMeasurementStack.png b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpToMeasurementStack.png new file mode 100644 index 000000000..69251a4be Binary files /dev/null and b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpToMeasurementStack.png differ diff --git a/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-scrollAway.png b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-scrollAway.png new file mode 100644 index 000000000..36fc28945 Binary files /dev/null and b/tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-scrollAway.png differ diff --git a/tests/utils/screenShotPaths.ts b/tests/utils/screenShotPaths.ts index f493107dd..fa43a26f3 100644 --- a/tests/utils/screenShotPaths.ts +++ b/tests/utils/screenShotPaths.ts @@ -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 }; diff --git a/yarn.lock b/yarn.lock index b31a1cf54..ef84e6e93 100644 --- a/yarn.lock +++ b/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.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"