Compare commits

..
Author SHA1 Message Date
ohif-bot 522531cc5c chore(version): Update package versions [skip ci] 2025-08-05 13:48:31 +00:00
ohif-bot 8c89d9472c chore(version): version.json [skip ci] 2025-08-05 13:48:23 +00:00
Alireza Sedghi 9d94e26d5d chore: release 3p11 2025-08-05 09:39:33 -04:00
771 changed files with 9504 additions and 38512 deletions

No files matched your search

+10 -34
View File
@@ -15,13 +15,22 @@ defaults: &defaults
commands:
install_bun:
steps:
- restore_cache:
keys:
- bun-cache-v2-{{ arch }}-latest
- run:
name: Install Bun
command: |
curl -fsSL https://bun.sh/install | bash -s "bun-v1.2.23"
if [ ! -d "$HOME/.bun" ]; then
curl -fsSL https://bun.sh/install | bash
fi
echo 'export BUN_INSTALL="$HOME/.bun"' >> $BASH_ENV
echo 'export PATH="$BUN_INSTALL/bin:$PATH"' >> $BASH_ENV
source $BASH_ENV
- save_cache:
key: bun-cache-v2-{{ arch }}-latest
paths:
- ~/.bun
jobs:
UNIT_TESTS:
@@ -112,34 +121,6 @@ jobs:
- checkout
- attach_workspace:
at: ~/repo
# SECURITY AUDIT
- run:
name: 'Security Audit - High Risk Vulnerabilities'
command: |
echo "🔍 Running bun audit for security vulnerabilities..."
echo "Checking for HIGH-RISK vulnerabilities..."
if bun audit --audit-level high; then
echo "✅ No high-risk vulnerabilities found"
echo "🎉 Security audit passed!"
else
echo ""
echo "❌ HIGH-RISK VULNERABILITIES DETECTED!"
echo "======================================"
echo ""
echo "🔧 To fix these issues:"
echo " 1. Run: bun audit"
echo " 2. Review the vulnerability details"
echo " 3. Update affected packages to secure versions"
echo " 4. Test your changes"
echo " 5. Re-run: bun audit --audit-level high"
echo ""
echo "📋 Full audit report:"
bun audit --audit-level low || true
echo ""
echo "❌ This build cannot proceed until high-risk vulnerabilities are resolved."
exit 1
fi
- run:
name: Install Dependencies
command: bun install --frozen-lockfile
@@ -474,13 +455,8 @@ workflows:
filters:
branches:
only: master
# - HOLD_FOR_APPROVAL:
# type: approval
# requires:
# - BUILD
- NPM_PUBLISH:
requires:
# - HOLD_FOR_APPROVAL
- BUILD
- DOCKER_BETA_PUBLISH:
requires:
+21 -22
View File
@@ -51,29 +51,28 @@ body:
validations:
required: true
- type: textarea
id: system_info
- type: input
id: os
attributes:
label: 'System Information'
description: 'Please run the following command in your terminal and paste the output:'
placeholder: |
Run: npx envinfo --system --binaries --browsers
Then paste the output here. It should look something like:
System:
OS: Windows 10 10.0.19042
CPU: (8) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Memory: 15.89 GB / 31.74 GB
Shell: 1.0.0 - C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe
Binaries:
Node: 20.18.1 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.22 - C:\Users\user\AppData\Roaming\npm\yarn.CMD
npm: 10.8.2 - C:\Program Files\nodejs\npm.CMD
Browsers:
Chrome: 83.0.4103.116
Edge: Spartan (44.19041.1266.0), Chromium (83.0.478.58)
Firefox: 77.0.1
label: 'OS'
description: 'Your operating system.'
placeholder: 'e.g., Windows 10, macOS 10.15.4'
validations:
required: true
- type: input
id: node-version
attributes:
label: 'Node version'
description: 'Your Node.js version.'
placeholder: 'e.g., 20.18.1'
validations:
required: true
- type: input
id: browser
attributes:
label: 'Browser'
description: 'Your browser.'
placeholder: 'e.g., Chrome 83.0.4103.116, Firefox 77.0.1, Safari 13.1.1'
validations:
required: true
-2
View File
@@ -23,8 +23,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.23
- uses: actions/setup-node@v4
with:
node-version: 20 # Or your desired Node version
-2
View File
@@ -18,8 +18,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.23
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
+1 -1
View File
@@ -10,6 +10,6 @@
"deploy": "netlify deploy --prod --dir ./../platform/app/dist"
},
"devDependencies": {
"netlify-cli": "2.21.0"
"netlify-cli": "^2.21.0"
}
}
+2
View File
@@ -201,6 +201,8 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
'@hooks': path.resolve(__dirname, '../platform/app/src/hooks'),
'@routes': path.resolve(__dirname, '../platform/app/src/routes'),
'@state': path.resolve(__dirname, '../platform/app/src/state'),
'dicom-microscopy-viewer':
'dicom-microscopy-viewer/dist/dynamic-import/dicomMicroscopyViewer.min.js',
},
// Which directories to search when resolving modules
modules: [
+1 -664
View File
@@ -3,670 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
### Bug Fixes
* **docker:** Use bun version 1.2.23 for docker build. ([#5501](https://github.com/OHIF/Viewers/issues/5501)) ([86e924f](https://github.com/OHIF/Viewers/commit/86e924fb88cc146f91c7d826ee071e451de47942))
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
### Bug Fixes
* Segmentation not displayed and unable to draw segments in volume viewport after converting Labelmap to Surface ([#5488](https://github.com/OHIF/Viewers/issues/5488)) ([faea09e](https://github.com/OHIF/Viewers/commit/faea09e7ef2667aad5cef1efc2a6516677ca2915))
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
### Bug Fixes
* **segmentationService:** prevent no representation crash ([#5495](https://github.com/OHIF/Viewers/issues/5495)) ([3137aed](https://github.com/OHIF/Viewers/commit/3137aed0170196a61e16c6dac0c15bcc41691919))
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
### Bug Fixes
* **docs:** Set exact version for glob. ([#5497](https://github.com/OHIF/Viewers/issues/5497)) ([0d2f4e7](https://github.com/OHIF/Viewers/commit/0d2f4e715a941fa458ebf8e64972088580a04952))
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
### Bug Fixes
* **security:** Run bun audit before bun install. ([#5496](https://github.com/OHIF/Viewers/issues/5496)) ([fbd9371](https://github.com/OHIF/Viewers/commit/fbd9371884d0e87376d67eb98c17a4d62e4e6dd1))
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
### Bug Fixes
* **bun:** Use fixed bun version ([#5493](https://github.com/OHIF/Viewers/issues/5493)) ([3fe67d5](https://github.com/OHIF/Viewers/commit/3fe67d5c98a22edc32cb0d3de5edc05b722dd68e))
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
### Bug Fixes
* Update Dockerfile ([#5484](https://github.com/OHIF/Viewers/issues/5484)) ([41c0287](https://github.com/OHIF/Viewers/commit/41c028781b80f0ca80f4d6cde3ca8e7958f1baed))
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
### Bug Fixes
* **segmentation:** Segmentation not displayed and unable to draw segments when switching back to volume/stack viewport from 3D viewport ([#5430](https://github.com/OHIF/Viewers/issues/5430)) ([78df8ff](https://github.com/OHIF/Viewers/commit/78df8ff03a371904a390c63a95628d5511dad136))
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
### Bug Fixes
* validation for Percentage of Max SUV input field in TMTV module ([#5417](https://github.com/OHIF/Viewers/issues/5417)) ([5040c94](https://github.com/OHIF/Viewers/commit/5040c947d2f47e5ec66285fa353e3e0ffd127ba7))
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
### Bug Fixes
* **Segmentation:** [Bug [#5420](https://github.com/OHIF/Viewers/issues/5420)] Segmentation color resets after using toggleOneUp ([#5465](https://github.com/OHIF/Viewers/issues/5465)) ([2c6ce21](https://github.com/OHIF/Viewers/commit/2c6ce21c8f79f8b3e6dc47b5aa6f717b6b5cbd2a))
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
### Bug Fixes
* **measurementTrackingMachine:** remove unreachable states ([#5460](https://github.com/OHIF/Viewers/issues/5460)) ([7f12f39](https://github.com/OHIF/Viewers/commit/7f12f398fb145a31e5557dbee51c0a9b0f40a35b))
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
### Bug Fixes
* **toolbar:** prevent duplicate command execution on repeated interactions ([#5456](https://github.com/OHIF/Viewers/issues/5456)) ([4d04dd5](https://github.com/OHIF/Viewers/commit/4d04dd5651f4cf8dcaf4732baee5e218e92bd3c1))
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
### Bug Fixes
* viewport ready event firing logic ([#5462](https://github.com/OHIF/Viewers/issues/5462)) ([d2ae13b](https://github.com/OHIF/Viewers/commit/d2ae13b03f21e3ab9545d50d7403ec52004bfbfc))
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
### Bug Fixes
* **router:** opt in to React Router v7 future flags ([#5461](https://github.com/OHIF/Viewers/issues/5461)) ([e2964bf](https://github.com/OHIF/Viewers/commit/e2964bf66947d18af06f292d9395b465545fdf9d))
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
### Bug Fixes
* Safely handle missing active viewport ([#5448](https://github.com/OHIF/Viewers/issues/5448)) ([792db26](https://github.com/OHIF/Viewers/commit/792db26ff1591f9e054486f1bee01c422e774101))
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
### Features
* **MeasurementService:** add rendering of unmapped measurements ([#5416](https://github.com/OHIF/Viewers/issues/5416)) ([851e74d](https://github.com/OHIF/Viewers/commit/851e74d7b867a806befb5d85fd71ff9a75e9f2d2))
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
### Bug Fixes
* duplicate segmentation entries on layout switch ([#5458](https://github.com/OHIF/Viewers/issues/5458)) ([9e951e8](https://github.com/OHIF/Viewers/commit/9e951e88005af505ae0c07fbcd3366de12b773dd))
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
### Bug Fixes
* study browser mode for multiple studies ([#5457](https://github.com/OHIF/Viewers/issues/5457)) ([6a1df9d](https://github.com/OHIF/Viewers/commit/6a1df9de4ad3c10a4653f18e03aff6bda4962fd1))
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
### Bug Fixes
* **studyBrowserCustomization:** fix thumbnail menu item actions in study browser ([#5283](https://github.com/OHIF/Viewers/issues/5283)) ([2f28a0d](https://github.com/OHIF/Viewers/commit/2f28a0deab5e43f432bbd9b5989928c9b0450278))
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
### Features
* Add inherit from basic/longitudinal modes ([#5435](https://github.com/OHIF/Viewers/issues/5435)) ([9ad0d7f](https://github.com/OHIF/Viewers/commit/9ad0d7fc8cf34867fa2cfc2bf2b2f63451ec03e5))
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
### Bug Fixes
* **segmentation:** Restored segment label tool in longitudinal mode. ([#5443](https://github.com/OHIF/Viewers/issues/5443)) ([dd86a8e](https://github.com/OHIF/Viewers/commit/dd86a8eabd0712d25e7652a242377fe09597e8d5))
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
### Bug Fixes
* **security:** Removed dependency on tar-fs by removing dependencies on storybook and sharp. ([#5438](https://github.com/OHIF/Viewers/issues/5438)) ([80f314a](https://github.com/OHIF/Viewers/commit/80f314a422ec45fff6cdd5869f4293a110d00125))
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
### Bug Fixes
* Improve rt handling for bulkdata ([#5415](https://github.com/OHIF/Viewers/issues/5415)) ([d88a341](https://github.com/OHIF/Viewers/commit/d88a341884f7361865bc10159c1a82633667ab5b))
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
### Bug Fixes
* **i18n:** Add and update i18n Translation for Toolbar Tool Names ([#5392](https://github.com/OHIF/Viewers/issues/5392)) ([7783d0f](https://github.com/OHIF/Viewers/commit/7783d0f558bbb7016c29513b6fe76ed069dbd75c))
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
### Features
* **Segmentation:** Segmentation highlight animation function selection ([#5401](https://github.com/OHIF/Viewers/issues/5401)) ([69dbe27](https://github.com/OHIF/Viewers/commit/69dbe2778493cdfc7a8da5e757c95217bb31edc2))
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
### Bug Fixes
* **SegmentationService:** _getSegmentCenter wrongly handling seg center availability ([#5396](https://github.com/OHIF/Viewers/issues/5396)) ([74b82bd](https://github.com/OHIF/Viewers/commit/74b82bd93442411ad68f4872992126c40321eb9e))
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
### Bug Fixes
* **ErrorBoundary:** Fixing console errors and Improving error logging for better developer experience ([#5378](https://github.com/OHIF/Viewers/issues/5378)) ([56c5828](https://github.com/OHIF/Viewers/commit/56c5828b02d53bfd17182bb8d0c126b7cd4f400e))
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
### Bug Fixes
* **cornerstone-dicom-rt:** adding caching usage to cornerstone-dicom-rt SOP Class Handler ([#5387](https://github.com/OHIF/Viewers/issues/5387)) ([d6ea179](https://github.com/OHIF/Viewers/commit/d6ea179ac43a47d4011b8dcaf59d79d99382d6c8))
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
### Bug Fixes
* **oidc:** resolve navigation throttling and blank screen during sign-in/sign-out with Keycloak + dcm4chee + Traefik ([#5373](https://github.com/OHIF/Viewers/issues/5373)) ([79473c2](https://github.com/OHIF/Viewers/commit/79473c2ea4196b758614480860a8c596730b4147))
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
### Bug Fixes
* routerBasename declaration location ([#5347](https://github.com/OHIF/Viewers/issues/5347)) ([0f6f2bb](https://github.com/OHIF/Viewers/commit/0f6f2bb432d7021d3a92b38b58de51a380d9b0c9))
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
### Bug Fixes
* seg preview ([#5338](https://github.com/OHIF/Viewers/issues/5338)) ([d91cc23](https://github.com/OHIF/Viewers/commit/d91cc230e5231ca32ee28736f661a1497f7834c5))
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
### Bug Fixes
* docker deploy ([#5359](https://github.com/OHIF/Viewers/issues/5359)) ([368a176](https://github.com/OHIF/Viewers/commit/368a176c093aa2ccf4197c46543209d5ff69939a))
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
### Bug Fixes
* Docker build failure due to missing lerna command ([#5357](https://github.com/OHIF/Viewers/issues/5357)) ([bedb185](https://github.com/OHIF/Viewers/commit/bedb1854f96928f6db5707dce74ea75b1445e9bd))
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
### Bug Fixes
* local ModalitiesInStudy check ([#5285](https://github.com/OHIF/Viewers/issues/5285)) ([d269404](https://github.com/OHIF/Viewers/commit/d2694048a47faf9028184d0272df12e2602ae674))
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
### Bug Fixes
* add back clear segmentation representations ([#5336](https://github.com/OHIF/Viewers/issues/5336)) ([21d6ba2](https://github.com/OHIF/Viewers/commit/21d6ba2bb30df5b99bf5728163caff514aaa5dcd))
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
### Bug Fixes
* Segmentation stats customization module ([#5324](https://github.com/OHIF/Viewers/issues/5324)) ([b9d6f55](https://github.com/OHIF/Viewers/commit/b9d6f558cbe5957f83e6d92305a78662a0b186ed))
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
### Bug Fixes
* Add seriesInstanceUID fallback for callByRetrieveAETitle ([#5332](https://github.com/OHIF/Viewers/issues/5332)) ([840b5d3](https://github.com/OHIF/Viewers/commit/840b5d37d15537abc38316d636da7c144e6d9223))
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
### Features
* Jump to measurement moves the camera if it is not in the viewport ([#5023](https://github.com/OHIF/Viewers/issues/5023)) ([6fe9df6](https://github.com/OHIF/Viewers/commit/6fe9df6ec93c0f37e3bb9f8bf6425bae025e75c8))
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
### Bug Fixes
* header icon logic ([#5339](https://github.com/OHIF/Viewers/issues/5339)) ([a2133ef](https://github.com/OHIF/Viewers/commit/a2133ef6cb6e47fad22908fb1338ee57c67eb8f8))
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
### Features
* Add WSI bulk data annotations support (ANN) ([#4972](https://github.com/OHIF/Viewers/issues/4972)) ([2ed6818](https://github.com/OHIF/Viewers/commit/2ed681817142b13971bb17b3058be26bbab6b90b))
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
### Bug Fixes
* load 3dsr ([#5335](https://github.com/OHIF/Viewers/issues/5335)) ([becba78](https://github.com/OHIF/Viewers/commit/becba7861f99ae4e190caddea8b663e60a9883f5))
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
### Features
* **orientationMarkers:** improved the orientation markers with a simplified version ([#5328](https://github.com/OHIF/Viewers/issues/5328)) ([266b1f5](https://github.com/OHIF/Viewers/commit/266b1f58f01ac6742a9bb40dda28459e7cddc683))
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
### Bug Fixes
* Update Dockerfile ([#5319](https://github.com/OHIF/Viewers/issues/5319)) ([0e83f45](https://github.com/OHIF/Viewers/commit/0e83f4515c902ffa3dbab2d68ec81b93881e1768))
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
### Bug Fixes
* Guard series.instances in DicomJSONDataSource to prevent TypeError (OHI-2114) ([#5312](https://github.com/OHIF/Viewers/issues/5312)) ([f1d158b](https://github.com/OHIF/Viewers/commit/f1d158bbe81fd9f6052ce23c8588c9c93d76360c))
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package ohif-monorepo-root
+3 -4
View File
@@ -49,8 +49,8 @@ RUN apt-get update && apt-get install -y build-essential python3
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
RUN npm install -g bun@1.2.23
RUN npm install -g lerna@7.4.2
RUN npm install -g bun
# RUN npm install -g lerna@7.4.2
ENV PATH=/usr/src/app/node_modules/.bin:$PATH
# Do an initial install and then a final install
@@ -60,7 +60,6 @@ COPY --parents ./addOns/package.json ./addOns/*/*/package.json ./extensions/*/pa
RUN bun pm cache rm
RUN bun install
RUN bun add ajv@8.12.0
# Copy the local directory
COPY --link --exclude=yarn.lock --exclude=package.json --exclude=Dockerfile . .
@@ -99,7 +98,7 @@ COPY --from=builder /usr/src/app/platform/app/dist/dicom-microscopy-viewer /usr/
# In entrypoint.sh, app-config.js might be overwritten, so chmod it to be writeable.
# The nginx user cannot chmod it, so change to root.
USER root
RUN chown -R nginx:nginx /usr/share/nginx/html && chmod -R 777 /usr/share/nginx/html
RUN chown -R nginx:nginx /usr/share/nginx/html
USER nginx
ENTRYPOINT ["/usr/src/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
+1 -1
View File
@@ -76,7 +76,7 @@ contributions of individuals, research groups, and commercial organizations.
After more than 8-years of integrating with many companies and organizations,
The OHIF Viewer has been rebuilt from the ground up to better address the
varying workflow and configuration needs of its many users. All of the Viewer's
core features are built using its own extension system. The same extensibility
core features are built using it's own extension system. The same extensibility
that allows us to offer:
- 2D and 3D medical image viewing
+1 -535
View File
@@ -3,541 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
### Bug Fixes
* **security:** Removed dependency on tar-fs by removing dependencies on storybook and sharp. ([#5438](https://github.com/OHIF/Viewers/issues/5438)) ([80f314a](https://github.com/OHIF/Viewers/commit/80f314a422ec45fff6cdd5869f4293a110d00125))
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package @externals/devDependencies
+67 -67
View File
@@ -1,7 +1,7 @@
{
"name": "@externals/devDependencies",
"description": "External dev dependencies - put dev build dependencies here",
"version": "3.12.0-beta.66",
"version": "3.11.0",
"license": "MIT",
"private": true,
"engines": {
@@ -9,85 +9,85 @@
"yarn": ">=1.19.1"
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@babel/runtime": "^7.20.13",
"@kitware/vtk.js": "32.12.0",
"clsx": "2.1.1",
"core-js": "3.45.1",
"moment": "2.30.1"
"clsx": "^2.1.1",
"core-js": "^3.2.1",
"moment": "^2.30.1"
},
"peerDependencies": {
"react": "18.3.1",
"react-dom": "18.3.1"
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@babel/eslint-parser": "7.28.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.17",
"@rsbuild/core": "1.5.1",
"@rsbuild/plugin-node-polyfill": "1.4.2",
"@rsbuild/plugin-react": "1.4.0",
"@svgr/webpack": "8.1.0",
"@swc/helpers": "0.5.17",
"@types/jest": "27.5.2",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"autoprefixer": "10.4.21",
"babel-loader": "8.4.1",
"babel-plugin-module-resolver": "5.0.2",
"clean-webpack-plugin": "3.0.0",
"copy-webpack-plugin": "9.1.0",
"cross-env": "7.0.3",
"css-loader": "6.11.0",
"dotenv": "8.6.0",
"eslint": "8.57.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@rsbuild/core": "^1.3.16",
"@rsbuild/plugin-node-polyfill": "^1.3.0",
"@rsbuild/plugin-react": "^1.3.1",
"@svgr/webpack": "^8.1.0",
"@swc/helpers": "^0.5.15",
"@types/jest": "^27.5.0",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"autoprefixer": "^10.4.4",
"babel-loader": "^8.2.4",
"babel-plugin-module-resolver": "^5.0.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "^5.2.0",
"css-loader": "^6.8.1",
"dotenv": "^8.1.0",
"eslint": "^8.39.0",
"eslint-config-prettier": "7.2.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-cypress": "2.15.2",
"eslint-plugin-flowtype": "7.0.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-node": "11.1.0",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-flowtype": "^7.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "5.5.1",
"eslint-plugin-promise": "5.2.0",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-tsdoc": "0.2.17",
"eslint-webpack-plugin": "2.7.0",
"execa": "8.0.1",
"extract-css-chunks-webpack-plugin": "4.10.0",
"html-webpack-plugin": "5.6.3",
"husky": "3.1.0",
"jest": "29.7.0",
"jest-canvas-mock": "2.5.2",
"jest-environment-jsdom": "29.7.0",
"jest-junit": "6.4.0",
"lerna": "7.4.2",
"lint-staged": "9.5.0",
"mini-css-extract-plugin": "2.9.2",
"optimize-css-assets-webpack-plugin": "6.0.1",
"postcss": "8.5.6",
"postcss-import": "14.1.0",
"postcss-loader": "6.2.1",
"postcss-preset-env": "7.8.3",
"eslint-plugin-promise": "^5.2.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0",
"eslint-plugin-tsdoc": "^0.2.11",
"eslint-webpack-plugin": "^2.5.3",
"execa": "^8.0.1",
"extract-css-chunks-webpack-plugin": "^4.5.4",
"html-webpack-plugin": "^5.3.2",
"husky": "^3.0.0",
"jest": "^29.5.0",
"jest-canvas-mock": "^2.1.0",
"jest-environment-jsdom": "^29.5.0",
"jest-junit": "^6.4.0",
"lerna": "^7.2.0",
"lint-staged": "^9.0.2",
"mini-css-extract-plugin": "^2.1.0",
"optimize-css-assets-webpack-plugin": "^6.0.1",
"postcss": "^8.3.5",
"postcss-import": "^14.0.2",
"postcss-loader": "^6.1.1",
"postcss-preset-env": "^7.4.3",
"prettier": "3.6.2",
"prettier-plugin-tailwindcss": "0.6.9",
"react-refresh": "0.14.2",
"semver": "7.7.2",
"serve": "14.2.4",
"shader-loader": "1.3.1",
"shx": "0.3.4",
"source-map-loader": "4.0.2",
"style-loader": "1.3.0",
"terser-webpack-plugin": "5.3.14",
"react-refresh": "^0.14.2",
"semver": "^7.5.1",
"serve": "^14.2.4",
"shader-loader": "^1.3.1",
"shx": "^0.3.3",
"source-map-loader": "^4.0.1",
"style-loader": "^1.0.0",
"terser-webpack-plugin": "^5.1.4",
"typescript": "5.5.4",
"unused-webpack-plugin": "2.4.0",
"webpack": "5.95.0",
"webpack-bundle-analyzer": "4.10.2",
"webpack-cli": "5.1.4",
"webpack": "^5.94.0",
"webpack-bundle-analyzer": "^4.8.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "5.2.2",
"webpack-hot-middleware": "2.26.1",
"webpack-merge": "5.10.0",
"workbox-webpack-plugin": "6.6.1",
"worker-loader": "3.0.8"
"webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^5.7.3",
"workbox-webpack-plugin": "^6.1.5",
"worker-loader": "^3.0.8"
},
"scripts": {
"build": "Included as direct dependency"
+1 -535
View File
@@ -3,541 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
### Features
* Add WSI bulk data annotations support (ANN) ([#4972](https://github.com/OHIF/Viewers/issues/4972)) ([2ed6818](https://github.com/OHIF/Viewers/commit/2ed681817142b13971bb17b3058be26bbab6b90b))
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
+2 -2
View File
@@ -1,9 +1,9 @@
{
"name": "@externals/dicom-microscopy-viewer",
"description": "External reference to dicom-microscopy-viewer",
"version": "3.12.0-beta.66",
"version": "3.11.0",
"license": "MIT",
"dependencies": {
"dicom-microscopy-viewer": "0.48.6"
"dicom-microscopy-viewer": "^0.46.1"
}
}
+18 -17
View File
@@ -22,29 +22,30 @@
},
"devDependencies": {
"@babel/core": "7.28.0",
"@babel/plugin-transform-class-properties": "7.27.1",
"@babel/plugin-transform-object-rest-spread": "7.28.0",
"@babel/plugin-transform-private-methods": "7.27.1",
"@babel/plugin-transform-private-property-in-object": "7.27.1",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-transform-arrow-functions": "7.27.1",
"@babel/plugin-transform-regenerator": "7.28.1",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
"@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-arrow-functions": "^7.16.7",
"@babel/plugin-transform-regenerator": "^7.16.7",
"@babel/plugin-transform-runtime": "7.28.0",
"@babel/plugin-transform-typescript": "7.28.0",
"@babel/plugin-transform-typescript": "^7.28.0",
"@babel/preset-env": "7.28.0",
"@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.27.1"
"@babel/preset-react": "^7.27.1",
"@babel/preset-typescript": "^7.27.1"
},
"resolutions": {
"**/@babel/runtime": "7.28.2",
"**/@babel/runtime": "^7.20.13",
"commander": "8.3.0",
"dcmjs": "0.43.1",
"dicomweb-client": ">=0.10.4",
"nth-check": "2.1.1",
"trim-newlines": "5.0.0",
"glob-parent": "6.0.2",
"trim": "1.0.1",
"package-json": "8.1.1",
"typescript": "5.5.4"
"nth-check": "^2.1.1",
"trim-newlines": "^5.0.0",
"glob-parent": "^6.0.2",
"trim": "^1.0.0",
"package-json": "^8.1.0",
"typescript": "5.5.4",
"sharp": "^0.32.6"
}
}
-1
View File
@@ -35,7 +35,6 @@ module.exports = {
'@babel/plugin-transform-typescript',
'@babel/plugin-transform-class-static-block',
'@babel/plugin-transform-for-of',
['babel-plugin-transform-import-meta', { module: 'ES6' }],
],
},
production: {
+1726 -804
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1 +1 @@
42aa2df62622ab380f97025819bd929fa0579e14
9d94e26d5d15ccd19f12020dbc5438364d88d253
+1 -535
View File
@@ -3,541 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
### Features
* Add WSI bulk data annotations support (ANN) ([#4972](https://github.com/OHIF/Viewers/issues/4972)) ([2ed6818](https://github.com/OHIF/Viewers/commit/2ed681817142b13971bb17b3058be26bbab6b90b))
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
+15 -15
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.12.0-beta.66",
"version": "3.11.0",
"description": "DICOM Parametric Map read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,22 +33,22 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-default": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-i18next": "12.3.1",
"react-router": "6.30.1",
"react-router-dom": "6.30.1"
"@ohif/core": "3.11.0",
"@ohif/extension-cornerstone": "3.11.0",
"@ohif/extension-default": "3.11.0",
"@ohif/i18n": "3.11.0",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2",
"react-router": "^6.8.1",
"react-router-dom": "^6.8.1"
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.32.5",
"@cornerstonejs/core": "^3.32.5",
"@kitware/vtk.js": "32.12.0",
"react-color": "2.19.3"
"react-color": "^2.19.3"
}
}
+1 -538
View File
@@ -3,544 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
### Bug Fixes
* Improve rt handling for bulkdata ([#5415](https://github.com/OHIF/Viewers/issues/5415)) ([d88a341](https://github.com/OHIF/Viewers/commit/d88a341884f7361865bc10159c1a82633667ab5b))
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
### Bug Fixes
* **cornerstone-dicom-rt:** adding caching usage to cornerstone-dicom-rt SOP Class Handler ([#5387](https://github.com/OHIF/Viewers/issues/5387)) ([d6ea179](https://github.com/OHIF/Viewers/commit/d6ea179ac43a47d4011b8dcaf59d79d99382d6c8))
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
+13 -13
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.12.0-beta.66",
"version": "3.11.0",
"description": "DICOM RT read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,19 +33,19 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-default": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-i18next": "12.3.1",
"react-router": "6.30.1",
"react-router-dom": "6.30.1"
"@ohif/core": "3.11.0",
"@ohif/extension-cornerstone": "3.11.0",
"@ohif/extension-default": "3.11.0",
"@ohif/i18n": "3.11.0",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^10.11.0",
"react-router": "^6.23.1",
"react-router-dom": "^6.23.1"
},
"dependencies": {
"@babel/runtime": "7.28.2",
"react-color": "2.19.3"
"@babel/runtime": "^7.20.13",
"react-color": "^2.19.3"
}
}
@@ -4,11 +4,7 @@ import i18n from '@ohif/i18n';
import { SOPClassHandlerId } from './id';
import loadRTStruct from './loadRTStruct';
const { sopClassDictionary } = utils;
const sopClassUids = [sopClassDictionary.RTStructureSetStorage];
const cachedRTStructsSEG = new Set<string>();
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.481.3'];
const loadPromises = {};
@@ -117,45 +113,39 @@ function _load(
) {
const { SOPInstanceUID } = rtDisplaySet;
const { segmentationService } = servicesManager.services;
if (
(rtDisplaySet.loading || rtDisplaySet.isLoaded) &&
loadPromises[SOPInstanceUID] &&
cachedRTStructsSEG.has(rtDisplaySet.displaySetInstanceUID)
_segmentationExistsInCache(rtDisplaySet, segmentationService)
) {
return loadPromises[SOPInstanceUID];
}
rtDisplaySet.loading = true;
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
(evt: { rtDisplaySet: { displaySetInstanceUID: string } }) => {
if (evt.rtDisplaySet?.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) {
cachedRTStructsSEG.add(rtDisplaySet.displaySetInstanceUID);
unsubscribe();
}
}
);
// We don't want to fire multiple loads, so we'll wait for the first to finish
// and also return the same promise to any other callers.
loadPromises[SOPInstanceUID] = new Promise<void>(async (resolve, reject) => {
try {
if (!rtDisplaySet.structureSet) {
const structureSet = await loadRTStruct(extensionManager, rtDisplaySet, headers);
rtDisplaySet.structureSet = structureSet;
}
loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
if (!rtDisplaySet.structureSet) {
const structureSet = await loadRTStruct(extensionManager, rtDisplaySet, headers);
if (createSegmentation) {
await segmentationService.createSegmentationForRTDisplaySet(rtDisplaySet);
}
rtDisplaySet.structureSet = structureSet;
}
resolve();
} catch (error) {
reject(error);
} finally {
if (createSegmentation) {
segmentationService
.createSegmentationForRTDisplaySet(rtDisplaySet)
.then(() => {
rtDisplaySet.loading = false;
resolve();
})
.catch(error => {
rtDisplaySet.loading = false;
reject(error);
});
} else {
rtDisplaySet.loading = false;
resolve();
}
});
@@ -197,6 +187,11 @@ function _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
return ReferencedSeriesSequence;
}
function _segmentationExistsInCache() {
// Todo: fix this
return false;
}
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
const { servicesManager, extensionManager } = params;
@@ -2,34 +2,9 @@ import dcmjs from 'dcmjs';
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
const dicomlab2RGB = dcmjs.data.Colors.dicomlab2RGB;
/**
* Checks and loads contour data for RT Structure Set, handling both inline and bulk data URIs.
* Processes ROIContourSequence to extract contour data and resolve any bulk data references.
*
* @async
* @function checkAndLoadContourData
* @param {Object} params - Parameters object
* @param {Object} params.instance - Initial RT Structure instance
* @param {Object} params.dataSource - Data source for retrieving bulk data
* @param {Object} params.extensionManager - OHIF extension manager
* @param {Object} params.rtStructDisplaySet - RT Structure display set
* @param {Object} params.headers - HTTP headers for requests
* @returns {Promise<Object>} Promise that resolves to the processed RT Structure instance with loaded contour data
* @throws {Promise<string>} Rejects with error message if instance is invalid or data retrieval fails
*/
async function checkAndLoadContourData({
instance: initialInstance,
dataSource,
extensionManager,
rtStructDisplaySet,
headers,
}) {
let instance = initialInstance;
async function checkAndLoadContourData(instance, datasource) {
if (!instance || !instance.ROIContourSequence) {
instance = await getRTStructInstance({ extensionManager, rtStructDisplaySet, headers });
if (!instance || !instance.ROIContourSequence) {
return Promise.reject('Invalid instance object or ROIContourSequence');
}
return Promise.reject('Invalid instance object or ROIContourSequence');
}
const promisesMap = new Map();
@@ -55,11 +30,11 @@ async function checkAndLoadContourData({
} else if (contourData && contourData.BulkDataURI) {
const bulkDataURI = contourData.BulkDataURI;
if (!dataSource || !dataSource.retrieve || !dataSource.retrieve.bulkDataURI) {
return Promise.reject('Invalid dataSource object or retrieve function');
if (!datasource || !datasource.retrieve || !datasource.retrieve.bulkDataURI) {
return Promise.reject('Invalid datasource object or retrieve function');
}
const bulkDataPromise = dataSource.retrieve.bulkDataURI({
const bulkDataPromise = datasource.retrieve.bulkDataURI({
BulkDataURI: bulkDataURI,
StudyInstanceUID: instance.StudyInstanceUID,
SeriesInstanceUID: instance.SeriesInstanceUID,
@@ -112,74 +87,35 @@ async function checkAndLoadContourData({
console.error(error);
}
});
return instance;
}
/**
* Retrieves and parses RT Structure Set instance from DICOM data.
* Uses the cornerstone utility module to load DICOM data and converts it to a naturalized dataset.
*
* @async
* @function getRTStructInstance
* @param {Object} params - Parameters object
* @param {Object} params.extensionManager - OHIF extension manager
* @param {Object} params.rtStructDisplaySet - RT Structure display set
* @param {Object} params.headers - HTTP headers for requests
* @returns {Promise<Object>} Promise that resolves to the parsed RT Structure dataset
*/
const getRTStructInstance = async ({ extensionManager, rtStructDisplaySet, headers }) => {
export default async function loadRTStruct(extensionManager, rtStructDisplaySet, headers) {
const utilityModule = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.utilityModule.common'
);
const { dicomLoaderService } = utilityModule.exports;
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(
rtStructDisplaySet,
null,
headers
);
const dicomData = DicomMessage.readFile(segArrayBuffer);
const rtStructDataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
rtStructDataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
return rtStructDataset;
};
/**
* Main function to load and process RT Structure Set data.
* Creates a structure set object with ROI contours, metadata, and visualization properties.
* Handles both bulk data URI and inline contour data scenarios.
*
* @async
* @function loadRTStruct
* @param {Object} extensionManager - OHIF extension manager
* @param {Object} rtStructDisplaySet - RT Structure display set to process
* @param {Object} headers - HTTP headers for data requests
* @returns {Promise<Object>} Promise that resolves to a structure set object containing:
* - StructureSetLabel: Label of the structure set
* - SeriesInstanceUID: Series instance UID
* - ROIContours: Array of ROI contour data with points and metadata
* - visible: Visibility state
* - ReferencedSOPInstanceUIDsSet: Set of referenced SOP instance UIDs
*/
export default async function loadRTStruct(extensionManager, rtStructDisplaySet, headers) {
const dataSource = extensionManager.getActiveDataSource()[0];
const { bulkDataURI } = dataSource.getConfig?.() || {};
const { dicomLoaderService } = utilityModule.exports;
// Set here is loading is asynchronous.
// If this function throws its set back to false.
rtStructDisplaySet.isLoaded = true;
let instance = rtStructDisplaySet.instance;
if (!bulkDataURI || !bulkDataURI.enabled) {
instance = await getRTStructInstance({ extensionManager, rtStructDisplaySet, headers });
} else {
instance = await checkAndLoadContourData({
instance,
dataSource,
extensionManager,
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(
rtStructDisplaySet,
headers,
});
null,
headers
);
const dicomData = DicomMessage.readFile(segArrayBuffer);
const rtStructDataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
rtStructDataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
instance = rtStructDataset;
} else {
await checkAndLoadContourData(instance, dataSource);
}
const { StructureSetROISequence, ROIContourSequence, RTROIObservationsSequence } = instance;
@@ -196,16 +132,21 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
for (let i = 0; i < ROIContourSequence.length; i++) {
const ROIContour = ROIContourSequence[i];
const { ContourSequence } = ROIContour;
if (!ContourSequence) {
continue;
}
const isSupported = false;
const ContourSequenceArray = _toArray(ContourSequence);
const contourPoints = [];
for (const ContourSequenceItem of ContourSequenceArray) {
for (let c = 0; c < ContourSequenceArray.length; c++) {
const { ContourData, NumberOfContourPoints, ContourGeometricType, ContourImageSequence } =
ContourSequenceItem;
ContourSequenceArray[c];
let isSupported = false;
const points = [];
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
@@ -216,18 +157,22 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
});
}
const supportedContourTypesMap = new Map([
['CLOSED_PLANAR', false],
['OPEN_NONPLANAR', false],
['OPEN_PLANAR', false],
['POINT', true],
]);
switch (ContourGeometricType) {
case 'CLOSED_PLANAR':
case 'OPEN_PLANAR':
case 'POINT':
isSupported = true;
break;
default:
continue;
}
contourPoints.push({
numberOfPoints: NumberOfContourPoints,
points,
type: ContourGeometricType,
isSupported: supportedContourTypesMap.get(ContourGeometricType) ?? false,
isSupported,
});
if (ContourImageSequence?.ReferencedSOPInstanceUID) {
@@ -242,30 +187,20 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
StructureSetROISequence,
RTROIObservationsSequence,
ROIContour,
contourPoints
contourPoints,
isSupported
);
}
return structureSet;
}
/**
* Sets metadata for ROI contour data and adds it to the structure set.
* Extracts ROI information from StructureSetROISequence and RTROIObservationsSequence,
* then creates a complete ROI contour data object with visualization properties.
*
* @function _setROIContourMetadata
* @param {Object} structureSet - The structure set object to add ROI contour to
* @param {Array} StructureSetROISequence - Array of structure set ROI definitions
* @param {Array} RTROIObservationsSequence - Array of RT ROI observations
* @param {Object} ROIContour - ROI contour object containing contour data
* @param {Array} contourPoints - Array of processed contour points
*/
function _setROIContourMetadata(
structureSet,
StructureSetROISequence,
RTROIObservationsSequence,
ROIContour,
contourPoints
contourPoints,
isSupported
) {
const StructureSetROI = StructureSetROISequence.find(
structureSetROI => structureSetROI.ROINumber === ROIContour.ReferencedROINumber
@@ -276,9 +211,9 @@ function _setROIContourMetadata(
ROIName: StructureSetROI.ROIName,
ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm,
ROIDescription: StructureSetROI.ROIDescription,
isSupported,
contourPoints,
visible: true,
colorArray: [],
};
_setROIContourDataColor(ROIContour, ROIContourData);
@@ -295,14 +230,6 @@ function _setROIContourMetadata(
structureSet.ROIContours.push(ROIContourData);
}
/**
* Sets the display color for ROI contour data.
* Uses ROIDisplayColor if available, otherwise converts RecommendedDisplayCIELabValue to RGB.
*
* @function _setROIContourDataColor
* @param {Object} ROIContour - ROI contour object containing color information
* @param {Object} ROIContourData - ROI contour data object to set color on
*/
function _setROIContourDataColor(ROIContour, ROIContourData) {
let { ROIDisplayColor, RecommendedDisplayCIELabValue } = ROIContour;
@@ -316,15 +243,6 @@ function _setROIContourDataColor(ROIContour, ROIContourData) {
}
}
/**
* Sets RT ROI observations metadata for ROI contour data.
* Finds matching RTROIObservations by ROINumber and adds observation details to contour data.
*
* @function _setROIContourRTROIObservations
* @param {Object} ROIContourData - ROI contour data object to add observations to
* @param {Array} RTROIObservationsSequence - Array of RT ROI observations
* @param {number} ROINumber - ROI number to match observations
*/
function _setROIContourRTROIObservations(ROIContourData, RTROIObservationsSequence, ROINumber) {
const RTROIObservations = RTROIObservationsSequence.find(
RTROIObservations => RTROIObservations.ReferencedROINumber === ROINumber
@@ -344,14 +262,6 @@ function _setROIContourRTROIObservations(ROIContourData, RTROIObservationsSequen
}
}
/**
* Converts a single object or array to an array.
* Utility function to ensure consistent array handling for DICOM sequences.
*
* @function _toArray
* @param {*} objOrArray - Object or array to convert
* @returns {Array} Array containing the input (if already array) or wrapped in array
*/
function _toArray(objOrArray) {
return Array.isArray(objOrArray) ? objOrArray : [objOrArray];
}
+1 -535
View File
@@ -3,541 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
### Features
* Add WSI bulk data annotations support (ANN) ([#4972](https://github.com/OHIF/Viewers/issues/4972)) ([2ed6818](https://github.com/OHIF/Viewers/commit/2ed681817142b13971bb17b3058be26bbab6b90b))
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
+15 -15
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.12.0-beta.66",
"version": "3.11.0",
"description": "DICOM SEG read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,22 +33,22 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-default": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-i18next": "12.3.1",
"react-router": "6.30.1",
"react-router-dom": "6.30.1"
"@ohif/core": "3.11.0",
"@ohif/extension-cornerstone": "3.11.0",
"@ohif/extension-default": "3.11.0",
"@ohif/i18n": "3.11.0",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2",
"react-router": "^6.23.1",
"react-router-dom": "^6.23.1"
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.32.5",
"@cornerstonejs/core": "^3.32.5",
"@kitware/vtk.js": "32.12.0",
"react-color": "2.19.3"
"react-color": "^2.19.3"
}
}
+1 -541
View File
@@ -3,547 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
### Features
* **MeasurementService:** add rendering of unmapped measurements ([#5416](https://github.com/OHIF/Viewers/issues/5416)) ([851e74d](https://github.com/OHIF/Viewers/commit/851e74d7b867a806befb5d85fd71ff9a75e9f2d2))
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
### Features
* Add WSI bulk data annotations support (ANN) ([#4972](https://github.com/OHIF/Viewers/issues/4972)) ([2ed6818](https://github.com/OHIF/Viewers/commit/2ed681817142b13971bb17b3058be26bbab6b90b))
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
### Bug Fixes
* load 3dsr ([#5335](https://github.com/OHIF/Viewers/issues/5335)) ([becba78](https://github.com/OHIF/Viewers/commit/becba7861f99ae4e190caddea8b663e60a9883f5))
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.12.0-beta.66",
"version": "3.11.0",
"description": "OHIF extension for an SR Cornerstone Viewport",
"author": "OHIF",
"license": "MIT",
@@ -34,21 +34,21 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-measurement-tracking": "3.12.0-beta.66",
"@ohif/ui": "3.12.0-beta.66",
"@ohif/core": "3.11.0",
"@ohif/extension-cornerstone": "3.11.0",
"@ohif/extension-measurement-tracking": "3.11.0",
"@ohif/ui": "3.11.0",
"dcmjs": "0.43.1",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
"react": "18.3.1"
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"classnames": "2.5.1"
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.32.5",
"@cornerstonejs/core": "^3.32.5",
"@cornerstonejs/tools": "^3.32.5",
"classnames": "^2.3.2"
}
}
@@ -86,7 +86,7 @@ function OHIFCornerstoneSRMeasurementViewport(props) {
const { presentationIds } = viewportOptions;
const measurement = srDisplaySet.measurements[newMeasurementSelected];
setPositionPresentation(presentationIds.positionPresentationId, {
viewReference: measurement.viewReference || {
viewReference: {
referencedImageId: measurement.imageId,
},
});
@@ -22,7 +22,7 @@ const {
CodeScheme: Cornerstone3DCodeScheme,
} = adaptersSR.Cornerstone3D;
type InstanceMetadata = OhifTypes.InstanceMetadata;
type InstanceMetadata = Types.InstanceMetadata;
/**
* TODO
@@ -35,7 +35,6 @@ const sopClassUids = [
sopClassDictionary.BasicTextSR,
sopClassDictionary.EnhancedSR,
sopClassDictionary.ComprehensiveSR,
sopClassDictionary.Comprehensive3DSR,
];
const validateSameStudyUID = (uid: string, instances): void => {
@@ -54,7 +53,7 @@ const validateSameStudyUID = (uid: string, instances): void => {
* @param instances is a list of instances from THIS series that are not
* in this DICOM SR Display Set already.
*/
function addInstances(instances: InstanceMetadata[], _displaySetService: DisplaySetService) {
function addInstances(instances: InstanceMetadata[], displaySetService: DisplaySetService) {
this.instances.push(...instances);
utils.sortStudyInstances(this.instances);
// The last instance is the newest one, so is the one most interesting.
@@ -69,10 +68,9 @@ function addInstances(instances: InstanceMetadata[], _displaySetService: Display
* DICOM SR SOP Class Handler
* For all referenced images in the TID 1500/300 sections, add an image to the
* display.
* @param {InstanceMetadata[]} instances - A set of instances all from the same series
* @param {AppTypes.ServicesManager} servicesManager - The services that can be used for creating
* @param {AppTypes.ExtensionManager} extensionManager - The extension manager
* @returns {Types.DisplaySet[]} The list of display sets created for the given instances object
* @param instances is a set of instances all from the same series
* @param servicesManager is the services that can be used for creating
* @returns The list of display sets created for the given instances object
*/
function _getDisplaySetsFromSeries(
instances,
@@ -144,7 +142,7 @@ function _getDisplaySetsFromSeries(
* @param extensionManager - The extension manager containing data sources.
*/
async function _load(
srDisplaySet: OhifTypes.DisplaySet,
srDisplaySet: Types.DisplaySet,
servicesManager: AppTypes.ServicesManager,
extensionManager: AppTypes.ExtensionManager
) {
@@ -228,9 +226,17 @@ function _measurementBelongsToDisplaySet({ measurement, displaySet }) {
);
}
/**
* Checks if measurements can be added to a display set.
*
* @param srDisplaySet - The source display set containing measurements.
* @param newDisplaySet - The new display set to check if measurements can be added.
* @param dataSource - The data source used to retrieve image IDs.
* @param servicesManager - The services manager.
*/
function _checkIfCanAddMeasurementsToDisplaySet(
srDisplaySet: OhifTypes.DisplaySet,
newDisplaySet: OhifTypes.DisplaySet,
srDisplaySet,
newDisplaySet,
dataSource,
servicesManager: AppTypes.ServicesManager
) {
@@ -282,7 +288,7 @@ function _checkIfCanAddMeasurementsToDisplaySet(
is3DMeasurement &&
_measurementBelongsToDisplaySet({ measurement, displaySet: newDisplaySet })
) {
addSRAnnotation({ measurement, displaySet: newDisplaySet });
addSRAnnotation(measurement, null, null);
measurement.loaded = true;
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
unloadedMeasurements.splice(j, 1);
@@ -303,12 +309,15 @@ function _checkIfCanAddMeasurementsToDisplaySet(
imageId &&
_measurementReferencesSOPInstanceUID(measurement, ReferencedSOPInstanceUID, frame)
) {
addSRAnnotation({ measurement, imageId, frameNumber: frame, displaySet: newDisplaySet });
addSRAnnotation(measurement, imageId, frame);
// Update measurement properties
measurement.loaded = true;
measurement.imageId = imageId;
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
measurement.ReferencedSOPInstanceUID = ReferencedSOPInstanceUID;
measurement.frameNumber = frame;
unloadedMeasurements.splice(j, 1);
}
}
@@ -316,10 +325,10 @@ function _checkIfCanAddMeasurementsToDisplaySet(
/**
* Checks if a measurement references a specific SOP Instance UID.
* @param {any} measurement - The measurement object.
* @param {string} sopInstanceUID - The SOP Instance UID to check against.
* @param {number} frameNumber - The frame number to check against (optional).
* @returns {boolean} True if the measurement references the specified SOP Instance UID, false otherwise.
* @param measurement - The measurement object.
* @param SOPInstanceUID - The SOP Instance UID to check against.
* @param frameNumber - The frame number to check against (optional).
* @returns True if the measurement references the specified SOP Instance UID, false otherwise.
*/
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
const { coords } = measurement;
@@ -351,8 +360,10 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
/**
* Retrieves the SOP class handler module.
*
* @param {OhifTypes.Extensions.ExtensionParams} params - The extension parameters.
* @returns {Array} An array containing the SOP class handler modules.
* @param {Object} options - The options for retrieving the SOP class handler module.
* @param {Object} options.servicesManager - The services manager.
* @param {Object} options.extensionManager - The extension manager.
* @returns {Array} An array containing the SOP class handler module.
*/
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
const { servicesManager, extensionManager } = params;
@@ -376,8 +387,8 @@ function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams)
/**
* Retrieves the measurements from the ImagingMeasurementReportContentSequence.
*
* @param {any[]} imagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
* @returns {any[]} The array of measurements.
* @param {Array} ImagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
* @returns {Array} - The array of measurements.
*/
function _getMeasurements(ImagingMeasurementReportContentSequence) {
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
@@ -415,8 +426,8 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
/**
* Retrieves merged content sequences by tracking unique identifiers.
*
* @param {any[]} measurementGroups - The measurement groups.
* @returns {Object} The merged content sequences by tracking unique identifiers.
* @param {Array} MeasurementGroups - The measurement groups.
* @returns {Object} - The merged content sequences by tracking unique identifiers.
*/
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
@@ -463,8 +474,8 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
* it calls the _processTID1410Measurement function.
* Otherwise, it calls the _processNonGeometricallyDefinedMeasurement function.
*
* @param {any[]} mergedContentSequence - The merged content sequence to process.
* @returns {any} The processed measurement result.
* @param {Array<Object>} mergedContentSequence - The merged content sequence to process.
* @returns {any} - The processed measurement result.
*/
function _processMeasurement(mergedContentSequence) {
if (mergedContentSequence.some(group => isScoordOr3d(group) && !isTextPosition(group))) {
@@ -479,8 +490,8 @@ function _processMeasurement(mergedContentSequence) {
* TID 1410 style measurements have a SCOORD or SCOORD3D at the top level,
* and non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D.
*
* @param {any[]} mergedContentSequence - The merged content sequence containing the measurements.
* @returns {any} The measurement object containing the loaded status, labels, coordinates, tracking unique identifier, and tracking identifier.
* @param mergedContentSequence - The merged content sequence containing the measurements.
* @returns The measurement object containing the loaded status, labels, coordinates, tracking unique identifier, and tracking identifier.
*/
function _processTID1410Measurement(mergedContentSequence) {
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
@@ -553,8 +564,8 @@ function _processTID1410Measurement(mergedContentSequence) {
/**
* Processes the non-geometrically defined measurement from the merged content sequence.
*
* @param {any[]} mergedContentSequence The merged content sequence containing the measurement data.
* @returns {any} The processed measurement object.
* @param mergedContentSequence The merged content sequence containing the measurement data.
* @returns The processed measurement object.
*/
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
@@ -654,8 +665,8 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
/**
* Extracts coordinates from a graphic item of type SCOORD or SCOORD3D.
* @param {any} graphicItem - The graphic item containing the coordinates.
* @returns {any} The extracted coordinates.
* @param {object} graphicItem - The graphic item containing the coordinates.
* @returns {object} - The extracted coordinates.
*/
const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
const { ValueType, GraphicType, GraphicData } = graphicItem;
@@ -669,9 +680,9 @@ const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
/**
* Retrieves the label and value from the provided ConceptNameCodeSequence and MeasuredValueSequence.
* @param {any} conceptNameCodeSequence - The ConceptNameCodeSequence object.
* @param {any} measuredValueSequence - The MeasuredValueSequence object.
* @returns {Object} An object containing the label and value.
* @param {Object} ConceptNameCodeSequence - The ConceptNameCodeSequence object.
* @param {Object} MeasuredValueSequence - The MeasuredValueSequence object.
* @returns {Object} - An object containing the label and value.
* The label represents the CodeMeaning from the ConceptNameCodeSequence.
* The value represents the formatted NumericValue and CodeValue from the MeasuredValueSequence.
* Example: { label: 'Long Axis', value: '31.00 mm' }
@@ -690,8 +701,8 @@ function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredVal
/**
* Retrieves a list of referenced images from the Imaging Measurement Report Content Sequence.
*
* @param {any[]} imagingMeasurementReportContentSequence - The Imaging Measurement Report Content Sequence.
* @returns {any[]} The list of referenced images.
* @param {Array} ImagingMeasurementReportContentSequence - The Imaging Measurement Report Content Sequence.
* @returns {Array} - The list of referenced images.
*/
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
@@ -738,7 +749,7 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
* Otherwise, the sequence is wrapped in an array and returned.
*
* @param {any} sequence - The DICOM sequence to convert.
* @returns {any[]} The converted array.
* @returns {any[]} - The converted array.
*/
function _getSequenceAsArray(sequence) {
if (!sequence) {
+3 -3
View File
@@ -9,10 +9,10 @@ import {
LengthTool,
PlanarFreehandROITool,
RectangleROITool,
utilities as csToolsUtils,
} from '@cornerstonejs/tools';
import { Types } from '@ohif/core';
import { Types, MeasurementService } from '@ohif/core';
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
import SCOORD3DPointTool from './tools/SCOORD3DPointTool';
import SRSCOOR3DProbeMapper from './utils/SRSCOOR3DProbeMapper';
@@ -28,7 +28,7 @@ export default function init({
configuration = {},
servicesManager,
}: Types.Extensions.ExtensionParams): void {
const { measurementService } = servicesManager.services;
const { measurementService, cornerstoneViewportService } = servicesManager.services;
addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
addToolInstance(toolNames.SRLength, LengthTool);
@@ -7,53 +7,8 @@ import toolNames from '../tools/toolNames';
const { MeasurementReport } = adaptersSR.Cornerstone3D;
/**
* Adds a DICOM SR (Structured Report) annotation to the annotation manager.
* This function processes measurement data from DICOM SR and converts it into
* a format suitable for display in the Cornerstone3D viewer.
*
* @param {Object} params - The parameters object
* @param {Object} params.measurement - The DICOM SR measurement data containing coordinates, labels, and metadata
* @param {Array} params.measurement.coords - Array of coordinate objects with GraphicType, ValueType, and other properties
* @param {string} params.measurement.TrackingUniqueIdentifier - Unique identifier for the measurement
* @param {string} params.measurement.TrackingIdentifier - Tracking identifier for adapter lookup
* @param {Array} [params.measurement.labels] - Optional array of label objects
* @param {string} [params.measurement.displayText] - Optional display text for the annotation
* @param {Object} [params.measurement.textBox] - Optional text box configuration
* @param {string|null} [params.imageId] - Optional image ID for the referenced image (defaults to null)
* @param {number|null} [params.frameNumber] - Optional frame number for multi-frame images (defaults to null)
* @param {Object} params.displaySet - The display set containing the image
* @param {string} params.displaySet.displaySetInstanceUID - Unique identifier for the display set
* @returns {void}
*
* @example
* ```typescript
* addSRAnnotation({
* measurement: {
* TrackingUniqueIdentifier: '1.2.3.4.5',
* TrackingIdentifier: 'POINT',
* coords: [{
* GraphicType: 'POINT',
* ValueType: 'SCOORD',
* // ... other coordinate properties
* }],
* labels: [{ value: 'Measurement Point' }],
* displayText: 'Point measurement'
* },
* imageId: 'wadouri:file://path/to/image.dcm', // Optional
* frameNumber: 0, // Optional
* displaySet: { displaySetInstanceUID: '1.2.3.4' }
* });
* ```
*/
export default function addSRAnnotation({ measurement, imageId = null, frameNumber = null, displaySet }) {
/** @type {string} The tool name to use for the annotation, defaults to DICOMSRDisplay */
export default function addSRAnnotation(measurement, imageId, frameNumber) {
let toolName = toolNames.DICOMSRDisplay;
/**
* @type {Object} Renderable data organized by graphic type
* Groups coordinate data by GraphicType for efficient rendering
*/
const renderableData = measurement.coords.reduce((acc, coordProps) => {
acc[coordProps.GraphicType] = acc[coordProps.GraphicType] || [];
acc[coordProps.GraphicType].push(getRenderableData({ ...coordProps, imageId }));
@@ -66,7 +21,6 @@ export default function addSRAnnotation({ measurement, imageId = null, frameNumb
/** TODO: Read the tool name from the DICOM SR identification type in the future. */
let frameOfReferenceUID = null;
let planeRestriction = null;
if (imageId) {
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
@@ -83,38 +37,19 @@ export default function addSRAnnotation({ measurement, imageId = null, frameNumb
// get the ReferencedFrameOfReferenceUID from the measurement
frameOfReferenceUID = measurement.coords[0].ReferencedFrameOfReferenceSequence;
planeRestriction = {
FrameOfReferenceUID: frameOfReferenceUID,
point: graphicTypePoints[0][0],
};
}
// Store the view reference for use in initial navigation
measurement.viewReference = {
planeRestriction,
FrameOfReferenceUID: frameOfReferenceUID,
referencedImageId: imageId,
};
/**
* @type {Types.Annotation} The annotation object to be added to the annotation manager
* Contains all necessary metadata and data for rendering the DICOM SR measurement
*/
const SRAnnotation: Types.Annotation = {
annotationUID: TrackingUniqueIdentifier,
highlighted: false,
isLocked: false,
isPreview: toolName === toolNames.DICOMSRDisplay,
invalidated: false,
metadata: {
toolName,
planeRestriction,
valueType,
graphicType,
FrameOfReferenceUID: frameOfReferenceUID,
referencedImageId: imageId,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
},
data: {
label: measurement.labels?.[0]?.value || undefined,
@@ -132,11 +67,8 @@ export default function addSRAnnotation({ measurement, imageId = null, frameNumb
};
/**
* Add the annotation to the annotation state manager.
* Note: Using annotation.state.addAnnotation() instead of annotationManager.addAnnotation()
* because the latter was not triggering annotation_added events properly.
*
* @param {Types.Annotation} SRAnnotation - The annotation to add
* const annotationManager = annotation.annotationState.getAnnotationManager();
* was not triggering annotation_added events.
*/
annotation.state.addAnnotation(SRAnnotation);
}
@@ -3,541 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
### Features
* Add WSI bulk data annotations support (ANN) ([#4972](https://github.com/OHIF/Viewers/issues/4972)) ([2ed6818](https://github.com/OHIF/Viewers/commit/2ed681817142b13971bb17b3058be26bbab6b90b))
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.12.0-beta.66",
"version": "3.11.0",
"description": "OHIF extension for 4D volumes data",
"author": "OHIF",
"license": "MIT",
@@ -29,21 +29,21 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-default": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"@ohif/ui": "3.12.0-beta.66",
"@ohif/core": "3.11.0",
"@ohif/extension-cornerstone": "3.11.0",
"@ohif/extension-default": "3.11.0",
"@ohif/i18n": "3.11.0",
"@ohif/ui": "3.11.0",
"dcmjs": "0.43.1",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
"react": "18.3.1"
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"classnames": "2.5.1"
"@babel/runtime": "^7.20.13",
"@cornerstonejs/core": "^3.32.5",
"@cornerstonejs/tools": "^3.32.5",
"classnames": "^2.3.2"
}
}
+1 -583
View File
@@ -3,589 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.64...v3.12.0-beta.65) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.63...v3.12.0-beta.64) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.62...v3.12.0-beta.63) (2025-10-15)
### Bug Fixes
* Segmentation not displayed and unable to draw segments in volume viewport after converting Labelmap to Surface ([#5488](https://github.com/OHIF/Viewers/issues/5488)) ([faea09e](https://github.com/OHIF/Viewers/commit/faea09e7ef2667aad5cef1efc2a6516677ca2915))
# [3.12.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.61...v3.12.0-beta.62) (2025-10-15)
### Bug Fixes
* **segmentationService:** prevent no representation crash ([#5495](https://github.com/OHIF/Viewers/issues/5495)) ([3137aed](https://github.com/OHIF/Viewers/commit/3137aed0170196a61e16c6dac0c15bcc41691919))
# [3.12.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.60...v3.12.0-beta.61) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.59...v3.12.0-beta.60) (2025-10-15)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.58...v3.12.0-beta.59) (2025-10-14)
### Bug Fixes
* **security:** Use exact versioning for dependencies in package.json files. ([#5494](https://github.com/OHIF/Viewers/issues/5494)) ([c7d2017](https://github.com/OHIF/Viewers/commit/c7d2017f081c5803a70bab6c75bba0c975028125))
# [3.12.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.57...v3.12.0-beta.58) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.56...v3.12.0-beta.57) (2025-10-14)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.55...v3.12.0-beta.56) (2025-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.54...v3.12.0-beta.55) (2025-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.53...v3.12.0-beta.54) (2025-10-08)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.52...v3.12.0-beta.53) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.51...v3.12.0-beta.52) (2025-10-07)
### Bug Fixes
* **segmentation:** Segmentation not displayed and unable to draw segments when switching back to volume/stack viewport from 3D viewport ([#5430](https://github.com/OHIF/Viewers/issues/5430)) ([78df8ff](https://github.com/OHIF/Viewers/commit/78df8ff03a371904a390c63a95628d5511dad136))
# [3.12.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.50...v3.12.0-beta.51) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.49...v3.12.0-beta.50) (2025-10-07)
### Bug Fixes
* **Segmentation:** [Bug [#5420](https://github.com/OHIF/Viewers/issues/5420)] Segmentation color resets after using toggleOneUp ([#5465](https://github.com/OHIF/Viewers/issues/5465)) ([2c6ce21](https://github.com/OHIF/Viewers/commit/2c6ce21c8f79f8b3e6dc47b5aa6f717b6b5cbd2a))
# [3.12.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.48...v3.12.0-beta.49) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.47...v3.12.0-beta.48) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.46...v3.12.0-beta.47) (2025-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.45...v3.12.0-beta.46) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.44...v3.12.0-beta.45) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.43...v3.12.0-beta.44) (2025-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.42...v3.12.0-beta.43) (2025-10-06)
### Features
* **MeasurementService:** add rendering of unmapped measurements ([#5416](https://github.com/OHIF/Viewers/issues/5416)) ([851e74d](https://github.com/OHIF/Viewers/commit/851e74d7b867a806befb5d85fd71ff9a75e9f2d2))
# [3.12.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.41...v3.12.0-beta.42) (2025-10-03)
### Bug Fixes
* duplicate segmentation entries on layout switch ([#5458](https://github.com/OHIF/Viewers/issues/5458)) ([9e951e8](https://github.com/OHIF/Viewers/commit/9e951e88005af505ae0c07fbcd3366de12b773dd))
# [3.12.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.40...v3.12.0-beta.41) (2025-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.39...v3.12.0-beta.40) (2025-10-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.38...v3.12.0-beta.39) (2025-10-01)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.37...v3.12.0-beta.38) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.36...v3.12.0-beta.37) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.35...v3.12.0-beta.36) (2025-09-30)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.34...v3.12.0-beta.35) (2025-09-29)
### Bug Fixes
* **segmentation:** Restored segment label tool in longitudinal mode. ([#5443](https://github.com/OHIF/Viewers/issues/5443)) ([dd86a8e](https://github.com/OHIF/Viewers/commit/dd86a8eabd0712d25e7652a242377fe09597e8d5))
# [3.12.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.33...v3.12.0-beta.34) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.32...v3.12.0-beta.33) (2025-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.31...v3.12.0-beta.32) (2025-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.30...v3.12.0-beta.31) (2025-09-25)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.29...v3.12.0-beta.30) (2025-09-25)
### Bug Fixes
* **i18n:** Add and update i18n Translation for Toolbar Tool Names ([#5392](https://github.com/OHIF/Viewers/issues/5392)) ([7783d0f](https://github.com/OHIF/Viewers/commit/7783d0f558bbb7016c29513b6fe76ed069dbd75c))
# [3.12.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.28...v3.12.0-beta.29) (2025-09-22)
### Features
* **Segmentation:** Segmentation highlight animation function selection ([#5401](https://github.com/OHIF/Viewers/issues/5401)) ([69dbe27](https://github.com/OHIF/Viewers/commit/69dbe2778493cdfc7a8da5e757c95217bb31edc2))
# [3.12.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.27...v3.12.0-beta.28) (2025-09-19)
### Bug Fixes
* **SegmentationService:** _getSegmentCenter wrongly handling seg center availability ([#5396](https://github.com/OHIF/Viewers/issues/5396)) ([74b82bd](https://github.com/OHIF/Viewers/commit/74b82bd93442411ad68f4872992126c40321eb9e))
# [3.12.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.26...v3.12.0-beta.27) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.25...v3.12.0-beta.26) (2025-09-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.24...v3.12.0-beta.25) (2025-09-16)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.23...v3.12.0-beta.24) (2025-09-10)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.22...v3.12.0-beta.23) (2025-09-05)
### Bug Fixes
* **cornerstone-dicom-rt:** adding caching usage to cornerstone-dicom-rt SOP Class Handler ([#5387](https://github.com/OHIF/Viewers/issues/5387)) ([d6ea179](https://github.com/OHIF/Viewers/commit/d6ea179ac43a47d4011b8dcaf59d79d99382d6c8))
# [3.12.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.21...v3.12.0-beta.22) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.20...v3.12.0-beta.21) (2025-08-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.19...v3.12.0-beta.20) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.18...v3.12.0-beta.19) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.17...v3.12.0-beta.18) (2025-08-28)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.16...v3.12.0-beta.17) (2025-08-28)
### Bug Fixes
* seg preview ([#5338](https://github.com/OHIF/Viewers/issues/5338)) ([d91cc23](https://github.com/OHIF/Viewers/commit/d91cc230e5231ca32ee28736f661a1497f7834c5))
# [3.12.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.15...v3.12.0-beta.16) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.14...v3.12.0-beta.15) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.13...v3.12.0-beta.14) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.12...v3.12.0-beta.13) (2025-08-25)
### Bug Fixes
* add back clear segmentation representations ([#5336](https://github.com/OHIF/Viewers/issues/5336)) ([21d6ba2](https://github.com/OHIF/Viewers/commit/21d6ba2bb30df5b99bf5728163caff514aaa5dcd))
# [3.12.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.11...v3.12.0-beta.12) (2025-08-25)
### Bug Fixes
* Segmentation stats customization module ([#5324](https://github.com/OHIF/Viewers/issues/5324)) ([b9d6f55](https://github.com/OHIF/Viewers/commit/b9d6f558cbe5957f83e6d92305a78662a0b186ed))
# [3.12.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.10...v3.12.0-beta.11) (2025-08-25)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.9...v3.12.0-beta.10) (2025-08-22)
### Features
* Jump to measurement moves the camera if it is not in the viewport ([#5023](https://github.com/OHIF/Viewers/issues/5023)) ([6fe9df6](https://github.com/OHIF/Viewers/commit/6fe9df6ec93c0f37e3bb9f8bf6425bae025e75c8))
# [3.12.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.8...v3.12.0-beta.9) (2025-08-22)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.7...v3.12.0-beta.8) (2025-08-20)
### Features
* Add WSI bulk data annotations support (ANN) ([#4972](https://github.com/OHIF/Viewers/issues/4972)) ([2ed6818](https://github.com/OHIF/Viewers/commit/2ed681817142b13971bb17b3058be26bbab6b90b))
# [3.12.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.6...v3.12.0-beta.7) (2025-08-15)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.5...v3.12.0-beta.6) (2025-08-13)
### Features
* **orientationMarkers:** improved the orientation markers with a simplified version ([#5328](https://github.com/OHIF/Viewers/issues/5328)) ([266b1f5](https://github.com/OHIF/Viewers/commit/266b1f58f01ac6742a9bb40dda28459e7cddc683))
# [3.12.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.4...v3.12.0-beta.5) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.3...v3.12.0-beta.4) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.2...v3.12.0-beta.3) (2025-08-11)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.1...v3.12.0-beta.2) (2025-08-08)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.0...v3.12.0-beta.1) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.12.0-beta.0) (2025-08-05)
# [3.11.0](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.116...v3.11.0) (2025-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone
+2 -6
View File
@@ -1,13 +1,9 @@
const base = require('../../jest.config.base.js');
const pkg = require('./package');
module.exports = {
...base,
moduleNameMapper: {
...base.moduleNameMapper,
'@ohif/(.*)': '<rootDir>/../../platform/$1/src',
'^@cornerstonejs/([^/]+)/(.*)$': '<rootDir>/../../node_modules/@cornerstonejs/$1/dist/esm/$2',
'^@cornerstonejs/([^/]+)$': '<rootDir>/../../node_modules/@cornerstonejs/$1/dist/esm',
},
displayName: pkg.name,
// rootDir: "../.."
// testMatch: [
// //`<rootDir>/platform/${pack.name}/**/*.spec.js`
+30 -32
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "3.12.0-beta.66",
"version": "3.11.0",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -31,43 +31,41 @@
"dev:cornerstone": "yarn run dev",
"build": "cross-env NODE_ENV=production webpack --progress --config .webpack/webpack.prod.js",
"build:package-1": "yarn run build",
"start": "yarn run dev",
"test:unit": "jest --watchAll",
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
"start": "yarn run dev"
},
"peerDependencies": {
"@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.7",
"@cornerstonejs/dicom-image-loader": "4.5.5",
"@ohif/core": "3.12.0-beta.66",
"@ohif/ui": "3.12.0-beta.66",
"@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.32.5",
"@ohif/core": "3.11.0",
"@ohif/ui": "3.11.0",
"dcmjs": "0.43.1",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-resize-detector": "10.0.1"
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-resize-detector": "^10.0.1"
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/ai": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/labelmap-interpolation": "4.5.5",
"@cornerstonejs/polymorphic-segmentation": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.32.5",
"@cornerstonejs/ai": "^3.32.5",
"@cornerstonejs/core": "^3.32.5",
"@cornerstonejs/labelmap-interpolation": "^3.32.5",
"@cornerstonejs/polymorphic-segmentation": "^3.32.5",
"@cornerstonejs/tools": "^3.32.5",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "1.4.1",
"lodash.compact": "3.0.1",
"lodash.debounce": "4.0.8",
"lodash.flatten": "4.4.0",
"lodash.merge": "4.6.2",
"lodash.zip": "4.2.0",
"shader-loader": "1.3.1",
"worker-loader": "3.0.8"
"html2canvas": "^1.4.1",
"lodash.compact": "^3.0.1",
"lodash.debounce": "^4.0.8",
"lodash.flatten": "^4.4.0",
"lodash.merge": "^4.6.2",
"lodash.zip": "^4.2.0",
"shader-loader": "^1.3.1",
"worker-loader": "^3.0.8"
}
}
@@ -1,6 +1,12 @@
import React, { useEffect, useState, useMemo } from 'react';
import React, { useEffect, useState, useMemo, useRef } from 'react';
import classNames from 'classnames';
import { metaData, Enums, getEnabledElement } from '@cornerstonejs/core';
import {
metaData,
Enums,
Types,
getEnabledElement,
utilities as coreUtilities,
} from '@cornerstonejs/core';
import { utilities } from '@cornerstonejs/tools';
import { vec3 } from 'gl-matrix';
@@ -16,36 +22,75 @@ function ViewportOrientationMarkers({
servicesManager,
orientationMarkers = ['top', 'left'],
}: withAppTypes) {
const [cameraModifiedTime, setCameraModifiedTime] = useState(0);
// Rotation is in degrees
const [rotation, setRotation] = useState(0);
const [flipHorizontal, setFlipHorizontal] = useState(false);
const [flipVertical, setFlipVertical] = useState(false);
const { isViewportBackgroundLight: isLight } = useViewportRendering(viewportId);
const { cornerstoneViewportService } = servicesManager.services;
// Store initial viewUp and viewRight for volume viewports
const initialVolumeOrientationRef = useRef<{
initialViewUp: number[] | null;
initialViewRight: number[] | null;
}>({
initialViewUp: null,
initialViewRight: null,
});
useEffect(() => {
const cameraModifiedListener = () => setCameraModifiedTime(Date.now());
initialVolumeOrientationRef.current.initialViewUp = null;
initialVolumeOrientationRef.current.initialViewRight = null;
if (viewportData?.viewportType !== 'stack' && element && getEnabledElement(element)) {
const { viewport } = getEnabledElement(element);
const { viewUp, viewPlaneNormal } = viewport.getCamera();
const viewRight = vec3.create();
vec3.cross(viewRight, viewUp, viewPlaneNormal);
initialVolumeOrientationRef.current.initialViewUp = [...viewUp];
initialVolumeOrientationRef.current.initialViewRight = [...viewRight];
}
}, [element, viewportData]);
useEffect(() => {
const cameraModifiedListener = (evt: Types.EventTypes.CameraModifiedEvent) => {
const { previousCamera, camera } = evt.detail;
const { rotation } = camera;
if (rotation !== undefined) {
setRotation(rotation);
}
if (
camera.flipHorizontal !== undefined &&
previousCamera.flipHorizontal !== camera.flipHorizontal
) {
setFlipHorizontal(camera.flipHorizontal);
}
if (
camera.flipVertical !== undefined &&
previousCamera.flipVertical !== camera.flipVertical
) {
setFlipVertical(camera.flipVertical);
}
};
element.addEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener);
return () => {
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener);
};
}, [element]);
}, []);
const markers = useMemo(() => {
if (!viewportData || cameraModifiedTime === 0) {
return '';
}
if (!element || !getEnabledElement(element)) {
console.log(`ViewportOrientationMarkers :: Viewport element not enabled (${viewportId})`);
return '';
}
const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId);
if (!ohifViewport) {
console.log(`ViewportOrientationMarkers :: No viewport (${viewportId})`);
if (!viewportData) {
return '';
}
let rowCosines, columnCosines, isDefaultValueSetForRowCosine, isDefaultValueSetForColumnCosine;
if (viewportData.viewportType === 'stack') {
const imageIndex = imageSliceData.imageIndex;
const imageId = viewportData.data[0].imageIds?.[imageIndex];
@@ -55,25 +100,58 @@ function ViewportOrientationMarkers({
return false;
}
const { isDefaultValueSetForRowCosine, isDefaultValueSetForColumnCosine } =
metaData.get('imagePlaneModule', imageId) || {};
({
rowCosines,
columnCosines,
isDefaultValueSetForColumnCosine,
isDefaultValueSetForColumnCosine,
} = metaData.get('imagePlaneModule', imageId) || {});
} else {
if (!element || !getEnabledElement(element)) {
return '';
}
if (isDefaultValueSetForColumnCosine || isDefaultValueSetForRowCosine) {
if (
initialVolumeOrientationRef.current.initialViewUp &&
initialVolumeOrientationRef.current.initialViewRight
) {
// Use initial orientation values for consistency, even as the camera changes
columnCosines = [
-initialVolumeOrientationRef.current.initialViewUp[0],
-initialVolumeOrientationRef.current.initialViewUp[1],
-initialVolumeOrientationRef.current.initialViewUp[2],
];
rowCosines = initialVolumeOrientationRef.current.initialViewRight;
} else {
console.warn('ViewportOrientationMarkers::No initial orientation values');
return '';
}
}
const { viewport } = getEnabledElement(element);
const p00 = viewport.canvasToWorld([0, 0]);
const p10 = viewport.canvasToWorld([1, 0]);
const p01 = viewport.canvasToWorld([0, 1]);
const rowCosines = vec3.sub(vec3.create(), p10, p00);
const columnCosines = vec3.sub(vec3.create(), p01, p00);
if (
!rowCosines ||
!columnCosines ||
rotation === undefined ||
isDefaultValueSetForRowCosine ||
isDefaultValueSetForColumnCosine
) {
return '';
}
vec3.normalize(rowCosines, rowCosines);
vec3.normalize(columnCosines, columnCosines);
const markers = _getOrientationMarkers(
rowCosines,
columnCosines,
rotation,
flipVertical,
flipHorizontal
);
const markers = _getOrientationMarkers(rowCosines, columnCosines);
const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId);
if (!ohifViewport) {
console.log('ViewportOrientationMarkers::No viewport');
return null;
}
return orientationMarkers.map((m, index) => (
<div
@@ -90,7 +168,16 @@ function ViewportOrientationMarkers({
<div className="orientation-marker-value">{markers[m]}</div>
</div>
));
}, [viewportData, imageSliceData, cameraModifiedTime, orientationMarkers, element, isLight]);
}, [
viewportData,
imageSliceData,
rotation,
flipVertical,
flipHorizontal,
orientationMarkers,
element,
isLight,
]);
return <div className="ViewportOrientationMarkers select-none">{markers}</div>;
}
@@ -102,13 +189,18 @@ function ViewportOrientationMarkers({
*
* @param {*} rowCosines
* @param {*} columnCosines
* @param {*} rotation in degrees
* @returns
*/
function _getOrientationMarkers(rowCosines, columnCosines) {
function _getOrientationMarkers(rowCosines, columnCosines, rotation, flipVertical, flipHorizontal) {
const rowString = getOrientationStringLPS(rowCosines);
const columnString = getOrientationStringLPS(columnCosines);
const oppositeRowString = invertOrientationStringLPS(rowString);
const oppositeColumnString = invertOrientationStringLPS(columnString);
// Round to 4 decimal places (after rotate right 3 times and flip horizontally -> rotation is 90.00000000000001)
rotation = Math.round(rotation * 10000) / 10000;
const markers = {
top: oppositeColumnString,
left: oppositeRowString,
@@ -116,6 +208,43 @@ function _getOrientationMarkers(rowCosines, columnCosines) {
bottom: columnString,
};
// If any vertical or horizontal flips are applied, change the orientation strings ahead of
// the rotation applications
if (flipVertical) {
markers.top = invertOrientationStringLPS(markers.top);
markers.bottom = invertOrientationStringLPS(markers.bottom);
}
if (flipHorizontal) {
markers.left = invertOrientationStringLPS(markers.left);
markers.right = invertOrientationStringLPS(markers.right);
}
// Swap the labels accordingly if the viewport has been rotated
// This could be done in a more complex way for intermediate rotation values (e.g. 45 degrees)
if (rotation === 90 || rotation === -270) {
return {
top: markers.left,
left: invertOrientationStringLPS(markers.top),
right: invertOrientationStringLPS(markers.bottom),
bottom: markers.right, // left
};
} else if (rotation === -90 || rotation === 270) {
return {
top: invertOrientationStringLPS(markers.left),
left: markers.top,
bottom: markers.left,
right: markers.bottom,
};
} else if (rotation === 180 || rotation === -180) {
return {
top: invertOrientationStringLPS(markers.top),
left: invertOrientationStringLPS(markers.left),
bottom: invertOrientationStringLPS(markers.bottom),
right: invertOrientationStringLPS(markers.right),
};
}
return markers;
}
+24 -73
View File
@@ -40,9 +40,6 @@ import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats
import { generateSegmentationCSVReport } from './utils/generateSegmentationCSVReport';
import { getUpdatedViewportsForSegmentation } from './utils/hydrationUtils';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import { isMeasurementWithinViewport } from './utils/isMeasurementWithinViewport';
import { getCenterExtent } from './utils/getCenterExtent';
import { EasingFunctionEnum } from './utils/transitions';
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
const toggleSyncFunctions = {
@@ -61,7 +58,10 @@ const getLabelmapTools = ({ toolGroupService }) => {
// tools is an object with toolName as the key and tool as the value
Object.keys(tools).forEach(toolName => {
const tool = tools[toolName];
if (tool instanceof cornerstoneTools.LabelmapBaseTool) {
if (
tool instanceof cornerstoneTools.LabelmapBaseTool &&
tool.shouldResolvePreviewRequests()
) {
labelmapTools.push(tool);
}
});
@@ -161,27 +161,6 @@ function commandsModule({
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
viewport.setViewReference(metadata);
viewport.render();
/**
* If the measurement is not visible inside the current viewport,
* we need to move the camera to the measurement.
*/
if (!isMeasurementWithinViewport(viewport, measurement)) {
const camera = viewport.getCamera();
const { focalPoint: cameraFocalPoint, position: cameraPosition } = camera;
const { center, extent } = getCenterExtent(measurement);
const position = vec3.sub(vec3.create(), cameraPosition, cameraFocalPoint);
vec3.add(position, position, center);
viewport.setCamera({ focalPoint: center, position: position as any });
/** Zoom out if the measurement is too large */
const measurementSize = vec3.dist(extent.min, extent.max);
if (measurementSize > camera.parallelScale) {
const scaleFactor = measurementSize / camera.parallelScale;
viewport.setZoom(viewport.getZoom() / scaleFactor);
}
viewport.render();
}
return;
}
@@ -518,8 +497,8 @@ function commandsModule({
if (!labelConfig) {
const label = await callInputDialog({
uiDialogService,
title: i18n.t('Tools:Edit Measurement Label'),
placeholder: measurement.label || i18n.t('Tools:Enter new label'),
title: 'Edit Measurement Label',
placeholder: measurement.label || 'Enter new label',
defaultValue: measurement.label,
});
@@ -733,8 +712,8 @@ function commandsModule({
if (!labelConfig) {
const label = await callInputDialog({
uiDialogService,
title: i18n.t('Tools:Edit Arrow Text'),
placeholder: data?.data?.label || i18n.t('Tools:Enter new text'),
title: 'Edit Arrow Text',
placeholder: data?.data?.label || 'Enter new text',
defaultValue: data?.data?.label || '',
});
@@ -978,8 +957,8 @@ function commandsModule({
if (!cornerstoneViewportService.getCornerstoneViewport(activeViewportId)) {
// Cannot download a non-cornerstone viewport (image).
uiNotificationService.show({
title: i18n.t('Tools:Download Image'),
message: i18n.t('Tools:Image cannot be downloaded'),
title: 'Download Image',
message: 'Image cannot be downloaded',
type: 'error',
});
return;
@@ -990,7 +969,7 @@ function commandsModule({
if (uiModalService) {
uiModalService.show({
content: CornerstoneViewportDownloadForm,
title: i18n.t('Tools:Download High Quality Image'),
title: 'Download High Quality Image',
contentProps: {
activeViewportId,
cornerstoneViewportService,
@@ -1454,7 +1433,7 @@ function commandsModule({
const segs = segmentationService.getSegmentations();
const label = options.label || `${i18n.t('Tools:Segmentation')} ${segs.length + 1}`;
const label = options.label || `Segmentation ${segs.length + 1}`;
const segmentationId = options.segmentationId || `${csUtils.uuidv4()}`;
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
@@ -1468,7 +1447,7 @@ function commandsModule({
segments: options.createInitialSegment
? {
1: {
label: `${i18n.t('Tools:Segment')} 1`,
label: `${i18n.t('Segment')} 1`,
active: true,
},
}
@@ -1519,33 +1498,7 @@ function commandsModule({
segmentationId
);
segmentationService.setActiveSegment(segmentationId, segmentIndex);
const { highlightAlpha, highlightSegment, animationLength, animationFunctionType } =
(customizationService.getCustomization(
'panelSegmentation.jumpToSegmentHighlightAnimationConfig'
) as Object as {
highlightAlpha?: number;
highlightSegment?: boolean;
animationLength?: number;
animationFunctionType?: EasingFunctionEnum;
}) ?? {};
const validAnimationFunctionType = Object.values(EasingFunctionEnum).includes(
animationFunctionType
)
? animationFunctionType
: undefined;
segmentationService.jumpToSegmentCenter(
segmentationId,
segmentIndex,
undefined,
highlightAlpha,
highlightSegment,
animationLength,
undefined,
validAnimationFunctionType
);
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex);
},
/**
@@ -1745,8 +1698,8 @@ function commandsModule({
callInputDialog({
uiDialogService,
title: i18n.t('Tools:Edit Segment Label'),
placeholder: i18n.t('Tools:Enter new label'),
title: 'Edit Segment Label',
placeholder: 'Enter new label',
defaultValue: segment.label,
}).then(label => {
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
@@ -1765,8 +1718,8 @@ function commandsModule({
callInputDialog({
uiDialogService,
title: i18n.t('Tools:Edit Segmentation Label'),
placeholder: i18n.t('Tools:Enter new label'),
title: 'Edit Segmentation Label',
placeholder: 'Enter new label',
defaultValue: label,
}).then(label => {
segmentationService.addOrUpdateSegmentation({ segmentationId, label });
@@ -1788,7 +1741,7 @@ function commandsModule({
uiDialogService.show({
content: colorPickerDialog,
title: i18n.t('Tools:Segment Color'),
title: 'Segment Color',
contentProps: {
value: rgbaColor,
onSave: newRgbaColor => {
@@ -1858,24 +1811,22 @@ function commandsModule({
}
});
},
toggleSegmentLabel: ({ enabled }: { enabled?: boolean }) => {
toggleSegmentLabel: () => {
const toolName = cornerstoneTools.SegmentLabelTool.toolName;
const toolGroupIds = toolGroupService.getToolGroupIds();
const isToolOn = toolGroupIds.some(toolGroupId => {
const isOn = toolGroupIds.some(toolGroupId => {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId);
const mode = toolGroup.getToolInstance(toolName)?.mode;
return mode === 'Active';
});
const enableTool = enabled !== undefined ? enabled : !isToolOn;
toolGroupIds.forEach(toolGroupId => {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId);
if (enableTool) {
toolGroup.setToolActive(toolName);
} else {
if (isOn) {
toolGroup.setToolDisabled(toolName);
} else {
toolGroup.setToolActive(toolName);
}
});
},
@@ -55,7 +55,7 @@ const DEFAULT_TYPES = [GroupAccordion, Content, Trigger];
* measurements panel for a practical, working example.
*/
export function AccordionGroup(props) {
const { grouping, items, children, sourceChildren } = props;
const { grouping, items, children, sourceChildren, type } = props;
const childProps = useSystem();
let defaultValue = props.defaultValue;
const groups = grouping.groupingFunction(items, grouping, childProps);
@@ -1,5 +1,6 @@
import React from 'react';
import { useActiveViewportDisplaySets, utils } from '@ohif/core';
import { useActiveViewportDisplaySets, useSystem, utils } from '@ohif/core';
// import { AccordionContent, AccordionItem, AccordionTrigger } from '@ohif/ui-next';
import { AccordionGroup } from './AccordionGroup';
import MeasurementsOrAdditionalFindings from './MeasurementsOrAdditionalFindings';
@@ -62,7 +63,8 @@ export const groupByStudy = (items, grouping, childProps) => {
export function StudyMeasurements(props): React.ReactNode {
const { items, grouping = {}, children } = props;
const activeDisplaySets = useActiveViewportDisplaySets();
const system = useSystem();
const activeDisplaySets = useActiveViewportDisplaySets(system);
const activeStudyUID = activeDisplaySets?.[0]?.StudyInstanceUID;
return (
@@ -1,11 +1,9 @@
import React, { ReactElement, useCallback } from 'react';
import { Switch } from '@ohif/ui-next';
import { useViewportRendering } from '../../hooks/useViewportRendering';
import { useTranslation } from 'react-i18next';
export function Colorbar({ viewportId }: { viewportId?: string } = {}): ReactElement {
const { hasColorbar, toggleColorbar } = useViewportRendering(viewportId);
const { t } = useTranslation('WindowLevelActionMenu');
const handleToggle = useCallback(() => {
toggleColorbar();
@@ -18,7 +16,7 @@ export function Colorbar({ viewportId }: { viewportId?: string } = {}): ReactEle
className="flex-grow"
onClick={handleToggle}
>
{t('Display Color bar')}
Display Color bar
</span>
<Switch
className="ml-2 flex-shrink-0"
@@ -1,11 +1,9 @@
import React, { ReactElement, useEffect, useRef, useState } from 'react';
import { AllInOneMenu, ScrollArea, Switch, Tabs, TabsList, TabsTrigger } from '@ohif/ui-next';
import { useViewportRendering } from '../../hooks/useViewportRendering';
import { useTranslation } from 'react-i18next';
export function Colormap({ viewportId }: { viewportId?: string } = {}): ReactElement {
const { viewportDisplaySets } = useViewportRendering(viewportId);
const { t } = useTranslation('WindowLevelActionMenu');
const [activeDisplaySetUID, setActiveDisplaySetUID] = useState<string | undefined>(
viewportDisplaySets?.[0]?.displaySetInstanceUID
@@ -76,7 +74,7 @@ export function Colormap({ viewportId }: { viewportId?: string } = {}): ReactEle
className="hover:bg-accent flex h-8 w-full flex-shrink-0 cursor-pointer items-center px-2 text-base hover:rounded"
onClick={() => setShowPreview(!showPreview)}
>
<span className="flex-shrink-0">{t('Preview in viewport')}</span>
<span className="flex-shrink-0">Preview in viewport</span>
<Switch
className="ml-auto flex-shrink-0"
checked={showPreview}
@@ -7,7 +7,6 @@ import { WindowLevel } from './WindowLevel';
import { VolumeRenderingPresets } from './VolumeRenderingPresets';
import { VolumeRenderingOptions } from './VolumeRenderingOptions';
import { useViewportRendering } from '../../hooks/useViewportRendering';
import i18n from 'i18next';
export type WindowLevelActionMenuProps = {
viewportId: string;
@@ -59,7 +58,6 @@ export function WindowLevelActionMenuContent({
isVisible={true}
align={align}
side={side}
backLabel={i18n.t('WindowLevelActionMenu:Back to Display Options')}
>
<AllInOneMenu.ItemPanel>
{!is3DVolume && <Colorbar viewportId={viewportId} />}
@@ -67,7 +65,7 @@ export function WindowLevelActionMenuContent({
{colorbarProperties?.colormaps && !is3DVolume && (
<AllInOneMenu.SubMenu
key="colorLUTPresets"
itemLabel={t('Color LUT')}
itemLabel="Color LUT"
itemIcon="icon-color-lut"
className="flex h-[calc(100%-32px)] flex-col"
>
@@ -88,7 +86,7 @@ export function WindowLevelActionMenuContent({
{volumeRenderingPresets && is3DVolume && <VolumeRenderingPresets viewportId={viewportId} />}
{volumeRenderingQualityRange && is3DVolume && (
<AllInOneMenu.SubMenu itemLabel={t('Rendering Options')}>
<AllInOneMenu.SubMenu itemLabel="Rendering Options">
<VolumeRenderingOptions viewportId={viewportId} />
</AllInOneMenu.SubMenu>
)}
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { ImageModal, FooterAction } from '@ohif/ui-next';
import { useTranslation } from 'react-i18next';
const MAX_TEXTURE_SIZE = 10000;
const DEFAULT_FILENAME = 'image';
@@ -37,7 +37,6 @@ function ViewportDownloadFormNew({
const [showWarningMessage, setShowWarningMessage] = useState(true);
const [filename, setFilename] = useState(DEFAULT_FILENAME);
const [fileType, setFileType] = useState('jpg');
const { t } = useTranslation('CaptureViewportModal');
useEffect(() => {
if (!viewportElement) {
@@ -83,7 +82,7 @@ function ViewportDownloadFormNew({
value={filename}
onChange={e => setFilename(e.target.value)}
>
{t('File name')}
File name
</ImageModal.Filename>
<ImageModal.Filetype
selected={fileType}
@@ -110,7 +109,7 @@ function ViewportDownloadFormNew({
maxWidth={MAX_TEXTURE_SIZE.toString()}
maxHeight={MAX_TEXTURE_SIZE.toString()}
>
{t('Image size')} <span className="text-muted-foreground">px</span>
Image size <span className="text-muted-foreground">px</span>
</ImageModal.ImageSize>
<ImageModal.SwitchOption
@@ -118,7 +117,7 @@ function ViewportDownloadFormNew({
checked={showAnnotations}
onCheckedChange={onAnnotationsChange}
>
{t('Include annotations')}
Include annotations
</ImageModal.SwitchOption>
{warningState.enabled && (
<ImageModal.SwitchOption
@@ -126,21 +125,19 @@ function ViewportDownloadFormNew({
checked={showWarningMessage}
onCheckedChange={setShowWarningMessage}
>
{t('Include warning message')}
Include warning message
</ImageModal.SwitchOption>
)}
<FooterAction className="mt-2">
<FooterAction.Right>
<FooterAction.Secondary onClick={onClose}>
{t('Common:Cancel')}
</FooterAction.Secondary>
<FooterAction.Secondary onClick={onClose}>Cancel</FooterAction.Secondary>
<FooterAction.Primary
onClick={() => {
onDownload(filename || DEFAULT_FILENAME, fileType);
onClose();
}}
>
{t('Common:Save')}
Save
</FooterAction.Primary>
</FooterAction.Right>
</FooterAction>
@@ -36,7 +36,6 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
'segmentationToolbox.config': () => {
// Get initial states based on current configuration
const [previewEdits, setPreviewEdits] = useState(false);
const [segmentLabelEnabled, setSegmentLabelEnabled] = useState(false);
const [toggleSegmentEnabled, setToggleSegmentEnabled] = useState(false);
const [useCenterAsSegmentIndex, setUseCenterAsSegmentIndex] = useState(false);
const handlePreviewEditsChange = checked => {
@@ -54,19 +53,14 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
commandsManager.run('toggleUseCenterSegmentIndex', { toggle: checked });
};
const handleSegmentLabelEnabledChange = checked => {
setSegmentLabelEnabled(checked);
commandsManager.run('toggleSegmentLabel', { enabled: checked });
};
return (
<div className="bg-muted flex flex-col gap-2 border-b border-b-[2px] border-black px-2 py-3">
<div className="bg-muted flex flex-col gap-4 border-b border-b-[2px] border-black px-2 py-3">
<div className="flex items-center gap-2">
<Switch
checked={previewEdits}
onCheckedChange={handlePreviewEditsChange}
/>
<span className="text-foreground text-base">Preview edits before creating</span>
<span className="text-base text-white">Preview edits before creating</span>
</div>
<div className="flex items-center gap-2">
@@ -74,7 +68,7 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
checked={useCenterAsSegmentIndex}
onCheckedChange={handleUseCenterAsSegmentIndexChange}
/>
<span className="text-foreground text-base">Use center as segment index</span>
<span className="text-base text-white">Use center as segment index</span>
</div>
<div className="flex items-center gap-2">
@@ -82,15 +76,7 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
checked={toggleSegmentEnabled}
onCheckedChange={handleToggleSegmentEnabledChange}
/>
<span className="text-foreground text-base">Hover on segment border to activate</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={segmentLabelEnabled}
onCheckedChange={handleSegmentLabelEnabledChange}
/>
<span className="text-foreground text-base">Show segment name on hover</span>
<span className="text-base text-white">Hover on segment border to activate</span>
</div>
</div>
);
@@ -1,7 +1,6 @@
import { useState, useEffect } from 'react';
import debounce from 'lodash.debounce';
import { useSystem } from '@ohif/core';
function mapMeasurementToDisplay(measurement, displaySetService) {
const { referenceSeriesUID } = measurement;
@@ -152,27 +152,15 @@ export function useViewportSegmentations({
const representations = segmentationService.getSegmentationRepresentations(viewportId);
// Deduplicate representations by segmentationId to prevent showing
// the same segmentation multiple times in the panel when it has
// multiple representation types (e.g., labelmap and surface)
const uniqueSegmentationMap = new Map();
representations.forEach(representation => {
if (!uniqueSegmentationMap.has(representation.segmentationId)) {
uniqueSegmentationMap.set(representation.segmentationId, representation);
}
const newSegmentationsWithRepresentations = representations.map(representation => {
const segmentation = segmentationService.getSegmentation(representation.segmentationId);
const mappedSegmentation = mapSegmentationToDisplay(segmentation, customizationService);
return {
representation,
segmentation: mappedSegmentation,
};
});
const newSegmentationsWithRepresentations = Array.from(uniqueSegmentationMap.values()).map(
representation => {
const segmentation = segmentationService.getSegmentation(representation.segmentationId);
const mappedSegmentation = mapSegmentationToDisplay(segmentation, customizationService);
return {
representation,
segmentation: mappedSegmentation,
};
}
);
setSegmentationsWithRepresentations({
segmentationsWithRepresentations: newSegmentationsWithRepresentations,
disabled: false,
+2 -2
View File
@@ -1,9 +1,9 @@
import { HYDRATE_SEG_SYNC_GROUP, VOI_SYNC_GROUP } from './mpr';
import i18n from 'i18next';
export const fourUp = {
id: 'fourUp',
locked: true,
name: i18n.t('Hps:3D four up'),
name: '3D four up',
icon: 'layout-advanced-3d-four-up',
isPreset: true,
createdDate: '2023-03-15T10:29:44.894Z',
+3 -3
View File
@@ -1,9 +1,9 @@
import { Types } from '@ohif/core';
import i18n from 'i18next';
const frameView: Types.HangingProtocol.Protocol = {
id: '@ohif/frameView',
description: i18n.t('Hps:Frame view for the active series'),
name: i18n.t('Hps:Frame View'),
description: 'Frame view for the active series',
name: 'Frame View',
icon: 'tool-stack-scroll',
isPreset: true,
toolGroupIds: ['default'],
+2 -2
View File
@@ -1,9 +1,9 @@
import { HYDRATE_SEG_SYNC_GROUP, VOI_SYNC_GROUP } from './mpr';
import i18n from 'i18next';
export const main3D = {
id: 'main3D',
locked: true,
name: i18n.t('Hps:3D main'),
name: '3D main',
icon: 'layout-advanced-3d-main',
isPreset: true,
createdDate: '2023-03-15T10:29:44.894Z',
+2 -2
View File
@@ -1,5 +1,5 @@
import { Types } from '@ohif/core';
import i18n from 'i18next';
export const VOI_SYNC_GROUP = {
type: 'voi',
id: 'mpr',
@@ -22,7 +22,7 @@ export const HYDRATE_SEG_SYNC_GROUP = {
export const mpr: Types.HangingProtocol.Protocol = {
id: 'mpr',
name: i18n.t('Hps:MPR'),
name: 'MPR',
locked: true,
icon: 'layout-advanced-mpr',
isPreset: true,
@@ -1,9 +1,9 @@
import { HYDRATE_SEG_SYNC_GROUP, VOI_SYNC_GROUP } from './mpr';
import i18n from 'i18next';
export const mprAnd3DVolumeViewport = {
id: 'mprAnd3DVolumeViewport',
locked: true,
name: i18n.t('Hps:mpr'),
name: 'mpr',
createdDate: '2023-03-15T10:29:44.894Z',
modifiedDate: '2023-03-15T10:29:44.894Z',
availableTo: {},
+1 -2
View File
@@ -1,10 +1,9 @@
import { HYDRATE_SEG_SYNC_GROUP, VOI_SYNC_GROUP } from './mpr';
import i18n from 'i18next';
export const only3D = {
id: 'only3D',
locked: true,
name: i18n.t('Hps:3D only'),
name: '3D only',
icon: 'layout-advanced-3d-only',
isPreset: true,
createdDate: '2023-03-15T10:29:44.894Z',
+2 -2
View File
@@ -1,9 +1,9 @@
import { HYDRATE_SEG_SYNC_GROUP, VOI_SYNC_GROUP } from './mpr';
import i18n from 'i18next';
export const primary3D = {
id: 'primary3D',
locked: true,
name: i18n.t('Hps:3D primary'),
name: '3D primary',
icon: 'layout-advanced-3d-primary',
isPreset: true,
createdDate: '2023-03-15T10:29:44.894Z',
@@ -1,9 +1,9 @@
import { HYDRATE_SEG_SYNC_GROUP, VOI_SYNC_GROUP } from './mpr';
import i18n from 'i18next';
export const primaryAxial = {
id: 'primaryAxial',
locked: true,
name: i18n.t('Hps:Axial Primary'),
name: 'Axial Primary',
icon: 'layout-advanced-axial-primary',
isPreset: true,
createdDate: '2023-03-15T10:29:44.894Z',
+1 -4
View File
@@ -55,10 +55,6 @@ export default async function init({
extensionManager,
appConfig,
}: withAppTypes): Promise<void> {
// Use a public library path of PUBLIC_URL plus the component name
// This safely separates components that are loaded as-is.
window.PUBLIC_LIB_URL ||= './${component}/';
// Note: this should run first before initializing the cornerstone
// DO NOT CHANGE THE ORDER
@@ -199,6 +195,7 @@ export default async function init({
commandsManager.runCommand('jumpToMeasurementViewport', { measurement, annotationUID, evt });
});
// When a custom image load is performed, update the relevant viewports
hangingProtocolService.subscribe(
hangingProtocolService.EVENTS.CUSTOM_IMAGE_LOAD_PERFORMED,
@@ -450,7 +450,7 @@ const connectMeasurementServiceToTools = ({
if (measurement?.metadata?.referencedImageId) {
imageId = measurement.metadata.referencedImageId;
frameNumber = getSOPInstanceAttributes(measurement.metadata.referencedImageId).frameNumber;
} else if (instance) {
} else {
imageId = dataSource.getImageIdsForInstance({ instance });
}
@@ -78,7 +78,6 @@ export default function PanelMeasurement(props): React.ReactNode {
);
return cloned;
}
// Need to merge defaults on the content props to ensure they get passed to children
return <StudyMeasurements items={displayMeasurements} />;
}
@@ -9,34 +9,30 @@ import {
utilities as csUtils,
metaData,
} from '@cornerstonejs/core';
import { ViewportType } from '@cornerstonejs/core/enums';
import {
Enums as csToolsEnums,
segmentation as cstSegmentation,
Types as cstTypes,
} from '@cornerstonejs/tools';
import { PubSubService, Types as OHIFTypes } from '@ohif/core';
import i18n from '@ohif/i18n';
import { VOLUME_LOADER_SCHEME } from '../../constants';
import { easeInOutBell, easeInOutBellRelative } from '../../utils/transitions';
import { mapROIContoursToRTStructData } from './RTSTRUCT/mapROIContoursToRTStructData';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import { addColorLUT } from '@cornerstonejs/tools/segmentation/addColorLUT';
import { getNextColorLUTIndex } from '@cornerstonejs/tools/segmentation/getNextColorLUTIndex';
import { Segment } from '@cornerstonejs/tools/types/SegmentationStateTypes';
import { ContourStyle, LabelmapStyle, SurfaceStyle } from '@cornerstonejs/tools/types';
import { ViewportType } from '@cornerstonejs/core/enums';
import { SegmentationPresentation, SegmentationPresentationItem } from '../../types/Presentation';
import { EasingFunctionEnum, EasingFunctionMap } from '../../utils/transitions';
import { updateLabelmapSegmentationImageReferences } from '@cornerstonejs/tools/segmentation/updateLabelmapSegmentationImageReferences';
import { triggerSegmentationRepresentationModified } from '@cornerstonejs/tools/segmentation/triggerSegmentationEvents';
import { convertStackToVolumeLabelmap } from '@cornerstonejs/tools/segmentation/helpers/convertStackToVolumeLabelmap';
import { getLabelmapImageIds } from '@cornerstonejs/tools/segmentation';
import { VOLUME_LOADER_SCHEME } from '../../constants';
const {
Labelmap: LABELMAP,
Contour: CONTOUR,
Surface: SURFACE,
} = csToolsEnums.SegmentationRepresentations;
const {
getLabelmapImageIds,
helpers: { convertStackToVolumeLabelmap },
state: { addColorLUT, updateLabelmapSegmentationImageReferences },
triggerSegmentationEvents: { triggerSegmentationRepresentationModified },
} = cstSegmentation;
const LABELMAP = csToolsEnums.SegmentationRepresentations.Labelmap;
const CONTOUR = csToolsEnums.SegmentationRepresentations.Contour;
export type SegmentRepresentation = {
segmentIndex: number;
@@ -153,11 +149,13 @@ class SegmentationService extends PubSubService {
const representations = this.getSegmentationRepresentations(viewportId);
for (const representation of representations) {
const { segmentationId } = representation;
if (!representation) {
continue;
}
const { segmentationId, type } = representation;
const { type } = representation;
segmentationsMap.set(segmentationId, {
segmentationId,
@@ -214,7 +212,7 @@ class SegmentationService extends PubSubService {
viewportId: string,
specifier: {
segmentationId?: string;
type?: csToolsEnums.SegmentationRepresentations;
type?: SegmentationRepresentations;
} = {}
): SegmentationRepresentation[] {
// Get all representations for the viewportId
@@ -247,19 +245,9 @@ class SegmentationService extends PubSubService {
this._onSegmentationDataModifiedFromSource
);
eventTarget.removeEventListener(
csToolsEnums.Events.SEGMENTATION_REPRESENTATION_MODIFIED,
this._onSegmentationRepresentationModifiedFromSource
);
eventTarget.removeEventListener(
csToolsEnums.Events.SEGMENTATION_REPRESENTATION_ADDED,
this._onSegmentationRepresentationModifiedFromSource
);
eventTarget.removeEventListener(
csToolsEnums.Events.SEGMENTATION_REPRESENTATION_REMOVED,
this._onSegmentationRepresentationModifiedFromSource
this._onSegmentationModifiedFromSource
);
eventTarget.removeEventListener(
@@ -295,13 +283,11 @@ class SegmentationService extends PubSubService {
const colorLUTIndex = this._segmentationIdToColorLUTIndexMap.get(segmentationId);
const defaultRepresentationType = csToolsEnums.SegmentationRepresentations.Labelmap;
let representationTypeToUse = type || defaultRepresentationType;
let isConverted = false;
const defaultRepresentationType: csToolsEnums.SegmentationRepresentations =
csViewport.type === ViewportType.VOLUME_3D ? SURFACE : LABELMAP;
let representationTypeToUse = type || defaultRepresentationType;
if (representationTypeToUse === LABELMAP) {
if (type === csToolsEnums.SegmentationRepresentations.Labelmap) {
const { isVolumeViewport, isVolumeSegmentation } = this.determineViewportAndSegmentationType(
csViewport,
segmentation
@@ -368,7 +354,7 @@ class SegmentationService extends PubSubService {
const derivedImages = await imageLoader.createAndCacheDerivedLabelmapImages(referenceImageIds);
const segs = this.getSegmentations();
const label = options?.label || `Segmentation ${segs.length + 1}`;
const label = options.label || `Segmentation ${segs.length + 1}`;
const segImageIds = derivedImages.map(image => image.imageId);
@@ -385,7 +371,7 @@ class SegmentationService extends PubSubService {
config: {
label,
segments:
options?.segments && Object.keys(options.segments).length > 0
options.segments && Object.keys(options.segments).length > 0
? options.segments
: {
1: {
@@ -407,7 +393,7 @@ class SegmentationService extends PubSubService {
segDisplaySet,
options: {
segmentationId?: string;
type: csToolsEnums.SegmentationRepresentations;
type: SegmentationRepresentations;
} = {
type: LABELMAP,
}
@@ -517,7 +503,9 @@ class SegmentationService extends PubSubService {
};
});
const colorLUTIndex = addColorLUT(colorLUT);
// get next color lut index
const colorLUTIndex = getNextColorLUTIndex();
addColorLUT(colorLUT, colorLUTIndex);
this._segmentationIdToColorLUTIndexMap.set(segmentationId, colorLUTIndex);
this._broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, {
@@ -552,7 +540,7 @@ class SegmentationService extends PubSubService {
rtDisplaySet,
options: {
segmentationId?: string;
type: csToolsEnums.SegmentationRepresentations;
type: SegmentationRepresentations;
} = {
type: CONTOUR,
}
@@ -586,19 +574,10 @@ class SegmentationService extends PubSubService {
// find the first image id that contains a referenced SOP instance UID
const firstSegmentedSliceImageId =
referencedImageIds?.find(imageId =>
referencedImageIdsWithGeometry.some(referencedId =>
imageId.includes(referencedId as string)
)
referencedImageIdsWithGeometry.some(referencedId => imageId.includes(referencedId))
) || null;
rtDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId;
if (!structureSet.ROIContours?.length) {
throw new Error(
'The structureSet does not contain any ROIContours. Please ensure the structureSet is loaded first.'
);
}
// Map ROI contours to RT Struct Data
const allRTStructData = mapROIContoursToRTStructData(structureSet, rtDisplaySetUID);
@@ -621,6 +600,12 @@ class SegmentationService extends PubSubService {
},
};
if (!structureSet.ROIContours?.length) {
throw new Error(
'The structureSet does not contain any ROIContours. Please ensure the structureSet is loaded first.'
);
}
const segments: { [segmentIndex: string]: cstTypes.Segment } = {};
let segmentsCachedStats = {};
@@ -628,9 +613,7 @@ class SegmentationService extends PubSubService {
const colorLUT = [[0, 0, 0, 0]]; // First entry is transparent for index 0
// Process each segment similarly to the SEG function
for (let i = 0; i < allRTStructData.length; i++) {
const rtStructData = allRTStructData[i];
for (const rtStructData of allRTStructData) {
const { data, id, color, segmentIndex, geometryId, group } = rtStructData;
// Add the color to the colorLUT array
@@ -679,7 +662,8 @@ class SegmentationService extends PubSubService {
}
// Create and register the colorLUT
const colorLUTIndex = addColorLUT(colorLUT);
const colorLUTIndex = getNextColorLUTIndex();
addColorLUT(colorLUT, colorLUTIndex);
this._segmentationIdToColorLUTIndexMap.set(segmentationId, colorLUTIndex);
// Assign processed segments to segmentation config
@@ -776,7 +760,7 @@ class SegmentationService extends PubSubService {
public hasCustomStyles(specifier: {
viewportId: string;
segmentationId: string;
type: csToolsEnums.SegmentationRepresentations;
type: SegmentationRepresentations;
}): boolean {
return cstSegmentation.config.style.hasCustomStyle(specifier);
}
@@ -784,7 +768,7 @@ class SegmentationService extends PubSubService {
public getStyle = (specifier: {
viewportId: string;
segmentationId: string;
type: csToolsEnums.SegmentationRepresentations;
type: SegmentationRepresentations;
segmentIndex?: number;
}) => {
const style = cstSegmentation.config.style.getStyle(specifier);
@@ -794,12 +778,12 @@ class SegmentationService extends PubSubService {
public setStyle = (
specifier: {
type: csToolsEnums.SegmentationRepresentations;
type: SegmentationRepresentations;
viewportId?: string;
segmentationId?: string;
segmentIndex?: number;
},
style: cstTypes.LabelmapStyle | cstTypes.ContourStyle | cstTypes.SurfaceStyle
style: LabelmapStyle | ContourStyle | SurfaceStyle
) => {
cstSegmentation.config.style.setStyle(specifier, style);
};
@@ -917,7 +901,7 @@ class SegmentationService extends PubSubService {
segmentationId: string,
segmentIndex: number,
isVisible: boolean,
type?: csToolsEnums.SegmentationRepresentations
type?: SegmentationRepresentations
): void {
this._setSegmentVisibility(viewportId, segmentationId, segmentIndex, isVisible, type);
}
@@ -954,7 +938,7 @@ class SegmentationService extends PubSubService {
viewportId: string,
segmentationId: string,
segmentIndex: number,
type: csToolsEnums.SegmentationRepresentations
type: SegmentationRepresentations
): void {
const isVisible = cstSegmentation.config.visibility.getSegmentIndexVisibility(
viewportId,
@@ -985,12 +969,6 @@ class SegmentationService extends PubSubService {
segmentIndex: number,
color: csTypes.Color
): void {
const segmentationRepresentations = this.getSegmentationRepresentations(viewportId, {
segmentationId,
});
const { colorLUTIndex } = segmentationRepresentations[0];
this._segmentationIdToColorLUTIndexMap.set(segmentationId, colorLUTIndex);
cstSegmentation.config.color.setSegmentIndexColor(
viewportId,
segmentationId,
@@ -1032,7 +1010,7 @@ class SegmentationService extends PubSubService {
public getLabelmapVolume(segmentationId: string) {
const csSegmentation = cstSegmentation.state.getSegmentation(segmentationId);
const labelmapData = csSegmentation.representationData[
LABELMAP
SegmentationRepresentations.Labelmap
] as cstTypes.LabelmapToolOperationDataVolume;
if (!labelmapData || !labelmapData.volumeId) {
@@ -1134,10 +1112,7 @@ class SegmentationService extends PubSubService {
*/
public toggleSegmentationRepresentationVisibility = (
viewportId: string,
{
segmentationId,
type,
}: { segmentationId: string; type: csToolsEnums.SegmentationRepresentations }
{ segmentationId, type }: { segmentationId: string; type: SegmentationRepresentations }
): void => {
this._toggleSegmentationRepresentationVisibility(viewportId, segmentationId, type);
};
@@ -1186,7 +1161,7 @@ class SegmentationService extends PubSubService {
viewportId: string,
specifier: {
segmentationId?: string;
type?: csToolsEnums.SegmentationRepresentations;
type?: SegmentationRepresentations;
} = {}
): void {
cstSegmentation.removeSegmentationRepresentations(viewportId, specifier);
@@ -1200,7 +1175,7 @@ class SegmentationService extends PubSubService {
highlightSegment = true,
animationLength = 750,
highlightHideOthers = false,
animationFunctionType: EasingFunctionEnum = EasingFunctionEnum.EASE_IN_OUT
highlightFunctionType = 'ease-in-out' // todo: make animation functions configurable from outside
): void {
const center = this._getSegmentCenter(segmentationId, segmentIndex);
if (!center) {
@@ -1226,8 +1201,7 @@ class SegmentationService extends PubSubService {
viewportId,
highlightAlpha,
animationLength,
highlightHideOthers,
animationFunctionType
highlightHideOthers
);
});
}
@@ -1239,7 +1213,7 @@ class SegmentationService extends PubSubService {
alpha = 0.9,
animationLength = 750,
hideOthers = true,
animationFunctionType: EasingFunctionEnum = EasingFunctionEnum.EASE_IN_OUT
highlightFunctionType = 'ease-in-out'
): void {
if (this.highlightIntervalId) {
clearInterval(this.highlightIntervalId);
@@ -1272,8 +1246,7 @@ class SegmentationService extends PubSubService {
segments,
viewportId,
animationLength,
representation,
animationFunctionType
representation
);
});
}
@@ -1298,7 +1271,7 @@ class SegmentationService extends PubSubService {
private _setSegmentationRepresentationVisibility(
viewportId: string,
segmentationId: string,
type: csToolsEnums.SegmentationRepresentations,
type: SegmentationRepresentations,
isVisible: boolean
): void {
const representations = this.getSegmentationRepresentations(viewportId, {
@@ -1358,17 +1331,14 @@ class SegmentationService extends PubSubService {
private async handleVolumeViewportCase(csViewport, segmentation, isVolumeSegmentation) {
if (csViewport.type === ViewportType.VOLUME_3D) {
return {
representationTypeToUse: SURFACE,
isConverted: false,
};
return { representationTypeToUse: SegmentationRepresentations.Surface, isConverted: false };
} else {
await this.handleVolumeViewport(
csViewport as csTypes.IVolumeViewport,
segmentation,
isVolumeSegmentation
);
return { representationTypeToUse: LABELMAP, isConverted: false };
return { representationTypeToUse: SegmentationRepresentations.Labelmap, isConverted: false };
}
}
@@ -1378,17 +1348,14 @@ class SegmentationService extends PubSubService {
isVolumeSegmentation: boolean,
viewportId: string,
segmentationId: string
): Promise<{
representationTypeToUse: csToolsEnums.SegmentationRepresentations;
isConverted: boolean;
}> {
): Promise<{ representationTypeToUse: SegmentationRepresentations; isConverted: boolean }> {
if (isVolumeSegmentation) {
const isConverted = await this.convertStackToVolumeViewport(csViewport);
return { representationTypeToUse: LABELMAP, isConverted };
return { representationTypeToUse: SegmentationRepresentations.Labelmap, isConverted };
}
if (updateLabelmapSegmentationImageReferences(viewportId, segmentationId)) {
return { representationTypeToUse: LABELMAP, isConverted: false };
return { representationTypeToUse: SegmentationRepresentations.Labelmap, isConverted: false };
}
const isConverted = await this.attemptStackToVolumeConversion(
@@ -1398,7 +1365,7 @@ class SegmentationService extends PubSubService {
segmentationId
);
return { representationTypeToUse: LABELMAP, isConverted };
return { representationTypeToUse: SegmentationRepresentations.Labelmap, isConverted };
}
private async _addSegmentationRepresentation(
@@ -1509,7 +1476,11 @@ class SegmentationService extends PubSubService {
segImage.FrameOfReferenceUID === frameOfReferenceUID
) {
const isConverted = await this.convertStackToVolumeViewport(viewport);
triggerSegmentationRepresentationModified(viewportId, segmentationId, LABELMAP);
triggerSegmentationRepresentationModified(
viewportId,
segmentationId,
SegmentationRepresentations.Labelmap
);
return isConverted;
}
@@ -1637,11 +1608,10 @@ class SegmentationService extends PubSubService {
segmentIndex: number,
alpha: number,
hideOthers: boolean,
segments: cstTypes.Segment[],
segments: Segment[],
viewportId: string,
animationLength: number,
representation: cstTypes.SegmentationRepresentation,
animationFunctionType: EasingFunctionEnum
representation: cstTypes.SegmentationRepresentation
) {
const { segmentationId } = representation;
const newSegmentSpecificConfig = {
@@ -1675,8 +1645,6 @@ class SegmentationService extends PubSubService {
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / animationLength, 1);
const easingFunction = EasingFunctionMap.get(animationFunctionType);
cstSegmentation.config.style.setStyle(
{
segmentationId,
@@ -1684,7 +1652,7 @@ class SegmentationService extends PubSubService {
type: LABELMAP,
},
{
fillAlpha: easingFunction(progress, fillAlpha),
fillAlpha: easeInOutBell(progress, fillAlpha),
}
);
@@ -1709,20 +1677,21 @@ class SegmentationService extends PubSubService {
segmentIndex: number,
alpha: number,
hideOthers: boolean,
segments: cstTypes.Segment[],
segments: Segment[],
viewportId: string,
animationLength: number,
representation: cstTypes.SegmentationRepresentation,
animationFunctionType: EasingFunctionEnum
representation: cstTypes.SegmentationRepresentation
) {
const { segmentationId } = representation;
const startTime = performance.now();
const prevStyle = cstSegmentation.config.style.getStyle({
type: CONTOUR,
}) as cstTypes.ContourStyle;
}) as ContourStyle;
const prevOutlineWidth = prevStyle.outlineWidth;
// make this configurable
const baseline = Math.max(prevOutlineWidth * 3.5, 5);
const animate = (currentTime: number) => {
const progress = (currentTime - startTime) / animationLength;
@@ -1731,8 +1700,7 @@ class SegmentationService extends PubSubService {
return;
}
const OUTLINE_ANIMATION_SCALE_FACTOR = 5;
const easingFunction = EasingFunctionMap.get(animationFunctionType);
const reversedProgress = easeInOutBellRelative(progress, baseline, prevOutlineWidth);
cstSegmentation.config.style.setStyle(
{
@@ -1741,7 +1709,7 @@ class SegmentationService extends PubSubService {
type: CONTOUR,
},
{
outlineWidth: easingFunction(progress, prevOutlineWidth, OUTLINE_ANIMATION_SCALE_FACTOR),
outlineWidth: reversedProgress,
}
);
@@ -1754,18 +1722,24 @@ class SegmentationService extends PubSubService {
private _toggleSegmentationRepresentationVisibility = (
viewportId: string,
segmentationId: string,
type: csToolsEnums.SegmentationRepresentations
type: SegmentationRepresentations
): void => {
const segmentsHidden = cstSegmentation.config.visibility.getHiddenSegmentIndices(viewportId, {
const representations = this.getSegmentationRepresentations(viewportId, {
segmentationId,
type,
});
const representation = representations[0];
const segmentsHidden = cstSegmentation.config.visibility.getHiddenSegmentIndices(viewportId, {
segmentationId,
type: representation.type,
});
const currentVisibility = segmentsHidden.size === 0;
this._setSegmentationRepresentationVisibility(
viewportId,
segmentationId,
type,
representation.type,
!currentVisibility
);
};
@@ -1780,10 +1754,7 @@ class SegmentationService extends PubSubService {
return `${volumeLoaderSchema}:${displaySet.displaySetInstanceUID}`;
}
private _getSegmentCenter(
segmentationId: string,
segmentIndex: number
): { image?: csTypes.Point3; world: csTypes.Point3 } | undefined {
private _getSegmentCenter(segmentationId, segmentIndex) {
const segmentation = this.getSegmentation(segmentationId);
if (!segmentation) {
@@ -1794,17 +1765,19 @@ class SegmentationService extends PubSubService {
const { cachedStats } = segments[segmentIndex];
if (cachedStats?.center) {
const { center } = cachedStats;
return center as { image: csTypes.Point3; world: csTypes.Point3 };
if (!cachedStats || !cachedStats?.center || !cachedStats?.namedStats?.center) {
return;
}
if (cachedStats?.namedStats?.center) {
const { center } = cachedStats;
if (!center) {
return {
world: cachedStats.namedStats.center.value,
};
}
return center;
}
private _setSegmentLockedStatus(segmentationId: string, segmentIndex: number, isLocked: boolean) {
@@ -1816,7 +1789,7 @@ class SegmentationService extends PubSubService {
segmentationId: string,
segmentIndex: number,
isVisible: boolean,
type?: csToolsEnums.SegmentationRepresentations
type?: SegmentationRepresentations
) {
cstSegmentation.config.visibility.setSegmentIndexVisibility(
viewportId,
@@ -65,11 +65,13 @@ const segmentationRepresentationModifiedCallback = async (
return;
}
// Ensure the segmentation representation aligns with the target viewport type.
const type: Enums.SegmentationRepresentations =
viewport.type === CoreEnums.ViewportType.VOLUME_3D
? Enums.SegmentationRepresentations.Surface
: Enums.SegmentationRepresentations.Labelmap;
// whatever type the source viewport has, we need to add that to the target viewport
const sourceViewportRepresentation = segmentationService.getSegmentationRepresentations(
sourceViewport.viewportId,
{ segmentationId }
);
const type = sourceViewportRepresentation[0].type;
await segmentationService.addSegmentationRepresentation(targetViewportId, {
segmentationId,
@@ -417,11 +417,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
// and we would lose the presentation.
this.storePresentation({ viewportId: viewportInfo.getViewportId() });
// Todo: i don't like this here, move it
this.servicesManager.services.segmentationService.clearSegmentationRepresentations(
viewportInfo.getViewportId()
);
if (!viewportInfo) {
throw new Error('element is not enabled for the given viewportId');
}
@@ -1335,18 +1330,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
segmentationPresentation.forEach((presentationItem: SegmentationPresentationItem) => {
const { segmentationId, type, hydrated } = presentationItem;
const { Labelmap, Surface } = csToolsEnums.SegmentationRepresentations;
const isVolume3D = viewport.type === csEnums.ViewportType.VOLUME_3D;
// Determine the appropriate segmentation representation for the viewport.
// If the current type is Surface but the viewport is not 3D, fallback to Labelmap.
// Otherwise, use the existing type.
const representationType = type === Surface && !isVolume3D ? Labelmap : type;
if (hydrated) {
segmentationService.addSegmentationRepresentation(viewport.id, {
segmentationId,
type: representationType,
type,
});
}
});
@@ -1,125 +0,0 @@
import { findNearbyToolData } from './findNearbyToolData';
describe('findNearbyToolData', () => {
const mockCommandsManager = {
runCommand: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should return undefined when event is null', () => {
const result = findNearbyToolData(mockCommandsManager, null);
expect(result).toBeUndefined();
expect(mockCommandsManager.runCommand).not.toHaveBeenCalled();
});
it('should return undefined when event is undefined', () => {
const result = findNearbyToolData(mockCommandsManager, undefined);
expect(result).toBeUndefined();
expect(mockCommandsManager.runCommand).not.toHaveBeenCalled();
});
it('should return undefined when event has no detail', () => {
const event = {};
const result = findNearbyToolData(mockCommandsManager, event);
expect(result).toBeUndefined();
expect(mockCommandsManager.runCommand).not.toHaveBeenCalled();
});
it('should return undefined when event detail is null', () => {
const event = { detail: null };
const result = findNearbyToolData(mockCommandsManager, event);
expect(result).toBeUndefined();
expect(mockCommandsManager.runCommand).not.toHaveBeenCalled();
});
it('should call runCommand with correct parameters when event has valid detail', () => {
const mockElement = document.createElement('div');
const mockCurrentPoints = { canvas: { x: 100, y: 200 } };
const mockToolData = { id: 'annotation-1' };
const event = {
detail: {
element: mockElement,
currentPoints: mockCurrentPoints,
},
};
mockCommandsManager.runCommand.mockReturnValue(mockToolData);
const result = findNearbyToolData(mockCommandsManager, event);
expect(mockCommandsManager.runCommand).toHaveBeenCalledWith(
'getNearbyAnnotation',
{
element: mockElement,
canvasCoordinates: mockCurrentPoints.canvas,
},
'CORNERSTONE'
);
expect(result).toBe(mockToolData);
});
it('should handle event with element but no currentPoints', () => {
const mockElement = document.createElement('div');
const event = {
detail: {
element: mockElement,
},
};
findNearbyToolData(mockCommandsManager, event);
expect(mockCommandsManager.runCommand).toHaveBeenCalledWith(
'getNearbyAnnotation',
{
element: mockElement,
canvasCoordinates: undefined,
},
'CORNERSTONE'
);
});
it('should handle event with currentPoints but no canvas coordinates', () => {
const mockElement = document.createElement('div');
const mockCurrentPoints = {};
const event = {
detail: {
element: mockElement,
currentPoints: mockCurrentPoints,
},
};
findNearbyToolData(mockCommandsManager, event);
expect(mockCommandsManager.runCommand).toHaveBeenCalledWith(
'getNearbyAnnotation',
{
element: mockElement,
canvasCoordinates: undefined,
},
'CORNERSTONE'
);
});
it('should return result from runCommand', () => {
const mockToolData = { id: 'test-annotation', data: 'test-data' };
const event = {
detail: {
element: document.createElement('div'),
currentPoints: { canvas: { x: 50, y: 75 } },
},
};
mockCommandsManager.runCommand.mockReturnValue(mockToolData);
const result = findNearbyToolData(mockCommandsManager, event);
expect(result).toBe(mockToolData);
});
});
@@ -1,319 +0,0 @@
import { generateSegmentationCSVReport } from './generateSegmentationCSVReport';
// Mock DOM APIs
Object.defineProperty(global, 'Blob', {
writable: true,
value: jest.fn().mockImplementation((content, options) => ({
content,
options,
})),
});
Object.defineProperty(global, 'URL', {
writable: true,
value: {
createObjectURL: jest.fn().mockReturnValue('mock-url'),
},
});
const mockDocument = {
addEventListener: jest.fn(),
createElement: jest.fn().mockReturnValue({
setAttribute: jest.fn(),
click: jest.fn(),
style: {},
}),
body: {
appendChild: jest.fn(),
removeChild: jest.fn(),
},
};
Object.defineProperty(global, 'document', {
writable: true,
value: mockDocument,
});
describe('generateSegmentationCSVReport', () => {
const mockInfo = {
reference: {
SeriesNumber: '1',
SeriesInstanceUID: 'series-uid-123',
StudyInstanceUID: 'study-uid-456',
SeriesDate: '20231201',
SeriesTime: '120000',
SeriesDescription: 'Test Series',
},
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should generate CSV with basic segmentation data', () => {
const segmentationData = {
segmentationId: 'seg-123',
label: 'Test Segmentation',
};
generateSegmentationCSVReport(segmentationData, mockInfo);
expect(global.Blob).toHaveBeenCalledWith([expect.stringContaining('Segmentation ID,seg-123')], {
type: 'text/csv;charset=utf-8;',
});
});
it('should handle segmentation data without id and label', () => {
const segmentationData = {};
generateSegmentationCSVReport(segmentationData, mockInfo);
expect(global.Blob).toHaveBeenCalledWith(
[expect.stringContaining('Segmentation ID,\nSegmentation Label,')],
{ type: 'text/csv;charset=utf-8;' }
);
});
it('should include reference information when provided', () => {
const segmentationData = {
segmentationId: 'seg-123',
label: 'Test Segmentation',
};
generateSegmentationCSVReport(segmentationData, mockInfo);
expect(global.Blob).toHaveBeenCalledWith(
[expect.stringContaining('reference Series Number,1')],
{ type: 'text/csv;charset=utf-8;' }
);
});
it('should skip empty reference values', () => {
const segmentationData = {
segmentationId: 'seg-123',
label: 'Test Segmentation',
};
const infoWithEmptyValues = {
reference: {
SeriesNumber: '1',
SeriesInstanceUID: '',
StudyInstanceUID: null,
SeriesDate: undefined,
SeriesTime: '120000',
SeriesDescription: 'Test Series',
},
};
generateSegmentationCSVReport(segmentationData, infoWithEmptyValues);
const csvContent = (global.Blob as jest.Mock).mock.calls[0][0][0];
expect(csvContent).toContain('reference Series Number,1');
expect(csvContent).toContain('reference Series Time,120000');
expect(csvContent).not.toContain('reference Series Instance UID');
expect(csvContent).not.toContain('reference Study Instance UID');
expect(csvContent).not.toContain('reference Series Date');
});
it('should handle segments with basic properties', () => {
const segmentationData = {
segmentationId: 'seg-123',
label: 'Test Segmentation',
segments: {
'1': {
segmentIndex: 1,
label: 'Segment 1',
locked: true,
active: false,
},
'2': {
segmentIndex: 2,
label: 'Segment 2',
locked: false,
active: true,
},
},
};
generateSegmentationCSVReport(segmentationData, mockInfo);
const csvContent = (global.Blob as jest.Mock).mock.calls[0][0][0];
expect(csvContent).toContain('Label,Segment 1,Segment 2');
expect(csvContent).toContain('Segment Index,1,2');
expect(csvContent).toContain('Locked,Yes,No');
expect(csvContent).toContain('Active,No,Yes');
});
it('should handle segments with statistics', () => {
const segmentationData = {
segmentationId: 'seg-123',
label: 'Test Segmentation',
segments: {
'1': {
segmentIndex: 1,
label: 'Segment 1',
locked: false,
active: true,
cachedStats: {
namedStats: {
mean: {
name: 'mean',
label: 'Mean',
value: 100.5,
unit: 'HU',
},
volume: {
name: 'volume',
label: 'Volume',
value: 250.75,
unit: 'mm³',
},
},
},
},
},
};
generateSegmentationCSVReport(segmentationData, mockInfo);
const csvContent = (global.Blob as jest.Mock).mock.calls[0][0][0];
expect(csvContent).toContain('Mean (HU),100.5');
expect(csvContent).toContain('Volume (mm³),250.75');
});
it('should handle segments with statistics without units', () => {
const segmentationData = {
segmentationId: 'seg-123',
segments: {
'1': {
segmentIndex: 1,
label: 'Segment 1',
cachedStats: {
namedStats: {
count: {
name: 'count',
label: 'Count',
value: 42,
},
},
},
},
},
};
generateSegmentationCSVReport(segmentationData, mockInfo);
const csvContent = (global.Blob as jest.Mock).mock.calls[0][0][0];
expect(csvContent).toContain('Count,42');
});
it('should handle segments without cachedStats', () => {
const segmentationData = {
segmentationId: 'seg-123',
segments: {
'1': {
segmentIndex: 1,
label: 'Segment 1',
locked: false,
active: true,
},
},
};
generateSegmentationCSVReport(segmentationData, mockInfo);
expect(global.Blob).toHaveBeenCalled();
});
it('should handle segments with empty namedStats', () => {
const segmentationData = {
segmentationId: 'seg-123',
segments: {
'1': {
segmentIndex: 1,
label: 'Segment 1',
cachedStats: {
namedStats: {},
},
},
},
};
generateSegmentationCSVReport(segmentationData, mockInfo);
expect(global.Blob).toHaveBeenCalled();
});
it('should escape CSV special characters', () => {
const segmentationData = {
segmentationId: 'seg,with,commas',
label: 'Test "quoted" label',
segments: {
'1': {
segmentIndex: 1,
label: 'Segment\nwith\nnewlines',
cachedStats: {
namedStats: {
test: {
name: 'test',
label: 'Test,Value',
value: 'value"with"quotes',
},
},
},
},
},
};
generateSegmentationCSVReport(segmentationData, mockInfo);
const csvContent = (global.Blob as jest.Mock).mock.calls[0][0][0];
expect(csvContent).toContain('"seg,with,commas"');
expect(csvContent).toContain('"Test ""quoted"" label"');
expect(csvContent).toContain('"Segment\nwith\nnewlines"');
});
it('should create download link with correct attributes', () => {
const mockLink = {
setAttribute: jest.fn(),
click: jest.fn(),
style: {},
};
mockDocument.createElement.mockReturnValue(mockLink);
const segmentationData = {
segmentationId: 'seg-123',
label: 'Test Segmentation',
};
generateSegmentationCSVReport(segmentationData, mockInfo);
expect(mockLink.setAttribute).toHaveBeenCalledWith('href', 'mock-url');
expect(mockLink.setAttribute).toHaveBeenCalledWith(
'download',
expect.stringMatching(/Test Segmentation_Report_\d{4}-\d{2}-\d{2}\.csv/)
);
expect(mockLink.click).toHaveBeenCalled();
expect(mockDocument.body.appendChild).toHaveBeenCalledWith(mockLink);
expect(mockDocument.body.removeChild).toHaveBeenCalledWith(mockLink);
});
it('should use default filename when label is missing', () => {
const mockLink = {
setAttribute: jest.fn(),
click: jest.fn(),
style: {},
};
mockDocument.createElement.mockReturnValue(mockLink);
const segmentationData = {
segmentationId: 'seg-123',
};
generateSegmentationCSVReport(segmentationData, mockInfo);
expect(mockLink.setAttribute).toHaveBeenCalledWith(
'download',
expect.stringMatching(/Segmentation_Report_\d{4}-\d{2}-\d{2}\.csv/)
);
});
});
@@ -1,70 +0,0 @@
import { getViewportEnabledElement } from './getViewportEnabledElement';
import getActiveViewportEnabledElement from './getActiveViewportEnabledElement';
jest.mock('./getViewportEnabledElement', () => ({
getViewportEnabledElement: jest.fn(),
}));
describe('getActiveViewportEnabledElement', () => {
const mockViewportGridService = {
getState: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should return enabled element for active viewport', () => {
const mockEnabledElement = { viewport: 'test-viewport' };
mockViewportGridService.getState.mockReturnValue({ activeViewportId: 'viewport-1' });
(getViewportEnabledElement as jest.Mock).mockReturnValue(mockEnabledElement);
const result = getActiveViewportEnabledElement(mockViewportGridService);
expect(mockViewportGridService.getState).toHaveBeenCalledTimes(1);
expect(getViewportEnabledElement).toHaveBeenCalledWith('viewport-1');
expect(result).toBe(mockEnabledElement);
});
it('should handle null activeViewportId', () => {
const mockEnabledElement = null;
mockViewportGridService.getState.mockReturnValue({ activeViewportId: null });
(getViewportEnabledElement as jest.Mock).mockReturnValue(mockEnabledElement);
const result = getActiveViewportEnabledElement(mockViewportGridService);
expect(getViewportEnabledElement).toHaveBeenCalledWith(null);
expect(result).toBe(null);
});
it('should handle undefined activeViewportId', () => {
const mockEnabledElement = undefined;
mockViewportGridService.getState.mockReturnValue({ activeViewportId: undefined });
(getViewportEnabledElement as jest.Mock).mockReturnValue(mockEnabledElement);
const result = getActiveViewportEnabledElement(mockViewportGridService);
expect(getViewportEnabledElement).toHaveBeenCalledWith(undefined);
expect(result).toBe(undefined);
});
it('should handle empty state object', () => {
const mockEnabledElement = undefined;
mockViewportGridService.getState.mockReturnValue({});
(getViewportEnabledElement as jest.Mock).mockReturnValue(mockEnabledElement);
const result = getActiveViewportEnabledElement(mockViewportGridService);
expect(getViewportEnabledElement).toHaveBeenCalledWith(undefined);
expect(result).toBe(undefined);
});
it('should handle getViewportEnabledElement returning null', () => {
mockViewportGridService.getState.mockReturnValue({ activeViewportId: 'viewport-1' });
(getViewportEnabledElement as jest.Mock).mockReturnValue(null);
const result = getActiveViewportEnabledElement(mockViewportGridService);
expect(result).toBe(null);
});
});
@@ -1,313 +0,0 @@
import { getCenterExtent } from './getCenterExtent';
describe('getCenterExtent', () => {
it('should return default values when points is undefined', () => {
const measurement = {};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [0, 0, 0],
extent: {
min: [0, 0, 0],
max: [0, 0, 0],
},
});
});
it('should return default values when points is null', () => {
const measurement = { points: null };
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [0, 0, 0],
extent: {
min: [0, 0, 0],
max: [0, 0, 0],
},
});
});
it('should return default values when points is not an array', () => {
const measurement = { points: 'invalid' };
// @ts-expect-error - purposely passing an invalid type
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [0, 0, 0],
extent: {
min: [0, 0, 0],
max: [0, 0, 0],
},
});
});
it('should return default values when points array is empty', () => {
const measurement = { points: [] };
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [0, 0, 0],
extent: {
min: [0, 0, 0],
max: [0, 0, 0],
},
});
});
it('should handle single point correctly', () => {
const measurement = {
points: [[5, 10, 15]],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [5, 10, 15],
extent: {
min: [5, 10, 15],
max: [5, 10, 15],
},
});
});
it('should calculate center and extent for two points', () => {
const measurement = {
points: [
[0, 0, 0],
[10, 20, 30],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [5, 10, 15],
extent: {
min: [0, 0, 0],
max: [10, 20, 30],
},
});
});
it('should calculate center and extent for multiple points', () => {
const measurement = {
points: [
[0, 0, 0],
[10, 20, 30],
[5, 5, 5],
[-5, 15, 25],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [2.5, 10, 15],
extent: {
min: [-5, 0, 0],
max: [10, 20, 30],
},
});
});
it('should handle negative coordinates', () => {
const measurement = {
points: [
[-10, -20, -30],
[-5, -15, -25],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [-7.5, -17.5, -27.5],
extent: {
min: [-10, -20, -30],
max: [-5, -15, -25],
},
});
});
it('should handle mixed positive and negative coordinates', () => {
const measurement = {
points: [
[-10, -5, 0],
[10, 5, 20],
[0, 0, -10],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [0, 0, 5],
extent: {
min: [-10, -5, -10],
max: [10, 5, 20],
},
});
});
it('should handle floating point coordinates', () => {
const measurement = {
points: [
[1.5, 2.3, 3.1],
[4.2, 8.9, 6.3],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [2.85, 5.6, 4.7],
extent: {
min: [1.5, 2.3, 3.1],
max: [4.2, 8.9, 6.3],
},
});
});
it('should handle points with same coordinates', () => {
const measurement = {
points: [
[5, 5, 5],
[5, 5, 5],
[5, 5, 5],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [5, 5, 5],
extent: {
min: [5, 5, 5],
max: [5, 5, 5],
},
});
});
it('should handle zero coordinates', () => {
const measurement = {
points: [
[0, 0, 0],
[0, 0, 0],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [0, 0, 0],
extent: {
min: [0, 0, 0],
max: [0, 0, 0],
},
});
});
it('should handle large coordinate values', () => {
const measurement = {
points: [
[1000000, 2000000, 3000000],
[1000001, 2000001, 3000001],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [1000000.5, 2000000.5, 3000000.5],
extent: {
min: [1000000, 2000000, 3000000],
max: [1000001, 2000001, 3000001],
},
});
});
it('should handle points in different order', () => {
const measurement = {
points: [
[10, 20, 30],
[0, 0, 0],
[5, 10, 15],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [5, 10, 15],
extent: {
min: [0, 0, 0],
max: [10, 20, 30],
},
});
});
it('should handle points where min and max are not the first point', () => {
const measurement = {
points: [
[5, 5, 5],
[0, 0, 0],
[10, 10, 10],
[3, 3, 3],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [5, 5, 5],
extent: {
min: [0, 0, 0],
max: [10, 10, 10],
},
});
});
it('should handle asymmetric extent distribution', () => {
const measurement = {
points: [
[0, 0, 0],
[100, 10, 1],
[50, 50, 50],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [50, 25, 25],
extent: {
min: [0, 0, 0],
max: [100, 50, 50],
},
});
});
it('should handle points with different ranges per dimension', () => {
const measurement = {
points: [
[0, 100, 1000],
[1, 101, 1001],
[0.5, 100.5, 1000.5],
],
};
const result = getCenterExtent(measurement);
expect(result).toEqual({
center: [0.5, 100.5, 1000.5],
extent: {
min: [0, 100, 1000],
max: [1, 101, 1001],
},
});
});
});
@@ -1,46 +0,0 @@
/**
* Calculates the center point and bounding box extent of a measurement based on its points.
* @param {Object} measurement - The measurement object containing points
* @param {Array<Array<number>>} measurement.points - Array of 3D points [x, y, z]
* @returns {Object} Object containing center and extent
* @returns {Array<number>} returns.center - The center point [x, y, z]
* @returns {Object} returns.extent - The bounding box extent with min and max points
*/
export const getCenterExtent = (measurement: { points?: number[][] }) => {
const { points } = measurement;
if (!points || !Array.isArray(points) || points.length === 0) {
// Return default values if no points are available
const defaultCenter: [number, number, number] = [0, 0, 0];
const defaultExtent = {
min: [0, 0, 0] as [number, number, number],
max: [0, 0, 0] as [number, number, number],
};
return { center: defaultCenter, extent: defaultExtent };
}
// Initialize min and max with the first point
const min: [number, number, number] = [...points[0]] as [number, number, number];
const max: [number, number, number] = [...points[0]] as [number, number, number];
// Find the bounding box by iterating through all points
for (let i = 1; i < points.length; i++) {
const point = points[i];
for (let j = 0; j < 3; j++) {
min[j] = Math.min(min[j], point[j]);
max[j] = Math.max(max[j], point[j]);
}
}
// Calculate the center point
const center: [number, number, number] = [
(min[0] + max[0]) / 2,
(min[1] + max[1]) / 2,
(min[2] + max[2]) / 2,
];
return {
center,
extent: { min, max },
};
};
@@ -1,59 +0,0 @@
import { Enums } from '@cornerstonejs/core';
import getCornerstoneBlendMode from './getCornerstoneBlendMode';
jest.mock('@cornerstonejs/core', () => ({
Enums: {
BlendModes: {
COMPOSITE: 'composite',
MAXIMUM_INTENSITY_BLEND: 'mip',
MINIMUM_INTENSITY_BLEND: 'minip',
AVERAGE_INTENSITY_BLEND: 'avg',
},
},
}));
describe('getCornerstoneBlendMode', () => {
it('should return COMPOSITE when blendMode is null', () => {
const result = getCornerstoneBlendMode(null);
expect(result).toBe(Enums.BlendModes.COMPOSITE);
});
it('should return COMPOSITE when blendMode is undefined', () => {
const result = getCornerstoneBlendMode(undefined);
expect(result).toBe(Enums.BlendModes.COMPOSITE);
});
it('should return COMPOSITE when blendMode is empty string', () => {
const result = getCornerstoneBlendMode('');
expect(result).toBe(Enums.BlendModes.COMPOSITE);
});
it('should return MAXIMUM_INTENSITY_BLEND when blendMode is mip', () => {
const result = getCornerstoneBlendMode('mip');
expect(result).toBe(Enums.BlendModes.MAXIMUM_INTENSITY_BLEND);
});
it('should return MAXIMUM_INTENSITY_BLEND when blendMode is MIP (uppercase)', () => {
const result = getCornerstoneBlendMode('MIP');
expect(result).toBe(Enums.BlendModes.MAXIMUM_INTENSITY_BLEND);
});
it('should return MINIMUM_INTENSITY_BLEND when blendMode is MINIP (uppercase)', () => {
const result = getCornerstoneBlendMode('MINIP');
expect(result).toBe(Enums.BlendModes.MINIMUM_INTENSITY_BLEND);
});
it('should return AVERAGE_INTENSITY_BLEND when blendMode is avg', () => {
const result = getCornerstoneBlendMode('avg');
expect(result).toBe(Enums.BlendModes.AVERAGE_INTENSITY_BLEND);
});
it('should throw error for unsupported blend mode', () => {
expect(() => getCornerstoneBlendMode('invalid')).toThrow('Unsupported blend mode: invalid');
});
it('should handle mixed case blend mode', () => {
const result = getCornerstoneBlendMode('MiP');
expect(result).toBe(Enums.BlendModes.MAXIMUM_INTENSITY_BLEND);
});
});
@@ -1,60 +0,0 @@
import { Enums } from '@cornerstonejs/core';
import getCornerstoneOrientation from './getCornerstoneOrientation';
jest.mock('@cornerstonejs/core', () => ({
Enums: {
OrientationAxis: {
AXIAL: 'axial',
SAGITTAL: 'sagittal',
CORONAL: 'coronal',
ACQUISITION: 'acquisition',
},
},
}));
describe('getCornerstoneOrientation', () => {
it('should return AXIAL when orientation is axial', () => {
const result = getCornerstoneOrientation('axial');
expect(result).toBe(Enums.OrientationAxis.AXIAL);
});
it('should return AXIAL when orientation is AXIAL (uppercase)', () => {
const result = getCornerstoneOrientation('AXIAL');
expect(result).toBe(Enums.OrientationAxis.AXIAL);
});
it('should return SAGITTAL when orientation is sagittal', () => {
const result = getCornerstoneOrientation('sagittal');
expect(result).toBe(Enums.OrientationAxis.SAGITTAL);
});
it('should return CORONAL when orientation is coronal', () => {
const result = getCornerstoneOrientation('coronal');
expect(result).toBe(Enums.OrientationAxis.CORONAL);
});
it('should return ACQUISITION for unknown orientation', () => {
const result = getCornerstoneOrientation('unknown');
expect(result).toBe(Enums.OrientationAxis.ACQUISITION);
});
it('should return ACQUISITION when orientation is null', () => {
const result = getCornerstoneOrientation(null);
expect(result).toBe(Enums.OrientationAxis.ACQUISITION);
});
it('should return ACQUISITION when orientation is undefined', () => {
const result = getCornerstoneOrientation(undefined);
expect(result).toBe(Enums.OrientationAxis.ACQUISITION);
});
it('should return ACQUISITION when orientation is empty string', () => {
const result = getCornerstoneOrientation('');
expect(result).toBe(Enums.OrientationAxis.ACQUISITION);
});
it('should handle mixed case orientation', () => {
const result = getCornerstoneOrientation('CoRoNaL');
expect(result).toBe(Enums.OrientationAxis.CORONAL);
});
});
@@ -1,91 +0,0 @@
import type { Types } from '@ohif/core';
import { Enums } from '@cornerstonejs/core';
import getCornerstoneViewportType from './getCornerstoneViewportType';
jest.mock('@cornerstonejs/core', () => ({
Enums: {
ViewportType: {
STACK: 'stack',
VIDEO: 'video',
WHOLE_SLIDE: 'wholeslide',
ORTHOGRAPHIC: 'orthographic',
VOLUME_3D: 'volume3d',
},
},
}));
describe('getCornerstoneViewportType', () => {
it('should return STACK when viewportType is stack', () => {
const result = getCornerstoneViewportType('stack');
expect(result).toBe(Enums.ViewportType.STACK);
});
it('should return STACK when viewportType is STACK (uppercase)', () => {
const result = getCornerstoneViewportType('STACK');
expect(result).toBe(Enums.ViewportType.STACK);
});
it('should return VIDEO when viewportType is video', () => {
const result = getCornerstoneViewportType('video');
expect(result).toBe(Enums.ViewportType.VIDEO);
});
it('should return WHOLE_SLIDE when viewportType is wholeslide', () => {
const result = getCornerstoneViewportType('wholeslide');
expect(result).toBe(Enums.ViewportType.WHOLE_SLIDE);
});
it('should return ORTHOGRAPHIC when viewportType is volume', () => {
const result = getCornerstoneViewportType('volume');
expect(result).toBe(Enums.ViewportType.ORTHOGRAPHIC);
});
it('should return ORTHOGRAPHIC when viewportType is orthographic', () => {
const result = getCornerstoneViewportType('orthographic');
expect(result).toBe(Enums.ViewportType.ORTHOGRAPHIC);
});
it('should return VOLUME_3D when viewportType is volume3d', () => {
const result = getCornerstoneViewportType('volume3d');
expect(result).toBe(Enums.ViewportType.VOLUME_3D);
});
it('should throw error for invalid viewport type', () => {
expect(() => getCornerstoneViewportType('invalid')).toThrow(
'Invalid viewport type: invalid. Valid types are: stack, volume, video, wholeslide'
);
});
it('should use displaySet viewportType when provided', () => {
const displaySets = [{ viewportType: 'stack' }] as Types.DisplaySet[];
const result = getCornerstoneViewportType('volume', displaySets);
expect(result).toBe(Enums.ViewportType.STACK);
});
it('should use displaySet viewportType with case insensitive matching', () => {
const displaySets = [{ viewportType: 'VIDEO' }] as Types.DisplaySet[];
const result = getCornerstoneViewportType('stack', displaySets);
expect(result).toBe(Enums.ViewportType.VIDEO);
});
it('should fallback to viewportType when displaySet has no viewportType', () => {
const displaySets = [{}] as Types.DisplaySet[];
const result = getCornerstoneViewportType('volume', displaySets);
expect(result).toBe(Enums.ViewportType.ORTHOGRAPHIC);
});
it('should handle empty displaySets array', () => {
const result = getCornerstoneViewportType('stack', []);
expect(result).toBe(Enums.ViewportType.STACK);
});
it('should handle null displaySets', () => {
const result = getCornerstoneViewportType('video', null);
expect(result).toBe(Enums.ViewportType.VIDEO);
});
it('should handle undefined displaySets', () => {
const result = getCornerstoneViewportType('wholeslide', undefined);
expect(result).toBe(Enums.ViewportType.WHOLE_SLIDE);
});
});
@@ -1,17 +1,4 @@
interface ImageIdToPrefetch {
imageId: string;
imageIdIndex: number;
}
export default function getInterleavedFrames(imageIds: string[]): ImageIdToPrefetch[] {
if (imageIds.length === 0) {
return [];
}
if (imageIds.length === 1) {
return [{ imageId: imageIds[0], imageIdIndex: 0 }];
}
export default function getInterleavedFrames(imageIds) {
const minImageIdIndex = 0;
const maxImageIdIndex = imageIds.length - 1;
@@ -21,7 +8,7 @@ export default function getInterleavedFrames(imageIds: string[]): ImageIdToPrefe
let upperImageIdIndex = middleImageIdIndex;
// Build up an array of images to prefetch, starting with the current image.
const imageIdsToPrefetch: ImageIdToPrefetch[] = [
const imageIdsToPrefetch = [
{ imageId: imageIds[middleImageIdIndex], imageIdIndex: middleImageIdIndex },
];
@@ -1,85 +0,0 @@
import getInterleavedFrames from './getInterleavedFrames';
describe('getInterleavedFrames', () => {
it('should return single element when input has one element', () => {
const imageIds = ['image-1'];
const result = getInterleavedFrames(imageIds);
expect(result).toEqual([{ imageId: 'image-1', imageIdIndex: 0 }]);
});
it('should return correct order for three elements', () => {
const imageIds = ['image-1', 'image-2', 'image-3'];
const result = getInterleavedFrames(imageIds);
expect(result).toEqual([
{ imageId: 'image-2', imageIdIndex: 1 },
{ imageId: 'image-1', imageIdIndex: 0 },
{ imageId: 'image-3', imageIdIndex: 2 },
]);
});
it('should start with middle element', () => {
const imageIds = ['image-1', 'image-2', 'image-3', 'image-4', 'image-5'];
const result = getInterleavedFrames(imageIds);
expect(result[0]).toEqual({ imageId: 'image-3', imageIdIndex: 2 });
});
it('should interleave elements correctly for even length array', () => {
const imageIds = ['image-1', 'image-2', 'image-3', 'image-4', 'image-5', 'image-6'];
const result = getInterleavedFrames(imageIds);
expect(result).toEqual([
{ imageId: 'image-4', imageIdIndex: 3 },
{ imageId: 'image-3', imageIdIndex: 2 },
{ imageId: 'image-5', imageIdIndex: 4 },
{ imageId: 'image-2', imageIdIndex: 1 },
{ imageId: 'image-6', imageIdIndex: 5 },
{ imageId: 'image-1', imageIdIndex: 0 },
]);
});
it('should interleave elements correctly for odd length array', () => {
const imageIds = ['image-1', 'image-2', 'image-3', 'image-4', 'image-5', 'image-6', 'image-7'];
const result = getInterleavedFrames(imageIds);
expect(result).toEqual([
{ imageId: 'image-4', imageIdIndex: 3 },
{ imageId: 'image-3', imageIdIndex: 2 },
{ imageId: 'image-5', imageIdIndex: 4 },
{ imageId: 'image-2', imageIdIndex: 1 },
{ imageId: 'image-6', imageIdIndex: 5 },
{ imageId: 'image-1', imageIdIndex: 0 },
{ imageId: 'image-7', imageIdIndex: 6 },
]);
});
it('should handle large array correctly', () => {
const imageIds = Array.from({ length: 10 }, (_, i) => `image-${i + 1}`);
const result = getInterleavedFrames(imageIds);
expect(result).toHaveLength(10);
expect(result[0]).toEqual({ imageId: 'image-6', imageIdIndex: 5 });
expect(result[8]).toEqual({ imageId: 'image-10', imageIdIndex: 9 });
expect(result[9]).toEqual({ imageId: 'image-1', imageIdIndex: 0 });
});
it('should handle empty array', () => {
const imageIds = [];
const result = getInterleavedFrames(imageIds);
expect(result).toEqual([]);
});
it('should handle duplicate imageIds with different indices', () => {
const imageIds = ['duplicate', 'unique', 'duplicate'];
const result = getInterleavedFrames(imageIds);
expect(result).toEqual([
{ imageId: 'unique', imageIdIndex: 1 },
{ imageId: 'duplicate', imageIdIndex: 0 },
{ imageId: 'duplicate', imageIdIndex: 2 },
]);
});
});
@@ -9,9 +9,12 @@
* What this does is return the first/center/start objects, as those
* are often used first, then a selection of objects scattered over the
* instances in order to allow making requests over a set of image instances.
*
* @param {[]} imageIds
* @returns [] reordered to be an nth selection
*/
export default function getNthFrames<T>(imageIds: T[]): T[] {
const frames: T[][] = [[], [], [], [], []];
export default function getNthFrames(imageIds) {
const frames = [[], [], [], [], []];
const centerStart = imageIds.length / 2 - 3;
const centerEnd = centerStart + 6;
@@ -1,145 +0,0 @@
import getNthFrames from './getNthFrames';
describe('getNthFrames', () => {
const createMockImageLoadRequest = (imageId: string, imageIdIndex: number) => ({
imageId,
callLoadImage: jest.fn(),
additionalDetails: 'test-details',
imageIdIndex,
options: { test: 'option' },
});
it('should return empty array when input is empty', () => {
const result = getNthFrames([]);
expect(result).toEqual([]);
});
it('should return same array when input has one element', () => {
const imageIds = [createMockImageLoadRequest('image-1', 0)];
const result = getNthFrames(imageIds);
expect(result).toEqual(imageIds);
});
it('should return same array when input has two elements', () => {
const imageIds = [
createMockImageLoadRequest('image-1', 0),
createMockImageLoadRequest('image-2', 1),
];
const result = getNthFrames(imageIds);
expect(result).toEqual(imageIds);
});
it('should prioritize first two elements', () => {
const imageIds = Array.from({ length: 10 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
expect(result[0]).toBe(imageIds[0]);
expect(result[1]).toBe(imageIds[1]);
});
it('should prioritize last three elements', () => {
const imageIds = Array.from({ length: 10 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
expect(result).toContain(imageIds[7]);
expect(result).toContain(imageIds[8]);
expect(result).toContain(imageIds[9]);
});
it('should include center elements', () => {
const imageIds = Array.from({ length: 20 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
const centerStart = imageIds.length / 2 - 3;
const centerEnd = centerStart + 6;
for (let i = Math.ceil(centerStart); i < centerEnd; i++) {
if (i >= 0 && i < imageIds.length) {
expect(result).toContain(imageIds[i]);
}
}
});
it('should include nth elements where i % 7 === 2', () => {
const imageIds = Array.from({ length: 30 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
for (let i = 0; i < imageIds.length; i++) {
if (i % 7 === 2 && i >= 2 && i <= imageIds.length - 4) {
const centerStart = imageIds.length / 2 - 3;
const centerEnd = centerStart + 6;
if (!(i > centerStart && i < centerEnd)) {
expect(result).toContain(imageIds[i]);
}
}
}
});
it('should include nth elements where i % 7 === 5', () => {
const imageIds = Array.from({ length: 30 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
for (let i = 0; i < imageIds.length; i++) {
if (i % 7 === 5 && i >= 2 && i <= imageIds.length - 4) {
const centerStart = imageIds.length / 2 - 3;
const centerEnd = centerStart + 6;
if (!(i > centerStart && i < centerEnd)) {
expect(result).toContain(imageIds[i]);
}
}
}
});
it('should handle large arrays correctly', () => {
const imageIds = Array.from({ length: 100 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
expect(result.length).toBe(100);
expect(result[0]).toBe(imageIds[0]);
expect(result[1]).toBe(imageIds[1]);
expect(result).toContain(imageIds[97]);
expect(result).toContain(imageIds[98]);
expect(result).toContain(imageIds[99]);
});
it('should handle arrays where centerStart is negative', () => {
const imageIds = Array.from({ length: 3 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
expect(result).toEqual(imageIds);
});
it('should handle arrays where centerEnd exceeds length', () => {
const imageIds = Array.from({ length: 4 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
expect(result).toEqual(expect.arrayContaining(imageIds));
expect(result.length).toBe(4);
});
it('should preserve object references', () => {
const imageIds = Array.from({ length: 10 }, (_, i) =>
createMockImageLoadRequest(`image-${i}`, i)
);
const result = getNthFrames(imageIds);
result.forEach(item => {
expect(imageIds).toContain(item);
});
});
});
@@ -1,83 +0,0 @@
import { getEnabledElement } from '@cornerstonejs/core';
import { getEnabledElement as OHIFgetEnabledElement } from '../state';
import { getViewportEnabledElement } from './getViewportEnabledElement';
jest.mock('@cornerstonejs/core', () => ({
getEnabledElement: jest.fn(),
}));
jest.mock('../state', () => ({
getEnabledElement: jest.fn(),
}));
describe('getViewportEnabledElement', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should return enabled element when OHIF getEnabledElement returns element', () => {
const mockElement = document.createElement('div');
const mockEnabledElement = { viewport: 'test-viewport' };
(OHIFgetEnabledElement as jest.Mock).mockReturnValue({ element: mockElement });
(getEnabledElement as jest.Mock).mockReturnValue(mockEnabledElement);
const result = getViewportEnabledElement('test-viewport-id');
expect(OHIFgetEnabledElement).toHaveBeenCalledWith('test-viewport-id');
expect(getEnabledElement).toHaveBeenCalledWith(mockElement);
expect(result).toBe(mockEnabledElement);
});
it('should return enabled element when OHIF getEnabledElement returns null', () => {
const mockEnabledElement = { viewport: 'test-viewport' };
(OHIFgetEnabledElement as jest.Mock).mockReturnValue(null);
(getEnabledElement as jest.Mock).mockReturnValue(mockEnabledElement);
const result = getViewportEnabledElement('test-viewport-id');
expect(OHIFgetEnabledElement).toHaveBeenCalledWith('test-viewport-id');
expect(getEnabledElement).toHaveBeenCalledWith(undefined);
expect(result).toBe(mockEnabledElement);
});
it('should return enabled element when OHIF getEnabledElement returns object without element', () => {
const mockEnabledElement = { viewport: 'test-viewport' };
(OHIFgetEnabledElement as jest.Mock).mockReturnValue({});
(getEnabledElement as jest.Mock).mockReturnValue(mockEnabledElement);
const result = getViewportEnabledElement('test-viewport-id');
expect(OHIFgetEnabledElement).toHaveBeenCalledWith('test-viewport-id');
expect(getEnabledElement).toHaveBeenCalledWith(undefined);
expect(result).toBe(mockEnabledElement);
});
it('should return null when cornerstone getEnabledElement returns null', () => {
const mockElement = document.createElement('div');
(OHIFgetEnabledElement as jest.Mock).mockReturnValue({ element: mockElement });
(getEnabledElement as jest.Mock).mockReturnValue(null);
const result = getViewportEnabledElement('test-viewport-id');
expect(OHIFgetEnabledElement).toHaveBeenCalledWith('test-viewport-id');
expect(getEnabledElement).toHaveBeenCalledWith(mockElement);
expect(result).toBe(null);
});
it('should return undefined when cornerstone getEnabledElement returns undefined', () => {
const mockElement = document.createElement('div');
(OHIFgetEnabledElement as jest.Mock).mockReturnValue({ element: mockElement });
(getEnabledElement as jest.Mock).mockReturnValue(undefined);
const result = getViewportEnabledElement('test-viewport-id');
expect(OHIFgetEnabledElement).toHaveBeenCalledWith('test-viewport-id');
expect(getEnabledElement).toHaveBeenCalledWith(mockElement);
expect(result).toBe(undefined);
});
});
@@ -1,50 +0,0 @@
import { getViewportOrientationFromImageOrientationPatient } from './getViewportOrientationFromImageOrientationPatient';
describe('getViewportOrientationFromImageOrientationPatient', () => {
it('should return undefined when imageOrientationPatient is null', () => {
const result = getViewportOrientationFromImageOrientationPatient(null);
expect(result).toBeUndefined();
});
it('should return undefined when imageOrientationPatient is undefined', () => {
const result = getViewportOrientationFromImageOrientationPatient(undefined);
expect(result).toBeUndefined();
});
it('should return undefined when imageOrientationPatient has length less than 6', () => {
const result = getViewportOrientationFromImageOrientationPatient([1, 0, 0, 0, 1]);
expect(result).toBeUndefined();
});
it('should return undefined when imageOrientationPatient has length greater than 6', () => {
const result = getViewportOrientationFromImageOrientationPatient([1, 0, 0, 0, 1, 0, 0]);
expect(result).toBeUndefined();
});
it('should return undefined when imageOrientationPatient is empty array', () => {
const result = getViewportOrientationFromImageOrientationPatient([]);
expect(result).toBeUndefined();
});
it('should return "axial" when imageOrientationPatient matches axial orientation', () => {
const result = getViewportOrientationFromImageOrientationPatient([1, 0, 0, 0, 1, 0]);
expect(result).toBe('axial');
});
it('should return "sagittal" when imageOrientationPatient matches sagittal orientation', () => {
const result = getViewportOrientationFromImageOrientationPatient([0, 1, 0, 0, 0, -1]);
expect(result).toBe('sagittal');
});
it('should return "coronal" when imageOrientationPatient matches coronal orientation', () => {
const result = getViewportOrientationFromImageOrientationPatient([1, 0, 0, 0, 0, -1]);
expect(result).toBe('coronal');
});
it('should return undefined when no orientation matches', () => {
const result = getViewportOrientationFromImageOrientationPatient([
0.5, 0.5, 0.7, 0.3, 0.8, 0.5,
]);
expect(result).toBeUndefined();
});
});
@@ -1,387 +0,0 @@
import { getUpdatedViewportsForSegmentation } from './hydrationUtils';
describe('getUpdatedViewportsForSegmentation', () => {
const mockHangingProtocolService = {
getViewportsRequireUpdate: jest.fn(),
};
const mockViewportGridService = {
getState: jest.fn(),
};
const mockServicesManager = {
services: {
hangingProtocolService: mockHangingProtocolService,
viewportGridService: mockViewportGridService,
},
};
const mockViewport = {
viewportOptions: {
viewportId: 'target-viewport-id',
},
};
const mockViewports = new Map([
['viewport-1', mockViewport],
['active-viewport-id', mockViewport],
]);
const defaultParameters = {
viewportId: 'viewport-1',
servicesManager: mockServicesManager as unknown as AppTypes.ServicesManager,
displaySetInstanceUIDs: ['display-set-1'],
};
beforeEach(() => {
jest.clearAllMocks();
mockViewportGridService.getState.mockReturnValue({
isHangingProtocolLayout: true,
viewports: mockViewports,
activeViewportId: 'active-viewport-id',
});
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue([]);
});
it('should get updated viewports for segmentation', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(mockViewportGridService.getState).toHaveBeenCalled();
expect(mockHangingProtocolService.getViewportsRequireUpdate).toHaveBeenCalledWith(
mockViewport.viewportOptions.viewportId,
defaultParameters.displaySetInstanceUIDs[0],
true
);
expect(result).toEqual(mockUpdatedViewports);
});
it('should filter out volume3d viewports', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume',
},
},
]);
});
it('should handle viewports without viewportOptions', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
{
someOtherProperty: 'value',
},
{
viewportOptions: null,
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([
{
viewportOptions: {
viewportType: 'stack',
},
},
{
someOtherProperty: 'value',
},
{
viewportOptions: null,
},
]);
});
it('should use activeViewportId when viewportId is not provided', () => {
const result = getUpdatedViewportsForSegmentation({
...defaultParameters,
viewportId: null,
});
expect(mockHangingProtocolService.getViewportsRequireUpdate).toHaveBeenCalledWith(
mockViewport.viewportOptions.viewportId,
defaultParameters.displaySetInstanceUIDs[0],
true
);
expect(result).toEqual([]);
});
it('should use activeViewportId when viewportId is undefined', () => {
const result = getUpdatedViewportsForSegmentation({
...defaultParameters,
viewportId: undefined,
});
expect(mockHangingProtocolService.getViewportsRequireUpdate).toHaveBeenCalledWith(
mockViewport.viewportOptions.viewportId,
defaultParameters.displaySetInstanceUIDs[0],
true
);
expect(result).toEqual([]);
});
it('should handle isHangingProtocolLayout false', () => {
mockViewportGridService.getState.mockReturnValue({
isHangingProtocolLayout: false,
viewports: mockViewports,
activeViewportId: 'active-viewport-id',
});
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(mockHangingProtocolService.getViewportsRequireUpdate).toHaveBeenCalledWith(
mockViewport.viewportOptions.viewportId,
defaultParameters.displaySetInstanceUIDs[0],
false
);
expect(result).toEqual([]);
});
it('should handle multiple displaySetInstanceUIDs by using first one', () => {
const result = getUpdatedViewportsForSegmentation({
...defaultParameters,
displaySetInstanceUIDs: ['display-set-1', 'display-set-2', 'display-set-3'],
});
expect(mockHangingProtocolService.getViewportsRequireUpdate).toHaveBeenCalledWith(
mockViewport.viewportOptions.viewportId,
'display-set-1',
true
);
expect(result).toEqual([]);
});
it('should handle empty displaySetInstanceUIDs array', () => {
const result = getUpdatedViewportsForSegmentation({
...defaultParameters,
displaySetInstanceUIDs: [],
});
expect(mockHangingProtocolService.getViewportsRequireUpdate).toHaveBeenCalledWith(
mockViewport.viewportOptions.viewportId,
undefined,
true
);
expect(result).toEqual([]);
});
it('should handle viewport not found in viewports map', () => {
mockViewportGridService.getState.mockReturnValue({
isHangingProtocolLayout: true,
viewports: new Map(),
activeViewportId: 'non-existent-viewport',
});
expect(() => getUpdatedViewportsForSegmentation(defaultParameters)).toThrow();
});
it('should handle viewport with missing viewportOptions', () => {
const viewportWithoutOptions = {};
const viewportsMap = new Map([['viewport-1', viewportWithoutOptions]]);
mockViewportGridService.getState.mockReturnValue({
isHangingProtocolLayout: true,
viewports: viewportsMap,
activeViewportId: 'active-viewport-id',
});
expect(() => getUpdatedViewportsForSegmentation(defaultParameters)).toThrow();
});
it('should handle viewport with null viewportOptions', () => {
const viewportWithNullOptions = {
viewportOptions: null,
};
const viewportsMap = new Map([['viewport-1', viewportWithNullOptions]]);
mockViewportGridService.getState.mockReturnValue({
isHangingProtocolLayout: true,
viewports: viewportsMap,
activeViewportId: 'active-viewport-id',
});
expect(() => getUpdatedViewportsForSegmentation(defaultParameters)).toThrow();
});
it('should handle getViewportsRequireUpdate returning null', () => {
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(null);
expect(() => getUpdatedViewportsForSegmentation(defaultParameters)).toThrow();
});
it('should handle mixed viewport types including volume3d', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'orthogonal',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'orthogonal',
},
},
]);
});
it('should handle all volume3d viewports', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([]);
});
it('should handle viewports with undefined viewportType', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: undefined,
},
},
{
viewportOptions: {
someOtherProperty: 'value',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual(mockUpdatedViewports);
});
it('should handle complex viewport structure', () => {
const complexViewport = {
viewportOptions: {
viewportId: 'complex-viewport-id',
viewportType: 'stack',
orientation: 'axial',
initialImageOptions: {
index: 0,
},
},
displaySetOptions: {
displaySetInstanceUID: 'display-set-1',
},
};
const viewportsMap = new Map([['viewport-1', complexViewport]]);
mockViewportGridService.getState.mockReturnValue({
isHangingProtocolLayout: true,
viewports: viewportsMap,
activeViewportId: 'active-viewport-id',
});
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(mockHangingProtocolService.getViewportsRequireUpdate).toHaveBeenCalledWith(
'complex-viewport-id',
defaultParameters.displaySetInstanceUIDs[0],
true
);
expect(result).toEqual(mockUpdatedViewports);
});
});
@@ -9,7 +9,6 @@ import promptHydrationDialog, {
HydrationCallback,
HydrationSRResult,
} from './promptHydrationDialog';
import { getCenterExtent } from './getCenterExtent';
const utils = {
handleSegmentChange,
@@ -17,7 +16,6 @@ const utils = {
setupSegmentationDataModifiedHandler,
setupSegmentationModifiedHandler,
promptHydrationDialog,
getCenterExtent,
};
export type { HydrationDialogProps, HydrationCallback, HydrationSRResult };
@@ -1,342 +0,0 @@
import { log, Enums } from '@ohif/core';
import { EVENTS } from '@cornerstonejs/core';
import initViewTiming from './initViewTiming';
jest.mock('@ohif/core', () => ({
log: {
timingKeys: {},
timeEnd: jest.fn(),
},
Enums: {
TimingEnum: {
DISPLAY_SETS_TO_ALL_IMAGES: 'DISPLAY_SETS_TO_ALL_IMAGES',
DISPLAY_SETS_TO_FIRST_IMAGE: 'DISPLAY_SETS_TO_FIRST_IMAGE',
STUDY_TO_FIRST_IMAGE: 'STUDY_TO_FIRST_IMAGE',
SCRIPT_TO_VIEW: 'SCRIPT_TO_VIEW',
},
},
}));
jest.mock('@cornerstonejs/core', () => ({
EVENTS: {
IMAGE_RENDERED: 'IMAGE_RENDERED',
},
}));
describe('initViewTiming', () => {
const mockElement = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
const defaultParameters = {
element: mockElement,
};
const clearUnevenlyHandledViewportState = () => {
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
imageRenderedListener({
detail: {
viewportStatus: 'rendered',
element: mockElement,
},
});
};
beforeEach(() => {
jest.clearAllMocks();
// @ts-expect-error - restarting the object that will have values set by each test
log.timingKeys = {};
});
it('should return early when no timing keys are set', () => {
// @ts-expect-error - the idea is to test an invalid state
log.timingKeys = {};
initViewTiming(defaultParameters);
expect(mockElement.addEventListener).not.toHaveBeenCalled();
});
it('should add event listener when timing keys are present', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
expect(mockElement.addEventListener).toHaveBeenCalledWith(
EVENTS.IMAGE_RENDERED,
expect.any(Function)
);
clearUnevenlyHandledViewportState();
});
it('should handle multiple timing keys', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES] = true;
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
expect(mockElement.addEventListener).toHaveBeenCalledWith(
EVENTS.IMAGE_RENDERED,
expect.any(Function)
);
clearUnevenlyHandledViewportState();
});
it('should return early in imageRenderedListener when viewportStatus is preRender', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
const evt = {
detail: {
viewportStatus: 'preRender',
element: mockElement,
},
};
imageRenderedListener(evt);
expect(log.timeEnd).not.toHaveBeenCalled();
expect(mockElement.removeEventListener).not.toHaveBeenCalled();
clearUnevenlyHandledViewportState();
});
it('should call timeEnd for timing keys when image is rendered', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
const evt = {
detail: {
viewportStatus: 'rendered',
element: mockElement,
},
};
imageRenderedListener(evt);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.STUDY_TO_FIRST_IMAGE);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.SCRIPT_TO_VIEW);
});
it('should remove event listener after image is rendered', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
const evt = {
detail: {
viewportStatus: 'rendered',
element: mockElement,
},
};
imageRenderedListener(evt);
expect(mockElement.removeEventListener).toHaveBeenCalledWith(
EVENTS.IMAGE_RENDERED,
imageRenderedListener
);
});
it('should not call timeEnd for ALL_IMAGES when viewports are still waiting', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
initViewTiming(defaultParameters);
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
const evt = {
detail: {
viewportStatus: 'rendered',
element: mockElement,
},
};
imageRenderedListener(evt);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.STUDY_TO_FIRST_IMAGE);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.SCRIPT_TO_VIEW);
expect(log.timeEnd).not.toHaveBeenCalledWith(Enums.TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
clearUnevenlyHandledViewportState();
});
it('should handle multiple viewports finishing in sequence', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
const mockElement2 = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
initViewTiming(defaultParameters);
initViewTiming({ element: mockElement2 });
const imageRenderedListener1 = mockElement.addEventListener.mock.calls[0][1];
const imageRenderedListener2 = mockElement2.addEventListener.mock.calls[0][1];
const evt1 = {
detail: {
viewportStatus: 'rendered',
element: mockElement,
},
};
const evt2 = {
detail: {
viewportStatus: 'rendered',
element: mockElement2,
},
};
imageRenderedListener1(evt1);
expect(log.timeEnd).not.toHaveBeenCalledWith(Enums.TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
imageRenderedListener2(evt2);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
});
it('should call timeEnd for ALL_IMAGES when last viewport finishes', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
const evt = {
detail: {
viewportStatus: 'rendered',
element: mockElement,
},
};
imageRenderedListener(evt);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
});
it('should handle different viewportStatus values', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
const evt = {
detail: {
viewportStatus: 'loading',
element: mockElement,
},
};
imageRenderedListener(evt);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
expect(mockElement.removeEventListener).toHaveBeenCalled();
});
it('should handle undefined viewportStatus', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
const evt = {
detail: {
element: mockElement,
},
};
imageRenderedListener(evt);
expect(log.timeEnd).toHaveBeenCalledWith(Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
expect(mockElement.removeEventListener).toHaveBeenCalled();
});
it('should handle missing detail object', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
const imageRenderedListener = mockElement.addEventListener.mock.calls[0][1];
const evt = {};
expect(() => imageRenderedListener(evt)).toThrow();
});
it('should handle timing keys with falsy values', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = false;
log.timingKeys[Enums.TimingEnum.STUDY_TO_FIRST_IMAGE] = null;
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES] = undefined;
initViewTiming(defaultParameters);
expect(mockElement.addEventListener).not.toHaveBeenCalled();
});
it('should handle timing keys with truthy values', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = 'some value';
log.timingKeys[Enums.TimingEnum.STUDY_TO_FIRST_IMAGE] = 1;
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES] = {};
initViewTiming(defaultParameters);
expect(mockElement.addEventListener).toHaveBeenCalledWith(
EVENTS.IMAGE_RENDERED,
expect.any(Function)
);
});
it('should initialize IMAGE_TIMING_KEYS on first call', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
expect(mockElement.addEventListener).toHaveBeenCalled();
});
it('should handle empty element object', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
const emptyElement = {};
expect(() => initViewTiming({ element: emptyElement })).toThrow();
});
it('should handle null element', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
expect(() => initViewTiming({ element: null })).toThrow();
});
it('should handle multiple calls with same element', () => {
log.timingKeys[Enums.TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE] = true;
initViewTiming(defaultParameters);
initViewTiming(defaultParameters);
initViewTiming(defaultParameters);
expect(mockElement.addEventListener).toHaveBeenCalledTimes(3);
});
it('should handle case where no timing keys match', () => {
// @ts-expect-error - the idea is to test an invalid state
log.timingKeys = {
someOtherKey: true,
anotherKey: 'value',
};
initViewTiming(defaultParameters);
expect(mockElement.addEventListener).not.toHaveBeenCalled();
});
});
@@ -1,381 +0,0 @@
import { eventTarget, EVENTS } from '@cornerstonejs/core';
import * as cornerstoneTools from '@cornerstonejs/tools';
import { initializeWebWorkerProgressHandler } from './initWebWorkerProgressHandler';
jest.mock('@cornerstonejs/core', () => ({
eventTarget: {
addEventListener: jest.fn(),
},
EVENTS: {
WEB_WORKER_PROGRESS: 'WEB_WORKER_PROGRESS',
},
}));
jest.mock('@cornerstonejs/tools', () => ({
Enums: {
WorkerTypes: {
COMPUTE_STATISTICS: 'COMPUTE_STATISTICS',
},
},
}));
describe('initializeWebWorkerProgressHandler', () => {
const mockUINotificationService = {
show: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(console, 'error').mockImplementation();
jest.spyOn(console, 'debug').mockImplementation();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should register event listener for WEB_WORKER_PROGRESS', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
expect(eventTarget.addEventListener).toHaveBeenCalledWith(
EVENTS.WEB_WORKER_PROGRESS,
expect.any(Function)
);
});
it('should skip notifications for COMPUTE_STATISTICS worker type', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: cornerstoneTools.Enums.WorkerTypes.COMPUTE_STATISTICS,
id: 'test-id',
};
eventHandler({ detail });
expect(mockUINotificationService.show).not.toHaveBeenCalled();
});
it('should show notification when progress is 0 for new task', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: 'TEST_WORKER',
id: 'test-id',
};
eventHandler({ detail });
expect(mockUINotificationService.show).toHaveBeenCalledWith({
id: 'worker-task-test_worker',
title: 'TEST_WORKER',
message: 'Computing...',
autoClose: false,
allowDuplicates: false,
deduplicationInterval: 60000,
promise: expect.any(Promise),
promiseMessages: {
loading: 'Computing...',
success: 'Completed successfully',
error: 'Web Worker failed',
},
});
});
it('should not show duplicate notification for same task type', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: 'TEST_WORKER',
id: 'test-id-1',
};
eventHandler({ detail });
eventHandler({ detail: { ...detail, id: 'test-id-2' } });
expect(mockUINotificationService.show).toHaveBeenCalledTimes(1);
expect(console.debug).toHaveBeenCalledWith(
'Already tracking a "TEST_WORKER" task, skipping duplicate notification'
);
});
it('should resolve promise when progress is 100', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const startDetail = {
progress: 0,
type: 'TEST_WORKER',
id: 'test-id',
};
eventHandler({ detail: startDetail });
const completeDetail = {
progress: 100,
type: 'TEST_WORKER',
id: 'test-id',
};
eventHandler({ detail: completeDetail });
expect(console.debug).toHaveBeenCalledWith('Worker task "TEST_WORKER" completed successfully');
});
it('should handle completion for non-tracked task gracefully', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 100,
type: 'UNKNOWN_WORKER',
id: 'test-id',
};
eventHandler({ detail });
expect(console.debug).not.toHaveBeenCalledWith(
'Worker task "UNKNOWN_WORKER" completed successfully'
);
});
it('should normalize task keys correctly', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: 'Test Worker Type',
id: 'test-id',
};
eventHandler({ detail });
expect(mockUINotificationService.show).toHaveBeenCalledWith(
expect.objectContaining({
id: 'worker-task-test-worker-type',
})
);
});
it('should handle error when setting active worker task', () => {
jest.spyOn(global, 'Promise').mockImplementationOnce(() => {
throw new Error('Promise creation failed');
});
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: 'TEST_WORKER',
id: 'test-id',
};
expect(() => eventHandler({ detail })).not.toThrow();
expect(console.error).toHaveBeenCalledWith(
'Error in web worker progress handler for type "TEST_WORKER":',
expect.any(Error)
);
jest.restoreAllMocks();
});
it('should handle error when showing notification', () => {
mockUINotificationService.show.mockImplementationOnce(() => {
throw new Error('Notification service failed');
});
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: 'TEST_WORKER',
id: 'test-id',
};
expect(() => eventHandler({ detail })).not.toThrow();
expect(console.error).toHaveBeenCalledWith(
'Error showing web worker notification for type "TEST_WORKER":',
expect.any(Error)
);
});
it('should handle missing detail object', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
expect(() => eventHandler({})).not.toThrow();
expect(console.error).toHaveBeenCalledWith(
'Error in web worker progress handler for type "undefined":',
expect.any(Error)
);
});
it('should handle undefined detail', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
expect(() => eventHandler({ detail: undefined })).not.toThrow();
expect(console.error).toHaveBeenCalledWith(
'Error in web worker progress handler for type "undefined":',
expect.any(Error)
);
});
it('should handle cleanup error gracefully', () => {
const mockMap = new Map();
mockMap.delete = jest.fn(() => {
throw new Error('Delete failed');
});
jest.spyOn(global, 'Map').mockImplementationOnce(() => mockMap);
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: 'TEST_WORKER',
id: 'test-id',
};
mockUINotificationService.show.mockImplementationOnce(() => {
throw new Error('Notification failed');
});
expect(() => eventHandler({ detail })).not.toThrow();
expect(console.error).toHaveBeenCalledWith(
'Error cleaning up active worker task for type "TEST_WORKER":',
expect.any(Error)
);
jest.restoreAllMocks();
});
it('should handle intermediate progress values', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const startDetail = {
progress: 0,
type: 'TEST_WORKER',
id: 'test-id',
};
eventHandler({ detail: startDetail });
const intermediateDetail = {
progress: 50,
type: 'TEST_WORKER',
id: 'test-id',
};
eventHandler({ detail: intermediateDetail });
expect(mockUINotificationService.show).toHaveBeenCalledTimes(1);
});
it('should handle multiple different worker types simultaneously', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail1 = {
progress: 0,
type: 'WORKER_TYPE_1',
id: 'test-id-1',
};
const detail2 = {
progress: 0,
type: 'WORKER_TYPE_2',
id: 'test-id-2',
};
eventHandler({ detail: detail1 });
eventHandler({ detail: detail2 });
expect(mockUINotificationService.show).toHaveBeenCalledTimes(2);
expect(mockUINotificationService.show).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
id: 'worker-task-worker_type_1',
title: 'WORKER_TYPE_1',
})
);
expect(mockUINotificationService.show).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
id: 'worker-task-worker_type_2',
title: 'WORKER_TYPE_2',
})
);
});
it('should complete multiple worker types independently', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
eventHandler({ detail: { progress: 0, type: 'WORKER_1', id: 'id-1' } });
eventHandler({ detail: { progress: 0, type: 'WORKER_2', id: 'id-2' } });
eventHandler({ detail: { progress: 100, type: 'WORKER_1', id: 'id-1' } });
expect(console.debug).toHaveBeenCalledWith('Worker task "WORKER_1" completed successfully');
eventHandler({ detail: { progress: 100, type: 'WORKER_2', id: 'id-2' } });
expect(console.debug).toHaveBeenCalledWith('Worker task "WORKER_2" completed successfully');
});
it('should handle special characters in worker type names', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: 'Test-Worker_Type@123',
id: 'test-id',
};
eventHandler({ detail });
expect(mockUINotificationService.show).toHaveBeenCalledWith(
expect.objectContaining({
id: 'worker-task-test-worker_type@123',
})
);
});
it('should handle empty worker type', () => {
initializeWebWorkerProgressHandler(mockUINotificationService);
const eventHandler = (eventTarget.addEventListener as jest.Mock).mock.calls[0][1];
const detail = {
progress: 0,
type: '',
id: 'test-id',
};
eventHandler({ detail });
expect(mockUINotificationService.show).toHaveBeenCalledWith(
expect.objectContaining({
id: 'worker-task-',
title: '',
})
);
});
});
@@ -3,30 +3,28 @@
* in the returned list, the second items are next etc.
* Does this in a O(n) fashion, and return lists[0] if there is only one list.
*
* @param lists - Array of arrays to interleave
* @returns Array reordered to be breadth first traversal of lists
* @param {[]} lists
* @returns [] reordered to be breadth first traversal of lists
*/
export default function interleave<T>(lists: T[][]): T[] {
export default function interleave(lists) {
if (!lists || !lists.length) {
return [];
}
if (lists.length === 1) {
return lists[0];
}
console.time('interleave');
const useLists = [...lists];
const ret = [];
for (let i = 0; useLists.length > 0; i++) {
for (let j = 0; j < useLists.length; j++) {
const list = useLists[j];
for (const list of useLists) {
if (i >= list.length) {
useLists.splice(j, 1);
j--; // Adjust index after removal to avoid iterator skipping
useLists.splice(useLists.indexOf(list), 1);
continue;
}
ret.push(list[i]);
}
}
console.timeEnd('interleave');
return ret;
}
@@ -1,304 +0,0 @@
import interleave from './interleave';
describe('interleave', () => {
beforeEach(() => {
jest.spyOn(console, 'time').mockImplementation();
jest.spyOn(console, 'timeEnd').mockImplementation();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should return empty array when input is null', () => {
const result = interleave(null);
expect(result).toEqual([]);
});
it('should return empty array when input is undefined', () => {
const result = interleave(undefined);
expect(result).toEqual([]);
});
it('should return empty array when input is empty array', () => {
const result = interleave([]);
expect(result).toEqual([]);
});
it('should return same array when only one list is provided', () => {
const input = [[1, 2, 3, 4]];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4]);
expect(result).toBe(input[0]);
});
it('should interleave two arrays of equal length', () => {
const input = [
[1, 3, 5],
[2, 4, 6],
];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4, 5, 6]);
});
it('should interleave three arrays of equal length', () => {
const input = [
[1, 4, 7],
[2, 5, 8],
[3, 6, 9],
];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
it('should handle arrays of different lengths', () => {
const input = [
[1, 4],
[2, 5, 7, 8],
[3, 6],
];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
});
it('should handle first array being longer', () => {
const input = [
[1, 3, 5, 7, 9, 11],
[2, 4, 6],
];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 9, 11]);
});
it('should handle second array being longer', () => {
const input = [
[1, 3, 5],
[2, 4, 6, 8, 10, 12],
];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4, 5, 6, 8, 10, 12]);
});
it('should handle empty arrays in the input', () => {
const input = [[1, 3, 5], [], [2, 4, 6]];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4, 5, 6]);
});
it('should handle all empty arrays', () => {
const input = [[], [], []];
const result = interleave(input);
expect(result).toEqual([]);
});
it('should handle single element arrays', () => {
const input = [[1], [2], [3], [4]];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4]);
});
it('should handle mixed single and multi-element arrays', () => {
const input = [[1], [2, 5, 8], [3], [4, 6, 7, 9]];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 4, 5, 6, 8, 7, 9]);
});
it('should handle string arrays', () => {
const input = [
['a', 'c', 'e'],
['b', 'd', 'f'],
];
const result = interleave(input);
expect(result).toEqual(['a', 'b', 'c', 'd', 'e', 'f']);
});
it('should handle object arrays', () => {
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const obj3 = { id: 3 };
const obj4 = { id: 4 };
const input = [
[obj1, obj3],
[obj2, obj4],
];
const result = interleave(input);
expect(result).toEqual([obj1, obj2, obj3, obj4]);
});
it('should handle large arrays efficiently', () => {
const array1 = Array.from({ length: 1000 }, (_, i) => i * 2);
const array2 = Array.from({ length: 1000 }, (_, i) => i * 2 + 1);
const input = [array1, array2];
const startTime = performance.now();
const result = interleave(input);
const endTime = performance.now();
expect(result).toHaveLength(2000);
expect(result[0]).toBe(0);
expect(result[1]).toBe(1);
expect(result[2]).toBe(2);
expect(result[3]).toBe(3);
expect(endTime - startTime).toBeLessThan(100);
});
it('should handle very large number of arrays', () => {
const input = Array.from({ length: 100 }, (_, i) => [i, i + 100, i + 200]);
const result = interleave(input);
expect(result).toHaveLength(300);
expect(result.slice(0, 100)).toEqual(Array.from({ length: 100 }, (_, i) => i));
});
it('should handle arrays with different data types', () => {
const input = [
[1, 'a', true],
[2, 'b', false],
[3, 'c', null],
];
const result = interleave(input);
expect(result).toEqual([1, 2, 3, 'a', 'b', 'c', true, false, null]);
});
it('should not mutate original arrays', () => {
const array1 = [1, 3, 5];
const array2 = [2, 4, 6];
const input = [array1, array2];
const originalArray1 = [...array1];
const originalArray2 = [...array2];
interleave(input);
expect(array1).toEqual(originalArray1);
expect(array2).toEqual(originalArray2);
});
it('should handle nested arrays', () => {
const input = [
[
[1, 2],
[5, 6],
],
[
[3, 4],
[7, 8],
],
];
const result = interleave(input);
expect(result).toEqual([
[1, 2],
[3, 4],
[5, 6],
[7, 8],
]);
});
it('should handle arrays with undefined and null values', () => {
const input = [
[1, undefined, 3],
[null, 2, 4],
];
const result = interleave(input);
expect(result).toEqual([1, null, undefined, 2, 3, 4]);
});
it('should handle extremely unbalanced arrays', () => {
const input = [[1], Array.from({ length: 1000 }, (_, i) => i + 2)];
const result = interleave(input);
expect(result).toHaveLength(1001);
expect(result[0]).toBe(1);
expect(result[1]).toBe(2);
expect(result[2]).toBe(3);
});
it('should handle performance with many small arrays', () => {
const input = Array.from({ length: 1000 }, (_, i) => [i]);
const startTime = performance.now();
const result = interleave(input);
const endTime = performance.now();
expect(result).toHaveLength(1000);
expect(result).toEqual(Array.from({ length: 1000 }, (_, i) => i));
expect(endTime - startTime).toBeLessThan(50);
});
it('should handle zero values correctly', () => {
const input = [
[0, 2, 4],
[1, 3, 5],
];
const result = interleave(input);
expect(result).toEqual([0, 1, 2, 3, 4, 5]);
});
it('should handle boolean arrays', () => {
const input = [
[true, false, true],
[false, true, false],
];
const result = interleave(input);
expect(result).toEqual([true, false, false, true, true, false]);
});
it('should handle single empty array with non-empty arrays', () => {
const input = [[], [1, 2, 3], [4, 5, 6]];
const result = interleave(input);
expect(result).toEqual([1, 4, 2, 5, 3, 6]);
});
it('should maintain order within each array', () => {
const input = [
[10, 20, 30, 40, 50],
[15, 25],
[12, 22, 32],
];
const result = interleave(input);
expect(result).toEqual([10, 15, 12, 20, 25, 22, 30, 32, 40, 50]);
});
});
@@ -1,547 +0,0 @@
import { cache, imageLoadPoolManager, Enums } from '@cornerstonejs/core';
import getInterleavedFrames from './getInterleavedFrames';
import zip from 'lodash.zip';
import compact from 'lodash.compact';
import flatten from 'lodash.flatten';
import interleaveCenterLoader from './interleaveCenterLoader';
jest.mock('@cornerstonejs/core', () => ({
cache: {
getVolume: jest.fn(),
},
imageLoadPoolManager: {
addRequest: jest.fn(),
},
Enums: {
RequestType: {
Prefetch: 'Prefetch',
},
},
}));
jest.mock('./getInterleavedFrames', () => jest.fn());
jest.mock('lodash.zip', () => jest.fn());
jest.mock('lodash.compact', () => jest.fn());
jest.mock('lodash.flatten', () => jest.fn());
describe('interleaveCenterLoader', () => {
const mockVolumeInput = {
volumeId: 'test-volume-id',
};
const mockVolume = {
metadata: {
SeriesInstanceUID: 'test-series-uid',
},
getImageLoadRequests: jest.fn(),
};
const mockImageLoadRequest = {
imageId: 'test-image-id',
callLoadImage: jest.fn(),
additionalDetails: 'test-details',
imageIdIndex: 0,
options: { test: 'option' },
};
const mockDisplaySetsInfo = [
{
displaySetInstanceUID: 'test-volume-id',
displaySetOptions: {},
},
];
const mockMatchDetails = new Map([
[
'viewport-1',
{
displaySetsInfo: mockDisplaySetsInfo,
},
],
]);
const defaultParameters = {
data: {
viewportId: 'test-viewport-id',
volumeInputArray: [mockVolumeInput],
},
displaySetsMatchDetails: {},
viewportMatchDetails: mockMatchDetails,
};
beforeEach(() => {
jest.clearAllMocks();
(cache.getVolume as jest.Mock).mockReturnValue(mockVolume);
mockVolume.getImageLoadRequests.mockReturnValue([mockImageLoadRequest]);
(getInterleavedFrames as jest.Mock).mockReturnValue([{ imageId: 'test-image-id' }]);
(zip as jest.Mock).mockImplementation((...args) => args[0]);
(compact as jest.Mock).mockImplementation(input => input?.filter(Boolean) ?? []);
(flatten as jest.Mock).mockImplementation(input => input?.flat() ?? []);
});
it('should store viewport and volume input array mapping on first call', () => {
interleaveCenterLoader(defaultParameters);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
});
it('should return early when volume is not found', () => {
(cache.getVolume as jest.Mock).mockReturnValue(null);
const result = interleaveCenterLoader(defaultParameters);
expect(result).toBeUndefined();
expect(mockVolume.getImageLoadRequests).not.toHaveBeenCalled();
});
it('should return early when volume input array size does not match display set UIDs', () => {
const mockMatchDetailsWithDifferentSize = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{ displaySetInstanceUID: 'display-set-1' },
{ displaySetInstanceUID: 'display-set-2' },
],
},
],
]);
const result = interleaveCenterLoader({
...defaultParameters,
viewportMatchDetails: mockMatchDetailsWithDifferentSize,
});
expect(result).toBeUndefined();
});
it('should process volume and create interleaved requests', () => {
const result = interleaveCenterLoader(defaultParameters);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
expect(mockVolume.getImageLoadRequests).toHaveBeenCalled();
expect(getInterleavedFrames).toHaveBeenCalledWith(['test-image-id']);
expect(zip).toHaveBeenCalled();
expect(compact).toHaveBeenCalled();
expect(flatten).toHaveBeenCalled();
expect(result).toBeInstanceOf(Map);
});
it('should add requests to imageLoadPoolManager with correct parameters', () => {
interleaveCenterLoader(defaultParameters);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledWith(
expect.any(Function),
Enums.RequestType.Prefetch,
mockImageLoadRequest.additionalDetails,
0
);
});
it('should bind callLoadImage with correct parameters', () => {
interleaveCenterLoader(defaultParameters);
const addRequestCall = (imageLoadPoolManager.addRequest as jest.Mock).mock.calls[0];
const boundFunction = addRequestCall[0];
boundFunction();
expect(mockImageLoadRequest.callLoadImage).toHaveBeenCalledWith(
mockImageLoadRequest.imageId,
mockImageLoadRequest.imageIdIndex,
mockImageLoadRequest.options
);
});
it('should handle volumes without image load requests', () => {
mockVolume.getImageLoadRequests.mockReturnValue([]);
const result = interleaveCenterLoader(defaultParameters);
expect(zip).toHaveBeenCalledWith();
expect(result).toBeInstanceOf(Map);
});
it('should handle volumes with empty requests array', () => {
mockVolume.getImageLoadRequests.mockReturnValue([]);
const result = interleaveCenterLoader(defaultParameters);
expect(getInterleavedFrames).not.toHaveBeenCalled();
expect(result).toBeInstanceOf(Map);
});
it('should handle volumes with null first request', () => {
mockVolume.getImageLoadRequests.mockReturnValue([null]);
const result = interleaveCenterLoader(defaultParameters);
expect(getInterleavedFrames).not.toHaveBeenCalled();
expect(result).toBeInstanceOf(Map);
});
it('should handle volumes with requests without imageId', () => {
const requestWithoutImageId = {
callLoadImage: jest.fn(),
additionalDetails: 'test-details',
};
mockVolume.getImageLoadRequests.mockReturnValue([requestWithoutImageId]);
const result = interleaveCenterLoader(defaultParameters);
expect(getInterleavedFrames).not.toHaveBeenCalled();
expect(result).toBeInstanceOf(Map);
});
it('should handle multiple volume inputs', () => {
const mockVolumeInput2 = {
volumeId: 'test-volume-id-2',
};
const mockVolume2 = {
metadata: {
SeriesInstanceUID: 'test-series-uid-2',
},
getImageLoadRequests: jest.fn().mockReturnValue([mockImageLoadRequest]),
};
(cache.getVolume as jest.Mock)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2);
const mockMatchDetailsWithMultipleVolumes = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{ displaySetInstanceUID: 'test-volume-id' },
{ displaySetInstanceUID: 'test-volume-id-2' },
],
},
],
]);
interleaveCenterLoader({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, mockVolumeInput2],
},
viewportMatchDetails: mockMatchDetailsWithMultipleVolumes,
});
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput2.volumeId);
});
it('should handle duplicate volume IDs by not adding them again', () => {
const duplicateVolumeInput = {
volumeId: mockVolumeInput.volumeId,
};
interleaveCenterLoader({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, duplicateVolumeInput],
},
});
expect(cache.getVolume).toHaveBeenCalledTimes(3);
});
it('should clear internal maps after processing', () => {
const firstResult = interleaveCenterLoader(defaultParameters);
const secondResult = interleaveCenterLoader(defaultParameters);
expect(firstResult).toBeInstanceOf(Map);
expect(secondResult).toBeInstanceOf(Map);
});
it('should handle multiple image load requests', () => {
const mockImageLoadRequest2 = {
imageId: 'test-image-id-2',
callLoadImage: jest.fn(),
additionalDetails: 'test-details-2',
imageIdIndex: 1,
options: { test: 'option2' },
};
mockVolume.getImageLoadRequests.mockReturnValue([mockImageLoadRequest, mockImageLoadRequest2]);
(getInterleavedFrames as jest.Mock).mockReturnValue([
{ imageId: 'test-image-id' },
{ imageId: 'test-image-id-2' },
]);
(compact as jest.Mock).mockReturnValue([mockImageLoadRequest, mockImageLoadRequest2]);
(flatten as jest.Mock).mockReturnValue([mockImageLoadRequest, mockImageLoadRequest2]);
interleaveCenterLoader(defaultParameters);
expect(getInterleavedFrames).toHaveBeenCalledWith(['test-image-id', 'test-image-id-2']);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledTimes(2);
});
it('should handle getInterleavedFrames returning empty array', () => {
(getInterleavedFrames as jest.Mock).mockReturnValue([]);
const result = interleaveCenterLoader(defaultParameters);
expect(imageLoadPoolManager.addRequest).not.toHaveBeenCalled();
expect(result).toBeInstanceOf(Map);
});
it('should handle zip returning empty result', () => {
(zip as jest.Mock).mockReturnValue([]);
const result = interleaveCenterLoader(defaultParameters);
expect(compact).toHaveBeenCalledWith([]);
expect(result).toBeInstanceOf(Map);
});
it('should handle compact filtering out null values', () => {
(compact as jest.Mock).mockReturnValue([]);
const result = interleaveCenterLoader(defaultParameters);
expect(imageLoadPoolManager.addRequest).not.toHaveBeenCalled();
expect(result).toBeInstanceOf(Map);
});
it('should map imageIds to requests correctly', () => {
const mockImageLoadRequest2 = {
imageId: 'test-image-id-2',
callLoadImage: jest.fn(),
};
mockVolume.getImageLoadRequests.mockReturnValue([mockImageLoadRequest, mockImageLoadRequest2]);
(getInterleavedFrames as jest.Mock).mockReturnValue([
{ imageId: 'test-image-id-2' },
{ imageId: 'test-image-id' },
]);
interleaveCenterLoader(defaultParameters);
expect(getInterleavedFrames).toHaveBeenCalledWith(['test-image-id', 'test-image-id-2']);
});
it('should return copy of viewport volume input array map', () => {
const result = interleaveCenterLoader(defaultParameters);
expect(result.has(defaultParameters.data.viewportId)).toBe(true);
expect(result.get(defaultParameters.data.viewportId)).toEqual(
defaultParameters.data.volumeInputArray
);
});
it('should handle multiple match details with different displaySets', () => {
const mockMultipleMatchDetails = new Map([
[
'viewport-1',
{
displaySetsInfo: [{ displaySetInstanceUID: 'display-set-1' }],
},
],
[
'viewport-2',
{
displaySetsInfo: [{ displaySetInstanceUID: 'display-set-2' }],
},
],
]);
const result = interleaveCenterLoader({
...defaultParameters,
viewportMatchDetails: mockMultipleMatchDetails,
});
expect(result).toBeUndefined();
});
it('should handle empty match details', () => {
const emptyMatchDetails = new Map();
const result = interleaveCenterLoader({
...defaultParameters,
viewportMatchDetails: emptyMatchDetails,
});
expect(result).toBeUndefined();
});
it('should handle empty volumeInputArray', () => {
const result = interleaveCenterLoader({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [],
},
});
expect(result).toBeUndefined();
});
it('should handle requests with missing properties gracefully', () => {
const incompleteRequest = {
imageId: 'test-image-id',
callLoadImage: jest.fn(),
};
mockVolume.getImageLoadRequests.mockReturnValue([incompleteRequest]);
(getInterleavedFrames as jest.Mock).mockReturnValue([{ imageId: 'test-image-id' }]);
(compact as jest.Mock).mockReturnValue([incompleteRequest]);
(flatten as jest.Mock).mockReturnValue([incompleteRequest]);
interleaveCenterLoader(defaultParameters);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledWith(
expect.any(Function),
Enums.RequestType.Prefetch,
undefined,
0
);
});
it('should process different viewport IDs separately', () => {
const firstResult = interleaveCenterLoader({
...defaultParameters,
data: {
viewportId: 'viewport-1',
volumeInputArray: [mockVolumeInput],
},
});
const secondResult = interleaveCenterLoader({
...defaultParameters,
data: {
viewportId: 'viewport-2',
volumeInputArray: [mockVolumeInput],
},
});
expect(firstResult.has('viewport-1')).toBe(true);
expect(secondResult.has('viewport-2')).toBe(true);
});
it('should accumulate state across multiple calls before processing', () => {
const mockVolumeInput2 = {
volumeId: 'test-volume-id-2',
};
const mockVolume2 = {
metadata: {
SeriesInstanceUID: 'test-series-uid-2',
},
getImageLoadRequests: jest.fn().mockReturnValue([mockImageLoadRequest]),
};
(cache.getVolume as jest.Mock)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2);
const firstCall = interleaveCenterLoader({
...defaultParameters,
data: {
viewportId: 'viewport-1',
volumeInputArray: [mockVolumeInput],
},
viewportMatchDetails: new Map([
[
'viewport-1',
{
displaySetsInfo: [
{ displaySetInstanceUID: 'test-volume-id' },
{ displaySetInstanceUID: 'missing-volume-id' },
],
},
],
]),
});
expect(firstCall).toBeUndefined();
const secondCall = interleaveCenterLoader({
...defaultParameters,
data: {
viewportId: 'viewport-2',
volumeInputArray: [mockVolumeInput2],
},
viewportMatchDetails: new Map([
[
'viewport-1',
{
displaySetsInfo: [{ displaySetInstanceUID: 'test-volume-id' }],
},
],
[
'viewport-2',
{
displaySetsInfo: [{ displaySetInstanceUID: 'test-volume-id-2' }],
},
],
]),
});
expect(secondCall).toBeInstanceOf(Map);
expect(secondCall.has('viewport-1')).toBe(true);
expect(secondCall.has('viewport-2')).toBe(true);
});
it('should handle volume with undefined metadata', () => {
const mockVolumeWithoutMetadata = {
getImageLoadRequests: jest.fn().mockReturnValue([]),
};
(cache.getVolume as jest.Mock).mockReturnValue(mockVolumeWithoutMetadata);
expect(() => interleaveCenterLoader(defaultParameters)).toThrow();
});
it('should handle multiple volumes with same image requests', () => {
const mockVolume2 = {
metadata: { SeriesInstanceUID: 'test-series-uid-2' },
getImageLoadRequests: jest.fn().mockReturnValue([mockImageLoadRequest]),
};
const mockVolumeInput2 = { volumeId: 'test-volume-id-2' };
(cache.getVolume as jest.Mock)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2);
const mockMatchDetailsWithBothVolumes = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{ displaySetInstanceUID: 'test-volume-id' },
{ displaySetInstanceUID: 'test-volume-id-2' },
],
},
],
]);
(compact as jest.Mock).mockReturnValue([mockImageLoadRequest]);
(flatten as jest.Mock).mockReturnValue([mockImageLoadRequest]);
interleaveCenterLoader({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, mockVolumeInput2],
},
viewportMatchDetails: mockMatchDetailsWithBothVolumes,
});
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledTimes(2);
});
});
@@ -1,487 +0,0 @@
import { cache, imageLoadPoolManager, Enums } from '@cornerstonejs/core';
import zip from 'lodash.zip';
import compact from 'lodash.compact';
import flatten from 'lodash.flatten';
import interleaveTopToBottom from './interleaveTopToBottom';
jest.mock('@cornerstonejs/core', () => ({
cache: {
getVolume: jest.fn(),
},
imageLoadPoolManager: {
addRequest: jest.fn(),
},
Enums: {
RequestType: {
Prefetch: 'Prefetch',
},
},
}));
jest.mock('lodash.zip', () => jest.fn());
jest.mock('lodash.compact', () => jest.fn());
jest.mock('lodash.flatten', () => jest.fn());
describe('interleaveTopToBottom', () => {
const mockVolumeInput = {
volumeId: 'test-volume-id',
};
const mockVolume = {
metadata: {
SeriesInstanceUID: 'test-series-uid',
},
getImageLoadRequests: jest.fn(),
};
const mockImageLoadRequest = {
imageId: 'test-image-id',
callLoadImage: jest.fn(),
additionalDetails: 'test-details',
imageIdIndex: 0,
options: { test: 'option' },
};
const mockDisplaySetsInfo = [
{
displaySetInstanceUID: 'display-set-1',
displaySetOptions: {},
},
];
const mockMatchDetails = new Map([
[
'viewport-1',
{
displaySetsInfo: mockDisplaySetsInfo,
},
],
]);
const defaultParameters = {
data: {
viewportId: 'test-viewport-id',
volumeInputArray: [mockVolumeInput],
},
displaySetsMatchDetails: {},
viewportMatchDetails: mockMatchDetails,
};
beforeEach(() => {
jest.clearAllMocks();
(cache.getVolume as jest.Mock).mockReturnValue(mockVolume);
mockVolume.getImageLoadRequests.mockReturnValue([mockImageLoadRequest]);
(zip as jest.Mock).mockImplementation((...args) => args[0]);
(compact as jest.Mock).mockImplementation(input => input?.filter(Boolean) ?? []);
(flatten as jest.Mock).mockImplementation(input => input?.flat() ?? []);
});
it('should store viewport and volume input array mapping', () => {
interleaveTopToBottom(defaultParameters);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
});
it('should return early when volume is not found', () => {
(cache.getVolume as jest.Mock).mockReturnValue(null);
const result = interleaveTopToBottom(defaultParameters);
expect(result).toBeUndefined();
expect(mockVolume.getImageLoadRequests).not.toHaveBeenCalled();
});
it('should process volume input array and create image load requests', () => {
const result = interleaveTopToBottom(defaultParameters);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
expect(mockVolume.getImageLoadRequests).toHaveBeenCalled();
expect(zip).toHaveBeenCalled();
expect(compact).toHaveBeenCalled();
expect(flatten).toHaveBeenCalled();
expect(result).toBeInstanceOf(Map);
});
it('should process displaySets without skipLoading option', () => {
const mockMatchDetailsWithoutSkipLoading = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{
displaySetInstanceUID: 'display-set-1',
displaySetOptions: { options: { skipLoading: false } },
},
],
},
],
]);
const result = interleaveTopToBottom({
...defaultParameters,
viewportMatchDetails: mockMatchDetailsWithoutSkipLoading,
});
expect(result).toBeInstanceOf(Map);
});
it('should return early when viewport volume display set UIDs size does not match', () => {
const mockMatchDetailsWithDifferentSize = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{
displaySetInstanceUID: 'display-set-1',
},
{
displaySetInstanceUID: 'display-set-2',
},
],
},
],
]);
const result = interleaveTopToBottom({
...defaultParameters,
viewportMatchDetails: mockMatchDetailsWithDifferentSize,
});
expect(result).toBeUndefined();
});
it('should handle multiple volume inputs', () => {
const mockVolumeInput2 = {
volumeId: 'test-volume-id-2',
};
const mockVolume2 = {
metadata: {
SeriesInstanceUID: 'test-series-uid-2',
},
getImageLoadRequests: jest.fn().mockReturnValue([mockImageLoadRequest]),
};
(cache.getVolume as jest.Mock)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2);
const mockMatchDetailsWithMultipleVolumes = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{ displaySetInstanceUID: 'test-volume-id' },
{ displaySetInstanceUID: 'test-volume-id-2' },
],
},
],
]);
interleaveTopToBottom({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, mockVolumeInput2],
},
viewportMatchDetails: mockMatchDetailsWithMultipleVolumes,
});
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput2.volumeId);
});
it('should add requests to imageLoadPoolManager with correct parameters', () => {
interleaveTopToBottom(defaultParameters);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledWith(
expect.any(Function),
Enums.RequestType.Prefetch,
mockImageLoadRequest.additionalDetails,
0
);
});
it('should bind callLoadImage with correct parameters', () => {
interleaveTopToBottom(defaultParameters);
const addRequestCall = (imageLoadPoolManager.addRequest as jest.Mock).mock.calls[0];
const boundFunction = addRequestCall[0];
boundFunction();
expect(mockImageLoadRequest.callLoadImage).toHaveBeenCalledWith(
mockImageLoadRequest.imageId,
mockImageLoadRequest.imageIdIndex,
mockImageLoadRequest.options
);
});
it('should handle volumes without image load requests', () => {
mockVolume.getImageLoadRequests.mockReturnValue(null);
const result = interleaveTopToBottom(defaultParameters);
expect(zip).toHaveBeenCalledWith();
expect(result).toBeInstanceOf(Map);
});
it('should handle volumes with empty image load requests', () => {
mockVolume.getImageLoadRequests.mockReturnValue([]);
const result = interleaveTopToBottom(defaultParameters);
expect(zip).toHaveBeenCalledWith();
expect(result).toBeInstanceOf(Map);
});
it('should handle volumes with requests without imageId', () => {
const requestWithoutImageId = {
callLoadImage: jest.fn(),
additionalDetails: 'test-details',
};
mockVolume.getImageLoadRequests.mockReturnValue([requestWithoutImageId]);
const result = interleaveTopToBottom(defaultParameters);
expect(zip).toHaveBeenCalledWith();
expect(result).toBeInstanceOf(Map);
});
it('should reverse the requests before adding to AllRequests', () => {
const mockRequests = [
{ imageId: 'image-1', callLoadImage: jest.fn() },
{ imageId: 'image-2', callLoadImage: jest.fn() },
];
mockVolume.getImageLoadRequests.mockReturnValue(mockRequests);
interleaveTopToBottom(defaultParameters);
expect(mockVolume.getImageLoadRequests).toHaveBeenCalled();
});
it('should handle duplicate volume IDs by not adding them again', () => {
const duplicateVolumeInput = {
volumeId: mockVolumeInput.volumeId,
};
interleaveTopToBottom({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, duplicateVolumeInput],
},
});
expect(cache.getVolume).toHaveBeenCalledTimes(3);
});
it('should clear internal maps after processing', () => {
const firstResult = interleaveTopToBottom(defaultParameters);
const secondResult = interleaveTopToBottom(defaultParameters);
expect(firstResult).toBeInstanceOf(Map);
expect(secondResult).toBeInstanceOf(Map);
});
it('should handle multiple match details with different displaySets', () => {
const mockMultipleMatchDetails = new Map([
[
'viewport-1',
{
displaySetsInfo: [{ displaySetInstanceUID: 'display-set-1' }],
},
],
[
'viewport-2',
{
displaySetsInfo: [{ displaySetInstanceUID: 'display-set-2' }],
},
],
]);
const result = interleaveTopToBottom({
...defaultParameters,
viewportMatchDetails: mockMultipleMatchDetails,
});
expect(result).toBeUndefined();
});
it('should handle undefined displaySetOptions', () => {
const mockMatchDetailsWithUndefinedOptions = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{
displaySetInstanceUID: 'display-set-1',
displaySetOptions: undefined,
},
],
},
],
]);
const result = interleaveTopToBottom({
...defaultParameters,
viewportMatchDetails: mockMatchDetailsWithUndefinedOptions,
});
expect(result).toBeInstanceOf(Map);
});
it('should handle displaySetOptions without options property', () => {
const mockMatchDetailsWithoutOptionsProperty = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{
displaySetInstanceUID: 'display-set-1',
displaySetOptions: { someOtherProperty: 'value' },
},
],
},
],
]);
const result = interleaveTopToBottom({
...defaultParameters,
viewportMatchDetails: mockMatchDetailsWithoutOptionsProperty,
});
expect(result).toBeInstanceOf(Map);
});
it('should handle empty match details', () => {
const emptyMatchDetails = new Map();
const result = interleaveTopToBottom({
...defaultParameters,
viewportMatchDetails: emptyMatchDetails,
});
expect(result).toBeUndefined();
});
it('should handle empty volumeInputArray', () => {
const result = interleaveTopToBottom({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [],
},
});
expect(result).toBeUndefined();
});
it('should handle interleavedRequests processing correctly', () => {
const mockRequest1 = { imageId: 'image-1', callLoadImage: jest.fn() };
const mockRequest2 = { imageId: 'image-2', callLoadImage: jest.fn() };
mockVolume.getImageLoadRequests.mockReturnValue([mockRequest1, mockRequest2]);
(compact as jest.Mock).mockReturnValue([mockRequest1, mockRequest2]);
(flatten as jest.Mock).mockReturnValue([mockRequest1, mockRequest2]);
interleaveTopToBottom(defaultParameters);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledTimes(2);
});
it('should return copy of viewport volume input array map', () => {
const result = interleaveTopToBottom(defaultParameters);
expect(result.has(defaultParameters.data.viewportId)).toBe(true);
expect(result.get(defaultParameters.data.viewportId)).toEqual(
defaultParameters.data.volumeInputArray
);
});
it('should process different viewport IDs separately', () => {
const firstResult = interleaveTopToBottom({
...defaultParameters,
data: {
viewportId: 'viewport-1',
volumeInputArray: [mockVolumeInput],
},
});
const secondResult = interleaveTopToBottom({
...defaultParameters,
data: {
viewportId: 'viewport-2',
volumeInputArray: [mockVolumeInput],
},
});
expect(firstResult.has('viewport-1')).toBe(true);
expect(secondResult.has('viewport-2')).toBe(true);
});
it('should handle requests with missing properties gracefully', () => {
const incompleteRequest = {
imageId: 'test-image-id',
callLoadImage: jest.fn(),
};
mockVolume.getImageLoadRequests.mockReturnValue([incompleteRequest]);
(compact as jest.Mock).mockReturnValue([incompleteRequest]);
(flatten as jest.Mock).mockReturnValue([incompleteRequest]);
interleaveTopToBottom(defaultParameters);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledWith(
expect.any(Function),
Enums.RequestType.Prefetch,
undefined,
0
);
});
it('should handle multiple volumes with same requests', () => {
const mockVolume2 = {
metadata: { SeriesInstanceUID: 'test-series-uid-2' },
getImageLoadRequests: jest.fn().mockReturnValue([mockImageLoadRequest]),
};
const mockVolumeInput2 = { volumeId: 'test-volume-id-2' };
(cache.getVolume as jest.Mock)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2);
const mockMatchDetailsWithBothVolumes = new Map([
[
'viewport-1',
{
displaySetsInfo: [
{ displaySetInstanceUID: 'test-volume-id' },
{ displaySetInstanceUID: 'test-volume-id-2' },
],
},
],
]);
(compact as jest.Mock).mockReturnValue([mockImageLoadRequest]);
(flatten as jest.Mock).mockReturnValue([mockImageLoadRequest]);
interleaveTopToBottom({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, mockVolumeInput2],
},
viewportMatchDetails: mockMatchDetailsWithBothVolumes,
});
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledTimes(2);
});
});
@@ -39,6 +39,31 @@ export default function interleaveTopToBottom({
}
}
const filteredMatchDetails = [];
const displaySetsToLoad = new Set();
// Check all viewports that have a displaySet to be loaded. In some cases
// (eg: line chart viewports which is not a Cornerstone viewport) the
// displaySet is created on the client and there are no instances to be
// downloaded. For those viewports the displaySet may have the `skipLoading`
// option set to true otherwise it may block the download of all other
// instances resulting in blank viewports.
Array.from(matchDetails.values()).forEach(curMatchDetails => {
const { displaySetsInfo } = curMatchDetails;
let numDisplaySetsToLoad = 0;
displaySetsInfo.forEach(({ displaySetInstanceUID, displaySetOptions }) => {
if (!displaySetOptions?.options?.skipLoading) {
numDisplaySetsToLoad++;
displaySetsToLoad.add(displaySetInstanceUID);
}
});
if (numDisplaySetsToLoad) {
filteredMatchDetails.push(curMatchDetails);
}
});
/**
* The following is checking if all the viewports that were matched in the HP has been
* successfully created their cornerstone viewport or not. Todo: This can be
@@ -1,340 +0,0 @@
import { isMeasurementWithinViewport } from './isMeasurementWithinViewport';
import { getCenterExtent } from './getCenterExtent';
jest.mock('./getCenterExtent', () => ({
getCenterExtent: jest.fn(),
}));
describe('isMeasurementWithinViewport', () => {
const mockViewport = {
getCamera: jest.fn(),
};
const mockMeasurement = {
points: [
[0, 0, 0],
[10, 10, 10],
],
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should return true when measurement is within viewport extent', () => {
const mockCamera = {
focalPoint: [5, 5, 5],
parallelScale: 15,
};
const mockExtent = {
extent: {
min: [0, 0, 0],
max: [10, 10, 10],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(mockViewport.getCamera).toHaveBeenCalled();
expect(getCenterExtent).toHaveBeenCalledWith(mockMeasurement);
expect(result).toBe(true);
});
it('should return false when measurement min point is outside viewport extent', () => {
const mockCamera = {
focalPoint: [5, 5, 5],
parallelScale: 4,
};
const mockExtent = {
extent: {
min: [0, 0, 0],
max: [10, 10, 10],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(false);
});
it('should return false when measurement max point is outside viewport extent', () => {
const mockCamera = {
focalPoint: [5, 5, 5],
parallelScale: 4,
};
const mockExtent = {
extent: {
min: [1, 1, 1],
max: [15, 15, 15],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(false);
});
it('should return false when min point exceeds parallelScale in first dimension', () => {
const mockCamera = {
focalPoint: [10, 5, 5],
parallelScale: 5,
};
const mockExtent = {
extent: {
min: [0, 0, 0],
max: [8, 8, 8],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(false);
});
it('should return false when max point exceeds parallelScale in second dimension', () => {
const mockCamera = {
focalPoint: [5, 5, 5],
parallelScale: 5,
};
const mockExtent = {
extent: {
min: [3, 3, 3],
max: [7, 12, 7],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(false);
});
it('should return false when max point exceeds parallelScale in third dimension', () => {
const mockCamera = {
focalPoint: [5, 5, 5],
parallelScale: 5,
};
const mockExtent = {
extent: {
min: [3, 3, 3],
max: [7, 7, 12],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(false);
});
it('should return true when measurement extent exactly matches parallelScale', () => {
const mockCamera = {
focalPoint: [5, 5, 5],
parallelScale: 5,
};
const mockExtent = {
extent: {
min: [0, 0, 0],
max: [10, 10, 10],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(true);
});
it('should handle negative coordinates correctly', () => {
const mockCamera = {
focalPoint: [-5, -5, -5],
parallelScale: 10,
};
const mockExtent = {
extent: {
min: [-10, -10, -10],
max: [0, 0, 0],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(true);
});
it('should handle zero parallelScale', () => {
const mockCamera = {
focalPoint: [0, 0, 0],
parallelScale: 0,
};
const mockExtent = {
extent: {
min: [0, 0, 0],
max: [0, 0, 0],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(true);
});
it('should handle zero parallelScale with non-zero extent', () => {
const mockCamera = {
focalPoint: [0, 0, 0],
parallelScale: 0,
};
const mockExtent = {
extent: {
min: [0, 0, 0],
max: [1, 1, 1],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(false);
});
it('should handle large parallelScale values', () => {
const mockCamera = {
focalPoint: [0, 0, 0],
parallelScale: 1000000,
};
const mockExtent = {
extent: {
min: [-500, -500, -500],
max: [500, 500, 500],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(true);
});
it('should handle floating point coordinates', () => {
const mockCamera = {
focalPoint: [1.5, 2.7, 3.1],
parallelScale: 5.5,
};
const mockExtent = {
extent: {
min: [0.1, 0.2, 0.3],
max: [2.9, 5.2, 5.9],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(true);
});
it('should fail when min point distance equals parallelScale plus epsilon', () => {
const mockCamera = {
focalPoint: [5, 5, 5],
parallelScale: 5,
};
const mockExtent = {
extent: {
min: [-0.1, 0, 0],
max: [10, 10, 10],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(false);
});
it('should pass when extent is within parallelScale boundary', () => {
const mockCamera = {
focalPoint: [5, 5, 5],
parallelScale: 6,
};
const mockExtent = {
extent: {
min: [-1, -1, -1],
max: [11, 11, 11],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(true);
});
it('should handle asymmetric extent around focal point', () => {
const mockCamera = {
focalPoint: [10, 10, 10],
parallelScale: 8,
};
const mockExtent = {
extent: {
min: [5, 5, 5],
max: [18, 18, 18],
},
};
mockViewport.getCamera.mockReturnValue(mockCamera);
(getCenterExtent as jest.Mock).mockReturnValue(mockExtent);
const result = isMeasurementWithinViewport(mockViewport, mockMeasurement);
expect(result).toBe(true);
});
});
@@ -1,33 +0,0 @@
import { getCenterExtent } from './getCenterExtent';
/**
* Determines if a measurement is within the current viewport extent.
* Uses the measurement's bounding box extent to efficiently check if the entire
* measurement fits within the viewport's visible area.
*
* @param {Object} viewport - The viewport object containing camera information
* @param {Object} measurement - The measurement object containing points to check
* @returns {boolean} True if the measurement extent is within the viewport, false otherwise
*/
export const isMeasurementWithinViewport = (viewport, measurement) => {
const camera = viewport.getCamera();
const { focalPoint, parallelScale } = camera;
// Get the measurement's bounding box extent
const { extent } = getCenterExtent(measurement);
const { min, max } = extent;
// Check if the entire bounding box fits within the viewport extent
// We need to check both the min and max corners of the bounding box
for (let i = 0; i < 3; i++) {
const minDistance = Math.abs(min[i] - focalPoint[i]);
const maxDistance = Math.abs(max[i] - focalPoint[i]);
// If either the min or max point is outside the viewport extent, return false
if (minDistance > parallelScale || maxDistance > parallelScale) {
return false;
}
}
return true; // The entire measurement extent is within the viewport
};
@@ -1,581 +0,0 @@
import { vec3 } from 'gl-matrix';
import { Enums } from '@cornerstonejs/core';
import getClosestOrientationFromIOP, { isReferenceViewable } from './isReferenceViewable';
jest.mock('gl-matrix', () => ({
vec3: {
fromValues: jest.fn(),
cross: jest.fn(),
dot: jest.fn(),
create: jest.fn(),
},
}));
jest.mock('@cornerstonejs/core', () => ({
Enums: {
OrientationAxis: {
AXIAL: 'axial',
CORONAL: 'coronal',
SAGITTAL: 'sagittal',
},
},
}));
describe('isReferenceViewable', () => {
const mockCornerstoneViewportService = {
getCornerstoneViewport: jest.fn(),
};
const mockDisplaySetService = {
getDisplaySetByUID: jest.fn(),
};
const mockServicesManager = {
services: {
cornerstoneViewportService: mockCornerstoneViewportService,
displaySetService: mockDisplaySetService,
},
};
const mockViewport = {
isReferenceViewable: jest.fn(),
};
const mockReference = {
displaySetInstanceUID: 'test-display-set-uid',
referencedImageId: 'test-image-id',
};
const defaultParameters = {
servicesManager: mockServicesManager,
viewportId: 'test-viewport-id',
reference: mockReference,
viewportOptions: undefined,
};
beforeEach(() => {
jest.clearAllMocks();
mockCornerstoneViewportService.getCornerstoneViewport.mockReturnValue(mockViewport);
mockViewport.isReferenceViewable.mockReturnValue(true);
});
it('should return viewport isReferenceViewable result when viewportOptions is undefined', () => {
mockViewport.isReferenceViewable.mockReturnValue(true);
const result = isReferenceViewable(
defaultParameters.servicesManager,
defaultParameters.viewportId,
defaultParameters.reference
);
expect(mockCornerstoneViewportService.getCornerstoneViewport).toHaveBeenCalledWith(
defaultParameters.viewportId
);
expect(mockViewport.isReferenceViewable).toHaveBeenCalledWith(mockReference, {
withNavigation: true,
asVolume: true,
});
expect(result).toBe(true);
});
it('should return false when viewport isReferenceViewable returns false', () => {
mockViewport.isReferenceViewable.mockReturnValue(false);
const result = isReferenceViewable(
defaultParameters.servicesManager,
defaultParameters.viewportId,
defaultParameters.reference
);
expect(result).toBe(false);
});
it('should check imageIds inclusion for stack viewport type', () => {
const mockDisplaySet = {
instances: [{ imageId: 'image-1' }, { imageId: 'test-image-id' }, { imageId: 'image-3' }],
};
const viewportOptions = {
viewportType: 'stack',
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const result = isReferenceViewable(
defaultParameters.servicesManager,
defaultParameters.viewportId,
defaultParameters.reference,
viewportOptions
);
expect(mockDisplaySetService.getDisplaySetByUID).toHaveBeenCalledWith(
mockReference.displaySetInstanceUID
);
expect(result).toBe(true);
});
it('should return false when imageId is not in stack', () => {
const mockDisplaySet = {
instances: [{ imageId: 'image-1' }, { imageId: 'image-2' }, { imageId: 'image-3' }],
};
const viewportOptions = {
viewportType: 'stack',
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const result = isReferenceViewable(
defaultParameters.servicesManager,
defaultParameters.viewportId,
defaultParameters.reference,
viewportOptions
);
expect(result).toBe(false);
});
it('should check orientation for volume viewport type', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [1, 0, 0, 0, 1, 0],
},
],
};
const viewportOptions = {
viewportType: 'volume',
orientation: 'axial',
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [1, 0, 0];
const mockColCosineVec = [0, 1, 0];
const mockScanAxisNormal = [0, 0, 1];
const mockAxialVector = [0, 0, 1];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec)
.mockReturnValueOnce(mockAxialVector);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock).mockReturnValue(1);
const result = isReferenceViewable(
defaultParameters.servicesManager,
defaultParameters.viewportId,
defaultParameters.reference,
viewportOptions
);
expect(result).toBe(true);
});
it('should return false when orientation does not match', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [1, 0, 0, 0, 1, 0],
},
],
};
const viewportOptions = {
viewportType: 'volume',
orientation: 'coronal',
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [1, 0, 0];
const mockColCosineVec = [0, 1, 0];
const mockScanAxisNormal = [0, 0, 1];
const mockAxialVector = [0, 0, 1];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec)
.mockReturnValueOnce(mockAxialVector);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock).mockReturnValue(1);
const result = isReferenceViewable(
defaultParameters.servicesManager,
defaultParameters.viewportId,
defaultParameters.reference,
viewportOptions
);
expect(result).toBe(false);
});
it('should handle empty instances array for stack viewport', () => {
const mockDisplaySet = {
instances: [],
};
const viewportOptions = {
viewportType: 'stack',
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const result = isReferenceViewable(
defaultParameters.servicesManager,
defaultParameters.viewportId,
defaultParameters.reference,
viewportOptions
);
expect(result).toBe(false);
});
});
describe('getClosestOrientationFromIOP', () => {
const mockDisplaySetService = {
getDisplaySetByUID: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should return undefined when ImageOrientationPatient is not length 6', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [1, 0, 0, 0, 1],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBeUndefined();
});
it('should return undefined when ImageOrientationPatient is undefined', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: undefined,
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBeUndefined();
});
it('should return undefined when ImageOrientationPatient is null', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: null,
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBeUndefined();
});
it('should return axial orientation for axial-aligned IOP', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [1, 0, 0, 0, 1, 0],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [1, 0, 0];
const mockColCosineVec = [0, 1, 0];
const mockScanAxisNormal = [0, 0, 1];
const mockAxialVector = [0, 0, 1];
const mockCoronalVector = [0, 1, 0];
const mockSagittalVector = [1, 0, 0];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec)
.mockReturnValueOnce(mockAxialVector)
.mockReturnValueOnce(mockCoronalVector)
.mockReturnValueOnce(mockSagittalVector);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock)
.mockReturnValueOnce(1.0)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(0.0);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBe(Enums.OrientationAxis.AXIAL);
});
it('should return coronal orientation for coronal-aligned IOP', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [1, 0, 0, 0, 0, -1],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [1, 0, 0];
const mockColCosineVec = [0, 0, -1];
const mockScanAxisNormal = [0, 1, 0];
const mockAxialVector = [0, 0, 1];
const mockCoronalVector = [0, 1, 0];
const mockSagittalVector = [1, 0, 0];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec)
.mockReturnValueOnce(mockAxialVector)
.mockReturnValueOnce(mockCoronalVector)
.mockReturnValueOnce(mockSagittalVector);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(1.0)
.mockReturnValueOnce(0.0);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBe(Enums.OrientationAxis.CORONAL);
});
it('should return sagittal orientation for sagittal-aligned IOP', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [0, 1, 0, 0, 0, -1],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [0, 1, 0];
const mockColCosineVec = [0, 0, -1];
const mockScanAxisNormal = [1, 0, 0];
const mockAxialVector = [0, 0, 1];
const mockCoronalVector = [0, 1, 0];
const mockSagittalVector = [1, 0, 0];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec)
.mockReturnValueOnce(mockAxialVector)
.mockReturnValueOnce(mockCoronalVector)
.mockReturnValueOnce(mockSagittalVector);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(1.0);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBe(Enums.OrientationAxis.SAGITTAL);
});
it('should handle negative dot products and return correct orientation', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [1, 0, 0, 0, -1, 0],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [1, 0, 0];
const mockColCosineVec = [0, -1, 0];
const mockScanAxisNormal = [0, 0, -1];
const mockAxialVector = [0, 0, 1];
const mockCoronalVector = [0, 1, 0];
const mockSagittalVector = [1, 0, 0];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec)
.mockReturnValueOnce(mockAxialVector)
.mockReturnValueOnce(mockCoronalVector)
.mockReturnValueOnce(mockSagittalVector);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock)
.mockReturnValueOnce(-1.0)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(0.0);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBe(Enums.OrientationAxis.AXIAL);
});
it('should handle oblique orientations and return closest match', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [0.7071, 0.7071, 0, 0, 0, 1],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [0.7071, 0.7071, 0];
const mockColCosineVec = [0, 0, 1];
const mockScanAxisNormal = [0.7071, -0.7071, 0];
const mockAxialVector = [0, 0, 1];
const mockCoronalVector = [0, 1, 0];
const mockSagittalVector = [1, 0, 0];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec)
.mockReturnValueOnce(mockAxialVector)
.mockReturnValueOnce(mockCoronalVector)
.mockReturnValueOnce(mockSagittalVector);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(0.7071)
.mockReturnValueOnce(0.7071);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBe(Enums.OrientationAxis.CORONAL);
});
it('should handle zero dot products', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [1, 0, 0, 0, 1, 0],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [1, 0, 0];
const mockColCosineVec = [0, 1, 0];
const mockScanAxisNormal = [0, 0, 1];
const mockAxialVector = [0, 0, 1];
const mockCoronalVector = [0, 1, 0];
const mockSagittalVector = [1, 0, 0];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec)
.mockReturnValueOnce(mockAxialVector)
.mockReturnValueOnce(mockCoronalVector)
.mockReturnValueOnce(mockSagittalVector);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(0.0);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(result).toBe('');
});
it('should call vec3 functions with correct parameters', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [1, 0, 0, 0, 1, 0],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [1, 0, 0];
const mockColCosineVec = [0, 1, 0];
const mockCreatedVec = [];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec);
(vec3.create as jest.Mock).mockReturnValue(mockCreatedVec);
(vec3.cross as jest.Mock).mockReturnValue([0, 0, 1]);
(vec3.dot as jest.Mock).mockReturnValue(1.0);
getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(vec3.fromValues).toHaveBeenNthCalledWith(1, 1, 0, 0);
expect(vec3.fromValues).toHaveBeenNthCalledWith(2, 0, 1, 0);
expect(vec3.cross).toHaveBeenCalledWith(mockCreatedVec, mockRowCosineVec, mockColCosineVec);
});
it('should handle floating point IOP values', () => {
const mockDisplaySet = {
instances: [
{
ImageOrientationPatient: [0.866, 0.5, 0, -0.5, 0.866, 0],
},
],
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
const mockRowCosineVec = [0.866, 0.5, 0];
const mockColCosineVec = [-0.5, 0.866, 0];
const mockScanAxisNormal = [0, 0, 1];
(vec3.fromValues as jest.Mock)
.mockReturnValueOnce(mockRowCosineVec)
.mockReturnValueOnce(mockColCosineVec);
(vec3.create as jest.Mock).mockReturnValue([]);
(vec3.cross as jest.Mock).mockReturnValue(mockScanAxisNormal);
(vec3.dot as jest.Mock)
.mockReturnValueOnce(1.0)
.mockReturnValueOnce(0.0)
.mockReturnValueOnce(0.0);
const result = getClosestOrientationFromIOP(mockDisplaySetService, 'test-display-set-uid');
expect(vec3.fromValues).toHaveBeenNthCalledWith(1, 0.866, 0.5, 0);
expect(vec3.fromValues).toHaveBeenNthCalledWith(2, -0.5, 0.866, 0);
expect(result).toBe(Enums.OrientationAxis.AXIAL);
});
});
@@ -1,317 +0,0 @@
import { cache, imageLoadPoolManager, Enums as csEnums } from '@cornerstonejs/core';
import interleaveNthLoader from './nthLoader';
import getNthFrames from './getNthFrames';
import interleave from './interleave';
jest.mock('@cornerstonejs/core', () => ({
...jest.requireActual('@cornerstonejs/core'),
cache: {
getVolume: jest.fn(),
},
imageLoadPoolManager: {
addRequest: jest.fn(),
},
}));
jest.mock('./getNthFrames', () => jest.fn());
jest.mock('./interleave', () => jest.fn());
describe('interleaveNthLoader', () => {
const mockVolumeInput = {
volumeId: 'test-volume-id',
};
const mockVolume = {
metadata: {
SeriesInstanceUID: 'test-series-uid',
},
getImageLoadRequests: jest.fn(),
};
const mockImageLoadRequest = {
imageId: 'test-image-id',
callLoadImage: jest.fn(),
additionalDetails: 'test-details',
imageIdIndex: 0,
options: { test: 'option' },
};
const defaultParameters = {
data: {
viewportId: 'test-viewport-id',
volumeInputArray: [mockVolumeInput],
},
displaySetsMatchDetails: {},
};
beforeEach(() => {
jest.clearAllMocks();
(cache.getVolume as jest.Mock).mockReturnValue(mockVolume);
mockVolume.getImageLoadRequests.mockReturnValue([mockImageLoadRequest]);
(getNthFrames as jest.Mock).mockImplementation(input => input);
(interleave as jest.Mock).mockImplementation(input => input.flat());
});
it('should store viewport and volume input array mapping', () => {
interleaveNthLoader(defaultParameters);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
});
it('should return early when volume is not found', () => {
(cache.getVolume as jest.Mock).mockReturnValue(null);
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const result = interleaveNthLoader(defaultParameters);
expect(consoleSpy).toHaveBeenCalledWith("interleaveNthLoader::No volume, can't load it");
expect(result).toBeUndefined();
expect(mockVolume.getImageLoadRequests).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
it('should process volume input array and create image load requests', () => {
const result = interleaveNthLoader(defaultParameters);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
expect(mockVolume.getImageLoadRequests).toHaveBeenCalled();
expect(getNthFrames).toHaveBeenCalledWith([mockImageLoadRequest]);
expect(interleave).toHaveBeenCalledWith([[mockImageLoadRequest]]);
expect(result).toBeInstanceOf(Map);
});
it('should add requests to imageLoadPoolManager with correct parameters', () => {
interleaveNthLoader(defaultParameters);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledWith(
expect.any(Function),
csEnums.RequestType.Prefetch,
mockImageLoadRequest.additionalDetails,
0
);
});
it('should handle multiple volume inputs', () => {
const mockVolumeInput2 = {
volumeId: 'test-volume-id-2',
};
const mockVolume2 = {
metadata: {
SeriesInstanceUID: 'test-series-uid-2',
},
getImageLoadRequests: jest.fn().mockReturnValue([mockImageLoadRequest]),
};
(cache.getVolume as jest.Mock)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2)
.mockReturnValueOnce(mockVolume)
.mockReturnValueOnce(mockVolume2);
interleaveNthLoader({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, mockVolumeInput2],
},
});
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput2.volumeId);
expect(mockVolume.getImageLoadRequests).toHaveBeenCalled();
expect(mockVolume2.getImageLoadRequests).toHaveBeenCalled();
});
it('should handle volumes without getImageLoadRequests method', () => {
const mockVolumeWithoutMethod = {
metadata: {
SeriesInstanceUID: 'test-series-uid',
},
};
(cache.getVolume as jest.Mock)
.mockReturnValueOnce(mockVolumeWithoutMethod)
.mockReturnValueOnce(mockVolumeWithoutMethod);
interleaveNthLoader(defaultParameters);
expect(getNthFrames).not.toHaveBeenCalled();
expect(interleave).toHaveBeenCalledWith([]);
});
it('should handle volumes with null getImageLoadRequests return', () => {
mockVolume.getImageLoadRequests.mockReturnValue(null);
interleaveNthLoader(defaultParameters);
expect(getNthFrames).not.toHaveBeenCalled();
expect(interleave).toHaveBeenCalledWith([]);
});
it('should filter out requests without imageId in first request', () => {
const requestWithoutImageId = {
callLoadImage: jest.fn(),
additionalDetails: 'test-details',
};
mockVolume.getImageLoadRequests
.mockReturnValueOnce([requestWithoutImageId, mockImageLoadRequest])
.mockReturnValueOnce([mockImageLoadRequest]);
interleaveNthLoader({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, { volumeId: 'test-volume-id-2' }],
},
});
expect(getNthFrames).toHaveBeenCalledWith([mockImageLoadRequest]);
});
it('should handle empty image load requests', () => {
mockVolume.getImageLoadRequests.mockReturnValue([]);
interleaveNthLoader(defaultParameters);
expect(getNthFrames).not.toHaveBeenCalled();
expect(interleave).toHaveBeenCalledWith([]);
});
it('should bind callLoadImage with correct parameters', () => {
interleaveNthLoader(defaultParameters);
const addRequestCall = (imageLoadPoolManager.addRequest as jest.Mock).mock.calls[0];
const boundFunction = addRequestCall[0];
boundFunction();
expect(mockImageLoadRequest.callLoadImage).toHaveBeenCalledWith(
mockImageLoadRequest.imageId,
mockImageLoadRequest.imageIdIndex,
mockImageLoadRequest.options
);
});
it('should handle duplicate volume IDs by not adding them again', () => {
const duplicateVolumeInput = {
volumeId: mockVolumeInput.volumeId,
};
interleaveNthLoader({
...defaultParameters,
data: {
...defaultParameters.data,
volumeInputArray: [mockVolumeInput, duplicateVolumeInput],
},
});
expect(cache.getVolume).toHaveBeenCalledTimes(3);
expect(cache.getVolume).toHaveBeenCalledWith(mockVolumeInput.volumeId);
});
it('should clear internal maps after processing', () => {
const firstResult = interleaveNthLoader(defaultParameters);
const secondResult = interleaveNthLoader(defaultParameters);
expect(firstResult).toBeInstanceOf(Map);
expect(secondResult).toBeInstanceOf(Map);
expect(firstResult.get(defaultParameters.data.viewportId)).toEqual(
defaultParameters.data.volumeInputArray
);
});
it('should handle multiple image load requests', () => {
const mockImageLoadRequest2 = {
imageId: 'test-image-id-2',
callLoadImage: jest.fn(),
additionalDetails: 'test-details-2',
imageIdIndex: 1,
options: { test: 'option2' },
};
mockVolume.getImageLoadRequests.mockReturnValue([mockImageLoadRequest, mockImageLoadRequest2]);
(interleave as jest.Mock).mockReturnValue([mockImageLoadRequest, mockImageLoadRequest2]);
interleaveNthLoader(defaultParameters);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledTimes(2);
expect(imageLoadPoolManager.addRequest).toHaveBeenNthCalledWith(
1,
expect.any(Function),
csEnums.RequestType.Prefetch,
mockImageLoadRequest.additionalDetails,
0
);
expect(imageLoadPoolManager.addRequest).toHaveBeenNthCalledWith(
2,
expect.any(Function),
csEnums.RequestType.Prefetch,
mockImageLoadRequest2.additionalDetails,
0
);
});
it('should handle volume with undefined metadata', () => {
const mockVolumeWithoutMetadata = {
getImageLoadRequests: jest.fn().mockReturnValue([]),
};
(cache.getVolume as jest.Mock).mockReturnValueOnce(mockVolumeWithoutMetadata);
expect(() => interleaveNthLoader(defaultParameters)).toThrow();
});
it('should return copy of viewport volume input array map', () => {
const result = interleaveNthLoader(defaultParameters);
expect(result.has(defaultParameters.data.viewportId)).toBe(true);
expect(result.get(defaultParameters.data.viewportId)).toEqual(
defaultParameters.data.volumeInputArray
);
});
it('should handle requests with missing properties gracefully', () => {
const incompleteRequest = {
imageId: 'test-image-id',
callLoadImage: jest.fn(),
};
mockVolume.getImageLoadRequests.mockReturnValue([incompleteRequest]);
(interleave as jest.Mock).mockReturnValue([incompleteRequest]);
interleaveNthLoader(defaultParameters);
expect(imageLoadPoolManager.addRequest).toHaveBeenCalledWith(
expect.any(Function),
csEnums.RequestType.Prefetch,
undefined,
0
);
});
it('should process different viewport IDs separately', () => {
const firstResult = interleaveNthLoader({
...defaultParameters,
data: {
viewportId: 'viewport-1',
volumeInputArray: [mockVolumeInput],
},
});
const secondResult = interleaveNthLoader({
...defaultParameters,
data: {
viewportId: 'viewport-2',
volumeInputArray: [mockVolumeInput],
},
});
expect(firstResult.has('viewport-1')).toBe(true);
expect(secondResult.has('viewport-2')).toBe(true);
expect(firstResult.has('viewport-2')).toBe(false);
});
});
@@ -1,490 +0,0 @@
import promptHydrationDialog, { HydrationType } from './promptHydrationDialog';
describe('promptHydrationDialog', () => {
const mockUIViewportDialogService = {
show: jest.fn(),
hide: jest.fn(),
};
const mockCustomizationService = {
getCustomization: jest.fn(),
};
const mockExtensionManager = {
_appConfig: {
measurementTrackingMode: 'standard',
disableConfirmationPrompts: false,
},
};
const mockServicesManager = {
services: {
uiViewportDialogService: mockUIViewportDialogService,
customizationService: mockCustomizationService,
},
_extensionManager: mockExtensionManager,
};
const mockDisplaySet = {
displaySetInstanceUID: 'test-display-set-uid',
SeriesInstanceUID: 'test-series-uid',
};
const mockHydrateCallback = jest.fn();
const mockPreHydrateCallback = jest.fn();
const defaultParameters = {
servicesManager: mockServicesManager as unknown as AppTypes.ServicesManager,
viewportId: 'test-viewport-id',
displaySet: mockDisplaySet as unknown as AppTypes.DisplaySet,
preHydrateCallbacks: [mockPreHydrateCallback],
hydrateCallback: mockHydrateCallback,
type: HydrationType.SEG,
};
beforeEach(() => {
jest.clearAllMocks();
mockCustomizationService.getCustomization.mockReturnValue('Test message');
mockHydrateCallback.mockResolvedValue(true);
jest.spyOn(window, 'setTimeout').mockImplementation((callback: () => void) => {
callback();
return 0 as unknown as NodeJS.Timeout;
});
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should return HYDRATE response when disableConfirmationPrompts is true for non-SR types', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
const result = await promptHydrationDialog(defaultParameters);
expect(mockUIViewportDialogService.show).not.toHaveBeenCalled();
expect(mockPreHydrateCallback).toHaveBeenCalled();
expect(mockHydrateCallback).toHaveBeenCalledWith({
segDisplaySet: mockDisplaySet,
viewportId: defaultParameters.viewportId,
});
expect(result).toBe(true);
});
it('should show dialog when disableConfirmationPrompts is false for non-SR types', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
expect(mockUIViewportDialogService.show).toHaveBeenCalledWith({
id: 'promptHydrateSEG',
viewportId: defaultParameters.viewportId,
type: 'info',
message: 'Test message',
actions: [
{
id: 'no-hydrate',
type: 'secondary',
text: 'No',
value: 0,
},
{
id: 'yes-hydrate',
type: 'primary',
text: 'Yes',
value: 5,
},
],
onSubmit: expect.any(Function),
onOutsideClick: expect.any(Function),
onKeyPress: expect.any(Function),
});
const onSubmit = mockUIViewportDialogService.show.mock.calls[0][0].onSubmit;
onSubmit(5);
const result = await promise;
expect(result).toBe(true);
});
it('should handle CANCEL response', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
const onSubmit = mockUIViewportDialogService.show.mock.calls[0][0].onSubmit;
onSubmit(0);
const result = await promise;
expect(result).toBe(false);
expect(mockHydrateCallback).not.toHaveBeenCalled();
});
it('should handle onOutsideClick', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
const result = await promise;
expect(result).toBe(false);
expect(mockUIViewportDialogService.hide).toHaveBeenCalled();
});
it('should handle onKeyPress Enter', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
const onKeyPress = mockUIViewportDialogService.show.mock.calls[0][0].onKeyPress;
onKeyPress({ key: 'Enter' });
const result = await promise;
expect(result).toBe(true);
expect(mockUIViewportDialogService.hide).toHaveBeenCalled();
});
it('should handle onKeyPress non-Enter key', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
const onKeyPress = mockUIViewportDialogService.show.mock.calls[0][0].onKeyPress;
onKeyPress({ key: 'Escape' });
expect(mockUIViewportDialogService.hide).not.toHaveBeenCalled();
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
const result = await promise;
expect(result).toBe(false);
});
it('should handle SEG type hydration with setTimeout', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
await promptHydrationDialog({
...defaultParameters,
type: HydrationType.SEG,
});
expect(window.setTimeout).toHaveBeenCalledWith(expect.any(Function), 0);
expect(mockHydrateCallback).toHaveBeenCalledWith({
segDisplaySet: mockDisplaySet,
viewportId: defaultParameters.viewportId,
});
});
it('should handle RTSTRUCT type hydration', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
const result = await promptHydrationDialog({
...defaultParameters,
type: HydrationType.RTSTRUCT,
});
expect(mockHydrateCallback).toHaveBeenCalledWith({
rtDisplaySet: mockDisplaySet,
viewportId: defaultParameters.viewportId,
servicesManager: mockServicesManager,
});
expect(result).toBe(true);
});
it('should handle SR type hydration when standardMode is true', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
const mockHydrationResult = {
StudyInstanceUID: 'test-study-uid',
SeriesInstanceUIDs: ['series-1', 'series-2'],
};
mockHydrateCallback.mockResolvedValue(mockHydrationResult);
const promise = promptHydrationDialog({
...defaultParameters,
type: HydrationType.SR,
});
const onSubmit = mockUIViewportDialogService.show.mock.calls[0][0].onSubmit;
onSubmit(5);
const result = await promise;
expect(result).toEqual({
userResponse: 5,
displaySetInstanceUID: mockDisplaySet.displaySetInstanceUID,
srSeriesInstanceUID: mockDisplaySet.SeriesInstanceUID,
viewportId: defaultParameters.viewportId,
StudyInstanceUID: mockHydrationResult.StudyInstanceUID,
SeriesInstanceUIDs: mockHydrationResult.SeriesInstanceUIDs,
});
});
it('should handle SR type hydration when standardMode is false', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'other';
const result = await promptHydrationDialog({
...defaultParameters,
type: HydrationType.SR,
});
expect(mockUIViewportDialogService.show).not.toHaveBeenCalled();
expect(result).toEqual({
userResponse: 5,
displaySetInstanceUID: mockDisplaySet.displaySetInstanceUID,
srSeriesInstanceUID: mockDisplaySet.SeriesInstanceUID,
viewportId: defaultParameters.viewportId,
StudyInstanceUID: undefined,
SeriesInstanceUIDs: undefined,
});
});
it('should handle SR type with CANCEL response', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
const promise = promptHydrationDialog({
...defaultParameters,
type: HydrationType.SR,
});
const onSubmit = mockUIViewportDialogService.show.mock.calls[0][0].onSubmit;
onSubmit(0);
const result = await promise;
expect(result).toEqual({
userResponse: 0,
displaySetInstanceUID: mockDisplaySet.displaySetInstanceUID,
srSeriesInstanceUID: mockDisplaySet.SeriesInstanceUID,
viewportId: defaultParameters.viewportId,
});
});
it('should handle undefined preHydrateCallbacks', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
await promptHydrationDialog({
...defaultParameters,
preHydrateCallbacks: undefined,
});
expect(mockHydrateCallback).toHaveBeenCalled();
});
it('should handle empty preHydrateCallbacks array', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
await promptHydrationDialog({
...defaultParameters,
preHydrateCallbacks: [],
});
expect(mockHydrateCallback).toHaveBeenCalled();
});
it('should execute multiple preHydrateCallbacks', async () => {
const mockPreHydrateCallback2 = jest.fn();
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
await promptHydrationDialog({
...defaultParameters,
preHydrateCallbacks: [mockPreHydrateCallback, mockPreHydrateCallback2],
});
expect(mockPreHydrateCallback).toHaveBeenCalled();
expect(mockPreHydrateCallback2).toHaveBeenCalled();
});
it('should get correct customization message key for SEG type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
type: HydrationType.SEG,
});
expect(mockCustomizationService.getCustomization).toHaveBeenCalledWith(
'viewportNotification.hydrateSEGMessage'
);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
await promise;
});
it('should get correct customization message key for RTSTRUCT type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
type: HydrationType.RTSTRUCT,
});
expect(mockCustomizationService.getCustomization).toHaveBeenCalledWith(
'viewportNotification.hydrateRTMessage'
);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
await promise;
});
it('should get correct customization message key for SR type', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
const promise = promptHydrationDialog({
...defaultParameters,
type: HydrationType.SR,
});
expect(mockCustomizationService.getCustomization).toHaveBeenCalledWith(
'viewportNotification.hydrateSRMessage'
);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
await promise;
});
it('should get default customization message key for unknown type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
type: 'UNKNOWN_TYPE',
});
expect(mockCustomizationService.getCustomization).toHaveBeenCalledWith(
'viewportNotification.hydrateMessage'
);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
await promise;
});
it('should get correct dialog id for SEG type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
type: HydrationType.SEG,
});
expect(mockUIViewportDialogService.show).toHaveBeenCalledWith(
expect.objectContaining({
id: 'promptHydrateSEG',
})
);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
await promise;
});
it('should get correct dialog id for SR type', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
const promise = promptHydrationDialog({
...defaultParameters,
type: HydrationType.SR,
});
expect(mockUIViewportDialogService.show).toHaveBeenCalledWith(
expect.objectContaining({
id: 'promptHydrateSR',
})
);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
await promise;
});
it('should get default dialog id for unknown type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
type: 'UNKNOWN_TYPE',
});
expect(mockUIViewportDialogService.show).toHaveBeenCalledWith(
expect.objectContaining({
id: 'promptHydrate',
})
);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
await promise;
});
it('should handle hydrateCallback returning false for SEG', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockHydrateCallback.mockResolvedValue(false);
const result = await promptHydrationDialog({
...defaultParameters,
type: HydrationType.SEG,
});
expect(result).toBe(false);
});
it('should handle hydrateCallback returning false for RTSTRUCT', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockHydrateCallback.mockResolvedValue(false);
const result = await promptHydrationDialog({
...defaultParameters,
type: HydrationType.RTSTRUCT,
});
expect(result).toBe(false);
});
it('should handle SR hydration with null result', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
mockHydrateCallback.mockResolvedValue(null);
const promise = promptHydrationDialog({
...defaultParameters,
type: HydrationType.SR,
});
const onSubmit = mockUIViewportDialogService.show.mock.calls[0][0].onSubmit;
onSubmit(5);
const result = await promise;
expect(result).toEqual({
userResponse: 5,
displaySetInstanceUID: mockDisplaySet.displaySetInstanceUID,
srSeriesInstanceUID: mockDisplaySet.SeriesInstanceUID,
viewportId: defaultParameters.viewportId,
StudyInstanceUID: undefined,
SeriesInstanceUIDs: undefined,
});
});
it('should handle RTSTRUCT type in getDialogId', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
type: 'RTSTRUCT',
});
expect(mockUIViewportDialogService.show).toHaveBeenCalledWith(
expect.objectContaining({
id: 'promptHydrateRT',
})
);
const onOutsideClick = mockUIViewportDialogService.show.mock.calls[0][0].onOutsideClick;
onOutsideClick();
await promise;
});
});
@@ -49,7 +49,7 @@ function getCustomizationMessageKey(type: string): string {
function getDialogId(type: string): string {
switch (type) {
case HydrationType.RTSTRUCT:
case HydrationType.RTSS:
return 'promptHydrateRT';
case HydrationType.SEG:
return 'promptHydrateSEG';
@@ -1,129 +0,0 @@
import { segmentation } from '@cornerstonejs/tools';
import removeViewportSegmentationRepresentations from './removeViewportSegmentationRepresentations';
jest.mock('@cornerstonejs/tools', () => ({
segmentation: {
state: {
getSegmentationRepresentations: jest.fn(),
removeSegmentationRepresentation: jest.fn(),
},
},
}));
describe('removeViewportSegmentationRepresentations', () => {
const viewportId = 'viewport-1';
beforeEach(() => {
jest.clearAllMocks();
});
it('should return early when no representations are found', () => {
(segmentation.state.getSegmentationRepresentations as jest.Mock).mockReturnValue(null);
removeViewportSegmentationRepresentations(viewportId);
expect(segmentation.state.getSegmentationRepresentations).toHaveBeenCalledWith(viewportId);
expect(segmentation.state.removeSegmentationRepresentation).not.toHaveBeenCalled();
});
it('should return early when representations array is empty', () => {
(segmentation.state.getSegmentationRepresentations as jest.Mock).mockReturnValue([]);
removeViewportSegmentationRepresentations(viewportId);
expect(segmentation.state.removeSegmentationRepresentation).not.toHaveBeenCalled();
});
it('should remove single segmentation representation', () => {
const representations = [
{
segmentationRepresentationUID: 'representation-1',
},
];
(segmentation.state.getSegmentationRepresentations as jest.Mock).mockReturnValue(
representations
);
removeViewportSegmentationRepresentations(viewportId);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenCalledTimes(1);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenCalledWith(
'representation-1'
);
});
it('should remove multiple segmentation representations', () => {
const representations = [
{
segmentationRepresentationUID: 'representation-1',
},
{
segmentationRepresentationUID: 'representation-2',
},
{
segmentationRepresentationUID: 'representation-3',
},
];
(segmentation.state.getSegmentationRepresentations as jest.Mock).mockReturnValue(
representations
);
removeViewportSegmentationRepresentations(viewportId);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenCalledTimes(3);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenNthCalledWith(
1,
'representation-1'
);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenNthCalledWith(
2,
'representation-2'
);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenNthCalledWith(
3,
'representation-3'
);
});
it('should handle representations with additional properties', () => {
const representations = [
{
segmentationRepresentationUID: 'representation-1',
type: 'LABELMAP',
active: true,
},
{
segmentationRepresentationUID: 'representation-2',
type: 'CONTOUR',
active: false,
visible: true,
},
];
(segmentation.state.getSegmentationRepresentations as jest.Mock).mockReturnValue(
representations
);
removeViewportSegmentationRepresentations(viewportId);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenCalledTimes(2);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenNthCalledWith(
1,
'representation-1'
);
expect(segmentation.state.removeSegmentationRepresentation).toHaveBeenNthCalledWith(
2,
'representation-2'
);
});
it('should handle undefined representations', () => {
(segmentation.state.getSegmentationRepresentations as jest.Mock).mockReturnValue(undefined);
removeViewportSegmentationRepresentations(viewportId);
expect(segmentation.state.removeSegmentationRepresentation).not.toHaveBeenCalled();
});
});
@@ -1,479 +0,0 @@
import { handleSegmentChange } from './segmentUtils';
describe('handleSegmentChange', () => {
const mockSegmentationService = {
getSegmentation: jest.fn(),
getActiveSegment: jest.fn(),
setActiveSegment: jest.fn(),
jumpToSegmentCenter: jest.fn(),
};
const mockSegmentation = {
segments: {
'1': {
segmentIndex: 1,
label: 'Segment 1',
},
'2': {
segmentIndex: 2,
label: 'Segment 2',
},
'3': {
segmentIndex: 3,
label: 'Segment 3',
},
},
};
const defaultParameters = {
direction: 1,
segmentationId: 'test-segmentation-id',
viewportId: 'test-viewport-id',
selectedSegmentObjectIndex: 0,
// @ts-expect-error - only part of the SegmentationService is needed
segmentationService: mockSegmentationService as AppTypes.SegmentationService,
};
beforeEach(() => {
jest.clearAllMocks();
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
});
it('should move to next segment when direction is positive and activeSegment is null', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.getSegmentation).toHaveBeenCalledWith(
defaultParameters.segmentationId
);
expect(mockSegmentationService.getActiveSegment).toHaveBeenCalledWith(
defaultParameters.viewportId
);
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
2
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
2,
defaultParameters.viewportId
);
});
it('should move to previous segment when direction is negative and activeSegment is null', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: -1,
selectedSegmentObjectIndex: 1,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1,
defaultParameters.viewportId
);
});
it('should update selectedSegmentObjectIndex when activeSegment is found', () => {
const mockActiveSegment = {
segmentIndex: 2,
};
mockSegmentationService.getActiveSegment.mockReturnValue(mockActiveSegment);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3,
defaultParameters.viewportId
);
});
it('should loop to first segment when going beyond last segment', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 2,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1,
defaultParameters.viewportId
);
});
it('should loop to last segment when going before first segment', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: -1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3,
defaultParameters.viewportId
);
});
it('should handle direction with value greater than 1', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 2,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3,
defaultParameters.viewportId
);
});
it('should handle direction with value less than -1', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: -2,
selectedSegmentObjectIndex: 1,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3,
defaultParameters.viewportId
);
});
it('should handle single segment scenario with positive direction', () => {
const singleSegmentSegmentation = {
segments: {
'1': {
segmentIndex: 1,
label: 'Only Segment',
},
},
};
mockSegmentationService.getSegmentation.mockReturnValue(singleSegmentSegmentation);
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1,
defaultParameters.viewportId
);
});
it('should handle single segment scenario with negative direction', () => {
const singleSegmentSegmentation = {
segments: {
'1': {
segmentIndex: 1,
label: 'Only Segment',
},
},
};
mockSegmentationService.getSegmentation.mockReturnValue(singleSegmentSegmentation);
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: -1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1,
defaultParameters.viewportId
);
});
it('should handle activeSegment not found in segments', () => {
const mockActiveSegment = {
segmentIndex: 99,
};
mockSegmentationService.getActiveSegment.mockReturnValue(mockActiveSegment);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1,
defaultParameters.viewportId
);
});
it('should handle zero direction', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 0,
selectedSegmentObjectIndex: 1,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
2
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
2,
defaultParameters.viewportId
);
});
it('should handle segments with non-sequential indices', () => {
const nonSequentialSegmentation = {
segments: {
'5': {
segmentIndex: 5,
label: 'Segment 5',
},
'10': {
segmentIndex: 10,
label: 'Segment 10',
},
'15': {
segmentIndex: 15,
label: 'Segment 15',
},
},
};
mockSegmentationService.getSegmentation.mockReturnValue(nonSequentialSegmentation);
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
10
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
10,
defaultParameters.viewportId
);
});
it('should handle wrap around with non-sequential indices', () => {
const nonSequentialSegmentation = {
segments: {
'5': {
segmentIndex: 5,
label: 'Segment 5',
},
'10': {
segmentIndex: 10,
label: 'Segment 10',
},
},
};
mockSegmentationService.getSegmentation.mockReturnValue(nonSequentialSegmentation);
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 1,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
5
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
5,
defaultParameters.viewportId
);
});
it('should handle empty segments object', () => {
const emptySegmentation = {
segments: {},
};
mockSegmentationService.getSegmentation.mockReturnValue(emptySegmentation);
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
undefined
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
undefined,
defaultParameters.viewportId
);
});
it('should handle large positive direction that wraps multiple times', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: 5,
selectedSegmentObjectIndex: 1,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1,
defaultParameters.viewportId
);
});
it('should handle large negative direction that wraps multiple times', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
direction: -5,
selectedSegmentObjectIndex: 1,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
3,
defaultParameters.viewportId
);
});
it('should handle activeSegment with segmentIndex 0', () => {
const mockActiveSegment = {
segmentIndex: 0,
};
mockSegmentationService.getActiveSegment.mockReturnValue(mockActiveSegment);
handleSegmentChange({
...defaultParameters,
direction: 1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
defaultParameters.segmentationId,
1,
defaultParameters.viewportId
);
});
it('should use different viewport and segmentation IDs', () => {
mockSegmentationService.getActiveSegment.mockReturnValue(null);
handleSegmentChange({
...defaultParameters,
segmentationId: 'different-segmentation-id',
viewportId: 'different-viewport-id',
direction: 1,
selectedSegmentObjectIndex: 0,
});
expect(mockSegmentationService.getSegmentation).toHaveBeenCalledWith(
'different-segmentation-id'
);
expect(mockSegmentationService.getActiveSegment).toHaveBeenCalledWith('different-viewport-id');
expect(mockSegmentationService.setActiveSegment).toHaveBeenCalledWith(
'different-segmentation-id',
2
);
expect(mockSegmentationService.jumpToSegmentCenter).toHaveBeenCalledWith(
'different-segmentation-id',
2,
'different-viewport-id'
);
});
});
@@ -1,614 +0,0 @@
import * as cornerstoneTools from '@cornerstonejs/tools';
import {
setupSegmentationDataModifiedHandler,
setupSegmentationModifiedHandler,
} from './segmentationHandlers';
import { updateSegmentationStats } from './updateSegmentationStats';
jest.mock('@cornerstonejs/tools', () => ({
annotation: {
state: {
getAllAnnotations: jest.fn(),
removeAnnotation: jest.fn(),
},
},
SegmentBidirectionalTool: {
toolName: 'SegmentBidirectional',
},
}));
jest.mock('./updateSegmentationStats', () => ({
updateSegmentationStats: jest.fn(),
}));
describe('setupSegmentationDataModifiedHandler', () => {
const mockSegmentationService = {
EVENTS: {
SEGMENTATION_DATA_MODIFIED: 'SEGMENTATION_DATA_MODIFIED',
},
subscribeDebounced: jest.fn(),
getSegmentation: jest.fn(),
addOrUpdateSegmentation: jest.fn(),
};
const mockCustomizationService = {
getCustomization: jest.fn(),
};
const mockCommandsManager = {
runCommand: jest.fn(),
};
const mockUnsubscribeDebounced = jest.fn();
const defaultParameters = {
segmentationService: mockSegmentationService,
customizationService: mockCustomizationService,
commandsManager: mockCommandsManager,
};
beforeEach(() => {
jest.clearAllMocks();
mockSegmentationService.subscribeDebounced.mockReturnValue({
unsubscribe: mockUnsubscribeDebounced,
});
(updateSegmentationStats as jest.Mock).mockResolvedValue(null);
});
it('should subscribe to SEGMENTATION_DATA_MODIFIED event with debounce', () => {
setupSegmentationDataModifiedHandler(defaultParameters);
expect(mockSegmentationService.subscribeDebounced).toHaveBeenCalledWith(
mockSegmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
expect.any(Function),
1000
);
});
it('should return unsubscribe function', () => {
const result = setupSegmentationDataModifiedHandler(defaultParameters);
expect(result).toEqual({
unsubscribe: expect.any(Function),
});
result.unsubscribe();
expect(mockUnsubscribeDebounced).toHaveBeenCalled();
});
it('should return early when segmentation is null', async () => {
mockSegmentationService.getSegmentation.mockReturnValue(null);
setupSegmentationDataModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribeDebounced.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(mockSegmentationService.getSegmentation).toHaveBeenCalledWith('test-id');
expect(updateSegmentationStats).not.toHaveBeenCalled();
expect(mockSegmentationService.addOrUpdateSegmentation).not.toHaveBeenCalled();
});
it('should return early when disableUpdateSegmentationStats is true', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
},
};
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
mockCustomizationService.getCustomization.mockReturnValue(true);
setupSegmentationDataModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribeDebounced.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(mockCustomizationService.getCustomization).toHaveBeenCalledWith(
'panelSegmentation.disableUpdateSegmentationStats'
);
expect(updateSegmentationStats).not.toHaveBeenCalled();
expect(mockSegmentationService.addOrUpdateSegmentation).not.toHaveBeenCalled();
});
it('should update segmentation stats when conditions are met', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
2: { segmentIndex: 2 },
},
};
const mockUpdatedSegmentation = {
segments: {
1: { segmentIndex: 1, stats: 'updated' },
2: { segmentIndex: 2, stats: 'updated' },
},
};
const mockReadableText = { mean: 'Mean' };
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
mockCustomizationService.getCustomization
.mockReturnValueOnce(false)
.mockReturnValueOnce(mockReadableText);
(updateSegmentationStats as jest.Mock).mockResolvedValue(mockUpdatedSegmentation);
setupSegmentationDataModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribeDebounced.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(updateSegmentationStats).toHaveBeenCalledWith({
segmentation: mockSegmentation,
segmentationId: 'test-id',
readableText: mockReadableText,
});
expect(mockSegmentationService.addOrUpdateSegmentation).toHaveBeenCalledWith({
segmentationId: 'test-id',
segments: mockUpdatedSegmentation.segments,
});
});
it('should not update segmentation when unsubscribed', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
},
};
const mockUpdatedSegmentation = {
segments: {
1: { segmentIndex: 1, stats: 'updated' },
},
};
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
mockCustomizationService.getCustomization.mockReturnValue(false);
(updateSegmentationStats as jest.Mock).mockResolvedValue(mockUpdatedSegmentation);
const result = setupSegmentationDataModifiedHandler(defaultParameters);
result.unsubscribe();
const callback = mockSegmentationService.subscribeDebounced.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(updateSegmentationStats).toHaveBeenCalled();
expect(mockSegmentationService.addOrUpdateSegmentation).not.toHaveBeenCalled();
});
it('should not update segmentation when updateSegmentationStats returns null', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
},
};
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
mockCustomizationService.getCustomization.mockReturnValue(false);
(updateSegmentationStats as jest.Mock).mockResolvedValue(null);
setupSegmentationDataModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribeDebounced.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(updateSegmentationStats).toHaveBeenCalled();
expect(mockSegmentationService.addOrUpdateSegmentation).not.toHaveBeenCalled();
});
it('should run bidirectional command for segments with bidirectional stats', async () => {
const mockSegmentation = {
segments: {
0: { segmentIndex: 0 },
1: {
segmentIndex: 1,
cachedStats: {
namedStats: {
bidirectional: { data: 'test' },
},
},
},
2: { segmentIndex: 2 },
3: {
segmentIndex: 3,
cachedStats: {
namedStats: {
bidirectional: { data: 'test2' },
},
},
},
},
};
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
mockCustomizationService.getCustomization.mockReturnValue(false);
(updateSegmentationStats as jest.Mock).mockResolvedValue(null);
setupSegmentationDataModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribeDebounced.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(mockCommandsManager.runCommand).toHaveBeenCalledTimes(2);
expect(mockCommandsManager.runCommand).toHaveBeenNthCalledWith(1, 'runSegmentBidirectional', {
segmentationId: 'test-id',
segmentIndex: 1,
});
expect(mockCommandsManager.runCommand).toHaveBeenNthCalledWith(2, 'runSegmentBidirectional', {
segmentationId: 'test-id',
segmentIndex: 3,
});
});
it('should not run bidirectional command for segment index 0', async () => {
const mockSegmentation = {
segments: {
0: {
segmentIndex: 0,
cachedStats: {
namedStats: {
bidirectional: { data: 'test' },
},
},
},
1: { segmentIndex: 1 },
},
};
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
mockCustomizationService.getCustomization.mockReturnValue(false);
(updateSegmentationStats as jest.Mock).mockResolvedValue(null);
setupSegmentationDataModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribeDebounced.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(mockCommandsManager.runCommand).not.toHaveBeenCalled();
});
it('should handle segment without cachedStats', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
2: {
segmentIndex: 2,
cachedStats: undefined,
},
3: {
segmentIndex: 3,
cachedStats: {
namedStats: undefined,
},
},
4: {
segmentIndex: 4,
cachedStats: {
namedStats: {
other: { data: 'test' },
},
},
},
},
};
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
mockCustomizationService.getCustomization.mockReturnValue(false);
(updateSegmentationStats as jest.Mock).mockResolvedValue(null);
setupSegmentationDataModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribeDebounced.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(mockCommandsManager.runCommand).not.toHaveBeenCalled();
});
});
describe('setupSegmentationModifiedHandler', () => {
const mockSegmentationService = {
EVENTS: {
SEGMENTATION_MODIFIED: 'SEGMENTATION_MODIFIED',
},
subscribe: jest.fn(),
getSegmentation: jest.fn(),
};
const mockUnsubscribe = jest.fn();
const defaultParameters = {
segmentationService: mockSegmentationService,
};
beforeEach(() => {
jest.clearAllMocks();
mockSegmentationService.subscribe.mockReturnValue({
unsubscribe: mockUnsubscribe,
});
(cornerstoneTools.annotation.state.getAllAnnotations as jest.Mock).mockReturnValue([]);
});
it('should subscribe to SEGMENTATION_MODIFIED event', () => {
setupSegmentationModifiedHandler(defaultParameters);
expect(mockSegmentationService.subscribe).toHaveBeenCalledWith(
mockSegmentationService.EVENTS.SEGMENTATION_MODIFIED,
expect.any(Function)
);
});
it('should return unsubscribe function', () => {
const result = setupSegmentationModifiedHandler(defaultParameters);
expect(result).toEqual({
unsubscribe: mockUnsubscribe,
});
});
it('should return early when segmentation is null', async () => {
mockSegmentationService.getSegmentation.mockReturnValue(null);
setupSegmentationModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(cornerstoneTools.annotation.state.getAllAnnotations).not.toHaveBeenCalled();
expect(cornerstoneTools.annotation.state.removeAnnotation).not.toHaveBeenCalled();
});
it('should remove bidirectional annotations for non-existent segments', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
3: { segmentIndex: 3 },
},
};
const mockAnnotations = [
{
annotationUID: 'annotation-1',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 1,
},
},
{
annotationUID: 'annotation-2',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 2,
},
},
{
annotationUID: 'annotation-3',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'other-id',
segmentIndex: 2,
},
},
{
annotationUID: 'annotation-4',
metadata: {
toolName: 'OtherTool',
segmentationId: 'test-id',
segmentIndex: 2,
},
},
];
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
(cornerstoneTools.annotation.state.getAllAnnotations as jest.Mock).mockReturnValue(
mockAnnotations
);
setupSegmentationModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(cornerstoneTools.annotation.state.removeAnnotation).toHaveBeenCalledTimes(1);
expect(cornerstoneTools.annotation.state.removeAnnotation).toHaveBeenCalledWith('annotation-2');
});
it('should not remove annotations for existing segments', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
2: { segmentIndex: 2 },
},
};
const mockAnnotations = [
{
annotationUID: 'annotation-1',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 1,
},
},
{
annotationUID: 'annotation-2',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 2,
},
},
];
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
(cornerstoneTools.annotation.state.getAllAnnotations as jest.Mock).mockReturnValue(
mockAnnotations
);
setupSegmentationModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(cornerstoneTools.annotation.state.removeAnnotation).not.toHaveBeenCalled();
});
it('should handle empty segments object', async () => {
const mockSegmentation = {
segments: {},
};
const mockAnnotations = [
{
annotationUID: 'annotation-1',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 1,
},
},
];
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
(cornerstoneTools.annotation.state.getAllAnnotations as jest.Mock).mockReturnValue(
mockAnnotations
);
setupSegmentationModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(cornerstoneTools.annotation.state.removeAnnotation).toHaveBeenCalledWith('annotation-1');
});
it('should filter out segment index 0', async () => {
const mockSegmentation = {
segments: {
0: { segmentIndex: 0 },
1: { segmentIndex: 1 },
},
};
const mockAnnotations = [
{
annotationUID: 'annotation-0',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 0,
},
},
{
annotationUID: 'annotation-1',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 1,
},
},
];
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
(cornerstoneTools.annotation.state.getAllAnnotations as jest.Mock).mockReturnValue(
mockAnnotations
);
setupSegmentationModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(cornerstoneTools.annotation.state.removeAnnotation).toHaveBeenCalledWith('annotation-0');
});
it('should handle no bidirectional annotations', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
},
};
const mockAnnotations = [
{
annotationUID: 'annotation-1',
metadata: {
toolName: 'OtherTool',
segmentationId: 'test-id',
segmentIndex: 1,
},
},
];
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
(cornerstoneTools.annotation.state.getAllAnnotations as jest.Mock).mockReturnValue(
mockAnnotations
);
setupSegmentationModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(cornerstoneTools.annotation.state.removeAnnotation).not.toHaveBeenCalled();
});
it('should handle mixed annotation types and segmentation IDs', async () => {
const mockSegmentation = {
segments: {
1: { segmentIndex: 1 },
},
};
const mockAnnotations = [
{
annotationUID: 'keep-1',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 1,
},
},
{
annotationUID: 'remove-1',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'test-id',
segmentIndex: 2,
},
},
{
annotationUID: 'keep-2',
metadata: {
toolName: 'SegmentBidirectional',
segmentationId: 'other-id',
segmentIndex: 2,
},
},
{
annotationUID: 'keep-3',
metadata: {
toolName: 'OtherTool',
segmentationId: 'test-id',
segmentIndex: 2,
},
},
];
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
(cornerstoneTools.annotation.state.getAllAnnotations as jest.Mock).mockReturnValue(
mockAnnotations
);
setupSegmentationModifiedHandler(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
await callback({ segmentationId: 'test-id' });
expect(cornerstoneTools.annotation.state.removeAnnotation).toHaveBeenCalledTimes(1);
expect(cornerstoneTools.annotation.state.removeAnnotation).toHaveBeenCalledWith('remove-1');
});
});
@@ -16,13 +16,9 @@ export function setupSegmentationDataModifiedHandler({
const { unsubscribe: debouncedUnsubscribe } = segmentationService.subscribeDebounced(
segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
async ({ segmentationId }) => {
const disableUpdateSegmentationStats = customizationService.getCustomization(
'panelSegmentation.disableUpdateSegmentationStats'
);
const segmentation = segmentationService.getSegmentation(segmentationId);
if (!segmentation || disableUpdateSegmentationStats) {
if (!segmentation) {
return;
}
@@ -86,21 +82,27 @@ export function setupSegmentationModifiedHandler({ segmentationService }) {
annotation.metadata.toolName === cornerstoneTools.SegmentBidirectionalTool.toolName
);
const segmentIndices = Object.keys(segmentation.segments)
.map(index => parseInt(index))
.filter(index => index > 0);
let toRemoveUIDs = [];
if (!segmentation) {
toRemoveUIDs = bidirectionalAnnotations.map(
annotation => annotation.metadata.segmentationId === segmentationId
);
return;
} else {
const segmentIndices = Object.keys(segmentation.segments)
.map(index => parseInt(index))
.filter(index => index > 0);
// check if there is a bidirectional data that exists but the segment
// does not exists anymore we need to remove the bidirectional data
const bidirectionalAnnotationsToRemove = bidirectionalAnnotations.filter(
annotation =>
annotation.metadata.segmentationId === segmentationId &&
!segmentIndices.includes(annotation.metadata.segmentIndex)
);
// check if there is a bidirectional data that exists but the segment
// does not exists anymore we need to remove the bidirectional data
const bidirectionalAnnotationsToRemove = bidirectionalAnnotations.filter(
annotation =>
annotation.metadata.segmentationId === segmentationId &&
!segmentIndices.includes(annotation.metadata.segmentIndex)
);
const toRemoveUIDs = bidirectionalAnnotationsToRemove.map(
annotation => annotation.annotationUID
);
toRemoveUIDs = bidirectionalAnnotationsToRemove.map(annotation => annotation.annotationUID);
}
toRemoveUIDs.forEach(uid => {
cornerstoneTools.annotation.state.removeAnnotation(uid);
@@ -1,348 +0,0 @@
import { setUpSegmentationEventHandlers } from './setUpSegmentationEventHandlers';
import {
setupSegmentationDataModifiedHandler,
setupSegmentationModifiedHandler,
} from './segmentationHandlers';
jest.mock('./segmentationHandlers', () => ({
setupSegmentationDataModifiedHandler: jest.fn(),
setupSegmentationModifiedHandler: jest.fn(),
}));
describe('setUpSegmentationEventHandlers', () => {
const mockSegmentationService = {
EVENTS: {
SEGMENTATION_ADDED: 'SEGMENTATION_ADDED',
},
subscribe: jest.fn(),
getSegmentation: jest.fn(),
};
const mockCustomizationService = {};
const mockDisplaySetService = {
getDisplaySetByUID: jest.fn(),
addDisplaySets: jest.fn(),
};
const mockCommandsManager = {};
const mockServicesManager = {
services: {
segmentationService: mockSegmentationService,
customizationService: mockCustomizationService,
displaySetService: mockDisplaySetService,
},
};
const mockUnsubscribeDataModified = jest.fn();
const mockUnsubscribeModified = jest.fn();
const mockUnsubscribeCreated = jest.fn();
const defaultParameters = {
servicesManager: mockServicesManager as unknown as AppTypes.ServicesManager,
commandsManager: mockCommandsManager,
};
beforeEach(() => {
jest.clearAllMocks();
(setupSegmentationDataModifiedHandler as jest.Mock).mockReturnValue({
unsubscribe: mockUnsubscribeDataModified,
});
(setupSegmentationModifiedHandler as jest.Mock).mockReturnValue({
unsubscribe: mockUnsubscribeModified,
});
mockSegmentationService.subscribe.mockReturnValue({
unsubscribe: mockUnsubscribeCreated,
});
});
it('should setup segmentation data modified handler', () => {
setUpSegmentationEventHandlers(defaultParameters);
expect(setupSegmentationDataModifiedHandler).toHaveBeenCalledWith({
segmentationService: mockSegmentationService,
customizationService: mockCustomizationService,
commandsManager: mockCommandsManager,
});
});
it('should setup segmentation modified handler', () => {
setUpSegmentationEventHandlers(defaultParameters);
expect(setupSegmentationModifiedHandler).toHaveBeenCalledWith({
segmentationService: mockSegmentationService,
});
});
it('should subscribe to SEGMENTATION_ADDED event', () => {
setUpSegmentationEventHandlers(defaultParameters);
expect(mockSegmentationService.subscribe).toHaveBeenCalledWith(
mockSegmentationService.EVENTS.SEGMENTATION_ADDED,
expect.any(Function)
);
});
it('should return unsubscriptions object', () => {
const result = setUpSegmentationEventHandlers(defaultParameters);
expect(result).toEqual({
unsubscriptions: [
mockUnsubscribeDataModified,
mockUnsubscribeModified,
mockUnsubscribeCreated,
],
});
});
it('should return early when displaySet already exists for segmentationId', () => {
const mockDisplaySet = {
displaySetInstanceUID: 'test-segmentation-id',
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
setUpSegmentationEventHandlers(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
const evt = { segmentationId: 'test-segmentation-id' };
callback(evt);
expect(mockDisplaySetService.getDisplaySetByUID).toHaveBeenCalledWith('test-segmentation-id');
expect(mockSegmentationService.getSegmentation).not.toHaveBeenCalled();
expect(mockDisplaySetService.addDisplaySets).not.toHaveBeenCalled();
});
it('should create and add display set when segmentation is added and no displaySet exists', () => {
const mockSegmentation = {
cachedStats: {
info: 'Test Segmentation Label',
},
representationData: {
Labelmap: {
imageIds: ['image-1', 'image-2', 'image-3'],
},
},
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(null);
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
setUpSegmentationEventHandlers(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
const evt = { segmentationId: 'test-segmentation-id' };
callback(evt);
expect(mockDisplaySetService.getDisplaySetByUID).toHaveBeenCalledWith('test-segmentation-id');
expect(mockSegmentationService.getSegmentation).toHaveBeenCalledWith('test-segmentation-id');
expect(mockDisplaySetService.addDisplaySets).toHaveBeenCalledWith({
displaySetInstanceUID: 'test-segmentation-id',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation.cachedStats.info,
Modality: 'SEG',
numImageFrames: mockSegmentation.representationData.Labelmap.imageIds.length,
imageIds: mockSegmentation.representationData.Labelmap.imageIds,
isOverlayDisplaySet: true,
label: mockSegmentation.cachedStats.info,
madeInClient: true,
segmentationId: 'test-segmentation-id',
isDerived: true,
});
});
it('should handle displaySet undefined when segmentation is added', () => {
const mockSegmentation = {
cachedStats: {
info: 'Test Segmentation Label',
},
representationData: {
Labelmap: {
imageIds: ['image-1', 'image-2'],
},
},
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(undefined);
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
setUpSegmentationEventHandlers(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
const evt = { segmentationId: 'test-segmentation-id' };
callback(evt);
expect(mockSegmentationService.getSegmentation).toHaveBeenCalledWith('test-segmentation-id');
expect(mockDisplaySetService.addDisplaySets).toHaveBeenCalledWith({
displaySetInstanceUID: 'test-segmentation-id',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation.cachedStats.info,
Modality: 'SEG',
numImageFrames: mockSegmentation.representationData.Labelmap.imageIds.length,
imageIds: mockSegmentation.representationData.Labelmap.imageIds,
isOverlayDisplaySet: true,
label: mockSegmentation.cachedStats.info,
madeInClient: true,
segmentationId: 'test-segmentation-id',
isDerived: true,
});
});
it('should handle empty imageIds array', () => {
const mockSegmentation = {
cachedStats: {
info: 'Empty Segmentation',
},
representationData: {
Labelmap: {
imageIds: [],
},
},
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(null);
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
setUpSegmentationEventHandlers(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
const evt = { segmentationId: 'empty-segmentation-id' };
callback(evt);
expect(mockDisplaySetService.addDisplaySets).toHaveBeenCalledWith({
displaySetInstanceUID: 'empty-segmentation-id',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation.cachedStats.info,
Modality: 'SEG',
numImageFrames: 0,
imageIds: [],
isOverlayDisplaySet: true,
label: mockSegmentation.cachedStats.info,
madeInClient: true,
segmentationId: 'empty-segmentation-id',
isDerived: true,
});
});
it('should handle different segmentation label values', () => {
const mockSegmentation = {
cachedStats: {
info: 'Custom Label Text',
},
representationData: {
Labelmap: {
imageIds: ['custom-image-1'],
},
},
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(null);
mockSegmentationService.getSegmentation.mockReturnValue(mockSegmentation);
setUpSegmentationEventHandlers(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
const evt = { segmentationId: 'custom-segmentation-id' };
callback(evt);
expect(mockDisplaySetService.addDisplaySets).toHaveBeenCalledWith({
displaySetInstanceUID: 'custom-segmentation-id',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: 'Custom Label Text',
Modality: 'SEG',
numImageFrames: 1,
imageIds: ['custom-image-1'],
isOverlayDisplaySet: true,
label: 'Custom Label Text',
madeInClient: true,
segmentationId: 'custom-segmentation-id',
isDerived: true,
});
});
it('should handle multiple segmentation events', () => {
const mockSegmentation1 = {
cachedStats: {
info: 'Segmentation 1',
},
representationData: {
Labelmap: {
imageIds: ['image-1'],
},
},
};
const mockSegmentation2 = {
cachedStats: {
info: 'Segmentation 2',
},
representationData: {
Labelmap: {
imageIds: ['image-2', 'image-3'],
},
},
};
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(null);
mockSegmentationService.getSegmentation
.mockReturnValueOnce(mockSegmentation1)
.mockReturnValueOnce(mockSegmentation2);
setUpSegmentationEventHandlers(defaultParameters);
const callback = mockSegmentationService.subscribe.mock.calls[0][1];
callback({ segmentationId: 'segmentation-1' });
callback({ segmentationId: 'segmentation-2' });
expect(mockDisplaySetService.addDisplaySets).toHaveBeenCalledTimes(2);
expect(mockDisplaySetService.addDisplaySets).toHaveBeenNthCalledWith(1, {
displaySetInstanceUID: 'segmentation-1',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation1.cachedStats.info,
Modality: 'SEG',
numImageFrames: 1,
imageIds: mockSegmentation1.representationData.Labelmap.imageIds,
isOverlayDisplaySet: true,
label: mockSegmentation1.cachedStats.info,
madeInClient: true,
segmentationId: 'segmentation-1',
isDerived: true,
});
expect(mockDisplaySetService.addDisplaySets).toHaveBeenNthCalledWith(2, {
displaySetInstanceUID: 'segmentation-2',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation2.cachedStats.info,
Modality: 'SEG',
numImageFrames: 2,
imageIds: mockSegmentation2.representationData.Labelmap.imageIds,
isOverlayDisplaySet: true,
label: mockSegmentation2.cachedStats.info,
madeInClient: true,
segmentationId: 'segmentation-2',
isDerived: true,
});
});
it('should call all unsubscribe functions in returned unsubscriptions array', () => {
const result = setUpSegmentationEventHandlers(defaultParameters);
result.unsubscriptions.forEach(unsubscribe => unsubscribe());
expect(mockUnsubscribeDataModified).toHaveBeenCalled();
expect(mockUnsubscribeModified).toHaveBeenCalled();
expect(mockUnsubscribeCreated).toHaveBeenCalled();
});
});
@@ -1,606 +0,0 @@
import toggleVOISliceSync from './toggleVOISliceSync';
describe('toggleVOISliceSync', () => {
const mockSyncGroupService = {
getSynchronizersForViewport: jest.fn(),
addViewportToSyncGroup: jest.fn(),
removeViewportFromSyncGroup: jest.fn(),
};
const mockViewportGridService = {
getState: jest.fn(),
};
const mockDisplaySetService = {
getDisplaySetByUID: jest.fn(),
};
const mockCornerstoneViewportService = {
getCornerstoneViewport: jest.fn(),
};
const mockServicesManager = {
services: {
syncGroupService: mockSyncGroupService,
viewportGridService: mockViewportGridService,
displaySetService: mockDisplaySetService,
cornerstoneViewportService: mockCornerstoneViewportService,
},
};
const mockViewport = {
id: 'viewport-1',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-1' })),
};
const mockViewport2 = {
id: 'viewport-2',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-2' })),
};
const mockGridViewport = {
viewportOptions: {
viewportId: 'viewport-1',
},
displaySetInstanceUIDs: ['displaySet-1'],
};
const mockDisplaySet = {
Modality: 'CT',
};
const defaultParameters = {
servicesManager: mockServicesManager as unknown as AppTypes.ServicesManager,
viewports: null,
syncId: null,
};
beforeEach(() => {
jest.clearAllMocks();
mockCornerstoneViewportService.getCornerstoneViewport.mockReturnValue(mockViewport);
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
});
it('should enable sync when no sync exists for single modality', () => {
const viewports = {
CT: [mockGridViewport],
};
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.getSynchronizersForViewport).toHaveBeenCalledWith('viewport-1');
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
});
it('should disable sync when sync already exists', () => {
const viewports = {
CT: [mockGridViewport],
};
const mockSyncState = {
id: 'VOI_SYNC_CT',
};
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([mockSyncState]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
'VOI_SYNC_CT'
);
expect(mockSyncGroupService.addViewportToSyncGroup).not.toHaveBeenCalled();
});
it('should use custom syncId when provided', () => {
const viewports = {
CT: [mockGridViewport],
};
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
syncId: 'CUSTOM_SYNC_ID',
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'CUSTOM_SYNC_ID',
source: true,
target: true,
}
);
});
it('should handle multiple viewports in same modality', () => {
const mockGridViewport2 = {
viewportOptions: {
viewportId: 'viewport-2',
},
displaySetInstanceUIDs: ['displaySet-2'],
};
const viewports = {
CT: [mockGridViewport, mockGridViewport2],
};
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(mockViewport2);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledTimes(2);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
1,
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
2,
'viewport-2',
'rendering-engine-2',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
});
it('should handle multiple modalities separately', () => {
const mockGridViewportMR = {
viewportOptions: {
viewportId: 'viewport-mr',
},
displaySetInstanceUIDs: ['displaySet-mr'],
};
const mockViewportMR = {
id: 'viewport-mr',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-mr' })),
};
const viewports = {
CT: [mockGridViewport],
MR: [mockGridViewportMR],
};
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(mockViewportMR);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledTimes(2);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
1,
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
2,
'viewport-mr',
'rendering-engine-mr',
{
type: 'voi',
id: 'VOI_SYNC_MR',
source: true,
target: true,
}
);
});
it('should skip viewport when cornerstone viewport is not found', () => {
const viewports = {
CT: [mockGridViewport],
};
mockCornerstoneViewportService.getCornerstoneViewport.mockReturnValue(null);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.addViewportToSyncGroup).not.toHaveBeenCalled();
});
it('should generate viewports from grid service when not provided', () => {
const mockViewportsMap = new Map([
[
'viewport-1',
{
...mockGridViewport,
},
],
]);
const mockState = {
viewports: mockViewportsMap,
};
mockViewportGridService.getState.mockReturnValue(mockState);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports: null,
});
expect(mockViewportGridService.getState).toHaveBeenCalled();
expect(mockDisplaySetService.getDisplaySetByUID).toHaveBeenCalledWith('displaySet-1');
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
});
it('should handle sync state with different id not matching', () => {
const viewports = {
CT: [mockGridViewport],
};
const mockSyncState = {
id: 'DIFFERENT_SYNC_ID',
};
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([mockSyncState]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.removeViewportFromSyncGroup).not.toHaveBeenCalled();
});
it('should handle mixed sync states where some match and some do not', () => {
const mockGridViewport2 = {
viewportOptions: {
viewportId: 'viewport-2',
},
displaySetInstanceUIDs: ['displaySet-2'],
};
const viewports = {
CT: [mockGridViewport, mockGridViewport2],
};
const mockSyncState = {
id: 'VOI_SYNC_CT',
};
mockSyncGroupService.getSynchronizersForViewport
.mockReturnValueOnce([mockSyncState])
.mockReturnValueOnce([]);
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(mockViewport2);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
'VOI_SYNC_CT'
);
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledWith(
'viewport-2',
'rendering-engine-2',
'VOI_SYNC_CT'
);
});
it('should disable sync for multiple viewports when sync exists', () => {
const mockGridViewport2 = {
viewportOptions: {
viewportId: 'viewport-2',
},
displaySetInstanceUIDs: ['displaySet-2'],
};
const mockViewport2 = {
id: 'viewport-2',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-2' })),
};
const viewports = {
CT: [mockGridViewport, mockGridViewport2],
};
const mockSyncState = {
id: 'VOI_SYNC_CT',
};
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(mockViewport2);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([mockSyncState]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledTimes(2);
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenNthCalledWith(
1,
'viewport-1',
'rendering-engine-1',
'VOI_SYNC_CT'
);
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenNthCalledWith(
2,
'viewport-2',
'rendering-engine-2',
'VOI_SYNC_CT'
);
});
it('should skip viewport during disable when cornerstone viewport is not found', () => {
const mockGridViewport2 = {
viewportOptions: {
viewportId: 'viewport-2',
},
displaySetInstanceUIDs: ['displaySet-2'],
};
const viewports = {
CT: [mockGridViewport, mockGridViewport2],
};
const mockSyncState = {
id: 'VOI_SYNC_CT',
};
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(null);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([mockSyncState]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledTimes(1);
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
'VOI_SYNC_CT'
);
});
it('should handle empty viewports object', () => {
const viewports = {};
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.getSynchronizersForViewport).not.toHaveBeenCalled();
expect(mockSyncGroupService.addViewportToSyncGroup).not.toHaveBeenCalled();
expect(mockSyncGroupService.removeViewportFromSyncGroup).not.toHaveBeenCalled();
});
it('should handle modality with empty viewport array', () => {
const viewports = {
CT: [],
};
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.getSynchronizersForViewport).not.toHaveBeenCalled();
expect(mockSyncGroupService.addViewportToSyncGroup).not.toHaveBeenCalled();
expect(mockSyncGroupService.removeViewportFromSyncGroup).not.toHaveBeenCalled();
});
it('should handle multiple displaySetInstanceUIDs by using first one', () => {
const mockGridViewportMultipleDisplaySets = {
viewportOptions: {
viewportId: 'viewport-1',
},
displaySetInstanceUIDs: ['displaySet-1', 'displaySet-2', 'displaySet-3'],
};
mockViewportGridService.getState.mockReturnValue({
viewports: new Map([['viewport-1', mockGridViewportMultipleDisplaySets]]),
});
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
toggleVOISliceSync({
...defaultParameters,
});
expect(mockDisplaySetService.getDisplaySetByUID).toHaveBeenCalledWith('displaySet-1');
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
});
it('should group viewports by modality correctly when generating from grid', () => {
const mockViewportsMap = new Map([
[
'viewport-ct-1',
{
viewportOptions: {
viewportId: 'viewport-ct-1',
},
displaySetInstanceUIDs: ['displaySet-ct-1'],
},
],
[
'viewport-ct-2',
{
viewportOptions: {
viewportId: 'viewport-ct-2',
},
displaySetInstanceUIDs: ['displaySet-ct-2'],
},
],
[
'viewport-mr-1',
{
viewportOptions: {
viewportId: 'viewport-mr-1',
},
displaySetInstanceUIDs: ['displaySet-mr-1'],
},
],
]);
const mockState = {
viewports: mockViewportsMap,
};
const mockDisplaySetCT = { Modality: 'CT' };
const mockDisplaySetMR = { Modality: 'MR' };
const mockViewportCT1 = {
id: 'viewport-ct-1',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-ct-1' })),
};
const mockViewportCT2 = {
id: 'viewport-ct-2',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-ct-2' })),
};
const mockViewportMR1 = {
id: 'viewport-mr-1',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-mr-1' })),
};
mockViewportGridService.getState.mockReturnValue(mockState);
mockDisplaySetService.getDisplaySetByUID
.mockReturnValueOnce(mockDisplaySetCT)
.mockReturnValueOnce(mockDisplaySetCT)
.mockReturnValueOnce(mockDisplaySetMR);
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewportCT1)
.mockReturnValueOnce(mockViewportCT2)
.mockReturnValueOnce(mockViewportMR1);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports: null,
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledTimes(3);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
1,
'viewport-ct-1',
'rendering-engine-ct-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
2,
'viewport-ct-2',
'rendering-engine-ct-2',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
3,
'viewport-mr-1',
'rendering-engine-mr-1',
{
type: 'voi',
id: 'VOI_SYNC_MR',
source: true,
target: true,
}
);
});
});
@@ -46,7 +46,7 @@ export default function toggleVOISliceSync({
}
syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, {
type: 'voi',
id: syncIdToUse as string,
id: syncIdToUse,
source: true,
target: true,
});
@@ -76,12 +76,12 @@ function groupViewportsByModality(
viewportGridService: ViewportGridService,
displaySetService: DisplaySetService
) {
const { viewports } = viewportGridService.getState();
let { viewports } = viewportGridService.getState();
const viewportsArray = [...viewports.values()];
viewports = [...viewports.values()];
// group the viewports by modality
return viewportsArray.reduce((acc, viewport) => {
return viewports.reduce((acc, viewport) => {
const { displaySetInstanceUIDs } = viewport;
// Todo: add proper fusion support
const displaySetInstanceUID = displaySetInstanceUIDs[0];
@@ -1,367 +0,0 @@
import { linear, ease, easeIn, easeOut, easeInOut, cubicBezier } from './transitions';
describe('Transitions Module', () => {
const EPSILON = 1e-6;
/**
* Helper function to check if two numbers are approximately equal
*/
const approxEqual = (actual: number, expected: number, tolerance = EPSILON): boolean => {
return Math.abs(actual - expected) < tolerance;
};
describe('Standard CSS Easing (Mode 1: no baseline)', () => {
describe('linear', () => {
it('should return 0 at timeProgress=0', () => {
expect(linear(0)).toBe(0);
});
it('should return 1 at timeProgress=1', () => {
expect(linear(1)).toBe(1);
});
it('should return timeProgress for linear progression', () => {
expect(linear(0.25)).toBe(0.25);
expect(linear(0.5)).toBe(0.5);
expect(linear(0.75)).toBe(0.75);
});
});
describe('easeInOut', () => {
it('should return 0 at timeProgress=0', () => {
expect(easeInOut(0)).toBe(0);
});
it('should return 1 at timeProgress=1', () => {
expect(easeInOut(1)).toBe(1);
});
it('should return 0.5 at timeProgress=0.5', () => {
const result = easeInOut(0.5);
expect(approxEqual(result, 0.5)).toBe(true);
});
it('should be slower at start than linear', () => {
const easedValue = easeInOut(0.2);
expect(easedValue).toBeLessThan(0.2);
});
it('should provide smoothing toward the end', () => {
const baseTime = 0.9;
const controlTimeProgressionDifference = 0.2;
const result1 = easeInOut(baseTime);
const result2 = easeInOut(baseTime - controlTimeProgressionDifference);
expect(result1 - result2).toBeLessThan(controlTimeProgressionDifference);
});
});
describe('easeIn', () => {
it('should return 0 at timeProgress=0', () => {
expect(easeIn(0)).toBe(0);
});
it('should return 1 at timeProgress=1', () => {
expect(easeIn(1)).toBe(1);
});
it('should be slower at start', () => {
const easedValue = easeIn(0.4);
expect(easedValue).toBeLessThan(0.4);
});
it('should accelerate toward the end', () => {
const baseTime = 0.9;
const controlTimeProgressionDifference = 0.2;
const result1 = easeIn(baseTime);
const result2 = easeIn(baseTime - controlTimeProgressionDifference);
expect(result1 - result2).toBeGreaterThan(controlTimeProgressionDifference);
});
});
describe('easeOut', () => {
it('should return 0 at timeProgress=0', () => {
expect(easeOut(0)).toBe(0);
});
it('should return 1 at timeProgress=1', () => {
expect(easeOut(1)).toBe(1);
});
it('should be faster at start', () => {
const easedValue = easeOut(0.4);
expect(easedValue).toBeGreaterThan(0.4);
});
it('should provide smoothing toward the end', () => {
const baseTime = 0.9;
const controlTimeProgressionDifference = 0.2;
const result1 = ease(baseTime);
const result2 = ease(baseTime - controlTimeProgressionDifference);
expect(result1 - result2).toBeLessThan(controlTimeProgressionDifference);
});
});
describe('ease', () => {
it('should return 0 at timeProgress=0', () => {
expect(ease(0)).toBe(0);
});
it('should return 1 at timeProgress=1', () => {
expect(ease(1)).toBe(1);
});
it('should provide significant growth at first half', () => {
const result = ease(0.5);
expect(result).toBeGreaterThan(0.75);
});
it('should provide smoothing toward the end', () => {
const baseTime = 0.9;
const controlTimeProgressionDifference = 0.2;
const result1 = ease(baseTime);
const result2 = ease(baseTime - controlTimeProgressionDifference);
expect(result1 - result2).toBeLessThan(controlTimeProgressionDifference);
});
});
});
describe('Baseline Bell-Curve (Mode 2: baseline only)', () => {
const baseline = 0.2;
describe('linear with baseline', () => {
it('should start and end at baseline', () => {
expect(linear(0, baseline)).toBe(baseline);
expect(linear(1, baseline)).toBe(baseline);
});
it('should peak at 1.0 at timeProgress=0.5', () => {
const result = linear(0.5, baseline);
expect(approxEqual(result, 1.0)).toBe(true);
});
it('should create symmetric bell curve', () => {
const quarterValue = linear(0.25, baseline);
const threeQuarterValue = linear(0.75, baseline);
expect(approxEqual(quarterValue, threeQuarterValue)).toBe(true);
});
it('should be between baseline and 1.0', () => {
for (let t = 0; t <= 1; t += 0.1) {
const result = linear(t, baseline);
expect(result).toBeGreaterThanOrEqual(baseline);
expect(result).toBeLessThanOrEqual(1.0);
}
});
});
describe('easeInOut with baseline', () => {
it('should start and end at baseline', () => {
expect(easeInOut(0, baseline)).toBe(baseline);
expect(easeInOut(1, baseline)).toBe(baseline);
});
it('should peak near 1.0 around timeProgress=0.5', () => {
const result = easeInOut(0.5, baseline);
expect(result).toBeGreaterThan(0.8);
expect(result).toBeLessThanOrEqual(1.0);
});
it('should create bell curve with easing', () => {
const quarterValue = easeInOut(0.25, baseline);
const threeQuarterValue = easeInOut(0.75, baseline);
expect(approxEqual(quarterValue, threeQuarterValue, 0.01)).toBe(true);
});
});
describe('all functions with baseline', () => {
const testBaseline = 0.3;
it('should all start and end at baseline', () => {
const functions = [linear, ease, easeIn, easeOut, easeInOut];
functions.forEach(fn => {
expect(fn(0, testBaseline)).toBe(testBaseline);
expect(fn(1, testBaseline)).toBe(testBaseline);
});
});
});
});
describe('Scaled Bell-Curve (Mode 3: baseline + scale)', () => {
const baseline = 2.0;
const scale = 3.0;
const expectedPeak = baseline * scale;
describe('linear with baseline and scale', () => {
it('should start and end at baseline', () => {
expect(linear(0, baseline, scale)).toBe(baseline);
expect(linear(1, baseline, scale)).toBe(baseline);
});
it('should peak at baseline*scale at timeProgress=0.5', () => {
const result = linear(0.5, baseline, scale);
expect(approxEqual(result, expectedPeak)).toBe(true);
});
it('should create symmetric scaled bell curve', () => {
const quarterValue = linear(0.25, baseline, scale);
const threeQuarterValue = linear(0.75, baseline, scale);
expect(approxEqual(quarterValue, threeQuarterValue)).toBe(true);
});
});
describe('easeInOut with baseline and scale', () => {
it('should start and end at baseline', () => {
expect(easeInOut(0, baseline, scale)).toBe(baseline);
expect(easeInOut(1, baseline, scale)).toBe(baseline);
});
it('should peak near baseline*scale around timeProgress=0.5', () => {
const result = easeInOut(0.5, baseline, scale);
expect(result).toBeGreaterThan(baseline + (expectedPeak - baseline) * 0.8);
expect(result).toBeLessThanOrEqual(expectedPeak);
});
});
describe('different scale values', () => {
it('should handle scale < 1 (shrinking)', () => {
const shrinkScale = 0.5;
const result = linear(0.5, 1.0, shrinkScale);
expect(approxEqual(result, 0.5)).toBe(true);
});
it('should handle scale > 1 (growing)', () => {
const growScale = 2.5;
const result = linear(0.5, 1.0, growScale);
expect(approxEqual(result, 2.5)).toBe(true);
});
it('should handle scale = 1 (baseline to baseline)', () => {
const result = linear(0.5, 0.5, 1);
expect(approxEqual(result, 0.5)).toBe(true);
});
});
});
describe('Edge Cases', () => {
describe('baseline = 0 behavior', () => {
it('should behave like standard easing when baseline=0', () => {
expect(linear(0.5, 0)).toBe(linear(0.5));
expect(easeInOut(0.5, 0)).toBe(easeInOut(0.5));
});
});
describe('extreme values', () => {
it('should handle negative baseline', () => {
const result = linear(0.5, -1.0, 2.0);
expect(approxEqual(result, -2.0)).toBe(true);
});
it('should handle large baseline values', () => {
const result = linear(0.5, 100, 1.5);
expect(approxEqual(result, 150)).toBe(true);
});
});
describe('timeProgress edge cases', () => {
it('should handle timeProgress < 0', () => {
expect(linear(-0.1)).toBe(0);
expect(easeInOut(-0.1, 0.2)).toBe(0.2);
});
it('should handle timeProgress > 1', () => {
expect(linear(1.1)).toBe(1);
expect(easeInOut(1.1, 0.2)).toBe(0.2);
});
});
});
describe('Mathematical Properties', () => {
describe('bell curve symmetry for symmetric cubic bezier functions', () => {
it('should be symmetric around timeProgress=0.5', () => {
const baseline = 0.1;
const scale = 2.0;
[linear, easeInOut].forEach(fn => {
for (let offset = 0.1; offset <= 0.4; offset += 0.1) {
const leftValue = fn(0.5 - offset, baseline, scale);
const rightValue = fn(0.5 + offset, baseline, scale);
expect(approxEqual(leftValue, rightValue, 0.01)).toBe(true);
}
});
});
});
describe('monotonicity in first half', () => {
it('should be non-decreasing from 0 to 0.5 for functions that grow under linear in first half', () => {
const baseline = 0.2;
[linear, easeIn, easeInOut].forEach(fn => {
let prevValue = fn(0, baseline);
for (let time = 0.1; time <= 0.5; time += 0.1) {
const currentValue = fn(time, baseline);
expect(currentValue).toBeGreaterThanOrEqual(prevValue - EPSILON);
prevValue = currentValue;
}
});
});
});
});
describe('Utility Functions', () => {
describe('cubicBezier', () => {
it('should create functions that return 0 at t=0 and 1 at t=1', () => {
const customEasing = cubicBezier(0.25, 0.1, 0.75, 0.9);
expect(customEasing(0)).toBe(0);
expect(customEasing(1)).toBe(1);
});
it('should throw error for invalid x values', () => {
expect(() => cubicBezier(-0.1, 0, 1, 1)).toThrow();
expect(() => cubicBezier(0, 0, 1.1, 1)).toThrow();
});
it('should fallback to binary search when Newton-Raphson fails due to small derivative', () => {
/**
* Create a cubic-bezier curve that has a very flat section (small derivative)
* This will cause Newton-Raphson to fail and use binary search fallback
* Using control points that create a nearly horizontal tangent
* Obs: Values obtained by imperative testing
*/
const problematicEasing = cubicBezier(0.98888, 0.00001, 0.00001, 0.98888);
/** Test a value in the problematic region where derivative is very small*/
const result = problematicEasing(0.5);
expect(typeof result).toBe('number');
expect(isNaN(result)).toBe(false);
expect(result).toBeGreaterThanOrEqual(0);
expect(result).toBeLessThanOrEqual(1);
});
});
});
describe('Performance Validation', () => {
it('should handle many rapid calls without issues', () => {
const startTime = performance.now();
for (let i = 0; i < 1000; i++) {
const t = i / 1000;
linear(t);
easeInOut(t, 0.1);
ease(t, 0.2, 1.5);
}
const endTime = performance.now();
expect(endTime - startTime).toBeLessThan(10);
});
});
});
+47 -284
View File
@@ -1,300 +1,63 @@
/** Cubic Bezier Implementation */
/**
* Finds the parameter t for a given x value on a cubic Bézier curve.
* Uses Newton's method first (fast), then falls back to binary search (reliable).
* Based on WebKit/Chromium's UnitBezier implementation.
* It is a bell curved function that uses ease in out quadratic for css
* transition timing function for each side of the curve.
*
* @param {number} targetX - The x value to find parameter t for
* @param {number} x1 - x-coordinate of first control point
* @param {number} x2 - x-coordinate of second control point
* @returns {number} The parameter t that gives the specified x value
* @param {number} x - The current time, in the range [0, 1].
* @param {number} baseline - The baseline value to start from and return to.
* @returns the value of the transition at time x.
*/
function solveCubicBezierX(targetX: number, x1: number, x2: number): number {
const epsilon = 1e-6;
export function easeInOutBell(x: number, baseline: number): number {
const alpha = 1 - baseline;
// Pre-compute polynomial coefficients for performance
const cx = 3.0 * x1;
const bx = 3.0 * (x2 - x1) - cx;
const ax = 1.0 - cx - bx;
function sampleCurveX(t: number): number {
// Evaluate: ax*t³ + bx*t² + cx*t using Horner's rule
return ((ax * t + bx) * t + cx) * t;
// prettier-ignore
if (x < 1 / 4) {
return 4 * Math.pow(2 * x, 3) * alpha + baseline;
} else if (x < 1 / 2) {
return (1 - Math.pow(-4 * x + 2, 3) / 2) * alpha + baseline;
} else if (x < 3 / 4) {
return (1 - Math.pow(4 * x - 2, 3) / 2) * alpha + baseline;
} else {
return (- 4 * Math.pow(2 * x - 2, 3)) * alpha + baseline;
}
function sampleCurveDerivativeX(t: number): number {
return (3.0 * ax * t + 2.0 * bx) * t + cx;
}
/**
* Newton's method - fast convergence with good initial guess
* Try this first as it's normally very fast
*/
function newtonRaphsonMethod(targetX: number): number | null {
let t = targetX;
for (let iteration = 0; iteration < 8; iteration++) {
const currentX = sampleCurveX(t);
const error = currentX - targetX;
if (Math.abs(error) < epsilon) {
return t;
}
const derivative = sampleCurveDerivativeX(t);
// Break if derivative is too small (avoid division by zero)
if (Math.abs(derivative) < epsilon) {
break;
}
// Newton-Raphson step: t_new = t_old - f(t_old) / f'(t_old)
t = t - error / derivative;
}
return null;
}
/**
* Binary search fallback - guaranteed to converge
* Use this when Newton's method fails
*/
function binarySearchFallback(targetX: number): number {
let lowerBound = 0.0;
let upperBound = 1.0;
let t = targetX;
while (lowerBound < upperBound) {
const currentX = sampleCurveX(t);
if (Math.abs(currentX - targetX) < epsilon) {
return t;
}
if (targetX > currentX) {
lowerBound = t;
} else {
upperBound = t;
}
t = (upperBound - lowerBound) * 0.5 + lowerBound;
}
return t;
}
const newtonResult = newtonRaphsonMethod(targetX);
if (newtonResult !== null) {
return newtonResult;
}
return binarySearchFallback(targetX);
}
/**
* Evaluates the Y coordinate of a cubic Bézier curve at parameter t.
* Optimized for CSS timing functions where P0=(0,0) and P3=(1,1).
* Uses pre-computed polynomial coefficients and Horner's rule for performance.
* A reversed bell curved function that starts from 1 and goes to baseline and
* come back to 1 again. It uses ease in out quadratic for css transition
* timing function for each side of the curve.
*
* @param {number} t - Parameter value along the curve, typically in [0, 1]
* @param {number} y1 - y-coordinate of first control point
* @param {number} y2 - y-coordinate of second control point
* @returns {number} The Y value of the curve at parameter t
* @param {number} x - The current time, in the range [0, 1].
* @param {number} baseline - The baseline value to start from and return to.
* @returns the value of the transition at time x.
*/
function sampleCurveY(t: number, y1: number, y2: number): number {
// Pre-compute polynomial coefficients for performance
const cy = 3.0 * y1;
const by = 3.0 * (y2 - y1) - cy;
const ay = 1.0 - cy - by;
// Evaluate: ay*t³ + by*t² + cy*t using Horner's rule
return ((ay * t + by) * t + cy) * t;
export function reverseEaseInOutBell(x: number, baseline: number): number {
const y = easeInOutBell(x, baseline);
return -y + 1 + baseline;
}
/**
* Cubic Bézier easing function implementation following CSS specifications.
*
* A cubic Bézier curve is defined by four points: P0, P1, P2, and P3.
* In CSS animations, P0 is fixed at (0, 0) and P3 is fixed at (1, 1).
* This function allows you to specify the intermediate control points P1 and P2.
*
* @param {number} x1 - x-coordinate of the first control point P1 (must be in [0, 1])
* @param {number} y1 - y-coordinate of the first control point P1
* @param {number} x2 - x-coordinate of the second control point P2 (must be in [0, 1])
* @param {number} y2 - y-coordinate of the second control point P2
* @returns {function} A function that takes time t ∈ [0, 1] and returns the eased value
*/
export function cubicBezier(x1: number, y1: number, x2: number, y2: number) {
if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) {
throw new Error('x1 and x2 must be in the range [0, 1]');
export function easeInOutBellRelative(
x: number,
baseline: number,
prevOutlineWidth: number
): number {
const range = baseline - prevOutlineWidth;
if (x < 1 / 4) {
return prevOutlineWidth + 4 * Math.pow(2 * x, 3) * range;
} else if (x < 1 / 2) {
return prevOutlineWidth + (1 - Math.pow(-4 * x + 2, 3) / 2) * range;
} else if (x < 3 / 4) {
return prevOutlineWidth + (1 - Math.pow(4 * x - 2, 3) / 2) * range;
} else {
return prevOutlineWidth + -4 * Math.pow(2 * x - 2, 3) * range;
}
return function (timeProgress: number): number {
if (timeProgress <= 0) return 0;
if (timeProgress >= 1) return 1;
if (x1 === y1 && x2 === y2) {
return timeProgress;
}
const curveParameter = solveCubicBezierX(timeProgress, x1, x2);
return sampleCurveY(curveParameter, y1, y2);
};
}
/** Core Easing Functions */
/**
* Linear easing function - constant speed throughout the animation.
* Equivalent to CSS: cubic-bezier(0.0, 0.0, 1.0, 1.0)
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @returns {number} The linear eased value.
*/
const linearCore = cubicBezier(0.0, 0.0, 1.0, 1.0);
/**
* Ease-in easing function - starts slow and accelerates.
* Equivalent to CSS: cubic-bezier(0.42, 0, 1.0, 1.0)
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @returns {number} The eased value.
*/
const easeInCore = cubicBezier(0.42, 0, 1.0, 1.0);
/**
* Ease-out easing function - starts fast and decelerates.
* Equivalent to CSS: cubic-bezier(0, 0, 0.58, 1.0)
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @returns {number} The eased value.
*/
const easeOutCore = cubicBezier(0, 0, 0.58, 1.0);
/**
* Ease-in-out easing function - starts slow, accelerates, then decelerates.
* Equivalent to CSS: cubic-bezier(0.42, 0, 0.58, 1.0)
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @returns {number} The eased value.
*/
const easeInOutCore = cubicBezier(0.42, 0, 0.58, 1.0);
/**
* Standard ease easing function (CSS default).
* Equivalent to CSS: cubic-bezier(0.25, 0.1, 0.25, 1.0)
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @returns {number} The eased value.
*/
const easeCore = cubicBezier(0.25, 0.1, 0.25, 1.0);
/** Flexible Easing Function Factory */
/**
* Flexible factory function that creates easing functions with optional baseline and scale support.
* Provides bell-curve behavior: baseline → baseline*scale → baseline
*
* @param {Function} coreEasingFn - The core easing function to wrap
* @returns {Function} A function that accepts (timeProgress, baseline?, scale?) and provides flexible behavior
*/
function flexibleEasingFunctionFactory(coreEasingFn: (timeProgress: number) => number) {
return function (timeProgress: number, baseline: number = 0, scale?: number): number {
if (baseline === 0) {
return coreEasingFn(timeProgress);
}
const easedProgress = coreEasingFn(timeProgress);
const targetValue = scale ? baseline * scale : 1;
const range = targetValue - baseline;
// Create bell-curve: baseline → targetValue → baseline
const bellMultiplier = 1 - Math.abs(2 * easedProgress - 1);
return baseline + bellMultiplier * range;
};
export function reverseEaseInOutBellRelative(
x: number,
baseline: number,
prevOutlineWidth: number
): number {
const y = easeInOutBellRelative(x, baseline, prevOutlineWidth);
return y;
}
/**
* Linear easing function with optional baseline and scale support.
* - No params: standard linear progression [0, 1]
* - With baseline only: bell-curve baseline → 1 → baseline
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
* @returns {number} The linear eased value.
*/
export const linear = flexibleEasingFunctionFactory(linearCore);
/**
* Standard ease easing function with optional baseline and scale support.
* - No params: standard ease progression [0, 1]
* - With baseline only: bell-curve baseline → 1 → baseline
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
* @returns {number} The eased value.
*/
export const ease = flexibleEasingFunctionFactory(easeCore);
/**
* Ease-in easing function with optional baseline and scale support.
* - No params: standard ease-in progression [0, 1]
* - With baseline only: bell-curve baseline → 1 → baseline
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
* @returns {number} The eased value.
*/
export const easeIn = flexibleEasingFunctionFactory(easeInCore);
/**
* Ease-out easing function with optional baseline and scale support.
* - No params: standard ease-out progression [0, 1]
* - With baseline only: bell-curve baseline → 1 → baseline
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
* @returns {number} The eased value.
*/
export const easeOut = flexibleEasingFunctionFactory(easeOutCore);
/**
* Ease-in-out easing function with optional baseline and scale support.
* - No params: standard ease-in-out progression [0, 1]
* - With baseline only: bell-curve baseline → 1 → baseline
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
*
* @param {number} timeProgress - The animation progress, in the range [0, 1].
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
* @returns {number} The eased value.
*/
export const easeInOut = flexibleEasingFunctionFactory(easeInOutCore);
/** Export interfaces */
export enum EasingFunctionEnum {
EASE = 'ease',
EASE_IN = 'ease-in',
EASE_OUT = 'ease-out',
EASE_IN_OUT = 'ease-in-out',
LINEAR = 'linear',
}
export const EasingFunctionMap = new Map([
[EasingFunctionEnum.EASE, ease],
[EasingFunctionEnum.EASE_IN, easeIn],
[EasingFunctionEnum.EASE_OUT, easeOut],
[EasingFunctionEnum.EASE_IN_OUT, easeInOut],
[EasingFunctionEnum.LINEAR, linear],
]);
Loaded 100 of 771 files, more files were not shown because too many files have changed in this diff. Show more