Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
522531cc5c | ||
|
|
8c89d9472c | ||
|
|
9d94e26d5d |
No files matched your search
+10
-34
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
"deploy": "netlify deploy --prod --dir ./../platform/app/dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"netlify-cli": "2.21.0"
|
||||
"netlify-cli": "^2.21.0"
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;"]
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
42aa2df62622ab380f97025819bd929fa0579e14
|
||||
9d94e26d5d15ccd19f12020dbc5438364d88d253
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
+2
-4
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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,
|
||||
|
||||
+7
-5
@@ -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);
|
||||
});
|
||||
});
|
||||
+2
-15
@@ -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 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
+5
-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);
|
||||
});
|
||||
});
|
||||
-50
@@ -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: '',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
+7
-9
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
Reference in new issue
Block a user