Compare commits
165
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ad7623a25 | ||
|
|
eb564aec5e | ||
|
|
35bfc9b0cc | ||
|
|
ab440f086a | ||
|
|
1d33bcf229 | ||
|
|
76f7d4e23a | ||
|
|
afa9131da1 | ||
|
|
5b5bca5e91 | ||
|
|
1f11c83006 | ||
|
|
1dd4f60034 | ||
|
|
2013c87ddb | ||
|
|
1009c60911 | ||
|
|
b517944cb5 | ||
|
|
67d6622ae4 | ||
|
|
a1544432db | ||
|
|
e0e1763472 | ||
|
|
b844b56839 | ||
|
|
8b9445c442 | ||
|
|
8193311841 | ||
|
|
c83710fa78 | ||
|
|
e07a87dcae | ||
|
|
2bc0647861 | ||
|
|
2a01dcce29 | ||
|
|
1df4f843fe | ||
|
|
cf0b561a49 | ||
|
|
f1675559ee | ||
|
|
ffd9ec3273 | ||
|
|
66e6d73ad6 | ||
|
|
07c45d0364 | ||
|
|
503780247c | ||
|
|
82c4a0a8d0 | ||
|
|
6a523ff78d | ||
|
|
170e860aa1 | ||
|
|
3e1d50c8d2 | ||
|
|
05b80305dc | ||
|
|
4620cc3acf | ||
|
|
cf8f4aa120 | ||
|
|
f75273ece6 | ||
|
|
42aa2df626 | ||
|
|
be6f3db1ce | ||
|
|
c41dbc8236 | ||
|
|
53694da216 | ||
|
|
9d68b6ef31 | ||
|
|
2300c70314 | ||
|
|
86e924fb88 | ||
|
|
e988216b2b | ||
|
|
23c8fdca00 | ||
|
|
faea09e7ef | ||
|
|
05ecdfe512 | ||
|
|
0b6c38e5e0 | ||
|
|
3137aed017 | ||
|
|
a1c6b5f3ec | ||
|
|
be13e7506a | ||
|
|
0d2f4e715a | ||
|
|
37231a5ffe | ||
|
|
c53ee335ee | ||
|
|
fbd9371884 | ||
|
|
5d57c05217 | ||
|
|
ec38af8c06 | ||
|
|
c7d2017f08 | ||
|
|
3d393df45d | ||
|
|
2f94e27d3b | ||
|
|
e9b57d5731 | ||
|
|
ff4ffbe975 | ||
|
|
458311252b | ||
|
|
3fe67d5c98 | ||
|
|
87f78069e7 | ||
|
|
f8f7a175c4 | ||
|
|
41c028781b | ||
|
|
f6cf4d127d | ||
|
|
131fb57cc9 | ||
|
|
efe97cd01d | ||
|
|
17e1641226 | ||
|
|
be1b44a4ba | ||
|
|
84f27593a0 | ||
|
|
2af95988df | ||
|
|
e2901eb30f | ||
|
|
377d5e2fb6 | ||
|
|
d3adeec109 | ||
|
|
fc3aec7a79 | ||
|
|
78df8ff03a | ||
|
|
c4902f2b9e | ||
|
|
1423ecc9d8 | ||
|
|
5040c947d2 | ||
|
|
c05698c5ec | ||
|
|
3dd18a453a | ||
|
|
2c6ce21c8f | ||
|
|
0803dce328 | ||
|
|
990ae853b0 | ||
|
|
3c421f124e | ||
|
|
44beac799f | ||
|
|
3b4b59c32b | ||
|
|
7f12f398fb | ||
|
|
891b752e69 | ||
|
|
42bf9c1484 | ||
|
|
4d04dd5651 | ||
|
|
9e79e76bab | ||
|
|
a58371e83b | ||
|
|
d2ae13b03f | ||
|
|
c9b687900e | ||
|
|
282633f907 | ||
|
|
e2964bf669 | ||
|
|
fae3b25b7b | ||
|
|
64ee6d529f | ||
|
|
792db26ff1 | ||
|
|
60633a8f3f | ||
|
|
07eeb8e5fe | ||
|
|
851e74d7b8 | ||
|
|
639863a8ef | ||
|
|
a00f610b2d | ||
|
|
9e951e8800 | ||
|
|
71c16615d1 | ||
|
|
b1fc82315c | ||
|
|
6a1df9de4a | ||
|
|
06445dcaa2 | ||
|
|
a344183355 | ||
|
|
2f28a0deab | ||
|
|
e04310a02d | ||
|
|
4efb7efd0e | ||
|
|
9ad0d7fc8c | ||
|
|
71d21ee184 | ||
|
|
9677dfdb90 | ||
|
|
5b361fb289 | ||
|
|
d9c5b840a5 | ||
|
|
ec3a70b5a7 | ||
|
|
a46b00f76b | ||
|
|
f0e9865ca1 | ||
|
|
0d37705435 | ||
|
|
12ac509c40 | ||
|
|
9353a840ee | ||
|
|
8030577fb2 | ||
|
|
dd86a8eabd | ||
|
|
47164463cc | ||
|
|
00b74664f9 | ||
|
|
c91752d575 | ||
|
|
41d4398342 | ||
|
|
f0a5795b68 | ||
|
|
80f314a422 | ||
|
|
a6408d6033 | ||
|
|
5332d3e5f5 | ||
|
|
d41eca4ed5 | ||
|
|
a5ddcf43de | ||
|
|
bd19ab983d | ||
|
|
d88a341884 | ||
|
|
6db50d26b2 | ||
|
|
3906c754f1 | ||
|
|
7783d0f558 | ||
|
|
add69e5e4f | ||
|
|
d32edcca45 | ||
|
|
69dbe27784 | ||
|
|
de201d567f | ||
|
|
26a042d8b9 | ||
|
|
74b82bd934 | ||
|
|
a7576e07a7 | ||
|
|
66b2f16c9e | ||
|
|
cf10e3cf67 | ||
|
|
3ea981b40c | ||
|
|
e1ce9c63ee | ||
|
|
06073af035 | ||
|
|
19f7bba5d8 | ||
|
|
5f7045eb09 | ||
|
|
8610c862a9 | ||
|
|
8c8a4c2222 | ||
|
|
57cc0b0ae3 | ||
|
|
56c5828b02 |
No files matched your search
+30
-11
@@ -15,22 +15,13 @@ defaults: &defaults
|
||||
commands:
|
||||
install_bun:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- bun-cache-v2-{{ arch }}-latest
|
||||
- run:
|
||||
name: Install Bun
|
||||
command: |
|
||||
if [ ! -d "$HOME/.bun" ]; then
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
fi
|
||||
curl -fsSL https://bun.sh/install | bash -s "bun-v1.2.23"
|
||||
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:
|
||||
@@ -121,6 +112,34 @@ 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
|
||||
@@ -352,7 +371,7 @@ jobs:
|
||||
name: Export Display Variable
|
||||
command: export DISPLAY=:99
|
||||
- cypress/install:
|
||||
install-command: yarn install --no-save
|
||||
install-command: yarn install --frozen-lockfile --no-save
|
||||
- cypress/run-tests:
|
||||
cypress-command: |
|
||||
npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record --parallel
|
||||
|
||||
@@ -23,6 +23,8 @@ 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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [master, release/*]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -18,6 +18,8 @@ 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 }}
|
||||
|
||||
@@ -19,7 +19,7 @@ echo 'Web application built and copied'
|
||||
|
||||
# Build && Move Docusaurus Output (for the docs themselves)
|
||||
cd platform/docs
|
||||
yarn install
|
||||
yarn install --frozen-lockfile
|
||||
yarn run build
|
||||
cd ../..
|
||||
mkdir -p ./.netlify/www/docs
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
"deploy": "netlify deploy --prod --dir ./../platform/app/dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"netlify-cli": "^2.21.0"
|
||||
"netlify-cli": "2.21.0"
|
||||
}
|
||||
}
|
||||
+554
@@ -3,6 +3,560 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **security:** Addressed dependabot alert CVE-2025-59288 concerning playwright ([#5527](https://github.com/OHIF/Viewers/issues/5527)) ([1f11c83](https://github.com/OHIF/Viewers/commit/1f11c83006bff98cb8434f0662b4de55d5b9bf78))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **security:** For bun, enforce frozen lockfile. For yarn, strongly suggest using frozen lockfile. ([#5508](https://github.com/OHIF/Viewers/issues/5508)) ([1009c60](https://github.com/OHIF/Viewers/commit/1009c6091107d2db0768622120f916208a391343))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **jpeg2000:** Add 16-bit RGB support to JPEG2000 decoder ([#5519](https://github.com/OHIF/Viewers/issues/5519)) ([a154443](https://github.com/OHIF/Viewers/commit/a1544432db42bf77864bb7df2f757cae819a4b4d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed the tooltip text of the window button, it was mistakenly written in Chinese in the en-us json ([#5513](https://github.com/OHIF/Viewers/issues/5513)) ([8b9445c](https://github.com/OHIF/Viewers/commit/8b9445c442d0a40b7e4393614313192bd006461b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** Fixed the manual scrolling of the segments list and implemented automatic scrolling to the active segment. ([#5510](https://github.com/OHIF/Viewers/issues/5510)) ([1df4f84](https://github.com/OHIF/Viewers/commit/1df4f843fe3c377643938563ff9e2e4f930d8537))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **rendering:** Fix palette color LUT conversion causing black images ([#5509](https://github.com/OHIF/Viewers/issues/5509)) ([ffd9ec3](https://github.com/OHIF/Viewers/commit/ffd9ec32730580e266ffae365a10ac3e524dd6cf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ErrorBoundary:** Allow for details to be shown in production. ([#5504](https://github.com/OHIF/Viewers/issues/5504)) ([4620cc3](https://github.com/OHIF/Viewers/commit/4620cc3acf89f218c3feaaf4c153a3dc8b023b23))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
|
||||
+3
-2
@@ -49,7 +49,7 @@ 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
|
||||
RUN npm install -g bun@1.2.23
|
||||
RUN npm install -g lerna@7.4.2
|
||||
ENV PATH=/usr/src/app/node_modules/.bin:$PATH
|
||||
|
||||
@@ -60,6 +60,7 @@ 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 . .
|
||||
|
||||
@@ -98,7 +99,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 666 /usr/share/nginx/html
|
||||
RUN chown -R nginx:nginx /usr/share/nginx/html && chmod -R 777 /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 it's own extension system. The same extensibility
|
||||
core features are built using its own extension system. The same extensibility
|
||||
that allows us to offer:
|
||||
|
||||
- 2D and 3D medical image viewing
|
||||
@@ -147,7 +147,14 @@ Here is a schematic representation of our development workflow:
|
||||
3. Navigate to the cloned project's directory
|
||||
4. Add this repo as a `remote` named `upstream`
|
||||
- `git remote add upstream https://github.com/OHIF/Viewers.git`
|
||||
5. `yarn install` to restore dependencies and link projects
|
||||
5. `yarn install --frozen-lockfile` to restore dependencies and link projects
|
||||
|
||||
:::danger
|
||||
In general run `yarn install` with the `--frozen-lockfile` flag to help avoid
|
||||
supply chain attacks by enforcing reproducible dependencies. That is, if the
|
||||
`yarn.lock` file is clean and does NOT reference compromised packages, then
|
||||
no compromised packages should land on your machine by using this flag.
|
||||
:::
|
||||
|
||||
#### To Develop
|
||||
|
||||
@@ -158,7 +165,7 @@ _From this repository's root directory:_
|
||||
yarn config set workspaces-experimental true
|
||||
|
||||
# Restore dependencies
|
||||
yarn install
|
||||
yarn install --frozen-lockfile
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
+446
@@ -3,6 +3,452 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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
|
||||
|
||||
+67
-67
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@externals/devDependencies",
|
||||
"description": "External dev dependencies - put dev build dependencies here",
|
||||
"version": "3.12.0-beta.23",
|
||||
"version": "3.12.0-beta.78",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"engines": {
|
||||
@@ -9,85 +9,85 @@
|
||||
"yarn": ">=1.19.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"core-js": "^3.2.1",
|
||||
"moment": "^2.30.1"
|
||||
"clsx": "2.1.1",
|
||||
"core-js": "3.45.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.15",
|
||||
"@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.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",
|
||||
"@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",
|
||||
"eslint-config-prettier": "7.2.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-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-plugin-prettier": "5.5.1",
|
||||
"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",
|
||||
"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",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-tailwindcss": "0.6.9",
|
||||
"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",
|
||||
"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",
|
||||
"typescript": "5.5.4",
|
||||
"unused-webpack-plugin": "2.4.0",
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-bundle-analyzer": "4.10.2",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.2.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"workbox-webpack-plugin": "^6.1.5",
|
||||
"worker-loader": "^3.0.8"
|
||||
"webpack-hot-middleware": "2.26.1",
|
||||
"webpack-merge": "5.10.0",
|
||||
"workbox-webpack-plugin": "6.6.1",
|
||||
"worker-loader": "3.0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "Included as direct dependency"
|
||||
|
||||
@@ -3,6 +3,449 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@externals/dicom-microscopy-viewer",
|
||||
"description": "External reference to dicom-microscopy-viewer",
|
||||
"version": "3.12.0-beta.23",
|
||||
"version": "3.12.0-beta.78",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dicom-microscopy-viewer": "^0.48.6"
|
||||
"dicom-microscopy-viewer": "0.48.6"
|
||||
}
|
||||
}
|
||||
+17
-18
@@ -22,30 +22,29 @@
|
||||
},
|
||||
"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.16.7",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@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-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.20.13",
|
||||
"**/@babel/runtime": "7.28.2",
|
||||
"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.0",
|
||||
"package-json": "^8.1.0",
|
||||
"typescript": "5.5.4",
|
||||
"sharp": "^0.32.6"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ 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: {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[install]
|
||||
frozenLockfile = true
|
||||
@@ -0,0 +1,2 @@
|
||||
[install]
|
||||
frozenLockfile = false
|
||||
+1
-1
@@ -1 +1 @@
|
||||
d6ea179ac43a47d4011b8dcaf59d79d99382d6c8
|
||||
35bfc9b0ccfea25a61bebf8e252284db7c40e602
|
||||
@@ -3,6 +3,458 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-pmap",
|
||||
"version": "3.12.0-beta.23",
|
||||
"version": "3.12.0-beta.78",
|
||||
"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.23",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.23",
|
||||
"@ohif/extension-default": "3.12.0-beta.23",
|
||||
"@ohif/i18n": "3.12.0-beta.23",
|
||||
"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"
|
||||
"@ohif/core": "3.12.0-beta.78",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.78",
|
||||
"@ohif/extension-default": "3.12.0-beta.78",
|
||||
"@ohif/i18n": "3.12.0-beta.78",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^4.0.0",
|
||||
"@cornerstonejs/core": "^4.0.0",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.5.20",
|
||||
"@cornerstonejs/core": "4.5.20",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
"react-color": "2.19.3"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,452 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||
"version": "3.12.0-beta.23",
|
||||
"version": "3.12.0-beta.78",
|
||||
"description": "DICOM RT read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -33,19 +33,19 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.23",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.23",
|
||||
"@ohif/extension-default": "3.12.0-beta.23",
|
||||
"@ohif/i18n": "3.12.0-beta.23",
|
||||
"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"
|
||||
"@ohif/core": "3.12.0-beta.78",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.78",
|
||||
"@ohif/extension-default": "3.12.0-beta.78",
|
||||
"@ohif/i18n": "3.12.0-beta.78",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"react-color": "^2.19.3"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"react-color": "2.19.3"
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,34 @@ import dcmjs from 'dcmjs';
|
||||
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
|
||||
const dicomlab2RGB = dcmjs.data.Colors.dicomlab2RGB;
|
||||
|
||||
async function checkAndLoadContourData(instance, datasource) {
|
||||
/**
|
||||
* 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;
|
||||
if (!instance || !instance.ROIContourSequence) {
|
||||
return Promise.reject('Invalid instance object or ROIContourSequence');
|
||||
instance = await getRTStructInstance({ extensionManager, rtStructDisplaySet, headers });
|
||||
if (!instance || !instance.ROIContourSequence) {
|
||||
return Promise.reject('Invalid instance object or ROIContourSequence');
|
||||
}
|
||||
}
|
||||
|
||||
const promisesMap = new Map();
|
||||
@@ -30,11 +55,11 @@ async function checkAndLoadContourData(instance, datasource) {
|
||||
} 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,
|
||||
@@ -87,35 +112,74 @@ async function checkAndLoadContourData(instance, datasource) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
export default async function loadRTStruct(extensionManager, rtStructDisplaySet, headers) {
|
||||
/**
|
||||
* 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 }) => {
|
||||
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) {
|
||||
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);
|
||||
instance = rtStructDataset;
|
||||
instance = await getRTStructInstance({ extensionManager, rtStructDisplaySet, headers });
|
||||
} else {
|
||||
await checkAndLoadContourData(instance, dataSource);
|
||||
instance = await checkAndLoadContourData({
|
||||
instance,
|
||||
dataSource,
|
||||
extensionManager,
|
||||
rtStructDisplaySet,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
const { StructureSetROISequence, ROIContourSequence, RTROIObservationsSequence } = instance;
|
||||
@@ -132,7 +196,6 @@ 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;
|
||||
}
|
||||
@@ -185,6 +248,18 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
|
||||
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,
|
||||
@@ -220,6 +295,14 @@ 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;
|
||||
|
||||
@@ -233,6 +316,15 @@ 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
|
||||
@@ -252,6 +344,14 @@ 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,6 +3,458 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||
"version": "3.12.0-beta.23",
|
||||
"version": "3.12.0-beta.78",
|
||||
"description": "DICOM SEG read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -33,22 +33,22 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.23",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.23",
|
||||
"@ohif/extension-default": "3.12.0-beta.23",
|
||||
"@ohif/i18n": "3.12.0-beta.23",
|
||||
"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"
|
||||
"@ohif/core": "3.12.0-beta.78",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.78",
|
||||
"@ohif/extension-default": "3.12.0-beta.78",
|
||||
"@ohif/i18n": "3.12.0-beta.78",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^4.0.0",
|
||||
"@cornerstonejs/core": "^4.0.0",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.5.20",
|
||||
"@cornerstonejs/core": "4.5.20",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
"react-color": "2.19.3"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { classes, Types } from '@ohif/core';
|
||||
import { cache, metaData } from '@cornerstonejs/core';
|
||||
import { segmentation as cornerstoneToolsSegmentation } from '@cornerstonejs/tools';
|
||||
import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters';
|
||||
import { createReportDialogPrompt } from '@ohif/extension-default';
|
||||
import { createReportDialogPrompt, useUIStateStore } from '@ohif/extension-default';
|
||||
import { DicomMetadataStore } from '@ohif/core';
|
||||
|
||||
import PROMPT_RESPONSES from '../../default/src/utils/_shared/PROMPT_RESPONSES';
|
||||
@@ -37,7 +37,7 @@ const commandsModule = ({
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
|
||||
const { segmentationService, displaySetService, viewportGridService, toolGroupService } =
|
||||
const { segmentationService, displaySetService, viewportGridService } =
|
||||
servicesManager.services as AppTypes.Services;
|
||||
|
||||
const actions = {
|
||||
@@ -307,6 +307,17 @@ const commandsModule = ({
|
||||
console.warn(e);
|
||||
}
|
||||
},
|
||||
toggleActiveSegmentationUtility: ({ itemId: buttonId }) => {
|
||||
const { uiState, setUIState } = useUIStateStore.getState();
|
||||
const isButtonActive = uiState['activeSegmentationUtility'] === buttonId;
|
||||
console.log('toggleActiveSegmentationUtility', isButtonActive, buttonId);
|
||||
// if the button is active, clear the active segmentation utility
|
||||
if (isButtonActive) {
|
||||
setUIState('activeSegmentationUtility', null);
|
||||
} else {
|
||||
setUIState('activeSegmentationUtility', buttonId);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -326,6 +337,9 @@ const commandsModule = ({
|
||||
downloadRTSS: {
|
||||
commandFn: actions.downloadRTSS,
|
||||
},
|
||||
toggleActiveSegmentationUtility: {
|
||||
commandFn: actions.toggleActiveSegmentationUtility,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useRunCommand, useSystem } from '@ohif/core';
|
||||
import { useActiveViewportSegmentationRepresentations } from '@ohif/extension-cornerstone';
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Input,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
Separator,
|
||||
Switch,
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@ohif/ui-next';
|
||||
import { Icons } from '@ohif/ui-next';
|
||||
import { contourSegmentation } from '@cornerstonejs/tools/utilities';
|
||||
import { Segment } from '@cornerstonejs/tools/types';
|
||||
|
||||
const { LogicalOperation } = contourSegmentation;
|
||||
const options = [
|
||||
{
|
||||
value: 'merge',
|
||||
logicalOperation: LogicalOperation.Union,
|
||||
label: 'Merge',
|
||||
icon: 'actions-combine-merge',
|
||||
helperIcon: 'helper-combine-merge',
|
||||
},
|
||||
{
|
||||
value: 'intersect',
|
||||
logicalOperation: LogicalOperation.Intersect,
|
||||
label: 'Intersect',
|
||||
icon: 'actions-combine-intersect',
|
||||
helperIcon: 'helper-combine-intersect',
|
||||
},
|
||||
{
|
||||
value: 'subtract',
|
||||
logicalOperation: LogicalOperation.Subtract,
|
||||
label: 'Subtract',
|
||||
icon: 'actions-combine-subtract',
|
||||
helperIcon: 'helper-combine-subtract',
|
||||
},
|
||||
];
|
||||
|
||||
// Shared component for segment selection
|
||||
function SegmentSelector({
|
||||
label,
|
||||
value,
|
||||
onValueChange,
|
||||
segments,
|
||||
placeholder = 'Select a segment',
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
segments: Segment[];
|
||||
placeholder?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex justify-between gap-6">
|
||||
<div>{label}</div>
|
||||
<Select
|
||||
key={`select-segment-${label}`}
|
||||
onValueChange={onValueChange}
|
||||
value={value}
|
||||
>
|
||||
<SelectTrigger className="overflow-hidden">
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{segments.map(segment => (
|
||||
<SelectItem
|
||||
key={segment.segmentIndex}
|
||||
value={segment.segmentIndex.toString()}
|
||||
>
|
||||
{segment.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LogicalContourOperationOptions() {
|
||||
const { servicesManager } = useSystem();
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const { segmentationsWithRepresentations } = useActiveViewportSegmentationRepresentations();
|
||||
|
||||
const activeRepresentation = segmentationsWithRepresentations?.find(
|
||||
({ representation }) => representation?.active
|
||||
);
|
||||
|
||||
const segments = activeRepresentation
|
||||
? Object.values(activeRepresentation.segmentation.segments)
|
||||
: [];
|
||||
|
||||
// Calculate the next available segment index
|
||||
const nextSegmentIndex = activeRepresentation
|
||||
? segmentationService.getNextAvailableSegmentIndex(
|
||||
activeRepresentation.segmentation.segmentationId
|
||||
)
|
||||
: 1;
|
||||
|
||||
const activeSegment = segments.find(segment => segment.active);
|
||||
|
||||
const activeSegmentIndex = activeSegment?.segmentIndex || 0;
|
||||
|
||||
const [operation, setOperation] = useState(options[0]);
|
||||
const [segmentA, setSegmentA] = useState<string>(activeSegmentIndex?.toString() || '');
|
||||
const [segmentB, setSegmentB] = useState<string>('');
|
||||
const [createNewSegment, setCreateNewSegment] = useState<boolean>(false);
|
||||
const [newSegmentName, setNewSegmentName] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
setSegmentA(activeSegmentIndex?.toString() || null);
|
||||
}, [activeSegmentIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
setNewSegmentName(`Segment ${nextSegmentIndex}`);
|
||||
}, [nextSegmentIndex]);
|
||||
|
||||
const runCommand = useRunCommand();
|
||||
|
||||
const applyLogicalContourOperation = useCallback(() => {
|
||||
let resultSegmentIndex = segmentA;
|
||||
if (createNewSegment) {
|
||||
resultSegmentIndex = nextSegmentIndex.toString();
|
||||
runCommand('addSegment', {
|
||||
segmentationId: activeRepresentation.segmentation.segmentationId,
|
||||
config: {
|
||||
label: newSegmentName,
|
||||
segmentIndex: nextSegmentIndex,
|
||||
},
|
||||
});
|
||||
}
|
||||
runCommand('applyLogicalContourOperation', {
|
||||
segmentAInfo: {
|
||||
segmentationId: activeRepresentation.segmentation.segmentationId,
|
||||
segmentIndex: parseInt(segmentA),
|
||||
},
|
||||
segmentBInfo: {
|
||||
segmentationId: activeRepresentation.segmentation.segmentationId,
|
||||
segmentIndex: parseInt(segmentB),
|
||||
},
|
||||
resultSegmentInfo: {
|
||||
segmentationId: activeRepresentation.segmentation.segmentationId,
|
||||
segmentIndex: parseInt(resultSegmentIndex),
|
||||
},
|
||||
logicalOperation: operation.logicalOperation,
|
||||
});
|
||||
}, [
|
||||
activeRepresentation?.segmentation?.segmentationId,
|
||||
createNewSegment,
|
||||
newSegmentName,
|
||||
nextSegmentIndex,
|
||||
operation.logicalOperation,
|
||||
runCommand,
|
||||
segmentA,
|
||||
segmentB,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="flex w-[245px] flex-col gap-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex w-auto flex-col items-center gap-2 text-base font-normal leading-none">
|
||||
<Tabs value={operation.value}>
|
||||
<TabsList className="inline-flex space-x-1">
|
||||
{options.map(option => {
|
||||
const { value, icon } = option;
|
||||
return (
|
||||
<TabsTrigger
|
||||
value={value}
|
||||
key={`logical-contour-operation-${value}`}
|
||||
onClick={() => setOperation(option)}
|
||||
>
|
||||
<Icons.ByName name={icon}></Icons.ByName>
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div>{operation.label}</div>
|
||||
</div>
|
||||
<div className="bg-primary-dark flex h-[62px] w-[88px] items-center justify-center rounded-lg">
|
||||
<Icons.ByName name={operation.helperIcon}></Icons.ByName>
|
||||
</div>
|
||||
</div>
|
||||
<SegmentSelector
|
||||
label="A"
|
||||
value={segmentA}
|
||||
onValueChange={setSegmentA}
|
||||
segments={segments}
|
||||
/>
|
||||
<SegmentSelector
|
||||
label="B"
|
||||
value={segmentB}
|
||||
onValueChange={setSegmentB}
|
||||
segments={segments}
|
||||
/>
|
||||
<div className="flex justify-end pl-[34px]">
|
||||
<Button
|
||||
className="border-primary/60 grow border"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
applyLogicalContourOperation();
|
||||
}}
|
||||
>
|
||||
{operation.label}
|
||||
</Button>
|
||||
</div>
|
||||
<Separator className="bg-input mt-2 h-[1px]" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<Switch
|
||||
id="logical-contour-operations-create-new-segment-switch"
|
||||
onCheckedChange={setCreateNewSegment}
|
||||
></Switch>
|
||||
<Label htmlFor="logical-contour-operations-create-new-segment-switch">
|
||||
Create a new segment
|
||||
</Label>
|
||||
</div>
|
||||
<div className="pl-9">
|
||||
<Input
|
||||
className={cn(createNewSegment ? 'visible' : 'hidden')}
|
||||
disabled={!createNewSegment}
|
||||
id="logical-contour-operations-create-new-segment-input"
|
||||
type="text"
|
||||
placeholder="New segment name"
|
||||
value={newSegmentName}
|
||||
onChange={e => setNewSegmentName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogicalContourOperationOptions;
|
||||
@@ -0,0 +1,71 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Input, Label, Separator } from '@ohif/ui-next';
|
||||
import { useRunCommand } from '@ohif/core';
|
||||
|
||||
function SimplifyContourOptions() {
|
||||
const [areaThreshold, setAreaThreshold] = useState(10);
|
||||
|
||||
const runCommand = useRunCommand();
|
||||
|
||||
return (
|
||||
<div className="flex w-auto w-[252px] flex-col gap-[8px] text-base font-normal leading-none">
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Fill contour holes</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
runCommand('removeContourHoles');
|
||||
}}
|
||||
>
|
||||
Fill Holes
|
||||
</Button>
|
||||
<Separator className="bg-input mt-[20px] h-[1px]" />
|
||||
</div>
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Remove Small Contours</div>
|
||||
<div className="flex items-center gap-2 self-end">
|
||||
<Label
|
||||
htmlFor="simplify-contour-options"
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
Area Threshold
|
||||
</Label>
|
||||
<Input
|
||||
id="simplify-contour-options"
|
||||
className="w-20"
|
||||
type="number"
|
||||
value={areaThreshold}
|
||||
onChange={e => setAreaThreshold(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
runCommand('removeSmallContours', {
|
||||
areaThreshold,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove Small Contours
|
||||
</Button>
|
||||
<Separator className="bg-input mt-[20px] h-[1px]" />
|
||||
</div>
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Create New Segment from Holes</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
runCommand('convertContourHoles');
|
||||
}}
|
||||
>
|
||||
Create New Segment
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SimplifyContourOptions;
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Button, Separator } from '@ohif/ui-next';
|
||||
import { useRunCommand } from '@ohif/core';
|
||||
|
||||
function SmoothContoursOptions() {
|
||||
const runCommand = useRunCommand();
|
||||
|
||||
return (
|
||||
<div className="flex w-auto w-[245px] flex-col gap-[8px] text-base font-normal leading-none">
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Smooth all edges</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
runCommand('smoothContours');
|
||||
}}
|
||||
>
|
||||
Smooth Edges
|
||||
</Button>
|
||||
<Separator className="bg-input mt-[20px] h-[1px]" />
|
||||
</div>
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Remove extra points</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
runCommand('decimateContours');
|
||||
}}
|
||||
>
|
||||
Remove Points
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SmoothContoursOptions;
|
||||
@@ -1,6 +1,32 @@
|
||||
import { useUIStateStore } from '@ohif/extension-default';
|
||||
import LogicalContourOperationsOptions from './components/LogicalContourOperationsOptions';
|
||||
import SimplifyContourOptions from './components/SimplifyContourOptions';
|
||||
import SmoothContoursOptions from './components/SmoothContoursOptions';
|
||||
|
||||
export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||
const { segmentationService, toolbarService, toolGroupService } = servicesManager.services;
|
||||
return [
|
||||
{
|
||||
name: 'cornerstone.SimplifyContourOptions',
|
||||
defaultComponent: SimplifyContourOptions,
|
||||
},
|
||||
{
|
||||
name: 'cornerstone.LogicalContourOperationsOptions',
|
||||
defaultComponent: LogicalContourOperationsOptions,
|
||||
},
|
||||
{
|
||||
name: 'cornerstone.SmoothContoursOptions',
|
||||
defaultComponent: SmoothContoursOptions,
|
||||
},
|
||||
{
|
||||
name: 'cornerstone.isActiveSegmentationUtility',
|
||||
evaluate: ({ button }) => {
|
||||
const { uiState } = useUIStateStore.getState();
|
||||
return {
|
||||
isActive: uiState[`activeSegmentationUtility`] === button.id,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.cornerstone.hasSegmentation',
|
||||
evaluate: ({ viewportId }) => {
|
||||
@@ -10,6 +36,30 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.cornerstone.hasSegmentationOfType',
|
||||
evaluate: ({ viewportId, segmentationRepresentationType }) => {
|
||||
const segmentations = segmentationService.getSegmentationRepresentations(viewportId);
|
||||
|
||||
if (!segmentations?.length) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledText: 'No segmentations available',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
!segmentations.some(segmentation =>
|
||||
Boolean(segmentation.type === segmentationRepresentationType)
|
||||
)
|
||||
) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledText: `No ${segmentationRepresentationType} segmentations available`,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.cornerstone.segmentation',
|
||||
evaluate: ({ viewportId, button, toolNames, disabledText }) => {
|
||||
|
||||
@@ -3,6 +3,461 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-sr",
|
||||
"version": "3.12.0-beta.23",
|
||||
"version": "3.12.0-beta.78",
|
||||
"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.23",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.23",
|
||||
"@ohif/extension-measurement-tracking": "3.12.0-beta.23",
|
||||
"@ohif/ui": "3.12.0-beta.23",
|
||||
"@ohif/core": "3.12.0-beta.78",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.78",
|
||||
"@ohif/extension-measurement-tracking": "3.12.0-beta.78",
|
||||
"@ohif/ui": "3.12.0-beta.78",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^4.0.0",
|
||||
"@cornerstonejs/core": "^4.0.0",
|
||||
"@cornerstonejs/tools": "^4.0.0",
|
||||
"classnames": "^2.3.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.5.20",
|
||||
"@cornerstonejs/core": "4.5.20",
|
||||
"@cornerstonejs/tools": "4.5.20",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ const {
|
||||
CodeScheme: Cornerstone3DCodeScheme,
|
||||
} = adaptersSR.Cornerstone3D;
|
||||
|
||||
type InstanceMetadata = Types.InstanceMetadata;
|
||||
type InstanceMetadata = OhifTypes.InstanceMetadata;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -69,9 +69,10 @@ 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 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
|
||||
* @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
|
||||
*/
|
||||
function _getDisplaySetsFromSeries(
|
||||
instances,
|
||||
@@ -143,7 +144,7 @@ function _getDisplaySetsFromSeries(
|
||||
* @param extensionManager - The extension manager containing data sources.
|
||||
*/
|
||||
async function _load(
|
||||
srDisplaySet: Types.DisplaySet,
|
||||
srDisplaySet: OhifTypes.DisplaySet,
|
||||
servicesManager: AppTypes.ServicesManager,
|
||||
extensionManager: AppTypes.ExtensionManager
|
||||
) {
|
||||
@@ -227,17 +228,9 @@ 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,
|
||||
newDisplaySet,
|
||||
srDisplaySet: OhifTypes.DisplaySet,
|
||||
newDisplaySet: OhifTypes.DisplaySet,
|
||||
dataSource,
|
||||
servicesManager: AppTypes.ServicesManager
|
||||
) {
|
||||
@@ -289,9 +282,7 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
is3DMeasurement &&
|
||||
_measurementBelongsToDisplaySet({ measurement, displaySet: newDisplaySet })
|
||||
) {
|
||||
_measurementBelongsToDisplaySet({ measurement, displaySet: newDisplaySet })
|
||||
|
||||
addSRAnnotation(measurement, null, null);
|
||||
addSRAnnotation({ measurement, displaySet: newDisplaySet });
|
||||
measurement.loaded = true;
|
||||
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
@@ -312,15 +303,12 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
imageId &&
|
||||
_measurementReferencesSOPInstanceUID(measurement, ReferencedSOPInstanceUID, frame)
|
||||
) {
|
||||
addSRAnnotation(measurement, imageId, frame);
|
||||
|
||||
// Update measurement properties
|
||||
addSRAnnotation({ measurement, imageId, frameNumber: frame, displaySet: newDisplaySet });
|
||||
measurement.loaded = true;
|
||||
measurement.imageId = imageId;
|
||||
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||
measurement.ReferencedSOPInstanceUID = ReferencedSOPInstanceUID;
|
||||
measurement.frameNumber = frame;
|
||||
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
}
|
||||
}
|
||||
@@ -328,10 +316,10 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
|
||||
/**
|
||||
* Checks if a measurement references a specific SOP Instance UID.
|
||||
* @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.
|
||||
* @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.
|
||||
*/
|
||||
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
|
||||
const { coords } = measurement;
|
||||
@@ -363,10 +351,8 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
|
||||
/**
|
||||
* Retrieves the SOP class handler module.
|
||||
*
|
||||
* @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.
|
||||
* @param {OhifTypes.Extensions.ExtensionParams} params - The extension parameters.
|
||||
* @returns {Array} An array containing the SOP class handler modules.
|
||||
*/
|
||||
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
|
||||
const { servicesManager, extensionManager } = params;
|
||||
@@ -390,8 +376,8 @@ function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams)
|
||||
/**
|
||||
* Retrieves the measurements from the ImagingMeasurementReportContentSequence.
|
||||
*
|
||||
* @param {Array} ImagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
|
||||
* @returns {Array} - The array of measurements.
|
||||
* @param {any[]} imagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
|
||||
* @returns {any[]} The array of measurements.
|
||||
*/
|
||||
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
||||
@@ -429,8 +415,8 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
/**
|
||||
* Retrieves merged content sequences by tracking unique identifiers.
|
||||
*
|
||||
* @param {Array} MeasurementGroups - The measurement groups.
|
||||
* @returns {Object} - The merged content sequences by tracking unique identifiers.
|
||||
* @param {any[]} measurementGroups - The measurement groups.
|
||||
* @returns {Object} The merged content sequences by tracking unique identifiers.
|
||||
*/
|
||||
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
|
||||
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
||||
@@ -477,8 +463,8 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
||||
* it calls the _processTID1410Measurement function.
|
||||
* Otherwise, it calls the _processNonGeometricallyDefinedMeasurement function.
|
||||
*
|
||||
* @param {Array<Object>} mergedContentSequence - The merged content sequence to process.
|
||||
* @returns {any} - The processed measurement result.
|
||||
* @param {any[]} mergedContentSequence - The merged content sequence to process.
|
||||
* @returns {any} The processed measurement result.
|
||||
*/
|
||||
function _processMeasurement(mergedContentSequence) {
|
||||
if (mergedContentSequence.some(group => isScoordOr3d(group) && !isTextPosition(group))) {
|
||||
@@ -493,8 +479,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 mergedContentSequence - The merged content sequence containing the measurements.
|
||||
* @returns The measurement object containing the loaded status, labels, coordinates, tracking unique identifier, and tracking identifier.
|
||||
* @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.
|
||||
*/
|
||||
function _processTID1410Measurement(mergedContentSequence) {
|
||||
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
||||
@@ -567,8 +553,8 @@ function _processTID1410Measurement(mergedContentSequence) {
|
||||
/**
|
||||
* Processes the non-geometrically defined measurement from the merged content sequence.
|
||||
*
|
||||
* @param mergedContentSequence The merged content sequence containing the measurement data.
|
||||
* @returns The processed measurement object.
|
||||
* @param {any[]} mergedContentSequence The merged content sequence containing the measurement data.
|
||||
* @returns {any} The processed measurement object.
|
||||
*/
|
||||
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
||||
@@ -668,8 +654,8 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
|
||||
/**
|
||||
* Extracts coordinates from a graphic item of type SCOORD or SCOORD3D.
|
||||
* @param {object} graphicItem - The graphic item containing the coordinates.
|
||||
* @returns {object} - The extracted coordinates.
|
||||
* @param {any} graphicItem - The graphic item containing the coordinates.
|
||||
* @returns {any} The extracted coordinates.
|
||||
*/
|
||||
const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
|
||||
const { ValueType, GraphicType, GraphicData } = graphicItem;
|
||||
@@ -683,9 +669,9 @@ const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
|
||||
|
||||
/**
|
||||
* Retrieves the label and value from the provided ConceptNameCodeSequence and MeasuredValueSequence.
|
||||
* @param {Object} ConceptNameCodeSequence - The ConceptNameCodeSequence object.
|
||||
* @param {Object} MeasuredValueSequence - The MeasuredValueSequence object.
|
||||
* @returns {Object} - An object containing the label and value.
|
||||
* @param {any} conceptNameCodeSequence - The ConceptNameCodeSequence object.
|
||||
* @param {any} 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' }
|
||||
@@ -704,8 +690,8 @@ function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredVal
|
||||
/**
|
||||
* Retrieves a list of referenced images from the Imaging Measurement Report Content Sequence.
|
||||
*
|
||||
* @param {Array} ImagingMeasurementReportContentSequence - The Imaging Measurement Report Content Sequence.
|
||||
* @returns {Array} - The list of referenced images.
|
||||
* @param {any[]} imagingMeasurementReportContentSequence - The Imaging Measurement Report Content Sequence.
|
||||
* @returns {any[]} The list of referenced images.
|
||||
*/
|
||||
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
||||
@@ -752,7 +738,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, MeasurementService } from '@ohif/core';
|
||||
import { Types } 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, cornerstoneViewportService } = servicesManager.services;
|
||||
const { measurementService } = servicesManager.services;
|
||||
|
||||
addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
|
||||
addToolInstance(toolNames.SRLength, LengthTool);
|
||||
|
||||
@@ -7,8 +7,53 @@ import toolNames from '../tools/toolNames';
|
||||
|
||||
const { MeasurementReport } = adaptersSR.Cornerstone3D;
|
||||
|
||||
export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
/**
|
||||
* 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 */
|
||||
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 }));
|
||||
@@ -52,10 +97,15 @@ export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
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,
|
||||
@@ -64,6 +114,7 @@ export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
graphicType,
|
||||
FrameOfReferenceUID: frameOfReferenceUID,
|
||||
referencedImageId: imageId,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
},
|
||||
data: {
|
||||
label: measurement.labels?.[0]?.value || undefined,
|
||||
@@ -81,8 +132,11 @@ export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
};
|
||||
|
||||
/**
|
||||
* const annotationManager = annotation.annotationState.getAnnotationManager();
|
||||
* was not triggering annotation_added events.
|
||||
* 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
|
||||
*/
|
||||
annotation.state.addAnnotation(SRAnnotation);
|
||||
}
|
||||
@@ -74,17 +74,13 @@ export default function hydrateStructuredReport(
|
||||
);
|
||||
|
||||
const sopInstanceUIDToImageId = {};
|
||||
const imageIdsForToolState = {};
|
||||
|
||||
displaySet.measurements.forEach(measurement => {
|
||||
const { ReferencedSOPInstanceUID, imageId, frameNumber } = measurement;
|
||||
const { ReferencedSOPInstanceUID, imageId, frameNumber=1 } = measurement;
|
||||
const key = `${ReferencedSOPInstanceUID}:${frameNumber}`;
|
||||
|
||||
if (!sopInstanceUIDToImageId[ReferencedSOPInstanceUID]) {
|
||||
sopInstanceUIDToImageId[ReferencedSOPInstanceUID] = imageId;
|
||||
imageIdsForToolState[ReferencedSOPInstanceUID] = [];
|
||||
}
|
||||
if (!imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber]) {
|
||||
imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber] = imageId;
|
||||
if (!sopInstanceUIDToImageId[key]) {
|
||||
sopInstanceUIDToImageId[key] = imageId;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,10 +129,8 @@ export default function hydrateStructuredReport(
|
||||
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
||||
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
||||
// in the wrong value.
|
||||
const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
|
||||
const imageId =
|
||||
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
|
||||
sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
||||
const frameNumber = toolData.annotation.data?.frameNumber || 1;
|
||||
const imageId = sopInstanceUIDToImageId[`${toolData.sopInstanceUid}:${frameNumber}`];
|
||||
|
||||
if (!imageIds.includes(imageId)) {
|
||||
imageIds.push(imageId);
|
||||
@@ -175,9 +169,7 @@ export default function hydrateStructuredReport(
|
||||
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
||||
// in the wrong value.
|
||||
const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
|
||||
const imageId =
|
||||
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
|
||||
sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
||||
const imageId = sopInstanceUIDToImageId[`${toolData.sopInstanceUid}:${frameNumber}`];
|
||||
|
||||
if (!imageId) {
|
||||
return getReferenceData3D(toolData, servicesManager);
|
||||
|
||||
@@ -3,6 +3,458 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dynamic-volume",
|
||||
"version": "3.12.0-beta.23",
|
||||
"version": "3.12.0-beta.78",
|
||||
"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.23",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.23",
|
||||
"@ohif/extension-default": "3.12.0-beta.23",
|
||||
"@ohif/i18n": "3.12.0-beta.23",
|
||||
"@ohif/ui": "3.12.0-beta.23",
|
||||
"@ohif/core": "3.12.0-beta.78",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.78",
|
||||
"@ohif/extension-default": "3.12.0-beta.78",
|
||||
"@ohif/i18n": "3.12.0-beta.78",
|
||||
"@ohif/ui": "3.12.0-beta.78",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^4.0.0",
|
||||
"@cornerstonejs/tools": "^4.0.0",
|
||||
"classnames": "^2.3.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/core": "4.5.20",
|
||||
"@cornerstonejs/tools": "4.5.20",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,491 @@
|
||||
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.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **jpeg2000:** Add 16-bit RGB support to JPEG2000 decoder ([#5519](https://github.com/OHIF/Viewers/issues/5519)) ([a154443](https://github.com/OHIF/Viewers/commit/a1544432db42bf77864bb7df2f757cae819a4b4d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
const base = require('../../jest.config.base.js');
|
||||
const pkg = require('./package');
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
displayName: pkg.name,
|
||||
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',
|
||||
},
|
||||
// rootDir: "../.."
|
||||
// testMatch: [
|
||||
// //`<rootDir>/platform/${pack.name}/**/*.spec.js`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "3.12.0-beta.23",
|
||||
"version": "3.12.0-beta.78",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -31,41 +31,43 @@
|
||||
"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"
|
||||
"start": "yarn run dev",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"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.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^4.0.0",
|
||||
"@ohif/core": "3.12.0-beta.23",
|
||||
"@ohif/ui": "3.12.0-beta.23",
|
||||
"@cornerstonejs/codec-charls": "1.2.3",
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "1.3.0",
|
||||
"@cornerstonejs/codec-openjph": "2.4.7",
|
||||
"@cornerstonejs/dicom-image-loader": "4.5.20",
|
||||
"@ohif/core": "3.12.0-beta.78",
|
||||
"@ohif/ui": "3.12.0-beta.78",
|
||||
"dcmjs": "0.43.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"
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^4.0.0",
|
||||
"@cornerstonejs/ai": "^4.0.0",
|
||||
"@cornerstonejs/core": "^4.0.0",
|
||||
"@cornerstonejs/labelmap-interpolation": "^4.0.0",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^4.0.0",
|
||||
"@cornerstonejs/tools": "^4.0.0",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.5.20",
|
||||
"@cornerstonejs/ai": "4.5.20",
|
||||
"@cornerstonejs/core": "4.5.20",
|
||||
"@cornerstonejs/labelmap-interpolation": "4.5.20",
|
||||
"@cornerstonejs/polymorphic-segmentation": "4.5.20",
|
||||
"@cornerstonejs/tools": "4.5.20",
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,18 @@ import {
|
||||
utilities as cstUtils,
|
||||
annotation,
|
||||
Types as ToolTypes,
|
||||
SplineContourSegmentationTool,
|
||||
} from '@cornerstonejs/tools';
|
||||
import {
|
||||
SegmentInfo,
|
||||
LogicalOperation,
|
||||
OperatorOptions,
|
||||
} from '@cornerstonejs/tools/utilities/contourSegmentation/logicalOperators';
|
||||
import * as cornerstoneTools from '@cornerstonejs/tools';
|
||||
import * as labelmapInterpolation from '@cornerstonejs/labelmap-interpolation';
|
||||
import { ONNXSegmentationController } from '@cornerstonejs/ai';
|
||||
|
||||
import { Types as OhifTypes, utils } from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
import {
|
||||
callInputDialogAutoComplete,
|
||||
createReportAsync,
|
||||
@@ -33,7 +38,11 @@ import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/u
|
||||
import { getViewportEnabledElement } from './utils/getViewportEnabledElement';
|
||||
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
||||
import toggleVOISliceSync from './utils/toggleVOISliceSync';
|
||||
import { usePositionPresentationStore, useSegmentationPresentationStore } from './stores';
|
||||
import {
|
||||
usePositionPresentationStore,
|
||||
useSegmentationPresentationStore,
|
||||
useSelectedSegmentationsForViewportStore,
|
||||
} from './stores';
|
||||
import { toolNames } from './initCornerstoneTools';
|
||||
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
||||
import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats';
|
||||
@@ -42,6 +51,12 @@ 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';
|
||||
import { createSegmentationForViewport } from './utils/createSegmentationForViewport';
|
||||
import { utilities as segmentationUtilities } from '@cornerstonejs/tools/segmentation';
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
const { add, intersect, subtract, copy } = cstUtils.contourSegmentation;
|
||||
|
||||
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
||||
const toggleSyncFunctions = {
|
||||
@@ -235,16 +250,28 @@ function commandsModule({
|
||||
return;
|
||||
}
|
||||
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (displaySet.isOverlayDisplaySet) {
|
||||
// update the previously stored segmentationPresentation with the new viewportId
|
||||
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||
// it will have the correct segmentation representation hydrated
|
||||
|
||||
const segmentationType =
|
||||
// Todo: check if PMAP modality should be handled such as SEG
|
||||
displaySet.Modality !== 'SEG'
|
||||
? SegmentationRepresentations.Contour
|
||||
: viewport.type === CoreEnums.ViewportType.VOLUME_3D
|
||||
? SegmentationRepresentations.Surface
|
||||
: SegmentationRepresentations.Labelmap;
|
||||
|
||||
commandsManager.runCommand('updateStoredSegmentationPresentation', {
|
||||
displaySet,
|
||||
type:
|
||||
displaySet.Modality === 'SEG'
|
||||
? SegmentationRepresentations.Labelmap
|
||||
: SegmentationRepresentations.Contour,
|
||||
type: segmentationType,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -265,10 +292,35 @@ function commandsModule({
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
storePositionPresentation(referencedDisplaySet);
|
||||
return commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
|
||||
const results = commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
|
||||
const disableEditing = customizationService.getCustomization(
|
||||
'panelSegmentation.disableEditing'
|
||||
);
|
||||
if (disableEditing) {
|
||||
const segmentationRepresentations = segmentationService.getSegmentationRepresentations(
|
||||
viewportId,
|
||||
{
|
||||
segmentationId: displaySet.displaySetInstanceUID,
|
||||
}
|
||||
);
|
||||
|
||||
segmentationRepresentations.forEach(representation => {
|
||||
const segmentIndices = Object.keys(representation.segments);
|
||||
segmentIndices.forEach(segmentIndex => {
|
||||
segmentationService.setSegmentLocked(
|
||||
representation.segmentationId,
|
||||
parseInt(segmentIndex),
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
return results;
|
||||
} else if (displaySet.Modality === 'SR') {
|
||||
const results = commandsManager.runCommand('hydrateStructuredReport', {
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
@@ -517,8 +569,8 @@ function commandsModule({
|
||||
if (!labelConfig) {
|
||||
const label = await callInputDialog({
|
||||
uiDialogService,
|
||||
title: 'Edit Measurement Label',
|
||||
placeholder: measurement.label || 'Enter new label',
|
||||
title: i18n.t('Tools:Edit Measurement Label'),
|
||||
placeholder: measurement.label || i18n.t('Tools:Enter new label'),
|
||||
defaultValue: measurement.label,
|
||||
});
|
||||
|
||||
@@ -732,8 +784,8 @@ function commandsModule({
|
||||
if (!labelConfig) {
|
||||
const label = await callInputDialog({
|
||||
uiDialogService,
|
||||
title: 'Edit Arrow Text',
|
||||
placeholder: data?.data?.label || 'Enter new text',
|
||||
title: i18n.t('Tools:Edit Arrow Text'),
|
||||
placeholder: data?.data?.label || i18n.t('Tools:Enter new text'),
|
||||
defaultValue: data?.data?.label || '',
|
||||
});
|
||||
|
||||
@@ -925,17 +977,21 @@ function commandsModule({
|
||||
}
|
||||
}
|
||||
},
|
||||
setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [] }) => {
|
||||
setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [], bindings }) => {
|
||||
// Sometimes it is passed as value (tools with options), sometimes as itemId (toolbar buttons)
|
||||
toolName = toolName || itemId || value;
|
||||
|
||||
toolGroupIds = toolGroupIds.length ? toolGroupIds : toolGroupService.getToolGroupIds();
|
||||
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
actions.setToolActive({ toolName, toolGroupId });
|
||||
actions.setToolActive({ toolName, toolGroupId, bindings });
|
||||
});
|
||||
},
|
||||
setToolActive: ({ toolName, toolGroupId = null }) => {
|
||||
setToolActive: ({
|
||||
toolName,
|
||||
toolGroupId = null,
|
||||
bindings = [{ mouseButton: Enums.MouseBindings.Primary }],
|
||||
}) => {
|
||||
const { viewports } = viewportGridService.getState();
|
||||
|
||||
if (!viewports.size) {
|
||||
@@ -963,11 +1019,7 @@ function commandsModule({
|
||||
|
||||
// Set the new toolName to be active
|
||||
toolGroup.setToolActive(toolName, {
|
||||
bindings: [
|
||||
{
|
||||
mouseButton: Enums.MouseBindings.Primary,
|
||||
},
|
||||
],
|
||||
bindings,
|
||||
});
|
||||
},
|
||||
// capture viewport
|
||||
@@ -977,8 +1029,8 @@ function commandsModule({
|
||||
if (!cornerstoneViewportService.getCornerstoneViewport(activeViewportId)) {
|
||||
// Cannot download a non-cornerstone viewport (image).
|
||||
uiNotificationService.show({
|
||||
title: 'Download Image',
|
||||
message: 'Image cannot be downloaded',
|
||||
title: i18n.t('Tools:Download Image'),
|
||||
message: i18n.t('Tools:Image cannot be downloaded'),
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
@@ -989,7 +1041,7 @@ function commandsModule({
|
||||
if (uiModalService) {
|
||||
uiModalService.show({
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
title: 'Download High Quality Image',
|
||||
title: i18n.t('Tools:Download High Quality Image'),
|
||||
contentProps: {
|
||||
activeViewportId,
|
||||
cornerstoneViewportService,
|
||||
@@ -1440,48 +1492,24 @@ function commandsModule({
|
||||
* as a segmentation representation to the viewport.
|
||||
*/
|
||||
createLabelmapForViewport: async ({ viewportId, options = {} }) => {
|
||||
const { viewportGridService, displaySetService, segmentationService } =
|
||||
servicesManager.services;
|
||||
const { viewports } = viewportGridService.getState();
|
||||
const targetViewportId = viewportId;
|
||||
|
||||
const viewport = viewports.get(targetViewportId);
|
||||
|
||||
// Todo: add support for multiple display sets
|
||||
const displaySetInstanceUID =
|
||||
options.displaySetInstanceUID || viewport.displaySetInstanceUIDs[0];
|
||||
|
||||
const segs = segmentationService.getSegmentations();
|
||||
|
||||
const label = options.label || `Segmentation ${segs.length + 1}`;
|
||||
const segmentationId = options.segmentationId || `${csUtils.uuidv4()}`;
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
// This will create the segmentation and register it as a display set
|
||||
const generatedSegmentationId = await segmentationService.createLabelmapForDisplaySet(
|
||||
displaySet,
|
||||
{
|
||||
label,
|
||||
segmentationId,
|
||||
segments: options.createInitialSegment
|
||||
? {
|
||||
1: {
|
||||
label: `${i18n.t('Segment')} 1`,
|
||||
active: true,
|
||||
},
|
||||
}
|
||||
: {},
|
||||
}
|
||||
);
|
||||
|
||||
// Also add the segmentation representation to the viewport
|
||||
await segmentationService.addSegmentationRepresentation(viewportId, {
|
||||
segmentationId,
|
||||
type: Enums.SegmentationRepresentations.Labelmap,
|
||||
return createSegmentationForViewport(servicesManager, {
|
||||
viewportId,
|
||||
options,
|
||||
segmentationType: SegmentationRepresentations.Labelmap,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Creates a contour for the active viewport
|
||||
*
|
||||
* The created contour will be registered as a display set and also added
|
||||
* as a segmentation representation to the viewport.
|
||||
*/
|
||||
createContourForViewport: async ({ viewportId, options = {} }) => {
|
||||
return createSegmentationForViewport(servicesManager, {
|
||||
viewportId,
|
||||
options,
|
||||
segmentationType: SegmentationRepresentations.Contour,
|
||||
});
|
||||
|
||||
return generatedSegmentationId;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1500,9 +1528,9 @@ function commandsModule({
|
||||
* Adds a new segment to a segmentation
|
||||
* @param props.segmentationId - The ID of the segmentation to add the segment to
|
||||
*/
|
||||
addSegmentCommand: ({ segmentationId }) => {
|
||||
addSegmentCommand: ({ segmentationId, config }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.addSegment(segmentationId);
|
||||
segmentationService.addSegment(segmentationId, config);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1518,7 +1546,33 @@ function commandsModule({
|
||||
segmentationId
|
||||
);
|
||||
segmentationService.setActiveSegment(segmentationId, segmentIndex);
|
||||
segmentationService.jumpToSegmentCenter(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
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1718,8 +1772,8 @@ function commandsModule({
|
||||
|
||||
callInputDialog({
|
||||
uiDialogService,
|
||||
title: 'Edit Segment Label',
|
||||
placeholder: 'Enter new label',
|
||||
title: i18n.t('Tools:Edit Segment Label'),
|
||||
placeholder: i18n.t('Tools:Enter new label'),
|
||||
defaultValue: segment.label,
|
||||
}).then(label => {
|
||||
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
|
||||
@@ -1738,8 +1792,8 @@ function commandsModule({
|
||||
|
||||
callInputDialog({
|
||||
uiDialogService,
|
||||
title: 'Edit Segmentation Label',
|
||||
placeholder: 'Enter new label',
|
||||
title: i18n.t('Tools:Edit Segmentation Label'),
|
||||
placeholder: i18n.t('Tools:Enter new label'),
|
||||
defaultValue: label,
|
||||
}).then(label => {
|
||||
segmentationService.addOrUpdateSegmentation({ segmentationId, label });
|
||||
@@ -1761,7 +1815,7 @@ function commandsModule({
|
||||
|
||||
uiDialogService.show({
|
||||
content: colorPickerDialog,
|
||||
title: 'Segment Color',
|
||||
title: i18n.t('Tools:Segment Color'),
|
||||
contentProps: {
|
||||
value: rgbaColor,
|
||||
onSave: newRgbaColor => {
|
||||
@@ -1831,22 +1885,24 @@ function commandsModule({
|
||||
}
|
||||
});
|
||||
},
|
||||
toggleSegmentLabel: () => {
|
||||
toggleSegmentLabel: ({ enabled }: { enabled?: boolean }) => {
|
||||
const toolName = cornerstoneTools.SegmentLabelTool.toolName;
|
||||
const toolGroupIds = toolGroupService.getToolGroupIds();
|
||||
|
||||
const isOn = toolGroupIds.some(toolGroupId => {
|
||||
const isToolOn = 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 (isOn) {
|
||||
toolGroup.setToolDisabled(toolName);
|
||||
} else {
|
||||
if (enableTool) {
|
||||
toolGroup.setToolActive(toolName);
|
||||
} else {
|
||||
toolGroup.setToolDisabled(toolName);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -2203,6 +2259,207 @@ function commandsModule({
|
||||
deleting,
|
||||
});
|
||||
},
|
||||
activateSelectedSegmentationOfType: ({ segmentationRepresentationType }) => {
|
||||
const { segmentationService, viewportGridService } = servicesManager.services;
|
||||
const activeViewportId = viewportGridService.getActiveViewportId();
|
||||
const { selectedSegmentationsForViewport } =
|
||||
useSelectedSegmentationsForViewportStore.getState();
|
||||
const segmentationId = selectedSegmentationsForViewport[activeViewportId]?.get(
|
||||
segmentationRepresentationType
|
||||
);
|
||||
|
||||
if (!segmentationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentationService.setActiveSegmentation(activeViewportId, segmentationId);
|
||||
},
|
||||
setDynamicCursorSizeForSculptorTool: ({ value: isDynamicCursorSize }) => {
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
const sculptorToolInstance = toolGroup.getToolInstance(toolNames.SculptorTool);
|
||||
const oldConfiguration = sculptorToolInstance.configuration;
|
||||
|
||||
sculptorToolInstance.configuration = {
|
||||
...oldConfiguration,
|
||||
updateCursorSize: isDynamicCursorSize ? 'dynamic' : '',
|
||||
};
|
||||
},
|
||||
setInterpolationToolConfiguration: ({ value: interpolateContours, toolNames }) => {
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
// Set the interpolation configuration for the active tool.
|
||||
const activeTool = toolGroupService.getActiveToolForViewport(viewportId);
|
||||
const interpolationConfig = {
|
||||
interpolation: {
|
||||
enabled: interpolateContours,
|
||||
showInterpolationPolyline: true,
|
||||
},
|
||||
};
|
||||
toolGroup.setToolConfiguration(activeTool, interpolationConfig);
|
||||
|
||||
// Now set the interpolation configuration for the other tools specified.
|
||||
if (toolNames) {
|
||||
Object.values(toolGroup.getToolInstances()).forEach(toolInstance => {
|
||||
if (toolNames?.includes(toolInstance.toolName)) {
|
||||
toolGroup.setToolConfiguration(toolInstance.toolName, interpolationConfig);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
setSimplifiedSplineForSplineContourSegmentationTool: ({ value: simplifiedSpline }) => {
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
Object.values(toolGroup.getToolInstances()).forEach(toolInstance => {
|
||||
if (toolInstance instanceof SplineContourSegmentationTool) {
|
||||
const oldConfiguration = toolInstance.configuration;
|
||||
toolInstance.configuration = {
|
||||
...oldConfiguration,
|
||||
simplifiedSpline,
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
removeSmallContours: ({ areaThreshold: threshold }) => {
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
|
||||
const activeSegment = segmentationService.getActiveSegment(viewportId);
|
||||
|
||||
if (!activeSegmentation || !activeSegment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { removeContourIslands } = segmentationUtilities;
|
||||
removeContourIslands(activeSegmentation.segmentationId, activeSegment.segmentIndex, {
|
||||
threshold,
|
||||
});
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
renderingEngine.render();
|
||||
},
|
||||
applyLogicalContourOperation: ({
|
||||
segmentAInfo,
|
||||
segmentBInfo,
|
||||
resultSegmentInfo,
|
||||
logicalOperation,
|
||||
}: {
|
||||
segmentAInfo: SegmentInfo;
|
||||
segmentBInfo: SegmentInfo;
|
||||
resultSegmentInfo: OperatorOptions;
|
||||
logicalOperation: LogicalOperation;
|
||||
}) => {
|
||||
switch (logicalOperation) {
|
||||
case LogicalOperation.Union:
|
||||
add(segmentAInfo, segmentBInfo, resultSegmentInfo);
|
||||
break;
|
||||
case LogicalOperation.Intersect:
|
||||
intersect(segmentAInfo, segmentBInfo, resultSegmentInfo);
|
||||
break;
|
||||
case LogicalOperation.Subtract:
|
||||
subtract(segmentAInfo, segmentBInfo, resultSegmentInfo);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported logical operation');
|
||||
break;
|
||||
}
|
||||
},
|
||||
copyContourSegment: ({
|
||||
sourceSegmentInfo,
|
||||
targetSegmentInfo,
|
||||
}: {
|
||||
sourceSegmentInfo: SegmentInfo;
|
||||
targetSegmentInfo?: SegmentInfo;
|
||||
}) => {
|
||||
if (!targetSegmentInfo) {
|
||||
targetSegmentInfo = {
|
||||
segmentationId: sourceSegmentInfo.segmentationId,
|
||||
segmentIndex: segmentationService.getNextAvailableSegmentIndex(
|
||||
sourceSegmentInfo.segmentationId
|
||||
),
|
||||
};
|
||||
segmentationService.addSegment(targetSegmentInfo.segmentationId, {
|
||||
segmentIndex: targetSegmentInfo.segmentIndex,
|
||||
});
|
||||
}
|
||||
|
||||
copy(sourceSegmentInfo, targetSegmentInfo);
|
||||
},
|
||||
smoothContours: () => {
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
|
||||
const activeSegment = segmentationService.getActiveSegment(viewportId);
|
||||
|
||||
if (!activeSegmentation || !activeSegment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { smoothContours } = segmentationUtilities;
|
||||
smoothContours(activeSegmentation.segmentationId, activeSegment.segmentIndex);
|
||||
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
renderingEngine.render();
|
||||
},
|
||||
removeContourHoles: () => {
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
|
||||
const activeSegment = segmentationService.getActiveSegment(viewportId);
|
||||
|
||||
if (!activeSegmentation || !activeSegment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { removeContourHoles } = segmentationUtilities;
|
||||
removeContourHoles(activeSegmentation.segmentationId, activeSegment.segmentIndex);
|
||||
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
renderingEngine.render();
|
||||
},
|
||||
decimateContours: () => {
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
|
||||
const activeSegment = segmentationService.getActiveSegment(viewportId);
|
||||
|
||||
if (!activeSegmentation || !activeSegment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { decimateContours } = segmentationUtilities;
|
||||
decimateContours(activeSegmentation.segmentationId, activeSegment.segmentIndex);
|
||||
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
renderingEngine.render();
|
||||
},
|
||||
convertContourHoles: () => {
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
|
||||
const activeSegment = segmentationService.getActiveSegment(viewportId);
|
||||
|
||||
if (!activeSegmentation || !activeSegment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetSegmentInfo = {
|
||||
segmentationId: activeSegmentation.segmentationId,
|
||||
segmentIndex: segmentationService.getNextAvailableSegmentIndex(
|
||||
activeSegmentation.segmentationId
|
||||
),
|
||||
};
|
||||
|
||||
segmentationService.addSegment(targetSegmentInfo.segmentationId, {
|
||||
segmentIndex: targetSegmentInfo.segmentIndex,
|
||||
});
|
||||
|
||||
const { convertContourHoles } = segmentationUtilities;
|
||||
convertContourHoles(
|
||||
activeSegmentation.segmentationId,
|
||||
activeSegment.segmentIndex,
|
||||
targetSegmentInfo.segmentationId,
|
||||
targetSegmentInfo.segmentIndex
|
||||
);
|
||||
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
renderingEngine.render();
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -2401,6 +2658,9 @@ function commandsModule({
|
||||
createLabelmapForViewport: {
|
||||
commandFn: actions.createLabelmapForViewport,
|
||||
},
|
||||
createContourForViewport: {
|
||||
commandFn: actions.createContourForViewport,
|
||||
},
|
||||
setActiveSegmentation: {
|
||||
commandFn: actions.setActiveSegmentation,
|
||||
},
|
||||
@@ -2502,6 +2762,18 @@ function commandsModule({
|
||||
toggleSegmentLabel: actions.toggleSegmentLabel,
|
||||
jumpToMeasurementViewport: actions.jumpToMeasurementViewport,
|
||||
initializeSegmentLabelTool: actions.initializeSegmentLabelTool,
|
||||
activateSelectedSegmentationOfType: actions.activateSelectedSegmentationOfType,
|
||||
setDynamicCursorSizeForSculptorTool: actions.setDynamicCursorSizeForSculptorTool,
|
||||
setSimplifiedSplineForSplineContourSegmentationTool:
|
||||
actions.setSimplifiedSplineForSplineContourSegmentationTool,
|
||||
removeSmallContours: actions.removeSmallContours,
|
||||
applyLogicalContourOperation: actions.applyLogicalContourOperation,
|
||||
copyContourSegment: actions.copyContourSegment,
|
||||
smoothContours: actions.smoothContours,
|
||||
removeContourHoles: actions.removeContourHoles,
|
||||
decimateContours: actions.decimateContours,
|
||||
convertContourHoles: actions.convertContourHoles,
|
||||
setInterpolationToolConfiguration: actions.setInterpolationToolConfiguration,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -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, type } = props;
|
||||
const { grouping, items, children, sourceChildren } = props;
|
||||
const childProps = useSystem();
|
||||
let defaultValue = props.defaultValue;
|
||||
const groups = grouping.groupingFunction(items, grouping, childProps);
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
Icons,
|
||||
} from '@ohif/ui-next';
|
||||
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
|
||||
interface ExportSegmentationSubMenuItemProps {
|
||||
segmentationId: string;
|
||||
segmentationRepresentationType: string;
|
||||
allowExport: boolean;
|
||||
actions: {
|
||||
storeSegmentation: (segmentationId: string) => Promise<void>;
|
||||
onSegmentationDownloadRTSS: (segmentationId: string) => void;
|
||||
onSegmentationDownload: (segmentationId: string) => void;
|
||||
downloadCSVSegmentationReport: (segmentationId: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const ExportSegmentationSubMenuItem: React.FC<ExportSegmentationSubMenuItemProps> = ({
|
||||
segmentationId,
|
||||
segmentationRepresentationType,
|
||||
allowExport,
|
||||
actions,
|
||||
}) => {
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
|
||||
return (
|
||||
<>
|
||||
{segmentationRepresentationType === SegmentationRepresentations.Labelmap && (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="pl-1">
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">{t('Download & Export')}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuLabel className="flex items-center pl-0">
|
||||
<Icons.Download className="h-5 w-5" />
|
||||
<span className="pl-1">{t('Download')}</span>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.downloadCSVSegmentationReport(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('CSV Report')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.onSegmentationDownload(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.onSegmentationDownloadRTSS(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('DICOM RTSS')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel className="flex items-center pl-0">
|
||||
<Icons.Export className="h-5 w-5" />
|
||||
<span className="pl-1 pt-1">{t('Export')}</span>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.storeSegmentation(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
)}
|
||||
{segmentationRepresentationType === SegmentationRepresentations.Contour && (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.onSegmentationDownloadRTSS(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">{t('Download DICOM RTSS')}</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Switch } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export default function SegmentationToolConfig() {
|
||||
const { commandsManager } = useSystem();
|
||||
|
||||
// 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 => {
|
||||
setPreviewEdits(checked);
|
||||
commandsManager.run('toggleSegmentPreviewEdit', { toggle: checked });
|
||||
};
|
||||
|
||||
const handleToggleSegmentEnabledChange = checked => {
|
||||
setToggleSegmentEnabled(checked);
|
||||
commandsManager.run('toggleSegmentSelect', { toggle: checked });
|
||||
};
|
||||
|
||||
const handleUseCenterAsSegmentIndexChange = checked => {
|
||||
setUseCenterAsSegmentIndex(checked);
|
||||
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="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={previewEdits}
|
||||
onCheckedChange={handlePreviewEditsChange}
|
||||
/>
|
||||
<span className="text-foreground text-base">Preview edits before creating</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={useCenterAsSegmentIndex}
|
||||
onCheckedChange={handleUseCenterAsSegmentIndexChange}
|
||||
/>
|
||||
<span className="text-foreground text-base">Use center as segment index</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { cn, ToolButton } from '@ohif/ui-next';
|
||||
import { useUIStateStore } from '@ohif/extension-default';
|
||||
|
||||
interface SegmentationUtilityButtonProps {
|
||||
className?: string;
|
||||
isActive?: boolean;
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A button that represents a segmentation utility.
|
||||
* It is implicitly the PopoverTrigger for the options Popover panel that is
|
||||
* opened in the PanelSegmentation component. It in essence is the PopoverTrigger
|
||||
* because typically this button has the associated options to display in the Popover.
|
||||
* Furthermore it also typically has a command for toggling the activeSegmentationUtility
|
||||
* value in the UIStateStore that triggers the fetching of its options.
|
||||
|
||||
* @param props - The props for the button.
|
||||
* @param props.className - The class name for the button.
|
||||
* @param props.isActive - Whether the button is active
|
||||
* @param props.id - The id of the button.
|
||||
*/
|
||||
function SegmentationUtilityButton(props: SegmentationUtilityButtonProps) {
|
||||
const { className, isActive, id } = props;
|
||||
|
||||
const activeSegmentationUtility = useUIStateStore(
|
||||
store => store.uiState.activeSegmentationUtility
|
||||
);
|
||||
|
||||
const toolButtonClassName = cn(
|
||||
'w-7 h-7 text-primary hover:text-primary hover:!bg-primary/30',
|
||||
className,
|
||||
isActive && 'bg-primary/30'
|
||||
);
|
||||
|
||||
const handleMouseDownCapture = useCallback(
|
||||
event => {
|
||||
if (activeSegmentationUtility === id) {
|
||||
// If this active button is clicked, prevent the default Popover
|
||||
// behaviour of closing the Popover on a pointer/mouse down.
|
||||
// Not doing this will cause the Popover to close and then reopen again.
|
||||
// Why? Because propagating this event will cause PanelSegmentation to
|
||||
// close the Popover by clearing the activeSegmentationUtility. Then
|
||||
// this button will set the activeSegmentationUtility again and the
|
||||
// Popover will reopen.
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
},
|
||||
[activeSegmentationUtility, id]
|
||||
);
|
||||
|
||||
return (
|
||||
<div onPointerDownCapture={handleMouseDownCapture}>
|
||||
<ToolButton
|
||||
{...props}
|
||||
className={toolButtonClassName}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SegmentationUtilityButton;
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useActiveViewportDisplaySets, useSystem, utils } from '@ohif/core';
|
||||
// import { AccordionContent, AccordionItem, AccordionTrigger } from '@ohif/ui-next';
|
||||
import { useActiveViewportDisplaySets, utils } from '@ohif/core';
|
||||
|
||||
import { AccordionGroup } from './AccordionGroup';
|
||||
import MeasurementsOrAdditionalFindings from './MeasurementsOrAdditionalFindings';
|
||||
@@ -63,8 +62,7 @@ export const groupByStudy = (items, grouping, childProps) => {
|
||||
export function StudyMeasurements(props): React.ReactNode {
|
||||
const { items, grouping = {}, children } = props;
|
||||
|
||||
const system = useSystem();
|
||||
const activeDisplaySets = useActiveViewportDisplaySets(system);
|
||||
const activeDisplaySets = useActiveViewportDisplaySets();
|
||||
const activeStudyUID = activeDisplaySets?.[0]?.StudyInstanceUID;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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();
|
||||
@@ -16,7 +18,7 @@ export function Colorbar({ viewportId }: { viewportId?: string } = {}): ReactEle
|
||||
className="flex-grow"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
Display Color bar
|
||||
{t('Display Color bar')}
|
||||
</span>
|
||||
<Switch
|
||||
className="ml-2 flex-shrink-0"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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
|
||||
@@ -74,7 +76,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">Preview in viewport</span>
|
||||
<span className="flex-shrink-0">{t('Preview in viewport')}</span>
|
||||
<Switch
|
||||
className="ml-auto flex-shrink-0"
|
||||
checked={showPreview}
|
||||
|
||||
+4
-2
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -58,6 +59,7 @@ export function WindowLevelActionMenuContent({
|
||||
isVisible={true}
|
||||
align={align}
|
||||
side={side}
|
||||
backLabel={i18n.t('WindowLevelActionMenu:Back to Display Options')}
|
||||
>
|
||||
<AllInOneMenu.ItemPanel>
|
||||
{!is3DVolume && <Colorbar viewportId={viewportId} />}
|
||||
@@ -65,7 +67,7 @@ export function WindowLevelActionMenuContent({
|
||||
{colorbarProperties?.colormaps && !is3DVolume && (
|
||||
<AllInOneMenu.SubMenu
|
||||
key="colorLUTPresets"
|
||||
itemLabel="Color LUT"
|
||||
itemLabel={t('Color LUT')}
|
||||
itemIcon="icon-color-lut"
|
||||
className="flex h-[calc(100%-32px)] flex-col"
|
||||
>
|
||||
@@ -86,7 +88,7 @@ export function WindowLevelActionMenuContent({
|
||||
{volumeRenderingPresets && is3DVolume && <VolumeRenderingPresets viewportId={viewportId} />}
|
||||
|
||||
{volumeRenderingQualityRange && is3DVolume && (
|
||||
<AllInOneMenu.SubMenu itemLabel="Rendering Options">
|
||||
<AllInOneMenu.SubMenu itemLabel={t('Rendering Options')}>
|
||||
<VolumeRenderingOptions viewportId={viewportId} />
|
||||
</AllInOneMenu.SubMenu>
|
||||
)}
|
||||
|
||||
@@ -3,17 +3,14 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
Icons,
|
||||
useSegmentationTableContext,
|
||||
useSegmentationExpanded,
|
||||
} from '@ohif/ui-next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
import { ExportSegmentationSubMenuItem } from '../components/ExportSegmentationSubMenuItem';
|
||||
|
||||
/**
|
||||
* Custom dropdown menu component for segmentation panel that uses context for data
|
||||
@@ -29,6 +26,8 @@ export const CustomDropdownMenuContent = () => {
|
||||
exportOptions,
|
||||
activeSegmentation,
|
||||
activeSegmentationId,
|
||||
segmentationRepresentationType,
|
||||
disableEditing,
|
||||
} = useSegmentationTableContext('CustomDropdownMenu');
|
||||
|
||||
// Try to get segmentation data from expanded context first, fall back to table context
|
||||
@@ -78,10 +77,14 @@ export const CustomDropdownMenuContent = () => {
|
||||
|
||||
return (
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem onClick={() => onSegmentationAdd(segmentationId)}>
|
||||
<Icons.Add className="text-foreground" />
|
||||
<span className="pl-2">{t('Create New Segmentation')}</span>
|
||||
</DropdownMenuItem>
|
||||
{!disableEditing && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => onSegmentationAdd({ segmentationId, segmentationRepresentationType })}
|
||||
>
|
||||
<Icons.Add className="text-foreground" />
|
||||
<span className="pl-2">{t('Create New Segmentation')}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>{t('Manage Current Segmentation')}</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={() => onSegmentationRemoveFromViewport(segmentationId)}>
|
||||
@@ -92,61 +95,12 @@ export const CustomDropdownMenuContent = () => {
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span className="pl-2">{t('Rename')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="pl-1">
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">{t('Download & Export')}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuLabel className="flex items-center pl-0">
|
||||
<Icons.Download className="h-5 w-5" />
|
||||
<span className="pl-1">{t('Download')}</span>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.downloadCSVSegmentationReport(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('CSV Report')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.onSegmentationDownload(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.onSegmentationDownloadRTSS(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('DICOM RTSS')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel className="flex items-center pl-0">
|
||||
<Icons.Export className="h-5 w-5" />
|
||||
<span className="pl-1 pt-1">{t('Export')}</span>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.storeSegmentation(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<ExportSegmentationSubMenuItem
|
||||
segmentationId={segmentationId}
|
||||
segmentationRepresentationType={segmentationRepresentationType}
|
||||
allowExport={allowExport}
|
||||
actions={actions}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => onSegmentationDelete(segmentationId)}>
|
||||
<Icons.Delete className="text-red-600" />
|
||||
|
||||
@@ -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,6 +37,7 @@ 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) {
|
||||
@@ -82,7 +83,7 @@ function ViewportDownloadFormNew({
|
||||
value={filename}
|
||||
onChange={e => setFilename(e.target.value)}
|
||||
>
|
||||
File name
|
||||
{t('File name')}
|
||||
</ImageModal.Filename>
|
||||
<ImageModal.Filetype
|
||||
selected={fileType}
|
||||
@@ -109,7 +110,7 @@ function ViewportDownloadFormNew({
|
||||
maxWidth={MAX_TEXTURE_SIZE.toString()}
|
||||
maxHeight={MAX_TEXTURE_SIZE.toString()}
|
||||
>
|
||||
Image size <span className="text-muted-foreground">px</span>
|
||||
{t('Image size')} <span className="text-muted-foreground">px</span>
|
||||
</ImageModal.ImageSize>
|
||||
|
||||
<ImageModal.SwitchOption
|
||||
@@ -117,7 +118,7 @@ function ViewportDownloadFormNew({
|
||||
checked={showAnnotations}
|
||||
onCheckedChange={onAnnotationsChange}
|
||||
>
|
||||
Include annotations
|
||||
{t('Include annotations')}
|
||||
</ImageModal.SwitchOption>
|
||||
{warningState.enabled && (
|
||||
<ImageModal.SwitchOption
|
||||
@@ -125,19 +126,21 @@ function ViewportDownloadFormNew({
|
||||
checked={showWarningMessage}
|
||||
onCheckedChange={setShowWarningMessage}
|
||||
>
|
||||
Include warning message
|
||||
{t('Include warning message')}
|
||||
</ImageModal.SwitchOption>
|
||||
)}
|
||||
<FooterAction className="mt-2">
|
||||
<FooterAction.Right>
|
||||
<FooterAction.Secondary onClick={onClose}>Cancel</FooterAction.Secondary>
|
||||
<FooterAction.Secondary onClick={onClose}>
|
||||
{t('Common:Cancel')}
|
||||
</FooterAction.Secondary>
|
||||
<FooterAction.Primary
|
||||
onClick={() => {
|
||||
onDownload(filename || DEFAULT_FILENAME, fileType);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Save
|
||||
{t('Common:Save')}
|
||||
</FooterAction.Primary>
|
||||
</FooterAction.Right>
|
||||
</FooterAction>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { CustomDropdownMenuContent } from './CustomDropdownMenuContent';
|
||||
import { CustomSegmentStatisticsHeader } from './CustomSegmentStatisticsHeader';
|
||||
import React, { useState } from 'react';
|
||||
import { Switch } from '@ohif/ui-next';
|
||||
import SegmentationToolConfig from '../components/SegmentationToolConfig';
|
||||
import React from 'react';
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
import * as cornerstoneTools from '@cornerstonejs/tools';
|
||||
|
||||
export default function getSegmentationPanelCustomization({ commandsManager, servicesManager }) {
|
||||
return {
|
||||
@@ -9,10 +11,25 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
|
||||
'panelSegmentation.customSegmentStatisticsHeader': CustomSegmentStatisticsHeader,
|
||||
'panelSegmentation.disableEditing': false,
|
||||
'panelSegmentation.showAddSegment': true,
|
||||
'panelSegmentation.onSegmentationAdd': () => {
|
||||
'panelSegmentation.onSegmentationAdd': async ({
|
||||
segmentationRepresentationType = SegmentationRepresentations.Labelmap,
|
||||
}) => {
|
||||
const { viewportGridService } = servicesManager.services;
|
||||
const viewportId = viewportGridService.getState().activeViewportId;
|
||||
commandsManager.run('createLabelmapForViewport', { viewportId });
|
||||
if (segmentationRepresentationType === SegmentationRepresentations.Labelmap) {
|
||||
commandsManager.run('createLabelmapForViewport', { viewportId });
|
||||
} else if (segmentationRepresentationType === SegmentationRepresentations.Contour) {
|
||||
const segmentationId = await commandsManager.run('createContourForViewport', {
|
||||
viewportId,
|
||||
});
|
||||
cornerstoneTools.segmentation.config.style.setStyle(
|
||||
{ segmentationId, type: SegmentationRepresentations.Contour },
|
||||
{
|
||||
fillAlpha: 0.5,
|
||||
renderFill: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
'panelSegmentation.tableMode': 'collapsed',
|
||||
'panelSegmentation.readableText': {
|
||||
@@ -33,53 +50,11 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
|
||||
lesionGlycolysis: 'Lesion Glycolysis',
|
||||
center: 'Center',
|
||||
},
|
||||
'segmentationToolbox.config': () => {
|
||||
// Get initial states based on current configuration
|
||||
const [previewEdits, setPreviewEdits] = useState(false);
|
||||
const [toggleSegmentEnabled, setToggleSegmentEnabled] = useState(false);
|
||||
const [useCenterAsSegmentIndex, setUseCenterAsSegmentIndex] = useState(false);
|
||||
const handlePreviewEditsChange = checked => {
|
||||
setPreviewEdits(checked);
|
||||
commandsManager.run('toggleSegmentPreviewEdit', { toggle: checked });
|
||||
};
|
||||
|
||||
const handleToggleSegmentEnabledChange = checked => {
|
||||
setToggleSegmentEnabled(checked);
|
||||
commandsManager.run('toggleSegmentSelect', { toggle: checked });
|
||||
};
|
||||
|
||||
const handleUseCenterAsSegmentIndexChange = checked => {
|
||||
setUseCenterAsSegmentIndex(checked);
|
||||
commandsManager.run('toggleUseCenterSegmentIndex', { toggle: checked });
|
||||
};
|
||||
|
||||
return (
|
||||
<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-base text-white">Preview edits before creating</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={useCenterAsSegmentIndex}
|
||||
onCheckedChange={handleUseCenterAsSegmentIndexChange}
|
||||
/>
|
||||
<span className="text-base text-white">Use center as segment index</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={toggleSegmentEnabled}
|
||||
onCheckedChange={handleToggleSegmentEnabledChange}
|
||||
/>
|
||||
<span className="text-base text-white">Hover on segment border to activate</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
'labelMapSegmentationToolbox.config': () => {
|
||||
return <SegmentationToolConfig />;
|
||||
},
|
||||
'contourSegmentationToolbox.config': () => {
|
||||
return <SegmentationToolConfig />;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,53 +1,67 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Toolbox } from '@ohif/extension-default';
|
||||
import PanelSegmentation from './panels/PanelSegmentation';
|
||||
import ActiveViewportWindowLevel from './components/ActiveViewportWindowLevel';
|
||||
import PanelMeasurement from './panels/PanelMeasurement';
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
const getPanelModule = ({ commandsManager, servicesManager, extensionManager }: withAppTypes) => {
|
||||
const wrappedPanelSegmentation = ({ configuration }) => {
|
||||
const { toolbarService } = servicesManager.services;
|
||||
|
||||
const toolSectionMap = {
|
||||
[SegmentationRepresentations.Labelmap]: toolbarService.sections.labelMapSegmentationToolbox,
|
||||
[SegmentationRepresentations.Contour]: toolbarService.sections.contourSegmentationToolbox,
|
||||
};
|
||||
|
||||
const wrappedPanelSegmentation = props => {
|
||||
return (
|
||||
<PanelSegmentation
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
configuration={{
|
||||
...configuration,
|
||||
...props?.configuration,
|
||||
}}
|
||||
segmentationRepresentationType={props?.segmentationRepresentationType}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const wrappedPanelSegmentationNoHeader = ({ configuration }) => {
|
||||
const wrappedPanelSegmentationNoHeader = props => {
|
||||
return (
|
||||
<PanelSegmentation
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
configuration={{
|
||||
...configuration,
|
||||
...props?.configuration,
|
||||
}}
|
||||
segmentationRepresentationType={props?.segmentationRepresentationType}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const wrappedPanelSegmentationWithTools = ({ configuration }) => {
|
||||
const { toolbarService } = servicesManager.services;
|
||||
const wrappedPanelSegmentationWithTools = props => {
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
const tKey = `${props.segmentationRepresentationType ?? 'Segmentation'} tools`;
|
||||
const tValue = t(tKey);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbox
|
||||
buttonSectionId={toolbarService.sections.segmentationToolbox}
|
||||
title="Segmentation Tools"
|
||||
buttonSectionId={toolSectionMap[props.segmentationRepresentationType]}
|
||||
title={tValue}
|
||||
/>
|
||||
<PanelSegmentation
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
configuration={{
|
||||
...configuration,
|
||||
...props?.configuration,
|
||||
}}
|
||||
segmentationRepresentationType={props?.segmentationRepresentationType}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -82,11 +96,26 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
|
||||
component: wrappedPanelSegmentationNoHeader,
|
||||
},
|
||||
{
|
||||
name: 'panelSegmentationWithTools',
|
||||
name: 'panelSegmentationWithToolsLabelMap',
|
||||
iconName: 'tab-segmentation',
|
||||
iconLabel: 'Segmentation',
|
||||
label: 'Segmentation',
|
||||
component: wrappedPanelSegmentationWithTools,
|
||||
label: i18n.t('SegmentationTable:Labelmap'),
|
||||
component: props =>
|
||||
wrappedPanelSegmentationWithTools({
|
||||
...props,
|
||||
segmentationRepresentationType: SegmentationRepresentations.Labelmap,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'panelSegmentationWithToolsContour',
|
||||
iconName: 'tab-contours',
|
||||
iconLabel: 'Segmentation',
|
||||
label: i18n.t('SegmentationTable:Contour'),
|
||||
component: props =>
|
||||
wrappedPanelSegmentationWithTools({
|
||||
...props,
|
||||
segmentationRepresentationType: SegmentationRepresentations.Contour,
|
||||
}),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
function mapMeasurementToDisplay(measurement, displaySetService) {
|
||||
const { referenceSeriesUID } = measurement;
|
||||
|
||||
|
||||
@@ -682,7 +682,7 @@ export function useViewportRendering(
|
||||
|
||||
const actorEntries = viewport.getActors();
|
||||
const actorEntry = actorEntries?.find(entry =>
|
||||
entry.referencedId.includes(activeDisplaySetInstanceUID)
|
||||
entry.referencedId?.includes(activeDisplaySetInstanceUID)
|
||||
);
|
||||
|
||||
if (!actorEntry) {
|
||||
|
||||
@@ -152,15 +152,27 @@ export function useViewportSegmentations({
|
||||
|
||||
const representations = segmentationService.getSegmentationRepresentations(viewportId);
|
||||
|
||||
const newSegmentationsWithRepresentations = representations.map(representation => {
|
||||
const segmentation = segmentationService.getSegmentation(representation.segmentationId);
|
||||
const mappedSegmentation = mapSegmentationToDisplay(segmentation, customizationService);
|
||||
return {
|
||||
representation,
|
||||
segmentation: mappedSegmentation,
|
||||
};
|
||||
// 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 = 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: '3D four up',
|
||||
name: i18n.t('Hps: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: 'Frame view for the active series',
|
||||
name: 'Frame View',
|
||||
description: i18n.t('Hps:Frame view for the active series'),
|
||||
name: i18n.t('Hps: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: '3D main',
|
||||
name: i18n.t('Hps: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: 'MPR',
|
||||
name: i18n.t('Hps: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: 'mpr',
|
||||
name: i18n.t('Hps:mpr'),
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||
availableTo: {},
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { HYDRATE_SEG_SYNC_GROUP, VOI_SYNC_GROUP } from './mpr';
|
||||
import i18n from 'i18next';
|
||||
|
||||
export const only3D = {
|
||||
id: 'only3D',
|
||||
locked: true,
|
||||
name: '3D only',
|
||||
name: i18n.t('Hps: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: '3D primary',
|
||||
name: i18n.t('Hps: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: 'Axial Primary',
|
||||
name: i18n.t('Hps:Axial Primary'),
|
||||
icon: 'layout-advanced-axial-primary',
|
||||
isPreset: true,
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
usePositionPresentationStore,
|
||||
useSegmentationPresentationStore,
|
||||
useSynchronizersStore,
|
||||
useSelectedSegmentationsForViewportStore,
|
||||
} from './stores';
|
||||
import { useToggleOneUpViewportGridStore } from '@ohif/extension-default';
|
||||
import { useActiveViewportSegmentationRepresentations } from './hooks/useActiveViewportSegmentationRepresentations';
|
||||
@@ -109,6 +110,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
toolbarService.registerEventForToolbarUpdate(segmentationService, [
|
||||
segmentationService.EVENTS.SEGMENTATION_REMOVED,
|
||||
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
|
||||
segmentationService.EVENTS.ANNOTATION_CUT_MERGE_PROCESS_COMPLETED,
|
||||
]);
|
||||
|
||||
toolbarService.registerEventForToolbarUpdate(cornerstone.eventTarget, [
|
||||
@@ -153,6 +155,9 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
useSynchronizersStore.getState().clearSynchronizersStore();
|
||||
useToggleOneUpViewportGridStore.getState().clearToggleOneUpViewportGridStore();
|
||||
useSegmentationPresentationStore.getState().clearSegmentationPresentationStore();
|
||||
useSelectedSegmentationsForViewportStore
|
||||
.getState()
|
||||
.clearSelectedSegmentationsForViewportStore();
|
||||
segmentationService.removeAllSegmentations();
|
||||
},
|
||||
|
||||
@@ -256,6 +261,7 @@ export {
|
||||
usePositionPresentationStore,
|
||||
useSegmentationPresentationStore,
|
||||
useSynchronizersStore,
|
||||
useSelectedSegmentationsForViewportStore,
|
||||
Enums,
|
||||
useMeasurements,
|
||||
useActiveViewportSegmentationRepresentations,
|
||||
|
||||
@@ -106,6 +106,12 @@ export default async function init({
|
||||
colorbarService.EVENTS.STATE_CHANGED,
|
||||
]);
|
||||
|
||||
toolbarService.registerEventForToolbarUpdate(segmentationService, [
|
||||
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
|
||||
segmentationService.EVENTS.SEGMENTATION_REPRESENTATION_MODIFIED,
|
||||
segmentationService.EVENTS.ANNOTATION_CUT_MERGE_PROCESS_COMPLETED,
|
||||
]);
|
||||
|
||||
window.services = servicesManager.services;
|
||||
window.extensionManager = extensionManager;
|
||||
window.commandsManager = commandsManager;
|
||||
|
||||
@@ -41,6 +41,10 @@ import {
|
||||
SegmentSelectTool,
|
||||
RegionSegmentPlusTool,
|
||||
SegmentLabelTool,
|
||||
LivewireContourSegmentationTool,
|
||||
SculptorTool,
|
||||
SplineContourSegmentationTool,
|
||||
LabelMapEditWithContourTool,
|
||||
} from '@cornerstonejs/tools';
|
||||
import { LabelmapSlicePropagationTool, MarkerLabelmapTool } from '@cornerstonejs/ai';
|
||||
import * as polySeg from '@cornerstonejs/polymorphic-segmentation';
|
||||
@@ -109,6 +113,10 @@ export default function initCornerstoneTools(configuration = {}) {
|
||||
addTool(LabelmapSlicePropagationTool);
|
||||
addTool(MarkerLabelmapTool);
|
||||
addTool(RegionSegmentPlusTool);
|
||||
addTool(LivewireContourSegmentationTool);
|
||||
addTool(SculptorTool);
|
||||
addTool(SplineContourSegmentationTool);
|
||||
addTool(LabelMapEditWithContourTool);
|
||||
// Modify annotation tools to use dashed lines on SR
|
||||
const annotationStyle = {
|
||||
textBoxFontSize: '15px',
|
||||
@@ -168,6 +176,10 @@ const toolNames = {
|
||||
LabelmapSlicePropagation: LabelmapSlicePropagationTool.toolName,
|
||||
MarkerLabelmap: MarkerLabelmapTool.toolName,
|
||||
RegionSegmentPlus: RegionSegmentPlusTool.toolName,
|
||||
LivewireContourSegmentation: LivewireContourSegmentationTool.toolName,
|
||||
SculptorTool: SculptorTool.toolName,
|
||||
SplineContourSegmentation: SplineContourSegmentationTool.toolName,
|
||||
LabelMapEditWithContourTool: LabelMapEditWithContourTool.toolName,
|
||||
};
|
||||
|
||||
export { toolNames };
|
||||
@@ -450,7 +450,7 @@ const connectMeasurementServiceToTools = ({
|
||||
if (measurement?.metadata?.referencedImageId) {
|
||||
imageId = measurement.metadata.referencedImageId;
|
||||
frameNumber = getSOPInstanceAttributes(measurement.metadata.referencedImageId).frameNumber;
|
||||
} else {
|
||||
} else if (instance) {
|
||||
imageId = dataSource.getImageIdsForInstance({ instance });
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ 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} />;
|
||||
}
|
||||
@@ -1,16 +1,92 @@
|
||||
import React from 'react';
|
||||
import { SegmentationTable } from '@ohif/ui-next';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import {
|
||||
IconPresentationProvider,
|
||||
Popover,
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
SegmentationTable,
|
||||
ToolSettings,
|
||||
} from '@ohif/ui-next';
|
||||
import { useActiveViewportSegmentationRepresentations } from '../hooks/useActiveViewportSegmentationRepresentations';
|
||||
import { metaData, cache } from '@cornerstonejs/core';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
import { useActiveToolOptions, useSystem } from '@ohif/core/src';
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
import { Toolbar, useUIStateStore } from '@ohif/extension-default';
|
||||
import SegmentationUtilityButton from '../components/SegmentationUtilityButton';
|
||||
import { useSelectedSegmentationsForViewportStore } from '../stores';
|
||||
import {
|
||||
hasExportableLabelMapData,
|
||||
hasExportableContourData,
|
||||
} from '../utils/segmentationExportUtils';
|
||||
|
||||
export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
type PanelSegmentationProps = {
|
||||
children?: React.ReactNode;
|
||||
|
||||
// The representation type for this segmentation panel. Undefined means all types.
|
||||
segmentationRepresentationType?: SegmentationRepresentations;
|
||||
} & withAppTypes;
|
||||
|
||||
export default function PanelSegmentation({
|
||||
children,
|
||||
segmentationRepresentationType,
|
||||
}: PanelSegmentationProps) {
|
||||
const { commandsManager, servicesManager } = useSystem();
|
||||
const { customizationService, displaySetService } = servicesManager.services;
|
||||
const {
|
||||
customizationService,
|
||||
displaySetService,
|
||||
viewportGridService,
|
||||
toolbarService,
|
||||
segmentationService,
|
||||
} = servicesManager.services;
|
||||
const { activeViewportId } = viewportGridService.getState();
|
||||
|
||||
const utilitiesSectionMap = {
|
||||
[SegmentationRepresentations.Labelmap]: toolbarService.sections.labelMapSegmentationUtilities,
|
||||
[SegmentationRepresentations.Contour]: toolbarService.sections.contourSegmentationUtilities,
|
||||
};
|
||||
|
||||
const selectedSegmentationsForViewportMap = useSelectedSegmentationsForViewportStore(
|
||||
store => store.selectedSegmentationsForViewport[activeViewportId]
|
||||
);
|
||||
|
||||
const selectedSegmentationIdForType = segmentationRepresentationType
|
||||
? selectedSegmentationsForViewportMap?.get(segmentationRepresentationType)
|
||||
: segmentationService?.getActiveSegmentation(activeViewportId)?.segmentationId;
|
||||
|
||||
const buttonSection = utilitiesSectionMap[segmentationRepresentationType];
|
||||
|
||||
const { activeToolOptions: activeUtilityOptions } = useActiveToolOptions({
|
||||
buttonSectionId: buttonSection,
|
||||
});
|
||||
|
||||
const { segmentationsWithRepresentations, disabled } =
|
||||
useActiveViewportSegmentationRepresentations();
|
||||
|
||||
const setUIState = useUIStateStore(store => store.setUIState);
|
||||
|
||||
// useEffect for handling clicks on any of the non-active viewports.
|
||||
// The ViewportGrid stops the propagation of pointer/mouse events
|
||||
// for non-active viewports so the Popover below
|
||||
// is not closed when clicking on any of the non-active viewports.
|
||||
useEffect(() => {
|
||||
setUIState('activeSegmentationUtility', null);
|
||||
toolbarService.refreshToolbarState({ viewportId: activeViewportId });
|
||||
}, [activeViewportId, setUIState, toolbarService]);
|
||||
|
||||
// The callback for handling clicks outside of the Popover and, the SegmentationUtilityButton
|
||||
// that triggered it to open. Clicks outside those components must close the Popover.
|
||||
// The Popover is made visible whenever the options associated with the
|
||||
// activeSegmentationUtility exist. Thus clearing the activeSegmentationUtility
|
||||
// clears the associated options and will keep the Popover closed.
|
||||
const handlePopoverOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
if (!open) {
|
||||
setUIState('activeSegmentationUtility', null);
|
||||
toolbarService.refreshToolbarState({ viewportId: activeViewportId });
|
||||
}
|
||||
},
|
||||
[activeViewportId, setUIState, toolbarService]
|
||||
);
|
||||
|
||||
// Extract customization options
|
||||
const segmentationTableMode = customizationService.getCustomization(
|
||||
'panelSegmentation.tableMode'
|
||||
@@ -35,6 +111,7 @@ export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
},
|
||||
onSegmentAdd: segmentationId => {
|
||||
commandsManager.run('addSegment', { segmentationId });
|
||||
commandsManager.run('setActiveSegmentation', { segmentationId });
|
||||
},
|
||||
onSegmentClick: (segmentationId, segmentIndex) => {
|
||||
commandsManager.run('setActiveSegmentAndCenter', { segmentationId, segmentIndex });
|
||||
@@ -51,6 +128,14 @@ export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
onSegmentDelete: (segmentationId, segmentIndex) => {
|
||||
commandsManager.run('deleteSegment', { segmentationId, segmentIndex });
|
||||
},
|
||||
onSegmentCopy:
|
||||
segmentationRepresentationType === SegmentationRepresentations.Contour
|
||||
? (segmentationId, segmentIndex) => {
|
||||
commandsManager.run('copyContourSegment', {
|
||||
sourceSegmentInfo: { segmentationId, segmentIndex },
|
||||
});
|
||||
}
|
||||
: undefined,
|
||||
onToggleSegmentVisibility: (segmentationId, segmentIndex, type) => {
|
||||
commandsManager.run('toggleSegmentVisibility', { segmentationId, segmentIndex, type });
|
||||
},
|
||||
@@ -96,52 +181,26 @@ export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
};
|
||||
|
||||
// Generate export options
|
||||
// Map each segmentation to an export option for it.
|
||||
// A segmentation is exportable if it has any labelmap or contour data.
|
||||
const exportOptions = segmentationsWithRepresentations.map(({ segmentation }) => {
|
||||
const { representationData, segmentationId } = segmentation;
|
||||
const { Labelmap } = representationData;
|
||||
const { Labelmap, Contour } = representationData;
|
||||
|
||||
if (!Labelmap) {
|
||||
if (!Labelmap && !Contour) {
|
||||
return { segmentationId, isExportable: true };
|
||||
}
|
||||
|
||||
// Check if any segments have anything drawn in any of the viewports
|
||||
const hasAnySegmentData = (() => {
|
||||
const imageIds = Labelmap.imageIds;
|
||||
if (!imageIds?.length) return false;
|
||||
|
||||
for (const imageId of imageIds) {
|
||||
const pixelData = cache.getImage(imageId)?.getPixelData();
|
||||
if (!pixelData) continue;
|
||||
|
||||
for (let i = 0; i < pixelData.length; i++) {
|
||||
if (pixelData[i] !== 0) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})();
|
||||
|
||||
if (!hasAnySegmentData) {
|
||||
if (
|
||||
!hasExportableLabelMapData(Labelmap, displaySetService) &&
|
||||
!hasExportableContourData(Contour)
|
||||
) {
|
||||
return { segmentationId, isExportable: false };
|
||||
}
|
||||
|
||||
const referencedImageIds = Labelmap.referencedImageIds;
|
||||
const firstImageId = referencedImageIds[0];
|
||||
const instance = metaData.get('instance', firstImageId);
|
||||
|
||||
if (!instance) {
|
||||
return { segmentationId, isExportable: false };
|
||||
}
|
||||
|
||||
const SOPInstanceUID = instance.SOPInstanceUID || instance.SopInstanceUID;
|
||||
const SeriesInstanceUID = instance.SeriesInstanceUID;
|
||||
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
return {
|
||||
segmentationId,
|
||||
isExportable: displaySet?.isReconstructable,
|
||||
isExportable: true,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -150,15 +209,34 @@ export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
disabled,
|
||||
data: segmentationsWithRepresentations,
|
||||
mode: segmentationTableMode,
|
||||
title: 'Segmentations',
|
||||
title: `${segmentationRepresentationType ? `${segmentationRepresentationType} ` : ''}Segmentations`,
|
||||
exportOptions,
|
||||
disableEditing,
|
||||
onSegmentationAdd,
|
||||
showAddSegment,
|
||||
renderInactiveSegmentations: handlers.getRenderInactiveSegmentations(),
|
||||
segmentationRepresentationType,
|
||||
selectedSegmentationIdForType,
|
||||
...handlers,
|
||||
};
|
||||
|
||||
const renderUtilitiesToolbar = () => {
|
||||
if (!buttonSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<IconPresentationProvider
|
||||
size="large"
|
||||
IconContainer={SegmentationUtilityButton}
|
||||
>
|
||||
<div className="flex flex-wrap gap-[3px] bg-transparent pb-[2px] pl-[8px] pt-[6px]">
|
||||
<Toolbar buttonSection={buttonSection} />
|
||||
</div>
|
||||
</IconPresentationProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSegments = () => {
|
||||
return (
|
||||
<SegmentationTable.Segments>
|
||||
@@ -175,6 +253,7 @@ export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
if (tableProps.mode === 'collapsed') {
|
||||
return (
|
||||
<SegmentationTable.Collapsed>
|
||||
{renderUtilitiesToolbar()}
|
||||
<SegmentationTable.Collapsed.Header>
|
||||
<SegmentationTable.Collapsed.DropdownMenu>
|
||||
<CustomDropdownMenuContent />
|
||||
@@ -193,6 +272,7 @@ export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
return (
|
||||
<>
|
||||
<SegmentationTable.Expanded>
|
||||
{renderUtilitiesToolbar()}
|
||||
<SegmentationTable.Expanded.Header>
|
||||
<SegmentationTable.Expanded.DropdownMenu>
|
||||
<CustomDropdownMenuContent />
|
||||
@@ -211,11 +291,27 @@ export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
};
|
||||
|
||||
return (
|
||||
<SegmentationTable {...tableProps}>
|
||||
{children}
|
||||
<SegmentationTable.Config />
|
||||
<SegmentationTable.AddSegmentationRow />
|
||||
{renderModeContent()}
|
||||
</SegmentationTable>
|
||||
<Popover
|
||||
open={!!activeUtilityOptions}
|
||||
onOpenChange={handlePopoverOpenChange}
|
||||
>
|
||||
<PopoverAnchor>
|
||||
<SegmentationTable {...tableProps}>
|
||||
{children}
|
||||
<SegmentationTable.Config />
|
||||
<SegmentationTable.AddSegmentationRow />
|
||||
{renderModeContent()}
|
||||
</SegmentationTable>
|
||||
</PopoverAnchor>
|
||||
{activeUtilityOptions && (
|
||||
<PopoverContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="w-auto"
|
||||
>
|
||||
<ToolSettings options={activeUtilityOptions} />
|
||||
</PopoverContent>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -9,30 +9,34 @@ 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 { 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 { 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 = csToolsEnums.SegmentationRepresentations.Labelmap;
|
||||
const CONTOUR = csToolsEnums.SegmentationRepresentations.Contour;
|
||||
import { VOLUME_LOADER_SCHEME } from '../../constants';
|
||||
import { mapROIContoursToRTStructData } from './RTSTRUCT/mapROIContoursToRTStructData';
|
||||
import { SegmentationPresentation, SegmentationPresentationItem } from '../../types/Presentation';
|
||||
import { EasingFunctionEnum, EasingFunctionMap } from '../../utils/transitions';
|
||||
|
||||
const {
|
||||
Labelmap: LABELMAP,
|
||||
Contour: CONTOUR,
|
||||
Surface: SURFACE,
|
||||
} = csToolsEnums.SegmentationRepresentations;
|
||||
|
||||
const {
|
||||
getLabelmapImageIds,
|
||||
helpers: { convertStackToVolumeLabelmap },
|
||||
state: { addColorLUT, updateLabelmapSegmentationImageReferences },
|
||||
triggerSegmentationEvents: { triggerSegmentationRepresentationModified },
|
||||
} = cstSegmentation;
|
||||
|
||||
export type SegmentRepresentation = {
|
||||
segmentIndex: number;
|
||||
@@ -77,6 +81,8 @@ const EVENTS = {
|
||||
SEGMENT_LOADING_COMPLETE: 'event::segment_loading_complete',
|
||||
// loading completed for all segments
|
||||
SEGMENTATION_LOADING_COMPLETE: 'event::segmentation_loading_complete',
|
||||
// fired when a contour annotation cut merge process is completed
|
||||
ANNOTATION_CUT_MERGE_PROCESS_COMPLETED: 'event::annotation_cut_merge_process_completed',
|
||||
};
|
||||
|
||||
const VALUE_TYPES = {};
|
||||
@@ -149,13 +155,11 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
const representations = this.getSegmentationRepresentations(viewportId);
|
||||
for (const representation of representations) {
|
||||
const { segmentationId } = representation;
|
||||
|
||||
if (!representation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { type } = representation;
|
||||
const { segmentationId, type } = representation;
|
||||
|
||||
segmentationsMap.set(segmentationId, {
|
||||
segmentationId,
|
||||
@@ -212,7 +216,7 @@ class SegmentationService extends PubSubService {
|
||||
viewportId: string,
|
||||
specifier: {
|
||||
segmentationId?: string;
|
||||
type?: SegmentationRepresentations;
|
||||
type?: csToolsEnums.SegmentationRepresentations;
|
||||
} = {}
|
||||
): SegmentationRepresentation[] {
|
||||
// Get all representations for the viewportId
|
||||
@@ -245,9 +249,19 @@ class SegmentationService extends PubSubService {
|
||||
this._onSegmentationDataModifiedFromSource
|
||||
);
|
||||
|
||||
eventTarget.removeEventListener(
|
||||
csToolsEnums.Events.SEGMENTATION_REPRESENTATION_MODIFIED,
|
||||
this._onSegmentationRepresentationModifiedFromSource
|
||||
);
|
||||
|
||||
eventTarget.removeEventListener(
|
||||
csToolsEnums.Events.SEGMENTATION_REPRESENTATION_ADDED,
|
||||
this._onSegmentationModifiedFromSource
|
||||
this._onSegmentationRepresentationModifiedFromSource
|
||||
);
|
||||
|
||||
eventTarget.removeEventListener(
|
||||
csToolsEnums.Events.SEGMENTATION_REPRESENTATION_REMOVED,
|
||||
this._onSegmentationRepresentationModifiedFromSource
|
||||
);
|
||||
|
||||
eventTarget.removeEventListener(
|
||||
@@ -283,11 +297,13 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
const colorLUTIndex = this._segmentationIdToColorLUTIndexMap.get(segmentationId);
|
||||
|
||||
const defaultRepresentationType = csToolsEnums.SegmentationRepresentations.Labelmap;
|
||||
let representationTypeToUse = type || defaultRepresentationType;
|
||||
let isConverted = false;
|
||||
|
||||
if (type === csToolsEnums.SegmentationRepresentations.Labelmap) {
|
||||
const defaultRepresentationType: csToolsEnums.SegmentationRepresentations =
|
||||
csViewport.type === ViewportType.VOLUME_3D ? SURFACE : LABELMAP;
|
||||
let representationTypeToUse = type || defaultRepresentationType;
|
||||
|
||||
if (representationTypeToUse === LABELMAP) {
|
||||
const { isVolumeViewport, isVolumeSegmentation } = this.determineViewportAndSegmentationType(
|
||||
csViewport,
|
||||
segmentation
|
||||
@@ -336,6 +352,39 @@ class SegmentationService extends PubSubService {
|
||||
FrameOfReferenceUID?: string;
|
||||
label?: string;
|
||||
}
|
||||
): Promise<string> {
|
||||
return this._createSegmentationForDisplaySet(displaySet, LABELMAP, options);
|
||||
}
|
||||
|
||||
public async createContourForDisplaySet(
|
||||
displaySet: AppTypes.DisplaySet,
|
||||
options?: {
|
||||
segmentationId?: string;
|
||||
segments?: { [segmentIndex: number]: Partial<cstTypes.Segment> };
|
||||
FrameOfReferenceUID?: string;
|
||||
label?: string;
|
||||
}
|
||||
): Promise<string> {
|
||||
return this._createSegmentationForDisplaySet(displaySet, CONTOUR, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method to create segmentation for a display set with the specified type
|
||||
*
|
||||
* @param displaySet - The display set to create the segmentation for
|
||||
* @param segmentationType - The type of segmentation (SegmentationRepresentations enum)
|
||||
* @param options - Optional parameters for creating the segmentation
|
||||
* @returns A promise that resolves to the created segmentation ID
|
||||
*/
|
||||
private async _createSegmentationForDisplaySet(
|
||||
displaySet: AppTypes.DisplaySet,
|
||||
segmentationType: SegmentationRepresentations,
|
||||
options?: {
|
||||
segmentationId?: string;
|
||||
segments?: { [segmentIndex: number]: Partial<cstTypes.Segment> };
|
||||
FrameOfReferenceUID?: string;
|
||||
label?: string;
|
||||
}
|
||||
): Promise<string> {
|
||||
// Todo: random does not makes sense, make this better, like
|
||||
// labelmap 1, 2, 3 etc
|
||||
@@ -354,14 +403,14 @@ 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);
|
||||
|
||||
const segmentationPublicInput: cstTypes.SegmentationPublicInput = {
|
||||
segmentationId,
|
||||
representation: {
|
||||
type: LABELMAP,
|
||||
type: segmentationType,
|
||||
data: {
|
||||
imageIds: segImageIds,
|
||||
// referencedVolumeId: this._getVolumeIdForDisplaySet(displaySet),
|
||||
@@ -371,7 +420,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: {
|
||||
@@ -393,7 +442,7 @@ class SegmentationService extends PubSubService {
|
||||
segDisplaySet,
|
||||
options: {
|
||||
segmentationId?: string;
|
||||
type: SegmentationRepresentations;
|
||||
type: csToolsEnums.SegmentationRepresentations;
|
||||
} = {
|
||||
type: LABELMAP,
|
||||
}
|
||||
@@ -503,9 +552,7 @@ class SegmentationService extends PubSubService {
|
||||
};
|
||||
});
|
||||
|
||||
// get next color lut index
|
||||
const colorLUTIndex = getNextColorLUTIndex();
|
||||
addColorLUT(colorLUT, colorLUTIndex);
|
||||
const colorLUTIndex = addColorLUT(colorLUT);
|
||||
this._segmentationIdToColorLUTIndexMap.set(segmentationId, colorLUTIndex);
|
||||
|
||||
this._broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, {
|
||||
@@ -540,7 +587,7 @@ class SegmentationService extends PubSubService {
|
||||
rtDisplaySet,
|
||||
options: {
|
||||
segmentationId?: string;
|
||||
type: SegmentationRepresentations;
|
||||
type: csToolsEnums.SegmentationRepresentations;
|
||||
} = {
|
||||
type: CONTOUR,
|
||||
}
|
||||
@@ -616,7 +663,9 @@ 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 (const rtStructData of allRTStructData) {
|
||||
for (let i = 0; i < allRTStructData.length; i++) {
|
||||
const rtStructData = allRTStructData[i];
|
||||
|
||||
const { data, id, color, segmentIndex, geometryId, group } = rtStructData;
|
||||
|
||||
// Add the color to the colorLUT array
|
||||
@@ -665,8 +714,7 @@ class SegmentationService extends PubSubService {
|
||||
}
|
||||
|
||||
// Create and register the colorLUT
|
||||
const colorLUTIndex = getNextColorLUTIndex();
|
||||
addColorLUT(colorLUT, colorLUTIndex);
|
||||
const colorLUTIndex = addColorLUT(colorLUT);
|
||||
this._segmentationIdToColorLUTIndexMap.set(segmentationId, colorLUTIndex);
|
||||
|
||||
// Assign processed segments to segmentation config
|
||||
@@ -763,7 +811,7 @@ class SegmentationService extends PubSubService {
|
||||
public hasCustomStyles(specifier: {
|
||||
viewportId: string;
|
||||
segmentationId: string;
|
||||
type: SegmentationRepresentations;
|
||||
type: csToolsEnums.SegmentationRepresentations;
|
||||
}): boolean {
|
||||
return cstSegmentation.config.style.hasCustomStyle(specifier);
|
||||
}
|
||||
@@ -771,7 +819,7 @@ class SegmentationService extends PubSubService {
|
||||
public getStyle = (specifier: {
|
||||
viewportId: string;
|
||||
segmentationId: string;
|
||||
type: SegmentationRepresentations;
|
||||
type: csToolsEnums.SegmentationRepresentations;
|
||||
segmentIndex?: number;
|
||||
}) => {
|
||||
const style = cstSegmentation.config.style.getStyle(specifier);
|
||||
@@ -781,12 +829,12 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
public setStyle = (
|
||||
specifier: {
|
||||
type: SegmentationRepresentations;
|
||||
type: csToolsEnums.SegmentationRepresentations;
|
||||
viewportId?: string;
|
||||
segmentationId?: string;
|
||||
segmentIndex?: number;
|
||||
},
|
||||
style: LabelmapStyle | ContourStyle | SurfaceStyle
|
||||
style: cstTypes.LabelmapStyle | cstTypes.ContourStyle | cstTypes.SurfaceStyle
|
||||
) => {
|
||||
cstSegmentation.config.style.setStyle(specifier, style);
|
||||
};
|
||||
@@ -795,6 +843,15 @@ class SegmentationService extends PubSubService {
|
||||
cstSegmentation.config.style.resetToGlobalStyle();
|
||||
};
|
||||
|
||||
public getNextAvailableSegmentIndex(segmentationId: string): number {
|
||||
const csSegmentation = this.getCornerstoneSegmentation(segmentationId);
|
||||
// grab the next available segment index based on the object keys,
|
||||
// so basically get the highest segment index value + 1
|
||||
|
||||
const segmentKeys = Object.keys(csSegmentation.segments);
|
||||
return segmentKeys.length === 0 ? 1 : Math.max(...segmentKeys.map(Number)) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new segment to the specified segmentation.
|
||||
* @param segmentationId - The ID of the segmentation to add the segment to.
|
||||
@@ -828,10 +885,7 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
let segmentIndex = config.segmentIndex;
|
||||
if (!segmentIndex) {
|
||||
// grab the next available segment index based on the object keys,
|
||||
// so basically get the highest segment index value + 1
|
||||
const segmentKeys = Object.keys(csSegmentation.segments);
|
||||
segmentIndex = segmentKeys.length === 0 ? 1 : Math.max(...segmentKeys.map(Number)) + 1;
|
||||
segmentIndex = this.getNextAvailableSegmentIndex(segmentationId);
|
||||
}
|
||||
|
||||
// update the segmentation
|
||||
@@ -904,7 +958,7 @@ class SegmentationService extends PubSubService {
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
isVisible: boolean,
|
||||
type?: SegmentationRepresentations
|
||||
type?: csToolsEnums.SegmentationRepresentations
|
||||
): void {
|
||||
this._setSegmentVisibility(viewportId, segmentationId, segmentIndex, isVisible, type);
|
||||
}
|
||||
@@ -941,7 +995,7 @@ class SegmentationService extends PubSubService {
|
||||
viewportId: string,
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
type: SegmentationRepresentations
|
||||
type: csToolsEnums.SegmentationRepresentations
|
||||
): void {
|
||||
const isVisible = cstSegmentation.config.visibility.getSegmentIndexVisibility(
|
||||
viewportId,
|
||||
@@ -972,6 +1026,12 @@ 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,
|
||||
@@ -1013,7 +1073,7 @@ class SegmentationService extends PubSubService {
|
||||
public getLabelmapVolume(segmentationId: string) {
|
||||
const csSegmentation = cstSegmentation.state.getSegmentation(segmentationId);
|
||||
const labelmapData = csSegmentation.representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
LABELMAP
|
||||
] as cstTypes.LabelmapToolOperationDataVolume;
|
||||
|
||||
if (!labelmapData || !labelmapData.volumeId) {
|
||||
@@ -1115,7 +1175,10 @@ class SegmentationService extends PubSubService {
|
||||
*/
|
||||
public toggleSegmentationRepresentationVisibility = (
|
||||
viewportId: string,
|
||||
{ segmentationId, type }: { segmentationId: string; type: SegmentationRepresentations }
|
||||
{
|
||||
segmentationId,
|
||||
type,
|
||||
}: { segmentationId: string; type: csToolsEnums.SegmentationRepresentations }
|
||||
): void => {
|
||||
this._toggleSegmentationRepresentationVisibility(viewportId, segmentationId, type);
|
||||
};
|
||||
@@ -1164,7 +1227,7 @@ class SegmentationService extends PubSubService {
|
||||
viewportId: string,
|
||||
specifier: {
|
||||
segmentationId?: string;
|
||||
type?: SegmentationRepresentations;
|
||||
type?: csToolsEnums.SegmentationRepresentations;
|
||||
} = {}
|
||||
): void {
|
||||
cstSegmentation.removeSegmentationRepresentations(viewportId, specifier);
|
||||
@@ -1178,7 +1241,7 @@ class SegmentationService extends PubSubService {
|
||||
highlightSegment = true,
|
||||
animationLength = 750,
|
||||
highlightHideOthers = false,
|
||||
highlightFunctionType = 'ease-in-out' // todo: make animation functions configurable from outside
|
||||
animationFunctionType: EasingFunctionEnum = EasingFunctionEnum.EASE_IN_OUT
|
||||
): void {
|
||||
const center = this._getSegmentCenter(segmentationId, segmentIndex);
|
||||
if (!center) {
|
||||
@@ -1204,7 +1267,8 @@ class SegmentationService extends PubSubService {
|
||||
viewportId,
|
||||
highlightAlpha,
|
||||
animationLength,
|
||||
highlightHideOthers
|
||||
highlightHideOthers,
|
||||
animationFunctionType
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1216,7 +1280,7 @@ class SegmentationService extends PubSubService {
|
||||
alpha = 0.9,
|
||||
animationLength = 750,
|
||||
hideOthers = true,
|
||||
highlightFunctionType = 'ease-in-out'
|
||||
animationFunctionType: EasingFunctionEnum = EasingFunctionEnum.EASE_IN_OUT
|
||||
): void {
|
||||
if (this.highlightIntervalId) {
|
||||
clearInterval(this.highlightIntervalId);
|
||||
@@ -1249,7 +1313,8 @@ class SegmentationService extends PubSubService {
|
||||
segments,
|
||||
viewportId,
|
||||
animationLength,
|
||||
representation
|
||||
representation,
|
||||
animationFunctionType
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1274,7 +1339,7 @@ class SegmentationService extends PubSubService {
|
||||
private _setSegmentationRepresentationVisibility(
|
||||
viewportId: string,
|
||||
segmentationId: string,
|
||||
type: SegmentationRepresentations,
|
||||
type: csToolsEnums.SegmentationRepresentations,
|
||||
isVisible: boolean
|
||||
): void {
|
||||
const representations = this.getSegmentationRepresentations(viewportId, {
|
||||
@@ -1334,14 +1399,17 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
private async handleVolumeViewportCase(csViewport, segmentation, isVolumeSegmentation) {
|
||||
if (csViewport.type === ViewportType.VOLUME_3D) {
|
||||
return { representationTypeToUse: SegmentationRepresentations.Surface, isConverted: false };
|
||||
return {
|
||||
representationTypeToUse: SURFACE,
|
||||
isConverted: false,
|
||||
};
|
||||
} else {
|
||||
await this.handleVolumeViewport(
|
||||
csViewport as csTypes.IVolumeViewport,
|
||||
segmentation,
|
||||
isVolumeSegmentation
|
||||
);
|
||||
return { representationTypeToUse: SegmentationRepresentations.Labelmap, isConverted: false };
|
||||
return { representationTypeToUse: LABELMAP, isConverted: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1351,14 +1419,17 @@ class SegmentationService extends PubSubService {
|
||||
isVolumeSegmentation: boolean,
|
||||
viewportId: string,
|
||||
segmentationId: string
|
||||
): Promise<{ representationTypeToUse: SegmentationRepresentations; isConverted: boolean }> {
|
||||
): Promise<{
|
||||
representationTypeToUse: csToolsEnums.SegmentationRepresentations;
|
||||
isConverted: boolean;
|
||||
}> {
|
||||
if (isVolumeSegmentation) {
|
||||
const isConverted = await this.convertStackToVolumeViewport(csViewport);
|
||||
return { representationTypeToUse: SegmentationRepresentations.Labelmap, isConverted };
|
||||
return { representationTypeToUse: LABELMAP, isConverted };
|
||||
}
|
||||
|
||||
if (updateLabelmapSegmentationImageReferences(viewportId, segmentationId)) {
|
||||
return { representationTypeToUse: SegmentationRepresentations.Labelmap, isConverted: false };
|
||||
return { representationTypeToUse: LABELMAP, isConverted: false };
|
||||
}
|
||||
|
||||
const isConverted = await this.attemptStackToVolumeConversion(
|
||||
@@ -1368,7 +1439,7 @@ class SegmentationService extends PubSubService {
|
||||
segmentationId
|
||||
);
|
||||
|
||||
return { representationTypeToUse: SegmentationRepresentations.Labelmap, isConverted };
|
||||
return { representationTypeToUse: LABELMAP, isConverted };
|
||||
}
|
||||
|
||||
private async _addSegmentationRepresentation(
|
||||
@@ -1479,11 +1550,7 @@ class SegmentationService extends PubSubService {
|
||||
segImage.FrameOfReferenceUID === frameOfReferenceUID
|
||||
) {
|
||||
const isConverted = await this.convertStackToVolumeViewport(viewport);
|
||||
triggerSegmentationRepresentationModified(
|
||||
viewportId,
|
||||
segmentationId,
|
||||
SegmentationRepresentations.Labelmap
|
||||
);
|
||||
triggerSegmentationRepresentationModified(viewportId, segmentationId, LABELMAP);
|
||||
|
||||
return isConverted;
|
||||
}
|
||||
@@ -1601,6 +1668,11 @@ class SegmentationService extends PubSubService {
|
||||
csToolsEnums.Events.SEGMENTATION_ADDED,
|
||||
this._onSegmentationAddedFromSource
|
||||
);
|
||||
|
||||
eventTarget.addEventListener(
|
||||
csToolsEnums.Events.ANNOTATION_CUT_MERGE_PROCESS_COMPLETED,
|
||||
this._onAnnotationCutMergeProcessCompletedFromSource
|
||||
);
|
||||
}
|
||||
|
||||
private getCornerstoneSegmentation(segmentationId: string) {
|
||||
@@ -1611,10 +1683,11 @@ class SegmentationService extends PubSubService {
|
||||
segmentIndex: number,
|
||||
alpha: number,
|
||||
hideOthers: boolean,
|
||||
segments: Segment[],
|
||||
segments: cstTypes.Segment[],
|
||||
viewportId: string,
|
||||
animationLength: number,
|
||||
representation: cstTypes.SegmentationRepresentation
|
||||
representation: cstTypes.SegmentationRepresentation,
|
||||
animationFunctionType: EasingFunctionEnum
|
||||
) {
|
||||
const { segmentationId } = representation;
|
||||
const newSegmentSpecificConfig = {
|
||||
@@ -1648,6 +1721,8 @@ 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,
|
||||
@@ -1655,7 +1730,7 @@ class SegmentationService extends PubSubService {
|
||||
type: LABELMAP,
|
||||
},
|
||||
{
|
||||
fillAlpha: easeInOutBell(progress, fillAlpha),
|
||||
fillAlpha: easingFunction(progress, fillAlpha),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1680,21 +1755,20 @@ class SegmentationService extends PubSubService {
|
||||
segmentIndex: number,
|
||||
alpha: number,
|
||||
hideOthers: boolean,
|
||||
segments: Segment[],
|
||||
segments: cstTypes.Segment[],
|
||||
viewportId: string,
|
||||
animationLength: number,
|
||||
representation: cstTypes.SegmentationRepresentation
|
||||
representation: cstTypes.SegmentationRepresentation,
|
||||
animationFunctionType: EasingFunctionEnum
|
||||
) {
|
||||
const { segmentationId } = representation;
|
||||
const startTime = performance.now();
|
||||
|
||||
const prevStyle = cstSegmentation.config.style.getStyle({
|
||||
type: CONTOUR,
|
||||
}) as ContourStyle;
|
||||
}) as cstTypes.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;
|
||||
@@ -1703,7 +1777,8 @@ class SegmentationService extends PubSubService {
|
||||
return;
|
||||
}
|
||||
|
||||
const reversedProgress = easeInOutBellRelative(progress, baseline, prevOutlineWidth);
|
||||
const OUTLINE_ANIMATION_SCALE_FACTOR = 5;
|
||||
const easingFunction = EasingFunctionMap.get(animationFunctionType);
|
||||
|
||||
cstSegmentation.config.style.setStyle(
|
||||
{
|
||||
@@ -1712,7 +1787,7 @@ class SegmentationService extends PubSubService {
|
||||
type: CONTOUR,
|
||||
},
|
||||
{
|
||||
outlineWidth: reversedProgress,
|
||||
outlineWidth: easingFunction(progress, prevOutlineWidth, OUTLINE_ANIMATION_SCALE_FACTOR),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1725,24 +1800,18 @@ class SegmentationService extends PubSubService {
|
||||
private _toggleSegmentationRepresentationVisibility = (
|
||||
viewportId: string,
|
||||
segmentationId: string,
|
||||
type: SegmentationRepresentations
|
||||
type: csToolsEnums.SegmentationRepresentations
|
||||
): void => {
|
||||
const representations = this.getSegmentationRepresentations(viewportId, {
|
||||
segmentationId,
|
||||
type,
|
||||
});
|
||||
const representation = representations[0];
|
||||
|
||||
const segmentsHidden = cstSegmentation.config.visibility.getHiddenSegmentIndices(viewportId, {
|
||||
segmentationId,
|
||||
type: representation.type,
|
||||
type,
|
||||
});
|
||||
|
||||
const currentVisibility = segmentsHidden.size === 0;
|
||||
this._setSegmentationRepresentationVisibility(
|
||||
viewportId,
|
||||
segmentationId,
|
||||
representation.type,
|
||||
type,
|
||||
!currentVisibility
|
||||
);
|
||||
};
|
||||
@@ -1757,7 +1826,10 @@ class SegmentationService extends PubSubService {
|
||||
return `${volumeLoaderSchema}:${displaySet.displaySetInstanceUID}`;
|
||||
}
|
||||
|
||||
private _getSegmentCenter(segmentationId, segmentIndex) {
|
||||
private _getSegmentCenter(
|
||||
segmentationId: string,
|
||||
segmentIndex: number
|
||||
): { image?: csTypes.Point3; world: csTypes.Point3 } | undefined {
|
||||
const segmentation = this.getSegmentation(segmentationId);
|
||||
|
||||
if (!segmentation) {
|
||||
@@ -1768,19 +1840,17 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
const { cachedStats } = segments[segmentIndex];
|
||||
|
||||
if (!cachedStats || !cachedStats?.center || !cachedStats?.namedStats?.center) {
|
||||
return;
|
||||
if (cachedStats?.center) {
|
||||
const { center } = cachedStats;
|
||||
|
||||
return center as { image: csTypes.Point3; world: csTypes.Point3 };
|
||||
}
|
||||
|
||||
const { center } = cachedStats;
|
||||
|
||||
if (!center) {
|
||||
if (cachedStats?.namedStats?.center) {
|
||||
return {
|
||||
world: cachedStats.namedStats.center.value,
|
||||
};
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
private _setSegmentLockedStatus(segmentationId: string, segmentIndex: number, isLocked: boolean) {
|
||||
@@ -1792,7 +1862,7 @@ class SegmentationService extends PubSubService {
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
isVisible: boolean,
|
||||
type?: SegmentationRepresentations
|
||||
type?: csToolsEnums.SegmentationRepresentations
|
||||
) {
|
||||
cstSegmentation.config.visibility.setSegmentIndexVisibility(
|
||||
viewportId,
|
||||
@@ -1855,6 +1925,13 @@ class SegmentationService extends PubSubService {
|
||||
segmentationId,
|
||||
});
|
||||
};
|
||||
|
||||
private _onAnnotationCutMergeProcessCompletedFromSource = evt => {
|
||||
const { segmentationId } = evt.detail;
|
||||
this._broadcastEvent(this.EVENTS.ANNOTATION_CUT_MERGE_PROCESS_COMPLETED, {
|
||||
segmentationId,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default SegmentationService;
|
||||
|
||||
+5
-7
@@ -65,13 +65,11 @@ const segmentationRepresentationModifiedCallback = async (
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
await segmentationService.addSegmentationRepresentation(targetViewportId, {
|
||||
segmentationId,
|
||||
|
||||
@@ -714,7 +714,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// is being used to navigate to the initial view position for measurement
|
||||
// navigation and other navigation forcing specific views.
|
||||
let initialImageIndexToUse =
|
||||
presentations?.positionPresentation?.initialImageIndex ?? <number>initialImageIndex;
|
||||
presentations?.positionPresentation?.initialImageIndex ?? (initialImageIndex as number);
|
||||
|
||||
const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();
|
||||
|
||||
@@ -933,6 +933,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetUIDs[0]);
|
||||
const displaySetModality = displaySet?.Modality;
|
||||
|
||||
// seems like a hack but we need the actor to be ready first before
|
||||
// we set the properties
|
||||
const timeoutViewportCallback = (callback: () => void) => setTimeout(callback, 0);
|
||||
|
||||
// filter overlay display sets (e.g. segmentation) since they will get handled below via the segmentation service
|
||||
const filteredVolumeInputArray = volumeInputArray
|
||||
.map((volumeInput, index) => {
|
||||
@@ -984,12 +988,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const backgroundDisplaySet = displaySetService.getDisplaySetsBy(
|
||||
displaySet =>
|
||||
!displaySet.isOverlayDisplaySet &&
|
||||
displaySet.images.some(image => image.imageId === sampleImageId)
|
||||
displaySet.images?.some(image => image.imageId === sampleImageId)
|
||||
);
|
||||
|
||||
if (backgroundDisplaySet.length !== 1) {
|
||||
throw new Error('Background display set not found');
|
||||
}
|
||||
|
||||
if (viewport.type === csEnums.ViewportType.VOLUME_3D) {
|
||||
timeoutViewportCallback(() => {
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId: viewport.id,
|
||||
displaySetInstanceUIDs: [backgroundDisplaySet[0].displaySetInstanceUID],
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1006,15 +1019,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
viewport.render();
|
||||
|
||||
volumesProperties.forEach(({ properties, volumeId }) => {
|
||||
setTimeout(() => {
|
||||
// seems like a hack but we need the actor to be ready first before
|
||||
// we set the properties
|
||||
timeoutViewportCallback(() => {
|
||||
viewport.setProperties(properties, volumeId);
|
||||
viewport.render();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||
this.setPresentations(viewport.id, presentations);
|
||||
|
||||
if (!presentations.positionPresentation) {
|
||||
const imageIndex = this._getInitialImageIndexForViewport(viewportInfo);
|
||||
@@ -1335,10 +1346,18 @@ 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,
|
||||
type: representationType,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,3 +2,4 @@ export { useLutPresentationStore } from './useLutPresentationStore';
|
||||
export { usePositionPresentationStore } from './usePositionPresentationStore';
|
||||
export { useSegmentationPresentationStore } from './useSegmentationPresentationStore';
|
||||
export { useSynchronizersStore } from './useSynchronizersStore';
|
||||
export { useSelectedSegmentationsForViewportStore } from './useSelectedSegmentationsForViewportStore';
|
||||
@@ -0,0 +1,85 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
import { SelectedSegmentationByTypeMap } from '../types/SelectedSegmentationByTypeMap';
|
||||
|
||||
const PRESENTATION_TYPE_ID = 'selectedSegmentationsForViewportId';
|
||||
const DEBUG_STORE = false;
|
||||
|
||||
/**
|
||||
* Represents the state and actions for managing selected segmentations for a viewport by representation type.
|
||||
*/
|
||||
type SelectedSegmentationsForViewportState = {
|
||||
/**
|
||||
* Stores a mapping from viewport id to a map of representation type to segmentation id.
|
||||
*/
|
||||
selectedSegmentationsForViewport: Record<string, SelectedSegmentationByTypeMap>;
|
||||
|
||||
/**
|
||||
* Sets the selected segmentations for a given viewport id.
|
||||
*
|
||||
* @param key - The viewport id.
|
||||
* @param value - The `SelectedSegmentationByTypeMap` to associate with the viewport id.
|
||||
*/
|
||||
setSelectedSegmentationsForViewport: (key: string, value: SelectedSegmentationByTypeMap) => void;
|
||||
|
||||
/**
|
||||
* Clears all selected segmentations for all viewports.
|
||||
*/
|
||||
clearSelectedSegmentationsForViewportStore: () => void;
|
||||
|
||||
/**
|
||||
* Type identifier for the store.
|
||||
*/
|
||||
type: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the Selected Segmentations For Viewport store.
|
||||
*
|
||||
* @param set - The zustand set function.
|
||||
* @returns The selected segmentations for viewport store state and actions.
|
||||
*/
|
||||
const createSelectedSegmentationsForViewportStore = (
|
||||
set
|
||||
): SelectedSegmentationsForViewportState => ({
|
||||
selectedSegmentationsForViewport: {},
|
||||
type: PRESENTATION_TYPE_ID,
|
||||
|
||||
/**
|
||||
* Sets the selected segmentations for a given viewport id.
|
||||
*/
|
||||
setSelectedSegmentationsForViewport: (key, value) =>
|
||||
set(
|
||||
state => ({
|
||||
selectedSegmentationsForViewport: {
|
||||
...state.selectedSegmentationsForViewport,
|
||||
[key]: value,
|
||||
},
|
||||
}),
|
||||
false,
|
||||
'setSelectedSegmentationsForViewport'
|
||||
),
|
||||
|
||||
/**
|
||||
* Clears all selected segmentations for all viewports.
|
||||
*/
|
||||
clearSelectedSegmentationsForViewportStore: () =>
|
||||
set(
|
||||
{ selectedSegmentationsForViewport: {} },
|
||||
false,
|
||||
'clearSelectedSegmentationsForViewportStore'
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* Zustand store for managing selected segmentations for a viewport by representation type.
|
||||
* Applies devtools middleware when DEBUG_STORE is enabled.
|
||||
*/
|
||||
export const useSelectedSegmentationsForViewportStore =
|
||||
create<SelectedSegmentationsForViewportState>()(
|
||||
DEBUG_STORE
|
||||
? devtools(createSelectedSegmentationsForViewportStore, {
|
||||
name: 'SelectedSegmentationsForViewportStore',
|
||||
})
|
||||
: createSelectedSegmentationsForViewportStore
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
|
||||
export type SelectedSegmentationByTypeMap = Map<SegmentationRepresentations, string>;
|
||||
@@ -0,0 +1,74 @@
|
||||
import { utilities as csUtils } from '@cornerstonejs/core';
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { ServicesManager } from '@ohif/core';
|
||||
|
||||
function _createDefaultSegments(createInitialSegment?: boolean) {
|
||||
return createInitialSegment
|
||||
? {
|
||||
1: {
|
||||
label: `${i18n.t('Tools:Segment')} 1`,
|
||||
active: true,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
}
|
||||
|
||||
type CreateSegmentationForViewportOptions = {
|
||||
displaySetInstanceUID?: string;
|
||||
label?: string;
|
||||
segmentationId?: string;
|
||||
createInitialSegment?: boolean;
|
||||
};
|
||||
|
||||
type CreateSegmentationForViewportParams = {
|
||||
viewportId: string;
|
||||
options?: CreateSegmentationForViewportOptions;
|
||||
segmentationType: SegmentationRepresentations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a segmentation for the active viewport
|
||||
*
|
||||
* The created segmentation will be registered as a display set and also added
|
||||
* as a segmentation representation to the viewport.
|
||||
*/
|
||||
export async function createSegmentationForViewport(
|
||||
servicesManager: ServicesManager,
|
||||
{ viewportId, options = {}, segmentationType }: CreateSegmentationForViewportParams
|
||||
): Promise<string> {
|
||||
const { viewportGridService, displaySetService, segmentationService } = servicesManager.services;
|
||||
const { viewports } = viewportGridService.getState();
|
||||
const targetViewportId = viewportId;
|
||||
|
||||
const viewport = viewports.get(targetViewportId);
|
||||
|
||||
// Todo: add support for multiple display sets
|
||||
const displaySetInstanceUID = options.displaySetInstanceUID || viewport.displaySetInstanceUIDs[0];
|
||||
|
||||
const segs = segmentationService.getSegmentations();
|
||||
|
||||
const label = options.label || `${i18n.t('Tools:Segmentation')} ${segs.length + 1}`;
|
||||
const segmentationId = options.segmentationId || `${csUtils.uuidv4()}`;
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
const segmentationCreationOptions = {
|
||||
label,
|
||||
segmentationId,
|
||||
segments: _createDefaultSegments(options.createInitialSegment),
|
||||
};
|
||||
|
||||
// This will create the segmentation and register it as a display set
|
||||
const generatedSegmentationId = await (segmentationType === SegmentationRepresentations.Labelmap
|
||||
? segmentationService.createLabelmapForDisplaySet(displaySet, segmentationCreationOptions)
|
||||
: segmentationService.createContourForDisplaySet(displaySet, segmentationCreationOptions));
|
||||
|
||||
// Also add the segmentation representation to the viewport
|
||||
await segmentationService.addSegmentationRepresentation(viewportId, {
|
||||
segmentationId,
|
||||
type: segmentationType,
|
||||
});
|
||||
|
||||
return generatedSegmentationId;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,319 @@
|
||||
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/)
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,313 @@
|
||||
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],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
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 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
+15
-2
@@ -1,4 +1,17 @@
|
||||
export default function getInterleavedFrames(imageIds) {
|
||||
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 }];
|
||||
}
|
||||
|
||||
const minImageIdIndex = 0;
|
||||
const maxImageIdIndex = imageIds.length - 1;
|
||||
|
||||
@@ -8,7 +21,7 @@ export default function getInterleavedFrames(imageIds) {
|
||||
let upperImageIdIndex = middleImageIdIndex;
|
||||
|
||||
// Build up an array of images to prefetch, starting with the current image.
|
||||
const imageIdsToPrefetch = [
|
||||
const imageIdsToPrefetch: ImageIdToPrefetch[] = [
|
||||
{ imageId: imageIds[middleImageIdIndex], imageIdIndex: middleImageIdIndex },
|
||||
];
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
+2
-5
@@ -9,12 +9,9 @@
|
||||
* 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(imageIds) {
|
||||
const frames = [[], [], [], [], []];
|
||||
export default function getNthFrames<T>(imageIds: T[]): T[] {
|
||||
const frames: T[][] = [[], [], [], [], []];
|
||||
const centerStart = imageIds.length / 2 - 3;
|
||||
const centerEnd = centerStart + 6;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
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
@@ -0,0 +1,50 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,307 @@
|
||||
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 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)).toEqual(null);
|
||||
});
|
||||
|
||||
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(mockUpdatedViewports);
|
||||
});
|
||||
|
||||
it('should handle viewports with undefined viewportType', () => {
|
||||
const mockUpdatedViewports = [
|
||||
{
|
||||
viewportOptions: {
|
||||
viewportType: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
viewportType: 'volume3d',
|
||||
},
|
||||
},
|
||||
{
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,7 @@ function getUpdatedViewportsForSegmentation({
|
||||
isHangingProtocolLayout
|
||||
);
|
||||
|
||||
return updatedViewports.filter(v => v.viewportOptions?.viewportType !== 'volume3d');
|
||||
return updatedViewports;
|
||||
}
|
||||
|
||||
const getTargetViewport = ({ viewportId, viewportGridService }) => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import promptHydrationDialog, {
|
||||
HydrationSRResult,
|
||||
} from './promptHydrationDialog';
|
||||
import { getCenterExtent } from './getCenterExtent';
|
||||
import { createSegmentationForViewport } from './createSegmentationForViewport';
|
||||
|
||||
const utils = {
|
||||
handleSegmentChange,
|
||||
@@ -18,6 +19,7 @@ const utils = {
|
||||
setupSegmentationModifiedHandler,
|
||||
promptHydrationDialog,
|
||||
getCenterExtent,
|
||||
createSegmentationForViewport,
|
||||
};
|
||||
|
||||
export type { HydrationDialogProps, HydrationCallback, HydrationSRResult };
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,381 @@
|
||||
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: '',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,304 @@
|
||||
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]);
|
||||
});
|
||||
});
|
||||
+9
-7
@@ -3,28 +3,30 @@
|
||||
* 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
|
||||
* @returns [] reordered to be breadth first traversal of lists
|
||||
* @param lists - Array of arrays to interleave
|
||||
* @returns Array reordered to be breadth first traversal of lists
|
||||
*/
|
||||
export default function interleave(lists) {
|
||||
export default function interleave<T>(lists: T[][]): T[] {
|
||||
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 (const list of useLists) {
|
||||
for (let j = 0; j < useLists.length; j++) {
|
||||
const list = useLists[j];
|
||||
if (i >= list.length) {
|
||||
useLists.splice(useLists.indexOf(list), 1);
|
||||
useLists.splice(j, 1);
|
||||
j--; // Adjust index after removal to avoid iterator skipping
|
||||
continue;
|
||||
}
|
||||
ret.push(list[i]);
|
||||
}
|
||||
}
|
||||
console.timeEnd('interleave');
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Loaded 100 of 435 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user