Compare commits
139
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07301fc020 | ||
|
|
1203477dd4 | ||
|
|
158a181670 | ||
|
|
aac3546bed | ||
|
|
1256a4d565 | ||
|
|
617043fe0d | ||
|
|
6f610b09e1 | ||
|
|
a2e320f9f8 | ||
|
|
566b25a544 | ||
|
|
79d5c36bda | ||
|
|
f47a94317d | ||
|
|
45b68e841d | ||
|
|
d70aae3233 | ||
|
|
345b137bec | ||
|
|
9bcd1ae6f5 | ||
|
|
6b79dff5c1 | ||
|
|
88859f75d5 | ||
|
|
feed2304c1 | ||
|
|
08815909c7 | ||
|
|
ed2234a67c | ||
|
|
f7fe91c5f6 | ||
|
|
21ec6860f2 | ||
|
|
ac474b7f8f | ||
|
|
e485d68fd4 | ||
|
|
5089229ff9 | ||
|
|
df09176d63 | ||
|
|
bb603bd1f8 | ||
|
|
82a4a7d377 | ||
|
|
c27fa9c802 | ||
|
|
4cdfdae814 | ||
|
|
596340803a | ||
|
|
ff4e17f5c2 | ||
|
|
21e8a2bd0b | ||
|
|
2a442e30eb | ||
|
|
8e8a66bd29 | ||
|
|
b3a0faf5f5 | ||
|
|
a2a0090eeb | ||
|
|
ea552027c5 | ||
|
|
0eac386a31 | ||
|
|
40b590d75a | ||
|
|
01df1763ce | ||
|
|
5bba98ed84 | ||
|
|
16785936d9 | ||
|
|
3deb73d642 | ||
|
|
ef46b5a704 | ||
|
|
ed2bf23cd2 | ||
|
|
2ff2f3f34d | ||
|
|
a47aeb8bd7 | ||
|
|
94f40192f5 | ||
|
|
1c43aa791a | ||
|
|
e4a116b074 | ||
|
|
2460d28afc | ||
|
|
4a958b8989 | ||
|
|
31b837fa90 | ||
|
|
9bb00d71fa | ||
|
|
40bbe33a9d | ||
|
|
911d672835 | ||
|
|
9c553fded4 | ||
|
|
144dd39393 | ||
|
|
df2f50748e | ||
|
|
83a1523408 | ||
|
|
5a62a983e5 | ||
|
|
d00a86b022 | ||
|
|
710a2dca72 | ||
|
|
9100b1f02e | ||
|
|
0049f4c030 | ||
|
|
b5cdf88a57 | ||
|
|
c793df727b | ||
|
|
1456a493d6 | ||
|
|
7c551f5043 | ||
|
|
0b2c2a235f | ||
|
|
b1efe40aa1 | ||
|
|
a8a0bdb2df | ||
|
|
6878c1a264 | ||
|
|
d181eb4fdc | ||
|
|
fd809e4a0b | ||
|
|
4c29670d76 | ||
|
|
f58725ce40 | ||
|
|
5a1cb842f0 | ||
|
|
c5bd79552a | ||
|
|
61bf22c6f8 | ||
|
|
3d98548072 | ||
|
|
bf9770a55c | ||
|
|
2049c0936c | ||
|
|
7d5607073d | ||
|
|
1b6910bf76 | ||
|
|
98650302c7 | ||
|
|
bf846c94c3 | ||
|
|
b5dc9047ba | ||
|
|
636d8b0458 | ||
|
|
e8858f3eb5 | ||
|
|
eb7c950d7e | ||
|
|
c6214d66b9 | ||
|
|
0eb502aaf0 | ||
|
|
44a5a24d4f | ||
|
|
b0bf5dc780 | ||
|
|
5915fada83 | ||
|
|
2fa335c6d9 | ||
|
|
de4ad88a09 | ||
|
|
6ca13c0a4c | ||
|
|
0177b625ba | ||
|
|
c4064acfbc | ||
|
|
7a05d169ef | ||
|
|
13d0c883f8 | ||
|
|
c45f60dce2 | ||
|
|
7bb2713c5c | ||
|
|
97cb1f94e1 | ||
|
|
f99645f3a1 | ||
|
|
ae16c7f972 | ||
|
|
9c6e1cd305 | ||
|
|
7f0ca857fa | ||
|
|
920256d3a4 | ||
|
|
e1f55e65f2 | ||
|
|
d1c07d90f0 | ||
|
|
b1a04ecac3 | ||
|
|
f707b4ebc9 | ||
|
|
805c53270f | ||
|
|
d14a8f0199 | ||
|
|
5a5c4f58af | ||
|
|
6e5d8b691e | ||
|
|
59576d695d | ||
|
|
78d4bea04d | ||
|
|
806c2f4b36 | ||
|
|
6651008fbb | ||
|
|
74c98b6e9f | ||
|
|
ecee25b1d5 | ||
|
|
f1a67647ae | ||
|
|
4f9a00f872 | ||
|
|
7673a887e4 | ||
|
|
924affa7b5 | ||
|
|
abac6f583e | ||
|
|
36ba20d6e8 | ||
|
|
9d1884d7d8 | ||
|
|
622f18bd3e | ||
|
|
8b6256e76f | ||
|
|
c4ff2c2f09 | ||
|
|
1be5613946 | ||
|
|
6edb1c1e4c | ||
|
|
404b0a5d53 |
No files matched your search
@@ -13,7 +13,7 @@ version: 2.1
|
||||
##
|
||||
orbs:
|
||||
codecov: codecov/codecov@1.0.5
|
||||
cypress: cypress-io/cypress@3.1.4
|
||||
cypress: cypress-io/cypress@3.3.1
|
||||
|
||||
executors:
|
||||
cypress-custom:
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Codespell
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
name: Check for spelling errors
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
Vendored
+1
-1
@@ -31,6 +31,6 @@
|
||||
"prettier.endOfLine": "lf",
|
||||
"workbench.colorCustomizations": {},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
||||
+480
@@ -3,6 +3,486 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **layout:** new layout selector with 3D volume rendering ([#3923](https://github.com/OHIF/Viewers/issues/3923)) ([617043f](https://github.com/OHIF/Viewers/commit/617043fe0da5de91fbea4ac33a27f1df16ae1ca6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **worklist:** new investigational use text ([#3999](https://github.com/OHIF/Viewers/issues/3999)) ([45b68e8](https://github.com/OHIF/Viewers/commit/45b68e841dcb9e28a2ea991c37ee7ac4a8c5b71e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **worklist:** New worklist buttons and tooltips ([#3989](https://github.com/OHIF/Viewers/issues/3989)) ([9bcd1ae](https://github.com/OHIF/Viewers/commit/9bcd1ae6f51d61786cc1e99624f396b56a47cd69))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SR display:** and the token based navigation ([#3995](https://github.com/OHIF/Viewers/issues/3995)) ([feed230](https://github.com/OHIF/Viewers/commit/feed2304c124dc2facc7a7371ed9851548c223c5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **delete measurement:** icon for measurement table ([#3775](https://github.com/OHIF/Viewers/issues/3775)) ([f7fe91c](https://github.com/OHIF/Viewers/commit/f7fe91c5f6c4f05f3f3f5f640d3a119bd40a5870))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** mode creation template ([#3876](https://github.com/OHIF/Viewers/issues/3876)) ([#3981](https://github.com/OHIF/Viewers/issues/3981)) ([e485d68](https://github.com/OHIF/Viewers/commit/e485d68fd4619ce7187113cbe59e47f9523dbcc8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docs:** Minor typos in hpModule.md ([#3962](https://github.com/OHIF/Viewers/issues/3962)) ([4cdfdae](https://github.com/OHIF/Viewers/commit/4cdfdae8149166cf9dc91a55c0d7f2a224e55d8f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **errorboundary:** format stack trace properly ([#3931](https://github.com/OHIF/Viewers/issues/3931)) ([0eac386](https://github.com/OHIF/Viewers/commit/0eac386a31a5d6965536360aa65a44769c1a5740))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Sort merge results based on default data source (input) ([#3903](https://github.com/OHIF/Viewers/issues/3903)) ([5bba98e](https://github.com/OHIF/Viewers/commit/5bba98ed848bdf46b5ba4fc4708527cced3308b5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* catch errors in getPTImageIdInstanceMetadata ([#3897](https://github.com/OHIF/Viewers/issues/3897)) ([a47aeb8](https://github.com/OHIF/Viewers/commit/a47aeb8bd729dcb8d2cfc13b27a31b0dd88f11ad))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **viewport-sync:** remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync ([#3849](https://github.com/OHIF/Viewers/issues/3849)) ([e4a116b](https://github.com/OHIF/Viewers/commit/e4a116b074fcb85c8cbcc9db44fdec565f3386db))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* is same orientaiton ([#3905](https://github.com/OHIF/Viewers/issues/3905)) ([31b837f](https://github.com/OHIF/Viewers/commit/31b837fa90f631d4984482c6e952373fbb8bdbfc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Check merge key for merge data source ([#3901](https://github.com/OHIF/Viewers/issues/3901)) ([911d672](https://github.com/OHIF/Viewers/commit/911d67283536b2fe7930948f2819ea0ad66e2a32))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **hp:** enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol ([#3804](https://github.com/OHIF/Viewers/issues/3804)) ([0049f4c](https://github.com/OHIF/Viewers/commit/0049f4c0303f0b6ea995972326fc8784259f5a47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **transferSyntax:** prefer server transcoded transfer syntax for all images ([#3883](https://github.com/OHIF/Viewers/issues/3883)) ([1456a49](https://github.com/OHIF/Viewers/commit/1456a493d66c90c787b022256c9f2846afb115fc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add on mode init hook ([#3882](https://github.com/OHIF/Viewers/issues/3882)) ([f58725c](https://github.com/OHIF/Viewers/commit/f58725ce40685f7297181ef98d81bc28420c8291))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ui:** sidePanel expandedWidth ([#3728](https://github.com/OHIF/Viewers/issues/3728)) ([61bf22c](https://github.com/OHIF/Viewers/commit/61bf22c6f80e764bdf5c3b56bb0124a95aa0f793))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve disableEditing flag ([#3875](https://github.com/OHIF/Viewers/issues/3875)) ([2049c09](https://github.com/OHIF/Viewers/commit/2049c0936c86f819604c243d3dc7b3fe971b5b2c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* convert radian to degree value for mip rotation ([#3881](https://github.com/OHIF/Viewers/issues/3881)) ([bf846c9](https://github.com/OHIF/Viewers/commit/bf846c94c378f04b9f44dcd71be3f056dbcfe0b5))
|
||||
* PDF display request in v3 ([#3878](https://github.com/OHIF/Viewers/issues/3878)) ([9865030](https://github.com/OHIF/Viewers/commit/98650302c7575f0aea386e32cfc4112c378035e6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* colormap for stack viewports via HangingProtocol ([#3866](https://github.com/OHIF/Viewers/issues/3866)) ([e8858f3](https://github.com/OHIF/Viewers/commit/e8858f3eb55552f695af4a55980f9ae2e9af7291))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **icon-style:** Ensure consistent icon dimensions ([#3727](https://github.com/OHIF/Viewers/issues/3727)) ([6ca13c0](https://github.com/OHIF/Viewers/commit/6ca13c0a4cb5a95bbb52b0db902b5dbf72f8aa6e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **overlay:** add inline binary overlays ([#3852](https://github.com/OHIF/Viewers/issues/3852)) ([0177b62](https://github.com/OHIF/Viewers/commit/0177b625ba86760168bc4db58c8a109aa9ee83cb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* address and improve system vulnerabilities ([#3851](https://github.com/OHIF/Viewers/issues/3851)) ([805c532](https://github.com/OHIF/Viewers/commit/805c53270f243ec61f142a3ffa0af500021cd5ec))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **config:** Add activateViewportBeforeInteraction parameter for viewport interaction customization ([#3847](https://github.com/OHIF/Viewers/issues/3847)) ([f707b4e](https://github.com/OHIF/Viewers/commit/f707b4ebc996f379cd30337badc06b07e6e35ac5))
|
||||
* **i18n:** enhanced i18n support ([#3761](https://github.com/OHIF/Viewers/issues/3761)) ([d14a8f0](https://github.com/OHIF/Viewers/commit/d14a8f0199db95cd9e85866a011b64d6bf830d57))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **HP:** Added new 3D hanging protocols to be used in the new layout selector ([#3844](https://github.com/OHIF/Viewers/issues/3844)) ([59576d6](https://github.com/OHIF/Viewers/commit/59576d695d4d26601d35c43f73d602f0b12d72bf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SM:** drag and drop is now fixed for SM ([#3813](https://github.com/OHIF/Viewers/issues/3813)) ([f1a6764](https://github.com/OHIF/Viewers/commit/f1a67647aed635437b188cea7cf5d5a8fb974bbe))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cine:** Set cine disabled on mode exit. ([#3812](https://github.com/OHIF/Viewers/issues/3812)) ([924affa](https://github.com/OHIF/Viewers/commit/924affa7b5d420c2f91522a075cecbb3c78e8f52))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Merge Data Source ([#3788](https://github.com/OHIF/Viewers/issues/3788)) ([c4ff2c2](https://github.com/OHIF/Viewers/commit/c4ff2c2f09546ce8b72eab9c5e7beed611e3cab0))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **events:** broadcast series summary metadata ([#3798](https://github.com/OHIF/Viewers/issues/3798)) ([404b0a5](https://github.com/OHIF/Viewers/commit/404b0a5d535182d1ae44e33f7232db500a7b2c16))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21)
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ module.exports = {
|
||||
'@babel/plugin-transform-typescript',
|
||||
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
|
||||
['@babel/plugin-proposal-private-methods', { loose: true }],
|
||||
'@babel/plugin-transform-class-static-block',
|
||||
],
|
||||
env: {
|
||||
test: {
|
||||
@@ -45,6 +46,7 @@ module.exports = {
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-transform-regenerator',
|
||||
'@babel/transform-destructuring',
|
||||
'@babel/plugin-transform-runtime',
|
||||
'@babel/plugin-transform-typescript',
|
||||
],
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
00e751933ac6d611a34773fa69594243f1b99082
|
||||
158a1816703e0ad66cae08cb9bd1ffb93bbd8d43
|
||||
@@ -3,6 +3,375 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **viewport-sync:** remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync ([#3849](https://github.com/OHIF/Viewers/issues/3849)) ([e4a116b](https://github.com/OHIF/Viewers/commit/e4a116b074fcb85c8cbcc9db44fdec565f3386db))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||
"version": "3.8.0-beta.21",
|
||||
"version": "3.8.0-beta.66",
|
||||
"description": "DICOM RT read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -24,6 +24,8 @@
|
||||
"yarn": ">=1.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||
"dev:dicom-seg": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
@@ -31,10 +33,10 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.8.0-beta.21",
|
||||
"@ohif/extension-cornerstone": "3.8.0-beta.21",
|
||||
"@ohif/extension-default": "3.8.0-beta.21",
|
||||
"@ohif/i18n": "3.8.0-beta.21",
|
||||
"@ohif/core": "3.8.0-beta.66",
|
||||
"@ohif/extension-cornerstone": "3.8.0-beta.66",
|
||||
"@ohif/extension-default": "3.8.0-beta.66",
|
||||
"@ohif/i18n": "3.8.0-beta.66",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
||||
@@ -168,6 +168,8 @@ function OHIFCornerstoneRTViewport(props) {
|
||||
}, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
// I'm not sure what is this, since in RT we support Overlapping segments
|
||||
// via contours
|
||||
const { unsubscribe } = segmentationService.subscribe(
|
||||
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||
evt => {
|
||||
|
||||
@@ -3,6 +3,396 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **layout:** new layout selector with 3D volume rendering ([#3923](https://github.com/OHIF/Viewers/issues/3923)) ([617043f](https://github.com/OHIF/Viewers/commit/617043fe0da5de91fbea4ac33a27f1df16ae1ca6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SR display:** and the token based navigation ([#3995](https://github.com/OHIF/Viewers/issues/3995)) ([feed230](https://github.com/OHIF/Viewers/commit/feed2304c124dc2facc7a7371ed9851548c223c5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||
"version": "3.8.0-beta.21",
|
||||
"version": "3.8.0-beta.66",
|
||||
"description": "DICOM SEG read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -24,6 +24,8 @@
|
||||
"yarn": ">=1.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||
"dev:dicom-seg": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
@@ -31,10 +33,10 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.8.0-beta.21",
|
||||
"@ohif/extension-cornerstone": "3.8.0-beta.21",
|
||||
"@ohif/extension-default": "3.8.0-beta.21",
|
||||
"@ohif/i18n": "3.8.0-beta.21",
|
||||
"@ohif/core": "3.8.0-beta.66",
|
||||
"@ohif/extension-cornerstone": "3.8.0-beta.66",
|
||||
"@ohif/extension-default": "3.8.0-beta.66",
|
||||
"@ohif/i18n": "3.8.0-beta.66",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -44,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.27.3",
|
||||
"@cornerstonejs/tools": "^1.27.3",
|
||||
"@kitware/vtk.js": "27.3.1",
|
||||
"@cornerstonejs/adapters": "^1.67.0",
|
||||
"@cornerstonejs/core": "^1.67.0",
|
||||
"@kitware/vtk.js": "29.7.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { cache, metaData } from '@cornerstonejs/core';
|
||||
import {
|
||||
segmentation as cornerstoneToolsSegmentation,
|
||||
Enums as cornerstoneToolsEnums,
|
||||
utilities,
|
||||
} from '@cornerstonejs/tools';
|
||||
import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters';
|
||||
import { classes, DicomMetadataStore } from '@ohif/core';
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
getUpdatedViewportsForSegmentation,
|
||||
getTargetViewport,
|
||||
} from './utils/hydrationUtils';
|
||||
const { segmentation: segmentationUtils } = utilities;
|
||||
|
||||
const { datasetToBlob } = dcmjs.data;
|
||||
|
||||
@@ -45,6 +47,7 @@ const commandsModule = ({
|
||||
uiDialogService,
|
||||
displaySetService,
|
||||
viewportGridService,
|
||||
toolGroupService,
|
||||
} = (servicesManager as ServicesManager).services;
|
||||
|
||||
const actions = {
|
||||
@@ -397,6 +400,60 @@ const commandsModule = ({
|
||||
console.warn(e);
|
||||
}
|
||||
},
|
||||
setBrushSize: ({ value, toolNames }) => {
|
||||
const brushSize = Number(value);
|
||||
|
||||
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
|
||||
if (toolNames?.length === 0) {
|
||||
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize);
|
||||
} else {
|
||||
toolNames?.forEach(toolName => {
|
||||
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
setThresholdRange: ({
|
||||
value,
|
||||
toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
|
||||
}) => {
|
||||
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
|
||||
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||
toolNames?.forEach(toolName => {
|
||||
toolGroup.setToolConfiguration(toolName, {
|
||||
strategySpecificConfiguration: {
|
||||
THRESHOLD: {
|
||||
threshold: value,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
toggleThresholdRangeAndDynamic() {
|
||||
const toolGroupIds = toolGroupService.getToolGroupIds();
|
||||
|
||||
if (!toolGroupIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||
const brushInstances = segmentationUtils.getBrushToolInstances(toolGroup.id);
|
||||
|
||||
brushInstances.forEach(({ configuration }) => {
|
||||
const { activeStrategy, strategySpecificConfiguration } = configuration;
|
||||
|
||||
if (activeStrategy.startsWith('THRESHOLD')) {
|
||||
const thresholdConfig = strategySpecificConfiguration.THRESHOLD;
|
||||
|
||||
if (thresholdConfig) {
|
||||
thresholdConfig.isDynamic = !thresholdConfig.isDynamic;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -424,11 +481,21 @@ const commandsModule = ({
|
||||
downloadRTSS: {
|
||||
commandFn: actions.downloadRTSS,
|
||||
},
|
||||
setBrushSize: {
|
||||
commandFn: actions.setBrushSize,
|
||||
},
|
||||
setThresholdRange: {
|
||||
commandFn: actions.setThresholdRange,
|
||||
},
|
||||
toggleThresholdRangeAndDynamic: {
|
||||
commandFn: actions.toggleThresholdRangeAndDynamic,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
actions,
|
||||
definitions,
|
||||
defaultContext: 'SEGMENTATION',
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useAppConfig } from '@state';
|
||||
import { Toolbox } from '@ohif/ui';
|
||||
import PanelSegmentation from './panels/PanelSegmentation';
|
||||
import SegmentationToolbox from './panels/SegmentationToolbox';
|
||||
|
||||
const getPanelModule = ({ commandsManager, servicesManager, extensionManager, configuration }) => {
|
||||
const getPanelModule = ({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
configuration,
|
||||
title,
|
||||
}) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
|
||||
const wrappedPanelSegmentation = configuration => {
|
||||
@@ -26,13 +32,14 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager, co
|
||||
};
|
||||
|
||||
const wrappedPanelSegmentationWithTools = configuration => {
|
||||
const [appConfig] = useAppConfig();
|
||||
return (
|
||||
<>
|
||||
<SegmentationToolbox
|
||||
<Toolbox
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
buttonSectionId="segmentationToolbox"
|
||||
title="Segmentation Tools"
|
||||
configuration={{
|
||||
...configuration,
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { utils } from '@ohif/core';
|
||||
import { metaData, cache, triggerEvent, eventTarget } from '@cornerstonejs/core';
|
||||
import { CONSTANTS } from '@cornerstonejs/tools';
|
||||
import { adaptersSEG, Enums } from '@cornerstonejs/adapters';
|
||||
|
||||
import { SOPClassHandlerId } from './id';
|
||||
@@ -141,7 +142,7 @@ async function _loadSegments({ extensionManager, servicesManager, segDisplaySet,
|
||||
'@ohif/extension-cornerstone.utilityModule.common'
|
||||
);
|
||||
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const { segmentationService, uiNotificationService } = servicesManager.services;
|
||||
|
||||
const { dicomLoaderService } = utilityModule.exports;
|
||||
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers);
|
||||
@@ -174,12 +175,40 @@ async function _loadSegments({ extensionManager, servicesManager, segDisplaySet,
|
||||
{ skipOverlapping, tolerance, eventTarget, triggerEvent }
|
||||
);
|
||||
|
||||
let usedRecommendedDisplayCIELabValue = true;
|
||||
results.segMetadata.data.forEach((data, i) => {
|
||||
if (i > 0) {
|
||||
data.rgba = dicomlabToRGB(data.RecommendedDisplayCIELabValue);
|
||||
data.rgba = data.RecommendedDisplayCIELabValue;
|
||||
|
||||
if (data.rgba) {
|
||||
data.rgba = dicomlabToRGB(data.rgba);
|
||||
} else {
|
||||
usedRecommendedDisplayCIELabValue = false;
|
||||
data.rgba = CONSTANTS.COLOR_LUT[i % CONSTANTS.COLOR_LUT.length];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (results.overlappingSegments) {
|
||||
uiNotificationService.show({
|
||||
title: 'Overlapping Segments',
|
||||
message:
|
||||
'Unsupported overlapping segments detected, segmentation rendering results may be incorrect.',
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
|
||||
if (!usedRecommendedDisplayCIELabValue) {
|
||||
// Display a notification about the non-utilization of RecommendedDisplayCIELabValue
|
||||
uiNotificationService.show({
|
||||
title: 'DICOM SEG import',
|
||||
message:
|
||||
'RecommendedDisplayCIELabValue not found for one or more segments. The default color was used instead.',
|
||||
type: 'warning',
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
Object.assign(segDisplaySet, results);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
export function getToolbarModule({ commandsManager, servicesManager }) {
|
||||
const { segmentationService, toolGroupService } = servicesManager.services;
|
||||
return [
|
||||
{
|
||||
name: 'evaluate.cornerstone.segmentation',
|
||||
evaluate: ({ viewportId, button, toolNames }) => {
|
||||
// Todo: we need to pass in the button section Id since we are kind of
|
||||
// forcing the button to have black background since initially
|
||||
// it is designed for the toolbox not the toolbar on top
|
||||
// we should then branch the buttonSectionId to have different styles
|
||||
const segmentations = segmentationService.getSegmentations();
|
||||
if (!segmentations?.length) {
|
||||
return {
|
||||
disabled: true,
|
||||
className: '!text-common-bright !bg-black opacity-50',
|
||||
};
|
||||
}
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
if (!toolGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolName = getToolNameForButton(button);
|
||||
|
||||
if (!toolGroup || !toolGroup.hasTool(toolName)) {
|
||||
return {
|
||||
disabled: true,
|
||||
className: '!text-common-bright ohif-disabled',
|
||||
};
|
||||
}
|
||||
|
||||
const isPrimaryActive = toolNames
|
||||
? toolNames.includes(toolGroup.getActivePrimaryMouseButtonTool())
|
||||
: toolGroup.getActivePrimaryMouseButtonTool() === toolName;
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
className: isPrimaryActive
|
||||
? '!text-black !bg-primary-light hover:bg-primary-light hover-text-black hover:cursor-pointer'
|
||||
: '!text-common-bright !bg-black hover:bg-primary-light hover:cursor-pointer hover:text-black',
|
||||
// Todo: isActive right now is used for nested buttons where the primary
|
||||
// button needs to be fully rounded (vs partial rounded) when active
|
||||
// otherwise it does not have any other use
|
||||
isActive: isPrimaryActive,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getToolNameForButton(button) {
|
||||
const { props } = button;
|
||||
|
||||
const commands = props?.commands || button.commands;
|
||||
|
||||
if (commands && commands.length) {
|
||||
const command = commands[0];
|
||||
const { commandOptions } = command;
|
||||
const { toolName } = commandOptions || { toolName: props?.id ?? button.id };
|
||||
return toolName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||
import getHangingProtocolModule from './getHangingProtocolModule';
|
||||
import getPanelModule from './getPanelModule';
|
||||
import getCommandsModule from './commandsModule';
|
||||
import { getToolbarModule } from './getToolbarModule';
|
||||
import preRegistration from './init';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
@@ -29,7 +30,6 @@ const extension = {
|
||||
*/
|
||||
id,
|
||||
preRegistration,
|
||||
|
||||
/**
|
||||
* PanelModule should provide a list of panels that will be available in OHIF
|
||||
* for Modes to consume and render. Each panel is defined by a {name,
|
||||
@@ -38,8 +38,8 @@ const extension = {
|
||||
*/
|
||||
getPanelModule,
|
||||
getCommandsModule,
|
||||
|
||||
getViewportModule({ servicesManager, extensionManager }) {
|
||||
getToolbarModule,
|
||||
getViewportModule({ servicesManager, extensionManager, commandsManager }) {
|
||||
const ExtendedOHIFCornerstoneSEGViewport = props => {
|
||||
return (
|
||||
<OHIFCornerstoneSEGViewport
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { addTool, BrushTool } from '@cornerstonejs/tools';
|
||||
|
||||
export default function init({ configuration = {} }): void {
|
||||
export default function init({ servicesManager }): void {
|
||||
addTool(BrushTool);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createReportAsync } from '@ohif/extension-default';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SegmentationGroupTable, LegacyButtonGroup, LegacyButton } from '@ohif/ui';
|
||||
import { SegmentationGroupTable } from '@ohif/ui';
|
||||
|
||||
import callInputDialog from './callInputDialog';
|
||||
import callColorPickerDialog from './colorPickerDialog';
|
||||
|
||||
@@ -1,405 +0,0 @@
|
||||
import React, { useCallback, useEffect, useState, useReducer } from 'react';
|
||||
import { AdvancedToolbox, InputDoubleRange, useViewportGrid } from '@ohif/ui';
|
||||
import { Types } from '@ohif/extension-cornerstone';
|
||||
import { utilities } from '@cornerstonejs/tools';
|
||||
|
||||
const { segmentation: segmentationUtils } = utilities;
|
||||
|
||||
const TOOL_TYPES = {
|
||||
CIRCULAR_BRUSH: 'CircularBrush',
|
||||
SPHERE_BRUSH: 'SphereBrush',
|
||||
CIRCULAR_ERASER: 'CircularEraser',
|
||||
SPHERE_ERASER: 'SphereEraser',
|
||||
CIRCLE_SHAPE: 'CircleScissor',
|
||||
RECTANGLE_SHAPE: 'RectangleScissor',
|
||||
SPHERE_SHAPE: 'SphereScissor',
|
||||
THRESHOLD_CIRCULAR_BRUSH: 'ThresholdCircularBrush',
|
||||
THRESHOLD_SPHERE_BRUSH: 'ThresholdSphereBrush',
|
||||
};
|
||||
|
||||
const ACTIONS = {
|
||||
SET_TOOL_CONFIG: 'SET_TOOL_CONFIG',
|
||||
SET_ACTIVE_TOOL: 'SET_ACTIVE_TOOL',
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
Brush: {
|
||||
brushSize: 15,
|
||||
mode: 'CircularBrush', // Can be 'CircularBrush' or 'SphereBrush'
|
||||
},
|
||||
Eraser: {
|
||||
brushSize: 15,
|
||||
mode: 'CircularEraser', // Can be 'CircularEraser' or 'SphereEraser'
|
||||
},
|
||||
Shapes: {
|
||||
brushSize: 15,
|
||||
mode: 'CircleScissor', // E.g., 'CircleScissor', 'RectangleScissor', or 'SphereScissor'
|
||||
},
|
||||
ThresholdBrush: {
|
||||
brushSize: 15,
|
||||
thresholdRange: [-500, 500],
|
||||
},
|
||||
activeTool: null,
|
||||
};
|
||||
|
||||
function toolboxReducer(state, action) {
|
||||
switch (action.type) {
|
||||
case ACTIONS.SET_TOOL_CONFIG:
|
||||
const { tool, config } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
[tool]: {
|
||||
...state[tool],
|
||||
...config,
|
||||
},
|
||||
};
|
||||
case ACTIONS.SET_ACTIVE_TOOL:
|
||||
return { ...state, activeTool: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function SegmentationToolbox({ servicesManager, extensionManager }) {
|
||||
const { toolbarService, segmentationService, toolGroupService } =
|
||||
servicesManager.services as Types.CornerstoneServices;
|
||||
|
||||
const [viewportGrid] = useViewportGrid();
|
||||
const { viewports, activeViewportId } = viewportGrid;
|
||||
|
||||
const [toolsEnabled, setToolsEnabled] = useState(false);
|
||||
const [state, dispatch] = useReducer(toolboxReducer, initialState);
|
||||
|
||||
const updateActiveTool = useCallback(() => {
|
||||
if (!viewports?.size || activeViewportId === undefined) {
|
||||
return;
|
||||
}
|
||||
const viewport = viewports.get(activeViewportId);
|
||||
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.SET_ACTIVE_TOOL,
|
||||
payload: toolGroupService.getActiveToolForViewport(viewport.viewportId),
|
||||
});
|
||||
}, [activeViewportId, viewports, toolGroupService, dispatch]);
|
||||
|
||||
const setToolActive = useCallback(
|
||||
toolName => {
|
||||
toolbarService.recordInteraction({
|
||||
interactionType: 'tool',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: {
|
||||
toolName,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
dispatch({ type: ACTIONS.SET_ACTIVE_TOOL, payload: toolName });
|
||||
},
|
||||
[toolbarService, dispatch]
|
||||
);
|
||||
|
||||
/**
|
||||
* sets the tools enabled IF there are segmentations
|
||||
*/
|
||||
useEffect(() => {
|
||||
const events = [
|
||||
segmentationService.EVENTS.SEGMENTATION_ADDED,
|
||||
segmentationService.EVENTS.SEGMENTATION_UPDATED,
|
||||
segmentationService.EVENTS.SEGMENTATION_REMOVED,
|
||||
];
|
||||
|
||||
const unsubscriptions = [];
|
||||
|
||||
events.forEach(event => {
|
||||
const { unsubscribe } = segmentationService.subscribe(event, () => {
|
||||
const segmentations = segmentationService.getSegmentations();
|
||||
|
||||
const activeSegmentation = segmentations?.find(seg => seg.isActive);
|
||||
|
||||
setToolsEnabled(activeSegmentation?.segmentCount > 0);
|
||||
});
|
||||
|
||||
unsubscriptions.push(unsubscribe);
|
||||
});
|
||||
|
||||
updateActiveTool();
|
||||
|
||||
return () => {
|
||||
unsubscriptions.forEach(unsubscribe => unsubscribe());
|
||||
};
|
||||
}, [activeViewportId, viewports, segmentationService, updateActiveTool]);
|
||||
|
||||
/**
|
||||
* Update the active tool when the toolbar state changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = toolbarService.subscribe(
|
||||
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
|
||||
() => {
|
||||
updateActiveTool();
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [toolbarService, updateActiveTool]);
|
||||
|
||||
useEffect(() => {
|
||||
// if the active tool is not a brush tool then do nothing
|
||||
if (!Object.values(TOOL_TYPES).includes(state.activeTool)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the tool is Segmentation and it is enabled then do nothing
|
||||
if (toolsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the tool is Segmentation and it is disabled, then switch
|
||||
// back to the window level tool to not confuse the user when no
|
||||
// segmentation is active or when there is no segment in the segmentation
|
||||
setToolActive('WindowLevel');
|
||||
}, [toolsEnabled, state.activeTool, setToolActive]);
|
||||
|
||||
const updateBrushSize = useCallback(
|
||||
(toolName, brushSize) => {
|
||||
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
|
||||
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName);
|
||||
});
|
||||
},
|
||||
[toolGroupService]
|
||||
);
|
||||
|
||||
const onBrushSizeChange = useCallback(
|
||||
(valueAsStringOrNumber, toolCategory) => {
|
||||
const value = Number(valueAsStringOrNumber);
|
||||
|
||||
_getToolNamesFromCategory(toolCategory).forEach(toolName => {
|
||||
updateBrushSize(toolName, value);
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.SET_TOOL_CONFIG,
|
||||
payload: {
|
||||
tool: toolCategory,
|
||||
config: { brushSize: value },
|
||||
},
|
||||
});
|
||||
},
|
||||
[toolGroupService, dispatch]
|
||||
);
|
||||
|
||||
const handleRangeChange = useCallback(
|
||||
newRange => {
|
||||
if (
|
||||
newRange[0] === state.ThresholdBrush.thresholdRange[0] &&
|
||||
newRange[1] === state.ThresholdBrush.thresholdRange[1]
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolNames = _getToolNamesFromCategory('ThresholdBrush');
|
||||
|
||||
toolNames.forEach(toolName => {
|
||||
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
|
||||
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||
toolGroup.setToolConfiguration(toolName, {
|
||||
strategySpecificConfiguration: {
|
||||
THRESHOLD_INSIDE_CIRCLE: {
|
||||
threshold: newRange,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.SET_TOOL_CONFIG,
|
||||
payload: {
|
||||
tool: 'ThresholdBrush',
|
||||
config: { thresholdRange: newRange },
|
||||
},
|
||||
});
|
||||
},
|
||||
[toolGroupService, dispatch, state.ThresholdBrush.thresholdRange]
|
||||
);
|
||||
|
||||
return (
|
||||
<AdvancedToolbox
|
||||
title="Segmentation Tools"
|
||||
items={[
|
||||
{
|
||||
name: 'Brush',
|
||||
icon: 'icon-tool-brush',
|
||||
disabled: !toolsEnabled,
|
||||
active:
|
||||
state.activeTool === TOOL_TYPES.CIRCULAR_BRUSH ||
|
||||
state.activeTool === TOOL_TYPES.SPHERE_BRUSH,
|
||||
onClick: () => setToolActive(TOOL_TYPES.CIRCULAR_BRUSH),
|
||||
options: [
|
||||
{
|
||||
name: 'Radius (mm)',
|
||||
id: 'brush-radius',
|
||||
type: 'range',
|
||||
min: 0.5,
|
||||
max: 99.5,
|
||||
value: state.Brush.brushSize,
|
||||
step: 0.5,
|
||||
onChange: value => onBrushSizeChange(value, 'Brush'),
|
||||
},
|
||||
{
|
||||
name: 'Mode',
|
||||
type: 'radio',
|
||||
id: 'brush-mode',
|
||||
value: state.Brush.mode,
|
||||
values: [
|
||||
{ value: TOOL_TYPES.CIRCULAR_BRUSH, label: 'Circle' },
|
||||
{ value: TOOL_TYPES.SPHERE_BRUSH, label: 'Sphere' },
|
||||
],
|
||||
onChange: value => setToolActive(value),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Eraser',
|
||||
icon: 'icon-tool-eraser',
|
||||
disabled: !toolsEnabled,
|
||||
active:
|
||||
state.activeTool === TOOL_TYPES.CIRCULAR_ERASER ||
|
||||
state.activeTool === TOOL_TYPES.SPHERE_ERASER,
|
||||
onClick: () => setToolActive(TOOL_TYPES.CIRCULAR_ERASER),
|
||||
options: [
|
||||
{
|
||||
name: 'Radius (mm)',
|
||||
type: 'range',
|
||||
id: 'eraser-radius',
|
||||
min: 0.5,
|
||||
max: 99.5,
|
||||
value: state.Eraser.brushSize,
|
||||
step: 0.5,
|
||||
onChange: value => onBrushSizeChange(value, 'Eraser'),
|
||||
},
|
||||
{
|
||||
name: 'Mode',
|
||||
type: 'radio',
|
||||
id: 'eraser-mode',
|
||||
value: state.Eraser.mode,
|
||||
values: [
|
||||
{ value: TOOL_TYPES.CIRCULAR_ERASER, label: 'Circle' },
|
||||
{ value: TOOL_TYPES.SPHERE_ERASER, label: 'Sphere' },
|
||||
],
|
||||
onChange: value => setToolActive(value),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Shapes',
|
||||
icon: 'icon-tool-shape',
|
||||
disabled: !toolsEnabled,
|
||||
active:
|
||||
state.activeTool === TOOL_TYPES.CIRCLE_SHAPE ||
|
||||
state.activeTool === TOOL_TYPES.RECTANGLE_SHAPE ||
|
||||
state.activeTool === TOOL_TYPES.SPHERE_SHAPE,
|
||||
onClick: () => setToolActive(TOOL_TYPES.CIRCLE_SHAPE),
|
||||
options: [
|
||||
{
|
||||
name: 'Mode',
|
||||
type: 'radio',
|
||||
value: state.Shapes.mode,
|
||||
id: 'shape-mode',
|
||||
values: [
|
||||
{ value: TOOL_TYPES.CIRCLE_SHAPE, label: 'Circle' },
|
||||
{ value: TOOL_TYPES.RECTANGLE_SHAPE, label: 'Rectangle' },
|
||||
{ value: TOOL_TYPES.SPHERE_SHAPE, label: 'Sphere' },
|
||||
],
|
||||
onChange: value => setToolActive(value),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Threshold Tool',
|
||||
icon: 'icon-tool-threshold',
|
||||
disabled: !toolsEnabled,
|
||||
active:
|
||||
state.activeTool === TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH ||
|
||||
state.activeTool === TOOL_TYPES.THRESHOLD_SPHERE_BRUSH,
|
||||
onClick: () => setToolActive(TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH),
|
||||
options: [
|
||||
{
|
||||
name: 'Radius (mm)',
|
||||
id: 'threshold-radius',
|
||||
type: 'range',
|
||||
min: 0.5,
|
||||
max: 99.5,
|
||||
value: state.ThresholdBrush.brushSize,
|
||||
step: 0.5,
|
||||
onChange: value => onBrushSizeChange(value, 'ThresholdBrush'),
|
||||
},
|
||||
{
|
||||
name: 'Mode',
|
||||
type: 'radio',
|
||||
id: 'threshold-mode',
|
||||
value: state.activeTool,
|
||||
values: [
|
||||
{ value: TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH, label: 'Circle' },
|
||||
{ value: TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, label: 'Sphere' },
|
||||
],
|
||||
onChange: value => setToolActive(value),
|
||||
},
|
||||
{
|
||||
type: 'custom',
|
||||
id: 'segmentation-threshold-range',
|
||||
children: () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-secondary-light h-[1px]"></div>
|
||||
<div className="mt-1 text-[13px] text-white">Threshold</div>
|
||||
<InputDoubleRange
|
||||
values={state.ThresholdBrush.thresholdRange}
|
||||
onChange={handleRangeChange}
|
||||
minValue={-1000}
|
||||
maxValue={1000}
|
||||
step={1}
|
||||
showLabel={true}
|
||||
allowNumberEdit={true}
|
||||
showAdjustmentArrows={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function _getToolNamesFromCategory(category) {
|
||||
let toolNames = [];
|
||||
switch (category) {
|
||||
case 'Brush':
|
||||
toolNames = ['CircularBrush', 'SphereBrush'];
|
||||
break;
|
||||
case 'Eraser':
|
||||
toolNames = ['CircularEraser', 'SphereEraser'];
|
||||
break;
|
||||
case 'ThresholdBrush':
|
||||
toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush'];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return toolNames;
|
||||
}
|
||||
|
||||
export default SegmentationToolbox;
|
||||
@@ -180,7 +180,8 @@ function getUpdatedViewportsForSegmentation({
|
||||
});
|
||||
}
|
||||
});
|
||||
return updatedViewports;
|
||||
|
||||
return updatedViewports.filter(v => v.viewportOptions?.viewportType !== 'volume3d');
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -116,7 +116,6 @@ function OHIFCornerstoneSEGViewport(props) {
|
||||
}}
|
||||
onElementEnabled={onElementEnabled}
|
||||
onElementDisabled={onElementDisabled}
|
||||
// initialImageIndex={initialImageIndex}
|
||||
></Component>
|
||||
);
|
||||
}, [viewportId, segDisplaySet, toolGroupId]);
|
||||
@@ -172,14 +171,6 @@ function OHIFCornerstoneSEGViewport(props) {
|
||||
if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) {
|
||||
setSegIsLoading(false);
|
||||
}
|
||||
|
||||
if (evt.overlappingSegments) {
|
||||
uiNotificationService.show({
|
||||
title: 'Overlapping Segments',
|
||||
message: 'Overlapping segments detected which is not currently supported',
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -3,6 +3,396 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SR display:** and the token based navigation ([#3995](https://github.com/OHIF/Viewers/issues/3995)) ([feed230](https://github.com/OHIF/Viewers/commit/feed2304c124dc2facc7a7371ed9851548c223c5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-sr",
|
||||
"version": "3.8.0-beta.21",
|
||||
"version": "3.8.0-beta.66",
|
||||
"description": "OHIF extension for an SR Cornerstone Viewport",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -23,6 +23,8 @@
|
||||
"ohif-extension"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||
"dev:cornerstone": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
@@ -32,10 +34,10 @@
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.8.0-beta.21",
|
||||
"@ohif/extension-cornerstone": "3.8.0-beta.21",
|
||||
"@ohif/extension-measurement-tracking": "3.8.0-beta.21",
|
||||
"@ohif/ui": "3.8.0-beta.21",
|
||||
"@ohif/core": "3.8.0-beta.66",
|
||||
"@ohif/extension-cornerstone": "3.8.0-beta.66",
|
||||
"@ohif/extension-measurement-tracking": "3.8.0-beta.66",
|
||||
"@ohif/ui": "3.8.0-beta.66",
|
||||
"dcmjs": "^0.29.12",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -44,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.27.3",
|
||||
"@cornerstonejs/core": "^1.27.3",
|
||||
"@cornerstonejs/tools": "^1.27.3",
|
||||
"@cornerstonejs/adapters": "^1.67.0",
|
||||
"@cornerstonejs/core": "^1.67.0",
|
||||
"@cornerstonejs/tools": "^1.67.0",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,9 @@ const _generateReport = (measurementData, additionalFindingTypes, options = {})
|
||||
return dataset;
|
||||
};
|
||||
|
||||
const commandsModule = ({}) => {
|
||||
const commandsModule = props => {
|
||||
const { servicesManager } = props;
|
||||
const { customizationService } = servicesManager.services;
|
||||
const actions = {
|
||||
/**
|
||||
*
|
||||
@@ -95,7 +97,15 @@ const commandsModule = ({}) => {
|
||||
throw new Error('Invalid report, no content');
|
||||
}
|
||||
|
||||
await dataSource.store.dicom(naturalizedReport);
|
||||
const onBeforeDicomStore =
|
||||
customizationService.getModeCustomization('onBeforeDicomStore')?.value;
|
||||
|
||||
let dicomDict;
|
||||
if (typeof onBeforeDicomStore === 'function') {
|
||||
dicomDict = onBeforeDicomStore({ measurementData, naturalizedReport });
|
||||
}
|
||||
|
||||
await dataSource.store.dicom(naturalizedReport, null, dicomDict);
|
||||
|
||||
if (StudyInstanceUID) {
|
||||
dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
|
||||
@@ -118,13 +128,9 @@ const commandsModule = ({}) => {
|
||||
const definitions = {
|
||||
downloadReport: {
|
||||
commandFn: actions.downloadReport,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
storeMeasurements: {
|
||||
commandFn: actions.storeMeasurements,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -171,7 +171,12 @@ function _load(displaySet, servicesManager, extensionManager) {
|
||||
|
||||
// Check currently added displaySets and add measurements if the sources exist.
|
||||
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(displaySet, activeDisplaySet, dataSource);
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
activeDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
);
|
||||
});
|
||||
|
||||
// Subscribe to new displaySets as the source may come in after.
|
||||
@@ -180,12 +185,23 @@ function _load(displaySet, servicesManager, extensionManager) {
|
||||
// If there are still some measurements that have not yet been loaded into cornerstone,
|
||||
// See if we can load them onto any of the new displaySets.
|
||||
displaySetsAdded.forEach(newDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(displaySet, newDisplaySet, dataSource);
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
newDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource) {
|
||||
function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
srDisplaySet,
|
||||
newDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
) {
|
||||
const { customizationService } = servicesManager.services;
|
||||
let unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||
measurement => measurement.loaded === false
|
||||
);
|
||||
@@ -244,7 +260,20 @@ function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dat
|
||||
|
||||
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||
const measurement = unloadedMeasurements[j];
|
||||
let measurement = unloadedMeasurements[j];
|
||||
|
||||
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
|
||||
'onBeforeSRAddMeasurement'
|
||||
)?.value;
|
||||
|
||||
if (typeof onBeforeSRAddMeasurement === 'function') {
|
||||
measurement = onBeforeSRAddMeasurement({
|
||||
measurement,
|
||||
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
||||
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
|
||||
});
|
||||
}
|
||||
|
||||
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
||||
addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID);
|
||||
|
||||
|
||||
@@ -339,7 +339,9 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
const textLines = this._getTextBoxLinesFromLabels(label);
|
||||
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
||||
|
||||
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
||||
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
||||
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
||||
}
|
||||
|
||||
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function addMeasurement(measurement, imageId, displaySetInstanceU
|
||||
data: {
|
||||
label: measurement.labels,
|
||||
handles: {
|
||||
textBox: {},
|
||||
textBox: measurement.textBox ?? {},
|
||||
},
|
||||
cachedStats: {
|
||||
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
||||
|
||||
@@ -90,7 +90,7 @@ export default function hydrateStructuredReport(
|
||||
const datasetToUse = _mapLegacyDataSet(instance);
|
||||
|
||||
// Use dcmjs to generate toolState.
|
||||
const storedMeasurementByAnnotationType = MeasurementReport.generateToolState(
|
||||
let storedMeasurementByAnnotationType = MeasurementReport.generateToolState(
|
||||
datasetToUse,
|
||||
// NOTE: we need to pass in the imageIds to dcmjs since the we use them
|
||||
// for the imageToWorld transformation. The following assumes that the order
|
||||
@@ -101,6 +101,16 @@ export default function hydrateStructuredReport(
|
||||
metaData
|
||||
);
|
||||
|
||||
const onBeforeSRHydration =
|
||||
customizationService.getModeCustomization('onBeforeSRHydration')?.value;
|
||||
|
||||
if (typeof onBeforeSRHydration === 'function') {
|
||||
storedMeasurementByAnnotationType = onBeforeSRHydration({
|
||||
storedMeasurementByAnnotationType,
|
||||
displaySet,
|
||||
});
|
||||
}
|
||||
|
||||
// Filter what is found by DICOM SR to measurements we support.
|
||||
const mappingDefinitions = mappings.map(m => m.annotationType);
|
||||
const hydratableMeasurementsInSR = {};
|
||||
|
||||
@@ -3,6 +3,411 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **layout:** new layout selector with 3D volume rendering ([#3923](https://github.com/OHIF/Viewers/issues/3923)) ([617043f](https://github.com/OHIF/Viewers/commit/617043fe0da5de91fbea4ac33a27f1df16ae1ca6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SR display:** and the token based navigation ([#3995](https://github.com/OHIF/Viewers/issues/3995)) ([feed230](https://github.com/OHIF/Viewers/commit/feed2304c124dc2facc7a7371ed9851548c223c5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **viewport-sync:** remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync ([#3849](https://github.com/OHIF/Viewers/issues/3849)) ([e4a116b](https://github.com/OHIF/Viewers/commit/e4a116b074fcb85c8cbcc9db44fdec565f3386db))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* colormap for stack viewports via HangingProtocol ([#3866](https://github.com/OHIF/Viewers/issues/3866)) ([e8858f3](https://github.com/OHIF/Viewers/commit/e8858f3eb55552f695af4a55980f9ae2e9af7291))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **overlay:** add inline binary overlays ([#3852](https://github.com/OHIF/Viewers/issues/3852)) ([0177b62](https://github.com/OHIF/Viewers/commit/0177b625ba86760168bc4db58c8a109aa9ee83cb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **HP:** Added new 3D hanging protocols to be used in the new layout selector ([#3844](https://github.com/OHIF/Viewers/issues/3844)) ([59576d6](https://github.com/OHIF/Viewers/commit/59576d695d4d26601d35c43f73d602f0b12d72bf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "3.8.0-beta.21",
|
||||
"version": "3.8.0-beta.66",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -25,6 +25,8 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||
"dev:cornerstone": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress --config .webpack/webpack.prod.js",
|
||||
@@ -36,9 +38,10 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.2",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.27.3",
|
||||
"@ohif/core": "3.8.0-beta.21",
|
||||
"@ohif/ui": "3.8.0-beta.21",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.67.0",
|
||||
"@icr/polyseg-wasm": "^0.4.0",
|
||||
"@ohif/core": "3.8.0-beta.66",
|
||||
"@ohif/ui": "3.8.0-beta.66",
|
||||
"dcmjs": "^0.29.12",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -52,11 +55,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.27.3",
|
||||
"@cornerstonejs/core": "^1.27.3",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^1.27.3",
|
||||
"@cornerstonejs/tools": "^1.27.3",
|
||||
"@kitware/vtk.js": "27.3.1",
|
||||
"@cornerstonejs/adapters": "^1.67.0",
|
||||
"@cornerstonejs/core": "^1.67.0",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^1.67.0",
|
||||
"@cornerstonejs/tools": "^1.67.0",
|
||||
"@kitware/vtk.js": "29.7.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.merge": "^4.6.2",
|
||||
|
||||
@@ -21,6 +21,7 @@ import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/
|
||||
import CornerstoneServices from '../types/CornerstoneServices';
|
||||
import CinePlayer from '../components/CinePlayer';
|
||||
import { Types } from '@ohif/core';
|
||||
import { LutPresentation, PositionPresentation } from '../types/Presentation';
|
||||
|
||||
const STACK = 'stack';
|
||||
|
||||
@@ -101,7 +102,6 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
||||
viewportOptions,
|
||||
displaySetOptions,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
onElementEnabled,
|
||||
onElementDisabled,
|
||||
isJumpToMeasurementDisabled,
|
||||
@@ -109,6 +109,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
||||
// of the imageData in the OHIFCornerstoneViewport. This prop is used
|
||||
// to set the initial state of the viewport's first image to render
|
||||
initialImageIndex,
|
||||
onReady,
|
||||
} = props;
|
||||
|
||||
const viewportId = viewportOptions.viewportId;
|
||||
@@ -175,8 +176,16 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
||||
|
||||
syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups);
|
||||
|
||||
const synchronizersStore = stateSyncService.getState().synchronizersStore;
|
||||
|
||||
if (synchronizersStore?.[viewportId]?.length) {
|
||||
// If the viewport used to have a synchronizer, re apply it again
|
||||
_rehydrateSynchronizers(synchronizersStore, viewportId, syncGroupService);
|
||||
}
|
||||
|
||||
if (onElementEnabled) {
|
||||
onElementEnabled(evt);
|
||||
onReady?.(evt);
|
||||
}
|
||||
},
|
||||
[viewportId, onElementEnabled, toolGroupService]
|
||||
@@ -197,13 +206,18 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
||||
return;
|
||||
}
|
||||
|
||||
cleanUpServices(viewportInfo);
|
||||
cornerstoneViewportService.storePresentation({ viewportId });
|
||||
|
||||
// This should be done after the store presentation since synchronizers
|
||||
// will get cleaned up and they need the viewportInfo to be present
|
||||
cleanUpServices(viewportInfo);
|
||||
|
||||
if (onElementDisabled) {
|
||||
onElementDisabled(viewportInfo);
|
||||
}
|
||||
|
||||
cornerstoneViewportService.disableElement(viewportId);
|
||||
|
||||
eventTarget.removeEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
|
||||
};
|
||||
}, []);
|
||||
@@ -265,7 +279,10 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
||||
// The presentation state will have been stored previously by closing
|
||||
// a viewport. Otherwise, this viewport will be unchanged and the
|
||||
// presentation information will be directly carried over.
|
||||
const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState();
|
||||
const state = stateSyncService.getState();
|
||||
const lutPresentationStore = state.lutPresentationStore as LutPresentation;
|
||||
const positionPresentationStore = state.positionPresentationStore as PositionPresentation;
|
||||
|
||||
const { presentationIds } = viewportOptions;
|
||||
const presentations = {
|
||||
positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId],
|
||||
@@ -348,8 +365,6 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
||||
<React.Fragment>
|
||||
<div className="viewport-wrapper">
|
||||
<ReactResizeDetector
|
||||
refreshMode="debounce"
|
||||
refreshRate={50} // Wait 50 ms after last move to render
|
||||
onResize={onResize}
|
||||
targetRef={elementRef.current}
|
||||
/>
|
||||
@@ -547,6 +562,58 @@ function _jumpToMeasurement(
|
||||
}
|
||||
}
|
||||
|
||||
function _rehydrateSynchronizers(
|
||||
synchronizersStore: { [key: string]: unknown },
|
||||
viewportId: string,
|
||||
syncGroupService: any
|
||||
) {
|
||||
synchronizersStore[viewportId].forEach(synchronizerObj => {
|
||||
if (!synchronizerObj.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { id, sourceViewports, targetViewports } = synchronizerObj;
|
||||
|
||||
const synchronizer = syncGroupService.getSynchronizer(id);
|
||||
|
||||
if (!synchronizer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceViewportInfo = sourceViewports.find(
|
||||
sourceViewport => sourceViewport.viewportId === viewportId
|
||||
);
|
||||
|
||||
const targetViewportInfo = targetViewports.find(
|
||||
targetViewport => targetViewport.viewportId === viewportId
|
||||
);
|
||||
|
||||
const isSourceViewportInSynchronizer = synchronizer
|
||||
.getSourceViewports()
|
||||
.find(sourceViewport => sourceViewport.viewportId === viewportId);
|
||||
|
||||
const isTargetViewportInSynchronizer = synchronizer
|
||||
.getTargetViewports()
|
||||
.find(targetViewport => targetViewport.viewportId === viewportId);
|
||||
|
||||
// if the viewport was previously a source viewport, add it again
|
||||
if (sourceViewportInfo && !isSourceViewportInSynchronizer) {
|
||||
synchronizer.addSource({
|
||||
viewportId: sourceViewportInfo.viewportId,
|
||||
renderingEngineId: sourceViewportInfo.renderingEngineId,
|
||||
});
|
||||
}
|
||||
|
||||
// if the viewport was previously a target viewport, add it again
|
||||
if (targetViewportInfo && !isTargetViewportInSynchronizer) {
|
||||
synchronizer.addTarget({
|
||||
viewportId: targetViewportInfo.viewportId,
|
||||
renderingEngineId: targetViewportInfo.renderingEngineId,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Component displayName
|
||||
OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';
|
||||
|
||||
|
||||
@@ -2,21 +2,25 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
import PropTypes from 'prop-types';
|
||||
import { metaData, Enums, utilities } from '@cornerstonejs/core';
|
||||
import { ViewportOverlay } from '@ohif/ui';
|
||||
import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||
import { InstanceMetadata } from 'platform/core/src/types';
|
||||
import { ServicesManager } from '@ohif/core';
|
||||
import { ImageSliceData } from '@cornerstonejs/core/dist/esm/types';
|
||||
import { ViewportOverlay } from '@ohif/ui';
|
||||
import { ServicesManager } from '@ohif/core';
|
||||
import { InstanceMetadata } from '@ohif/core/src/types';
|
||||
import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
|
||||
import './CustomizableViewportOverlay.css';
|
||||
|
||||
const EPSILON = 1e-4;
|
||||
|
||||
type ViewportData = StackViewportData | VolumeViewportData;
|
||||
|
||||
interface OverlayItemProps {
|
||||
element: any;
|
||||
viewportData: any;
|
||||
element: HTMLElement;
|
||||
viewportData: ViewportData;
|
||||
imageSliceData: ImageSliceData;
|
||||
servicesManager: ServicesManager;
|
||||
viewportId: string;
|
||||
instance: InstanceMetadata;
|
||||
customization: any;
|
||||
formatters: {
|
||||
@@ -35,67 +39,11 @@ interface OverlayItemProps {
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Window Level / Center Overlay item
|
||||
*/
|
||||
function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
|
||||
const { windowWidth, windowCenter } = voi;
|
||||
if (typeof windowCenter !== 'number' || typeof windowWidth !== 'number') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overlay-item flex flex-row"
|
||||
style={{ color: (customization && customization.color) || undefined }}
|
||||
>
|
||||
<span className="mr-1 shrink-0">W:</span>
|
||||
<span className="ml-1 mr-2 shrink-0 font-light">{windowWidth.toFixed(0)}</span>
|
||||
<span className="mr-1 shrink-0">L:</span>
|
||||
<span className="ml-1 shrink-0 font-light">{windowCenter.toFixed(0)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom Level Overlay item
|
||||
*/
|
||||
function ZoomOverlayItem({ scale, customization }: OverlayItemProps) {
|
||||
return (
|
||||
<div
|
||||
className="overlay-item flex flex-row"
|
||||
style={{ color: (customization && customization.color) || undefined }}
|
||||
>
|
||||
<span className="mr-1 shrink-0">Zoom:</span>
|
||||
<span className="font-light">{scale.toFixed(2)}x</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance Number Overlay Item
|
||||
*/
|
||||
function InstanceNumberOverlayItem({
|
||||
instanceNumber,
|
||||
imageSliceData,
|
||||
customization,
|
||||
}: OverlayItemProps) {
|
||||
const { imageIndex, numberOfSlices } = imageSliceData;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overlay-item flex flex-row"
|
||||
style={{ color: (customization && customization.color) || undefined }}
|
||||
>
|
||||
<span className="mr-1 shrink-0">I:</span>
|
||||
<span className="font-light">
|
||||
{instanceNumber !== undefined && instanceNumber !== null
|
||||
? `${instanceNumber} (${imageIndex + 1}/${numberOfSlices})`
|
||||
: `${imageIndex + 1}/${numberOfSlices}`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const OverlayItemComponents = {
|
||||
'ohif.overlayItem.windowLevel': VOIOverlayItem,
|
||||
'ohif.overlayItem.zoomLevel': ZoomOverlayItem,
|
||||
'ohif.overlayItem.instanceNumber': InstanceNumberOverlayItem,
|
||||
};
|
||||
|
||||
/**
|
||||
* Customizable Viewport Overlay
|
||||
@@ -106,12 +54,17 @@ function CustomizableViewportOverlay({
|
||||
imageSliceData,
|
||||
viewportId,
|
||||
servicesManager,
|
||||
}: {
|
||||
element: HTMLElement;
|
||||
viewportData: ViewportData;
|
||||
imageSliceData: ImageSliceData;
|
||||
viewportId: string;
|
||||
servicesManager: ServicesManager;
|
||||
}) {
|
||||
const { toolbarService, cornerstoneViewportService, customizationService } =
|
||||
const { cornerstoneViewportService, customizationService, toolGroupService } =
|
||||
servicesManager.services;
|
||||
const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null });
|
||||
const [scale, setScale] = useState(1);
|
||||
const [activeTools, setActiveTools] = useState([]);
|
||||
const { imageIndex } = imageSliceData;
|
||||
|
||||
const topLeftCustomization = customizationService.getModeCustomization(
|
||||
@@ -127,27 +80,18 @@ function CustomizableViewportOverlay({
|
||||
'cornerstoneOverlayBottomRight'
|
||||
);
|
||||
|
||||
const instance = useMemo(() => {
|
||||
if (viewportData != null) {
|
||||
return _getViewportInstance(viewportData, imageIndex);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [viewportData, imageIndex]);
|
||||
const instance = useMemo(
|
||||
() => (viewportData ? getViewportInstance(viewportData, imageIndex) : null),
|
||||
[viewportData, imageIndex]
|
||||
);
|
||||
|
||||
const instanceNumber = useMemo(() => {
|
||||
if (viewportData != null) {
|
||||
return _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService);
|
||||
}
|
||||
return null;
|
||||
}, [viewportData, viewportId, imageIndex, cornerstoneViewportService]);
|
||||
|
||||
/**
|
||||
* Initial toolbar state
|
||||
*/
|
||||
useEffect(() => {
|
||||
setActiveTools(toolbarService.getActiveTools());
|
||||
}, []);
|
||||
const instanceNumber = useMemo(
|
||||
() =>
|
||||
viewportData
|
||||
? getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService)
|
||||
: null,
|
||||
[viewportData, viewportId, imageIndex, cornerstoneViewportService]
|
||||
);
|
||||
|
||||
/**
|
||||
* Updating the VOI when the viewport changes its voi
|
||||
@@ -215,26 +159,9 @@ function CustomizableViewportOverlay({
|
||||
};
|
||||
}, [viewportId, viewportData, cornerstoneViewportService, element]);
|
||||
|
||||
/**
|
||||
* Updating the active tools when the toolbar changes
|
||||
*/
|
||||
// Todo: this should act on the toolGroups instead of the toolbar state
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = toolbarService.subscribe(
|
||||
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
|
||||
() => {
|
||||
setActiveTools(toolbarService.getActiveTools());
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [toolbarService]);
|
||||
|
||||
const _renderOverlayItem = useCallback(
|
||||
item => {
|
||||
const overlayItemProps: OverlayItemProps = {
|
||||
const overlayItemProps = {
|
||||
element,
|
||||
viewportData,
|
||||
imageSliceData,
|
||||
@@ -242,24 +169,26 @@ function CustomizableViewportOverlay({
|
||||
servicesManager,
|
||||
customization: item,
|
||||
formatters: {
|
||||
formatPN: formatPN,
|
||||
formatPN,
|
||||
formatDate: formatDICOMDate,
|
||||
formatTime: formatDICOMTime,
|
||||
formatNumberPrecision: formatNumberPrecision,
|
||||
formatNumberPrecision,
|
||||
},
|
||||
instance,
|
||||
// calculated
|
||||
voi,
|
||||
scale,
|
||||
instanceNumber,
|
||||
};
|
||||
|
||||
if (item.customizationType === 'ohif.overlayItem.windowLevel') {
|
||||
return <VOIOverlayItem {...overlayItemProps} />;
|
||||
} else if (item.customizationType === 'ohif.overlayItem.zoomLevel') {
|
||||
return <ZoomOverlayItem {...overlayItemProps} />;
|
||||
} else if (item.customizationType === 'ohif.overlayItem.instanceNumber') {
|
||||
return <InstanceNumberOverlayItem {...overlayItemProps} />;
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { customizationType } = item;
|
||||
const OverlayItemComponent = OverlayItemComponents[customizationType];
|
||||
|
||||
if (OverlayItemComponent) {
|
||||
return <OverlayItemComponent {...overlayItemProps} />;
|
||||
} else {
|
||||
const renderItem = customizationService.transform(item);
|
||||
|
||||
@@ -282,103 +211,106 @@ function CustomizableViewportOverlay({
|
||||
]
|
||||
);
|
||||
|
||||
const getTopLeftContent = useCallback(() => {
|
||||
const items = topLeftCustomization?.items || [
|
||||
{
|
||||
id: 'WindowLevel',
|
||||
customizationType: 'ohif.overlayItem.windowLevel',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{items.map((item, i) => (
|
||||
<div key={`topLeftOverlayItem_${i}`}>{_renderOverlayItem(item)}</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}, [topLeftCustomization, _renderOverlayItem]);
|
||||
const getContent = useCallback(
|
||||
(customization, defaultItems, keyPrefix) => {
|
||||
const items = customization?.items ?? defaultItems;
|
||||
|
||||
const getTopRightContent = useCallback(() => {
|
||||
const items = topRightCustomization?.items || [
|
||||
{
|
||||
id: 'InstanceNmber',
|
||||
customizationType: 'ohif.overlayItem.instanceNumber',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{items.map((item, i) => (
|
||||
<div key={`topRightOverlayItem_${i}`}>{_renderOverlayItem(item)}</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}, [topRightCustomization, _renderOverlayItem]);
|
||||
|
||||
const getBottomLeftContent = useCallback(() => {
|
||||
const items = bottomLeftCustomization?.items || [];
|
||||
return (
|
||||
<>
|
||||
{items.map((item, i) => (
|
||||
<div key={`bottomLeftOverlayItem_${i}`}>{_renderOverlayItem(item)}</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}, [bottomLeftCustomization, _renderOverlayItem]);
|
||||
|
||||
const getBottomRightContent = useCallback(() => {
|
||||
const items = bottomRightCustomization?.items || [];
|
||||
return (
|
||||
<>
|
||||
{items.map((item, i) => (
|
||||
<div key={`bottomRightOverlayItem_${i}`}>{_renderOverlayItem(item)}</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}, [bottomRightCustomization, _renderOverlayItem]);
|
||||
return (
|
||||
<>
|
||||
{items.map((item, index) => (
|
||||
<div key={`${keyPrefix}_${index}`}>
|
||||
{item?.condition
|
||||
? item.condition()
|
||||
? _renderOverlayItem(item)
|
||||
: null
|
||||
: _renderOverlayItem(item)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
},
|
||||
[_renderOverlayItem]
|
||||
);
|
||||
|
||||
return (
|
||||
<ViewportOverlay
|
||||
topLeft={getTopLeftContent()}
|
||||
topRight={getTopRightContent()}
|
||||
bottomLeft={getBottomLeftContent()}
|
||||
bottomRight={getBottomRightContent()}
|
||||
topLeft={
|
||||
/**
|
||||
* Inline default overlay items for a more standard expansion
|
||||
*/
|
||||
getContent(
|
||||
topLeftCustomization,
|
||||
[
|
||||
{
|
||||
id: 'WindowLevel',
|
||||
customizationType: 'ohif.overlayItem.windowLevel',
|
||||
},
|
||||
{
|
||||
id: 'ZoomLevel',
|
||||
customizationType: 'ohif.overlayItem.zoomLevel',
|
||||
condition: () => {
|
||||
const activeToolName = toolGroupService.getActiveToolForViewport(viewportId);
|
||||
return activeToolName === 'Zoom';
|
||||
},
|
||||
},
|
||||
],
|
||||
'topLeftOverlayItem'
|
||||
)
|
||||
}
|
||||
topRight={getContent(
|
||||
topRightCustomization,
|
||||
[
|
||||
{
|
||||
id: 'InstanceNumber',
|
||||
customizationType: 'ohif.overlayItem.instanceNumber',
|
||||
},
|
||||
],
|
||||
'topRightOverlayItem'
|
||||
)}
|
||||
bottomLeft={getContent(bottomLeftCustomization, [null], 'bottomLeftOverlayItem')}
|
||||
bottomRight={getContent(bottomRightCustomization, [null], 'bottomRightOverlayItem')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function _getViewportInstance(viewportData, imageIndex) {
|
||||
const getViewportInstance = (viewportData, imageIndex) => {
|
||||
const { viewportType, data } = viewportData;
|
||||
let imageId = null;
|
||||
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
||||
imageId = viewportData.data.imageIds[imageIndex];
|
||||
} else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
const volumes = viewportData.data;
|
||||
if (volumes && volumes.length == 1) {
|
||||
const volume = volumes[0];
|
||||
imageId = volume.imageIds[imageIndex];
|
||||
}
|
||||
}
|
||||
return imageId ? metaData.get('instance', imageId) || {} : {};
|
||||
}
|
||||
|
||||
function _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService) {
|
||||
switch (viewportType) {
|
||||
case Enums.ViewportType.STACK:
|
||||
imageId = data.imageIds[imageIndex];
|
||||
break;
|
||||
case Enums.ViewportType.ORTHOGRAPHIC:
|
||||
if (data?.length === 1) {
|
||||
imageId = data[0].imageIds[imageIndex];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return imageId ? metaData.get('instance', imageId) || {} : {};
|
||||
};
|
||||
|
||||
const getInstanceNumber = (viewportData, viewportId, imageIndex, cornerstoneViewportService) => {
|
||||
let instanceNumber;
|
||||
|
||||
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
||||
instanceNumber = _getInstanceNumberFromStack(viewportData, imageIndex);
|
||||
|
||||
if (!instanceNumber && instanceNumber !== 0) {
|
||||
return null;
|
||||
}
|
||||
} else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
instanceNumber = _getInstanceNumberFromVolume(
|
||||
viewportData,
|
||||
imageIndex,
|
||||
viewportId,
|
||||
cornerstoneViewportService
|
||||
);
|
||||
switch (viewportData.viewportType) {
|
||||
case Enums.ViewportType.STACK:
|
||||
instanceNumber = _getInstanceNumberFromStack(viewportData, imageIndex);
|
||||
break;
|
||||
case Enums.ViewportType.ORTHOGRAPHIC:
|
||||
instanceNumber = _getInstanceNumberFromVolume(
|
||||
viewportData,
|
||||
viewportId,
|
||||
cornerstoneViewportService
|
||||
);
|
||||
break;
|
||||
}
|
||||
return instanceNumber;
|
||||
}
|
||||
|
||||
return instanceNumber ?? null;
|
||||
};
|
||||
|
||||
function _getInstanceNumberFromStack(viewportData, imageIndex) {
|
||||
const imageIds = viewportData.data.imageIds;
|
||||
@@ -442,6 +374,68 @@ function _getInstanceNumberFromVolume(viewportData, viewportId, cornerstoneViewp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Window Level / Center Overlay item
|
||||
*/
|
||||
function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
|
||||
const { windowWidth, windowCenter } = voi;
|
||||
if (typeof windowCenter !== 'number' || typeof windowWidth !== 'number') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overlay-item flex flex-row"
|
||||
style={{ color: (customization && customization.color) || undefined }}
|
||||
>
|
||||
<span className="mr-1 shrink-0">W:</span>
|
||||
<span className="ml-1 mr-2 shrink-0 font-light">{windowWidth.toFixed(0)}</span>
|
||||
<span className="mr-1 shrink-0">L:</span>
|
||||
<span className="ml-1 shrink-0 font-light">{windowCenter.toFixed(0)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom Level Overlay item
|
||||
*/
|
||||
function ZoomOverlayItem({ scale, customization }: OverlayItemProps) {
|
||||
return (
|
||||
<div
|
||||
className="overlay-item flex flex-row"
|
||||
style={{ color: (customization && customization.color) || undefined }}
|
||||
>
|
||||
<span className="mr-1 shrink-0">Zoom:</span>
|
||||
<span className="font-light">{scale.toFixed(2)}x</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance Number Overlay Item
|
||||
*/
|
||||
function InstanceNumberOverlayItem({
|
||||
instanceNumber,
|
||||
imageSliceData,
|
||||
customization,
|
||||
}: OverlayItemProps) {
|
||||
const { imageIndex, numberOfSlices } = imageSliceData;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overlay-item flex flex-row"
|
||||
style={{ color: (customization && customization.color) || undefined }}
|
||||
>
|
||||
<span className="mr-1 shrink-0">I:</span>
|
||||
<span className="font-light">
|
||||
{instanceNumber !== undefined && instanceNumber !== null
|
||||
? `${instanceNumber} (${imageIndex + 1}/${numberOfSlices})`
|
||||
: `${imageIndex + 1}/${numberOfSlices}`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CustomizableViewportOverlay.propTypes = {
|
||||
viewportData: PropTypes.object,
|
||||
imageIndex: PropTypes.number,
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
import PropTypes from 'prop-types';
|
||||
import { metaData, Enums, utilities } from '@cornerstonejs/core';
|
||||
import { ViewportOverlay } from '@ohif/ui';
|
||||
import { ServicesManager } from '@ohif/core';
|
||||
|
||||
const EPSILON = 1e-4;
|
||||
|
||||
function CornerstoneViewportOverlay({
|
||||
element,
|
||||
viewportData,
|
||||
imageSliceData,
|
||||
viewportId,
|
||||
servicesManager,
|
||||
}) {
|
||||
const { cornerstoneViewportService, toolbarService } = servicesManager.services;
|
||||
const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null });
|
||||
const [scale, setScale] = useState(1);
|
||||
const [activeTools, setActiveTools] = useState([]);
|
||||
|
||||
/**
|
||||
* Initial toolbar state
|
||||
*/
|
||||
useEffect(() => {
|
||||
setActiveTools(toolbarService.getActiveTools());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
const { unsubscribe } = toolbarService.subscribe(
|
||||
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
|
||||
() => {
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTools(toolbarService.getActiveTools());
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Updating the VOI when the viewport changes its voi
|
||||
*/
|
||||
useEffect(() => {
|
||||
const updateVOI = eventDetail => {
|
||||
const { range } = eventDetail.detail;
|
||||
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lower, upper } = range;
|
||||
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper);
|
||||
|
||||
setVOI({ windowCenter, windowWidth });
|
||||
};
|
||||
|
||||
element.addEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
||||
};
|
||||
}, [viewportId, viewportData, voi, element]);
|
||||
|
||||
/**
|
||||
* Updating the scale when the viewport changes its zoom
|
||||
*/
|
||||
useEffect(() => {
|
||||
const updateScale = eventDetail => {
|
||||
const { previousCamera, camera } = eventDetail.detail;
|
||||
|
||||
if (
|
||||
previousCamera.parallelScale !== camera.parallelScale ||
|
||||
previousCamera.scale !== camera.scale
|
||||
) {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageData = viewport.getImageData();
|
||||
|
||||
if (!imageData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (camera.scale) {
|
||||
setScale(camera.scale);
|
||||
return;
|
||||
}
|
||||
|
||||
const { spacing } = imageData;
|
||||
// convert parallel scale to scale
|
||||
const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale;
|
||||
setScale(scale);
|
||||
}
|
||||
};
|
||||
|
||||
element.addEventListener(Enums.Events.CAMERA_MODIFIED, updateScale);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale);
|
||||
};
|
||||
}, [viewportId, viewportData]);
|
||||
|
||||
const getTopLeftContent = useCallback(() => {
|
||||
const { windowWidth, windowCenter } = voi;
|
||||
|
||||
if (activeTools.includes('WindowLevel')) {
|
||||
if (typeof windowCenter !== 'number' || typeof windowWidth !== 'number') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-row text-base">
|
||||
<span className="mr-1">W:</span>
|
||||
<span className="ml-1 mr-2 font-light">{windowWidth.toFixed(0)}</span>
|
||||
<span className="mr-1">L:</span>
|
||||
<span className="ml-1 font-light">{windowCenter.toFixed(0)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (activeTools.includes('Zoom')) {
|
||||
return (
|
||||
<div className="flex flex-row text-base">
|
||||
<span className="mr-1">Zoom:</span>
|
||||
<span className="font-light">{scale.toFixed(2)}x</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [voi, scale, activeTools]);
|
||||
|
||||
const getTopRightContent = useCallback(() => {
|
||||
const { imageIndex, numberOfSlices } = imageSliceData;
|
||||
if (!viewportData) {
|
||||
return;
|
||||
}
|
||||
|
||||
let instanceNumber;
|
||||
|
||||
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
||||
instanceNumber = _getInstanceNumberFromStack(viewportData, imageIndex);
|
||||
|
||||
if (!instanceNumber) {
|
||||
return null;
|
||||
}
|
||||
} else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
instanceNumber = _getInstanceNumberFromVolume(
|
||||
viewportData,
|
||||
imageIndex,
|
||||
viewportId,
|
||||
cornerstoneViewportService
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-row text-base">
|
||||
<span className="mr-1">I:</span>
|
||||
<span className="font-light">
|
||||
{instanceNumber !== undefined
|
||||
? `${instanceNumber} (${imageIndex + 1}/${numberOfSlices})`
|
||||
: `${imageIndex + 1}/${numberOfSlices}`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}, [imageSliceData, viewportData, viewportId]);
|
||||
|
||||
if (!viewportData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||
|
||||
if (!ohifViewport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const backgroundColor = ohifViewport.getViewportOptions().background;
|
||||
|
||||
// Todo: probably this can be done in a better way in which we identify bright
|
||||
// background
|
||||
const isLight = backgroundColor ? utilities.isEqual(backgroundColor, [1, 1, 1]) : false;
|
||||
|
||||
return (
|
||||
<ViewportOverlay
|
||||
topLeft={getTopLeftContent()}
|
||||
topRight={getTopRightContent()}
|
||||
color={isLight && 'text-[#0944B3]'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function _getInstanceNumberFromStack(viewportData, imageIndex) {
|
||||
const imageIds = viewportData.data.imageIds;
|
||||
const imageId = imageIds[imageIndex];
|
||||
|
||||
if (!imageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const generalImageModule = metaData.get('generalImageModule', imageId) || {};
|
||||
const { instanceNumber } = generalImageModule;
|
||||
|
||||
const stackSize = imageIds.length;
|
||||
|
||||
if (stackSize <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
return parseInt(instanceNumber);
|
||||
}
|
||||
|
||||
// Since volume viewports can be in any view direction, they can render
|
||||
// a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber
|
||||
// Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber
|
||||
function _getInstanceNumberFromVolume(
|
||||
viewportData,
|
||||
imageIndex,
|
||||
viewportId,
|
||||
cornerstoneViewportService
|
||||
) {
|
||||
const volumes = viewportData.volumes;
|
||||
|
||||
// Todo: support fusion of acquisition plane which has instanceNumber
|
||||
if (!volumes || volumes.length > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const volume = volumes[0];
|
||||
const { direction, imageIds } = volume;
|
||||
|
||||
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!cornerstoneViewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
const camera = cornerstoneViewport.getCamera();
|
||||
const { viewPlaneNormal } = camera;
|
||||
// checking if camera is looking at the acquisition plane (defined by the direction on the volume)
|
||||
|
||||
const scanAxisNormal = direction.slice(6, 9);
|
||||
|
||||
// check if viewPlaneNormal is parallel to scanAxisNormal
|
||||
const cross = vec3.cross(vec3.create(), viewPlaneNormal, scanAxisNormal);
|
||||
const isAcquisitionPlane = vec3.length(cross) < EPSILON;
|
||||
|
||||
if (isAcquisitionPlane) {
|
||||
const imageId = imageIds[imageIndex];
|
||||
|
||||
if (!imageId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { instanceNumber } = metaData.get('generalImageModule', imageId) || {};
|
||||
return parseInt(instanceNumber);
|
||||
}
|
||||
}
|
||||
|
||||
CornerstoneViewportOverlay.propTypes = {
|
||||
viewportData: PropTypes.object,
|
||||
imageIndex: PropTypes.number,
|
||||
viewportId: PropTypes.string,
|
||||
servicesManager: PropTypes.instanceOf(ServicesManager),
|
||||
};
|
||||
|
||||
export default CornerstoneViewportOverlay;
|
||||
@@ -17,10 +17,16 @@ import { vec3, mat4 } from 'gl-matrix';
|
||||
|
||||
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
||||
import callInputDialog from './utils/callInputDialog';
|
||||
import toggleStackImageSync from './utils/stackSync/toggleStackImageSync';
|
||||
import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync';
|
||||
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
|
||||
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
||||
import { CornerstoneServices } from './types';
|
||||
import toggleVOISliceSync from './utils/toggleVOISliceSync';
|
||||
|
||||
const toggleSyncFunctions = {
|
||||
imageSlice: toggleImageSliceSync,
|
||||
voi: toggleVOISliceSync,
|
||||
};
|
||||
|
||||
function commandsModule({
|
||||
servicesManager,
|
||||
@@ -30,11 +36,11 @@ function commandsModule({
|
||||
viewportGridService,
|
||||
toolGroupService,
|
||||
cineService,
|
||||
toolbarService,
|
||||
uiDialogService,
|
||||
cornerstoneViewportService,
|
||||
uiNotificationService,
|
||||
measurementService,
|
||||
syncGroupService,
|
||||
} = servicesManager.services as CornerstoneServices;
|
||||
|
||||
const { measurementServiceSource } = this;
|
||||
@@ -227,30 +233,10 @@ function commandsModule({
|
||||
arrowTextCallback: ({ callback, data }) => {
|
||||
callInputDialog(uiDialogService, data, callback);
|
||||
},
|
||||
cleanUpCrosshairs: () => {
|
||||
// if the crosshairs tool is active, deactivate it and set window level active
|
||||
// since we are going back to main non-mpr HP
|
||||
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
|
||||
|
||||
if (activeViewportToolGroup._toolInstances?.Crosshairs?.mode === Enums.ToolModes.Active) {
|
||||
actions.toolbarServiceRecordInteraction({
|
||||
interactionType: 'tool',
|
||||
commands: [
|
||||
{
|
||||
commandOptions: {
|
||||
toolName: 'WindowLevel',
|
||||
},
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
toggleCine: () => {
|
||||
const { viewports } = viewportGridService.getState();
|
||||
const { isCineEnabled } = cineService.getState();
|
||||
cineService.setIsCineEnabled(!isCineEnabled);
|
||||
toolbarService.setButton('Cine', { props: { isActive: !isCineEnabled } });
|
||||
viewports.forEach((_, index) => cineService.setCine({ id: index, isPlaying: false }));
|
||||
},
|
||||
setWindowLevel({ window, level, toolGroupId }) {
|
||||
@@ -279,35 +265,40 @@ function commandsModule({
|
||||
});
|
||||
viewport.render();
|
||||
},
|
||||
setToolEnabled: ({ toolName, toggle }) => {
|
||||
const { viewports } = viewportGridService.getState();
|
||||
|
||||
// Just call the toolbar service record interaction - allows
|
||||
// executing a toolbar command as a full toolbar command with side affects
|
||||
// coming from the ToolbarService itself.
|
||||
toolbarServiceRecordInteraction: props => {
|
||||
toolbarService.recordInteraction(props);
|
||||
},
|
||||
// Enable or disable a toggleable command, without calling the activation
|
||||
// Used to setup already active tools from hanging protocols
|
||||
setToolbarToggled: props => {
|
||||
toolbarService.setToggled(props.toolId, props.isActive ?? true);
|
||||
},
|
||||
setToolActive: ({ toolName, toolGroupId = null, toggledState }) => {
|
||||
if (toolName === 'Crosshairs') {
|
||||
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
|
||||
|
||||
if (!activeViewportToolGroup._toolInstances.Crosshairs) {
|
||||
uiNotificationService.show({
|
||||
title: 'Crosshairs',
|
||||
message:
|
||||
'You need to be in a MPR view to use Crosshairs. Click on MPR button in the toolbar to activate it.',
|
||||
type: 'info',
|
||||
duration: 3000,
|
||||
});
|
||||
|
||||
throw new Error('Crosshairs tool is not available in this viewport');
|
||||
}
|
||||
if (!viewports.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroup(null);
|
||||
|
||||
if (!toolGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolIsEnabled = toolGroup.getToolOptions(toolName).mode === Enums.ToolModes.Enabled;
|
||||
|
||||
// Toggle the tool's state only if the toggle is true
|
||||
if (toggle) {
|
||||
toolIsEnabled ? toolGroup.setToolDisabled(toolName) : toolGroup.setToolEnabled(toolName);
|
||||
}
|
||||
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
renderingEngine.render();
|
||||
},
|
||||
setToolActiveToolbar: ({ value, itemId, toolGroupIds = [] }) => {
|
||||
// Sometimes it is passed as value (tools with options), sometimes as itemId (toolbar buttons)
|
||||
const toolName = itemId || value;
|
||||
|
||||
toolGroupIds = toolGroupIds.length ? toolGroupIds : toolGroupService.getToolGroupIds();
|
||||
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
actions.setToolActive({ toolName, toolGroupId });
|
||||
});
|
||||
},
|
||||
setToolActive: ({ toolName, toolGroupId = null }) => {
|
||||
const { viewports } = viewportGridService.getState();
|
||||
|
||||
if (!viewports.size) {
|
||||
@@ -320,34 +311,17 @@ function commandsModule({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!toolGroup.getToolInstance(toolName)) {
|
||||
uiNotificationService.show({
|
||||
title: `${toolName} tool`,
|
||||
message: `The ${toolName} tool is not available in this viewport.`,
|
||||
type: 'info',
|
||||
duration: 3000,
|
||||
});
|
||||
|
||||
throw new Error(`ToolGroup ${toolGroup.id} does not have this tool.`);
|
||||
if (!toolGroup.hasTool(toolName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeToolName = toolGroup.getActivePrimaryMouseButtonTool();
|
||||
|
||||
if (activeToolName) {
|
||||
// Todo: this is a hack to prevent the crosshairs to stick around
|
||||
// after another tool is selected. We should find a better way to do this
|
||||
if (activeToolName === 'Crosshairs') {
|
||||
toolGroup.setToolDisabled(activeToolName);
|
||||
} else {
|
||||
toolGroup.setToolPassive(activeToolName);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a toggle state, then simply set the enabled/disabled state without
|
||||
// setting the tool active.
|
||||
if (toggledState != null) {
|
||||
toggledState ? toolGroup.setToolEnabled(toolName) : toolGroup.setToolDisabled(toolName);
|
||||
return;
|
||||
const activeToolOptions = toolGroup.getToolConfiguration(activeToolName);
|
||||
activeToolOptions?.disableOnPassive
|
||||
? toolGroup.setToolDisabled(activeToolName)
|
||||
: toolGroup.setToolPassive(activeToolName);
|
||||
}
|
||||
|
||||
// Set the new toolName to be active
|
||||
@@ -565,33 +539,59 @@ function commandsModule({
|
||||
(currentIndex + direction + viewportIds.length) % viewportIds.length;
|
||||
viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string);
|
||||
},
|
||||
/**
|
||||
* If the syncId is given and a synchronizer with that ID already exists, it will
|
||||
* toggle it on/off for the provided viewports. If not, it will attempt to create
|
||||
* a new synchronizer using the given syncId and type for the specified viewports.
|
||||
* If no viewports are provided, you may notice some default behavior.
|
||||
* - 'voi' type, we will aim to synchronize all viewports with the same modality
|
||||
* -'imageSlice' type, we will aim to synchronize all viewports with the same orientation.
|
||||
*
|
||||
* @param options
|
||||
* @param options.viewports - The viewports to synchronize
|
||||
* @param options.syncId - The synchronization group ID
|
||||
* @param options.type - The type of synchronization to perform
|
||||
*/
|
||||
toggleSynchronizer: ({ type, viewports, syncId }) => {
|
||||
const synchronizer = syncGroupService.getSynchronizer(syncId);
|
||||
|
||||
toggleStackImageSync: ({ toggledState }) => {
|
||||
toggleStackImageSync({
|
||||
servicesManager,
|
||||
toggledState,
|
||||
});
|
||||
if (synchronizer) {
|
||||
synchronizer.isDisabled() ? synchronizer.setEnabled(true) : synchronizer.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fn = toggleSyncFunctions[type];
|
||||
|
||||
if (fn) {
|
||||
fn({
|
||||
servicesManager,
|
||||
viewports,
|
||||
syncId,
|
||||
});
|
||||
}
|
||||
},
|
||||
setSourceViewportForReferenceLinesTool: ({ toggledState, viewportId }) => {
|
||||
setSourceViewportForReferenceLinesTool: ({ viewportId }) => {
|
||||
if (!viewportId) {
|
||||
const { activeViewportId } = viewportGridService.getState();
|
||||
viewportId = activeViewportId;
|
||||
viewportId = activeViewportId ?? 'default';
|
||||
}
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
toolGroup.setToolConfiguration(
|
||||
toolGroup?.setToolConfiguration(
|
||||
ReferenceLinesTool.toolName,
|
||||
{
|
||||
sourceViewportId: viewportId,
|
||||
},
|
||||
true // overwrite
|
||||
);
|
||||
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
renderingEngine.render();
|
||||
},
|
||||
storePresentation: ({ viewportId }) => {
|
||||
cornerstoneViewportService.storePresentation({ viewportId });
|
||||
},
|
||||
|
||||
attachProtocolViewportDataListener: ({ protocol, stageIndex }) => {
|
||||
const EVENT = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED;
|
||||
const command = protocol.callbacks.onViewportDataInitialized;
|
||||
@@ -608,6 +608,26 @@ function commandsModule({
|
||||
}
|
||||
});
|
||||
},
|
||||
resetCrosshairs: ({ viewportId }) => {
|
||||
const crosshairInstances = [];
|
||||
|
||||
const getCrosshairInstances = toolGroupId => {
|
||||
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||
crosshairInstances.push(toolGroup.getToolInstance('Crosshairs'));
|
||||
};
|
||||
|
||||
if (!viewportId) {
|
||||
const toolGroupIds = toolGroupService.getToolGroupIds();
|
||||
toolGroupIds.forEach(getCrosshairInstances);
|
||||
} else {
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
getCrosshairInstances(toolGroup.id);
|
||||
}
|
||||
|
||||
crosshairInstances.forEach(ins => {
|
||||
ins.resetCrosshairs();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -615,7 +635,6 @@ function commandsModule({
|
||||
// context menu
|
||||
showCornerstoneContextMenu: {
|
||||
commandFn: actions.showCornerstoneContextMenu,
|
||||
storeContexts: [],
|
||||
options: {
|
||||
menuCustomizationId: 'measurementsContextMenu',
|
||||
commands: [
|
||||
@@ -631,8 +650,6 @@ function commandsModule({
|
||||
},
|
||||
getNearbyAnnotation: {
|
||||
commandFn: actions.getNearbyAnnotation,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
deleteMeasurement: {
|
||||
commandFn: actions.deleteMeasurement,
|
||||
@@ -643,16 +660,18 @@ function commandsModule({
|
||||
updateMeasurement: {
|
||||
commandFn: actions.updateMeasurement,
|
||||
},
|
||||
|
||||
setWindowLevel: {
|
||||
commandFn: actions.setWindowLevel,
|
||||
},
|
||||
toolbarServiceRecordInteraction: {
|
||||
commandFn: actions.toolbarServiceRecordInteraction,
|
||||
},
|
||||
setToolActive: {
|
||||
commandFn: actions.setToolActive,
|
||||
},
|
||||
setToolActiveToolbar: {
|
||||
commandFn: actions.setToolActiveToolbar,
|
||||
},
|
||||
setToolEnabled: {
|
||||
commandFn: actions.setToolEnabled,
|
||||
},
|
||||
rotateViewportCW: {
|
||||
commandFn: actions.rotateViewport,
|
||||
options: { rotation: 90 },
|
||||
@@ -726,24 +745,21 @@ function commandsModule({
|
||||
setViewportColormap: {
|
||||
commandFn: actions.setViewportColormap,
|
||||
},
|
||||
toggleStackImageSync: {
|
||||
commandFn: actions.toggleStackImageSync,
|
||||
},
|
||||
setSourceViewportForReferenceLinesTool: {
|
||||
commandFn: actions.setSourceViewportForReferenceLinesTool,
|
||||
},
|
||||
storePresentation: {
|
||||
commandFn: actions.storePresentation,
|
||||
},
|
||||
setToolbarToggled: {
|
||||
commandFn: actions.setToolbarToggled,
|
||||
},
|
||||
cleanUpCrosshairs: {
|
||||
commandFn: actions.cleanUpCrosshairs,
|
||||
},
|
||||
attachProtocolViewportDataListener: {
|
||||
commandFn: actions.attachProtocolViewportDataListener,
|
||||
},
|
||||
resetCrosshairs: {
|
||||
commandFn: actions.resetCrosshairs,
|
||||
},
|
||||
toggleSynchronizer: {
|
||||
commandFn: actions.toggleSynchronizer,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,38 +1,18 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { CinePlayer, useCine } from '@ohif/ui';
|
||||
import { Enums, eventTarget } from '@cornerstonejs/core';
|
||||
import { useAppConfig } from '@state';
|
||||
|
||||
function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
|
||||
const {
|
||||
toolbarService,
|
||||
customizationService,
|
||||
displaySetService,
|
||||
viewportGridService,
|
||||
cineService,
|
||||
} = servicesManager.services;
|
||||
const [{ isCineEnabled, cines }] = useCine();
|
||||
const { customizationService, displaySetService, viewportGridService } = servicesManager.services;
|
||||
const [{ isCineEnabled, cines }, api] = useCine();
|
||||
const [newStackFrameRate, setNewStackFrameRate] = useState(24);
|
||||
const [appConfig] = useAppConfig();
|
||||
const isMountedRef = useRef(null);
|
||||
|
||||
const { component: CinePlayerComponent = CinePlayer } =
|
||||
customizationService.get('cinePlayer') ?? {};
|
||||
|
||||
const handleCineClose = () => {
|
||||
toolbarService.recordInteraction({
|
||||
groupId: 'MoreTools',
|
||||
interactionType: 'toggle',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'toggleCine',
|
||||
commandOptions: {},
|
||||
toolName: 'cine',
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const cineHandler = () => {
|
||||
if (!cines || !cines[viewportId] || !enabledVPElement) {
|
||||
return;
|
||||
@@ -45,11 +25,11 @@ function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
|
||||
const validFrameRate = Math.max(frameRate, 1);
|
||||
|
||||
if (isPlaying) {
|
||||
cineService.playClip(enabledVPElement, {
|
||||
api.playClip(enabledVPElement, {
|
||||
framesPerSecond: validFrameRate,
|
||||
});
|
||||
} else {
|
||||
cineService.stopClip(enabledVPElement);
|
||||
api.stopClip(enabledVPElement);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -70,34 +50,36 @@ function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
|
||||
});
|
||||
|
||||
if (isPlaying) {
|
||||
cineService.setIsCineEnabled(isPlaying);
|
||||
api.setIsCineEnabled(isPlaying);
|
||||
}
|
||||
cineService.setCine({ id: viewportId, isPlaying, frameRate });
|
||||
api.setCine({ id: viewportId, isPlaying, frameRate });
|
||||
setNewStackFrameRate(frameRate);
|
||||
}, [cineService, displaySetService, viewportId, viewportGridService, cines]);
|
||||
}, [displaySetService, viewportId, viewportGridService, cines]);
|
||||
|
||||
useEffect(() => {
|
||||
isMountedRef.current = true;
|
||||
|
||||
eventTarget.addEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, newStackCineHandler);
|
||||
|
||||
return () => {
|
||||
cineService.setCine({ id: viewportId, isPlaying: false });
|
||||
isMountedRef.current = false;
|
||||
api.stopClip(enabledVPElement);
|
||||
api.setCine({ id: viewportId, isPlaying: false });
|
||||
eventTarget.removeEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, newStackCineHandler);
|
||||
};
|
||||
}, [enabledVPElement, newStackCineHandler]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cines || !cines[viewportId] || !enabledVPElement) {
|
||||
if (!cines || !cines[viewportId] || !enabledVPElement || !isMountedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
cineHandler();
|
||||
|
||||
return () => {
|
||||
if (enabledVPElement && cines?.[viewportId]?.isPlaying) {
|
||||
cineService.stopClip(enabledVPElement);
|
||||
}
|
||||
api.stopClip(enabledVPElement);
|
||||
};
|
||||
}, [cines, viewportId, cineService, enabledVPElement, cineHandler]);
|
||||
}, [cines, viewportId, enabledVPElement, cineHandler]);
|
||||
|
||||
const cine = cines[viewportId];
|
||||
const isPlaying = (cine && cine.isPlaying) || false;
|
||||
@@ -108,15 +90,22 @@ function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
|
||||
className="absolute left-1/2 bottom-3 -translate-x-1/2"
|
||||
frameRate={newStackFrameRate}
|
||||
isPlaying={isPlaying}
|
||||
onClose={handleCineClose}
|
||||
onPlayPauseChange={isPlaying =>
|
||||
cineService.setCine({
|
||||
onClose={() => {
|
||||
// also stop the clip
|
||||
api.setCine({
|
||||
id: viewportId,
|
||||
isPlaying: false,
|
||||
});
|
||||
api.setIsCineEnabled(false);
|
||||
}}
|
||||
onPlayPauseChange={isPlaying => {
|
||||
api.setCine({
|
||||
id: viewportId,
|
||||
isPlaying,
|
||||
})
|
||||
}
|
||||
});
|
||||
}}
|
||||
onFrameRateChange={frameRate =>
|
||||
cineService.setCine({
|
||||
api.setCine({
|
||||
id: viewportId,
|
||||
frameRate,
|
||||
})
|
||||
|
||||
@@ -1,295 +1,10 @@
|
||||
import { Types } from '@ohif/core';
|
||||
|
||||
const mpr: Types.HangingProtocol.Protocol = {
|
||||
id: 'mpr',
|
||||
name: 'Multi-Planar Reconstruction',
|
||||
locked: true,
|
||||
createdDate: '2021-02-23',
|
||||
modifiedDate: '2023-08-15',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
// Unknown number of priors referenced - so just match any study
|
||||
numberOfPriorsReferenced: 0,
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'nth',
|
||||
callbacks: {
|
||||
// Switches out of MPR mode when the layout change button is used
|
||||
onLayoutChange: [
|
||||
{
|
||||
commandName: 'toggleHangingProtocol',
|
||||
commandOptions: { protocolId: 'mpr' },
|
||||
context: 'DEFAULT',
|
||||
},
|
||||
],
|
||||
// Turns off crosshairs when switching out of MPR mode
|
||||
onProtocolExit: [
|
||||
{
|
||||
commandName: 'cleanUpCrosshairs',
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySetSelectors: {
|
||||
activeDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
name: 'MPR 1x3',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 3,
|
||||
layoutOptions: [
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1 / 3,
|
||||
height: 1,
|
||||
},
|
||||
{
|
||||
x: 1 / 3,
|
||||
y: 0,
|
||||
width: 1 / 3,
|
||||
height: 1,
|
||||
},
|
||||
{
|
||||
x: 2 / 3,
|
||||
y: 0,
|
||||
width: 1 / 3,
|
||||
height: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
viewportId: 'mpr-axial',
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'axial',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'activeDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
viewportId: 'mpr-sagittal',
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'sagittal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'activeDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
viewportId: 'mpr-coronal',
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'coronal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'activeDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mprAnd3DVolumeViewport = {
|
||||
id: 'mprAnd3DVolumeViewport',
|
||||
locked: true,
|
||||
name: 'mpr',
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'interleaveCenter',
|
||||
displaySetSelectors: {
|
||||
mprDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
attribute: 'Modality',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: 'CT',
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
id: 'mpr3Stage',
|
||||
name: 'mpr',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'axial',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'volume3d',
|
||||
viewportType: 'volume3d',
|
||||
orientation: 'coronal',
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
},
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
options: {
|
||||
displayPreset: 'CT-Bone',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'coronal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'sagittal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
import { fourUp } from './hps/fourUp';
|
||||
import { main3D } from './hps/main3D';
|
||||
import { mpr } from './hps/mpr';
|
||||
import { mprAnd3DVolumeViewport } from './hps/mprAnd3DVolumeViewport';
|
||||
import { only3D } from './hps/only3D';
|
||||
import { primary3D } from './hps/primary3D';
|
||||
import { primaryAxial } from './hps/primaryAxial';
|
||||
|
||||
function getHangingProtocolModule() {
|
||||
return [
|
||||
@@ -301,6 +16,26 @@ function getHangingProtocolModule() {
|
||||
name: mprAnd3DVolumeViewport.id,
|
||||
protocol: mprAnd3DVolumeViewport,
|
||||
},
|
||||
{
|
||||
name: fourUp.id,
|
||||
protocol: fourUp,
|
||||
},
|
||||
{
|
||||
name: main3D.id,
|
||||
protocol: main3D,
|
||||
},
|
||||
{
|
||||
name: primaryAxial.id,
|
||||
protocol: primaryAxial,
|
||||
},
|
||||
{
|
||||
name: only3D.id,
|
||||
protocol: only3D,
|
||||
},
|
||||
{
|
||||
name: primary3D.id,
|
||||
protocol: primary3D,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
import { Enums } from '@cornerstonejs/tools';
|
||||
|
||||
const getToggledClassName = (isToggled: boolean) => {
|
||||
return isToggled
|
||||
? '!text-primary-active'
|
||||
: '!text-common-bright hover:!bg-primary-dark hover:text-primary-light';
|
||||
};
|
||||
|
||||
export default function getToolbarModule({ commandsManager, servicesManager }) {
|
||||
const {
|
||||
toolGroupService,
|
||||
syncGroupService,
|
||||
cornerstoneViewportService,
|
||||
hangingProtocolService,
|
||||
displaySetService,
|
||||
viewportGridService,
|
||||
} = servicesManager.services;
|
||||
|
||||
return [
|
||||
// functions/helpers to be used by the toolbar buttons to decide if they should
|
||||
// enabled or not
|
||||
{
|
||||
name: 'evaluate.cornerstoneTool',
|
||||
evaluate: ({ viewportId, button }) => {
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
if (!toolGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolName = getToolNameForButton(button);
|
||||
|
||||
if (!toolGroup || !toolGroup.hasTool(toolName)) {
|
||||
return {
|
||||
disabled: true,
|
||||
className: '!text-common-bright ohif-disabled',
|
||||
};
|
||||
}
|
||||
|
||||
const isPrimaryActive = toolGroup.getActivePrimaryMouseButtonTool() === toolName;
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
className: isPrimaryActive
|
||||
? '!text-black bg-primary-light'
|
||||
: '!text-common-bright hover:!bg-primary-dark hover:!text-primary-light',
|
||||
// Todo: isActive right now is used for nested buttons where the primary
|
||||
// button needs to be fully rounded (vs partial rounded) when active
|
||||
// otherwise it does not have any other use
|
||||
isActive: isPrimaryActive,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
|
||||
evaluate: ({ viewportId, button, itemId }) => {
|
||||
const { items } = button.props;
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
if (!toolGroup) {
|
||||
return {
|
||||
primary: button.props.primary,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
const activeToolName = toolGroup.getActivePrimaryMouseButtonTool();
|
||||
|
||||
// check if the active toolName is part of the items then we need
|
||||
// to move it to the primary button
|
||||
const activeToolIndex = items.findIndex(item => {
|
||||
const toolName = getToolNameForButton(item);
|
||||
return toolName === activeToolName;
|
||||
});
|
||||
|
||||
// if there is an active tool in the items dropdown bound to the primary mouse/touch
|
||||
// we should show that no matter what
|
||||
if (activeToolIndex > -1) {
|
||||
return {
|
||||
primary: items[activeToolIndex],
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
if (!itemId) {
|
||||
return {
|
||||
primary: button.props.primary,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
// other wise we can move the clicked tool to the primary button
|
||||
const clickedItemProps = items.find(item => item.id === itemId || item.itemId === itemId);
|
||||
|
||||
return {
|
||||
primary: clickedItemProps,
|
||||
items,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.action',
|
||||
evaluate: ({ viewportId, button }) => {
|
||||
return {
|
||||
className: '!text-common-bright hover:!bg-primary-dark hover:text-primary-light',
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.cornerstoneTool.toggle',
|
||||
evaluate: ({ viewportId, button }) => {
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
if (!toolGroup) {
|
||||
return;
|
||||
}
|
||||
const toolName = getToolNameForButton(button);
|
||||
|
||||
if (!toolGroup || !toolGroup.hasTool(toolName)) {
|
||||
return {
|
||||
disabled: true,
|
||||
className: '!text-common-bright ohif-disabled',
|
||||
};
|
||||
}
|
||||
|
||||
const isOff = [Enums.ToolModes.Disabled, Enums.ToolModes.Passive].includes(
|
||||
toolGroup.getToolOptions(toolName).mode
|
||||
);
|
||||
|
||||
return {
|
||||
className: getToggledClassName(!isOff),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.cornerstone.synchronizer',
|
||||
evaluate: ({ viewportId, button }) => {
|
||||
let synchronizers = syncGroupService.getSynchronizersForViewport(viewportId);
|
||||
|
||||
if (!synchronizers?.length) {
|
||||
return {
|
||||
className: getToggledClassName(false),
|
||||
};
|
||||
}
|
||||
|
||||
const synchronizerType = button?.commands?.[0]?.commandOptions?.type;
|
||||
|
||||
synchronizers = syncGroupService.getSynchronizersOfType(synchronizerType);
|
||||
|
||||
if (!synchronizers?.length) {
|
||||
return {
|
||||
className: getToggledClassName(false),
|
||||
};
|
||||
}
|
||||
|
||||
// Todo: we need a better way to find the synchronizers based on their
|
||||
// type, but for now we just check the first one and see if it is
|
||||
// enabled
|
||||
const synchronizer = synchronizers[0];
|
||||
|
||||
const isEnabled = synchronizer?._enabled;
|
||||
|
||||
return {
|
||||
className: getToggledClassName(isEnabled),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.not3D',
|
||||
evaluate: ({ viewportId, button }) => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (viewport?.type === 'volume3d') {
|
||||
return {
|
||||
disabled: true,
|
||||
className: '!text-common-bright ohif-disabled',
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.viewportProperties.toggle',
|
||||
evaluate: ({ viewportId, button }) => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport || viewport.isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const propId = button.id;
|
||||
|
||||
const properties = viewport.getProperties();
|
||||
const camera = viewport.getCamera();
|
||||
|
||||
const prop = camera?.[propId] || properties?.[propId];
|
||||
|
||||
if (!prop) {
|
||||
return {
|
||||
disabled: false,
|
||||
className: '!text-common-bright hover:!bg-primary-dark hover:text-primary-light',
|
||||
};
|
||||
}
|
||||
|
||||
const isToggled = prop;
|
||||
|
||||
return {
|
||||
className: getToggledClassName(isToggled),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'evaluate.mpr',
|
||||
evaluate: ({ viewportId, button }) => {
|
||||
const { protocol } = hangingProtocolService.getActiveProtocol();
|
||||
|
||||
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
|
||||
if (!displaySetUIDs?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const displaySets = displaySetUIDs.map(displaySetService.getDisplaySetByUID);
|
||||
|
||||
const areReconstructable = displaySets.every(displaySet => {
|
||||
return displaySet.isReconstructable;
|
||||
});
|
||||
|
||||
if (!areReconstructable) {
|
||||
return {
|
||||
disabled: true,
|
||||
className: '!text-common-bright ohif-disabled',
|
||||
};
|
||||
}
|
||||
|
||||
const isMpr = protocol?.id === 'mpr';
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
className: getToggledClassName(isMpr),
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getToolNameForButton(button) {
|
||||
const { props } = button;
|
||||
|
||||
const commands = props?.commands || button.commands;
|
||||
const commandsArray = Array.isArray(commands) ? commands : [commands];
|
||||
const firstCommand = commandsArray[0];
|
||||
if (typeof firstCommand === 'string') {
|
||||
// likely not a cornerstone tool
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('commandOptions' in firstCommand) {
|
||||
return firstCommand.commandOptions.toolName ?? props?.id ?? button.id;
|
||||
}
|
||||
|
||||
// use id as a fallback for toolName
|
||||
return props?.id ?? button.id;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
export const fourUp = {
|
||||
id: 'fourUp',
|
||||
locked: true,
|
||||
name: '3D four up',
|
||||
icon: 'layout-advanced-3d-four-up',
|
||||
isPreset: true,
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'interleaveCenter',
|
||||
displaySetSelectors: {
|
||||
mprDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
id: 'fourUpStage',
|
||||
name: 'fourUp',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'axial',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'volume3d',
|
||||
viewportType: 'volume3d',
|
||||
orientation: 'coronal',
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
},
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
options: {
|
||||
// ToDo: choose appropriate preset
|
||||
displayPreset: 'CT-Bone',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'coronal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'sagittal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
export const main3D = {
|
||||
id: 'main3D',
|
||||
locked: true,
|
||||
name: '3D main',
|
||||
icon: 'layout-advanced-3d-main',
|
||||
isPreset: true,
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'interleaveCenter',
|
||||
displaySetSelectors: {
|
||||
mprDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
id: 'main3DStage',
|
||||
name: 'main3D',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 3,
|
||||
layoutOptions: [
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1 / 2,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: 1 / 2,
|
||||
width: 1 / 3,
|
||||
height: 1 / 2,
|
||||
},
|
||||
{
|
||||
x: 1 / 3,
|
||||
y: 1 / 2,
|
||||
width: 1 / 3,
|
||||
height: 1 / 2,
|
||||
},
|
||||
{
|
||||
x: 2 / 3,
|
||||
y: 1 / 2,
|
||||
width: 1 / 3,
|
||||
height: 1 / 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'volume3d',
|
||||
viewportType: 'volume3d',
|
||||
orientation: 'coronal',
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
},
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
options: {
|
||||
// ToDo: choose appropriate preset
|
||||
displayPreset: 'CT-Bone',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'axial',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'coronal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'sagittal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
import { Types } from '@ohif/core';
|
||||
|
||||
export const mpr: Types.HangingProtocol.Protocol = {
|
||||
id: 'mpr',
|
||||
name: 'MPR',
|
||||
locked: true,
|
||||
icon: 'layout-advanced-mpr',
|
||||
isPreset: true,
|
||||
createdDate: '2021-02-23',
|
||||
modifiedDate: '2023-08-15',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
// Unknown number of priors referenced - so just match any study
|
||||
numberOfPriorsReferenced: 0,
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'nth',
|
||||
callbacks: {
|
||||
// Switches out of MPR mode when the layout change button is used
|
||||
onLayoutChange: [
|
||||
{
|
||||
commandName: 'toggleHangingProtocol',
|
||||
commandOptions: { protocolId: 'mpr' },
|
||||
context: 'DEFAULT',
|
||||
},
|
||||
],
|
||||
// Turns off crosshairs when switching out of MPR mode
|
||||
},
|
||||
displaySetSelectors: {
|
||||
activeDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
name: 'MPR 1x3',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 3,
|
||||
layoutOptions: [
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1 / 3,
|
||||
height: 1,
|
||||
},
|
||||
{
|
||||
x: 1 / 3,
|
||||
y: 0,
|
||||
width: 1 / 3,
|
||||
height: 1,
|
||||
},
|
||||
{
|
||||
x: 2 / 3,
|
||||
y: 0,
|
||||
width: 1 / 3,
|
||||
height: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
viewportId: 'mpr-axial',
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'axial',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'activeDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
viewportId: 'mpr-sagittal',
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'sagittal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'activeDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
viewportId: 'mpr-coronal',
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'coronal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'activeDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
export const mprAnd3DVolumeViewport = {
|
||||
id: 'mprAnd3DVolumeViewport',
|
||||
locked: true,
|
||||
name: 'mpr',
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'interleaveCenter',
|
||||
displaySetSelectors: {
|
||||
mprDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
attribute: 'Modality',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: 'CT',
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
id: 'mpr3Stage',
|
||||
name: 'mpr',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'axial',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'volume3d',
|
||||
viewportType: 'volume3d',
|
||||
orientation: 'coronal',
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
},
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
options: {
|
||||
displayPreset: 'CT-Bone',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'coronal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'sagittal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
export const only3D = {
|
||||
id: 'only3D',
|
||||
locked: true,
|
||||
name: '3D only',
|
||||
icon: 'layout-advanced-3d-only',
|
||||
isPreset: true,
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'interleaveCenter',
|
||||
displaySetSelectors: {
|
||||
mprDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
id: 'only3DStage',
|
||||
name: 'only3D',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'volume3d',
|
||||
viewportType: 'volume3d',
|
||||
orientation: 'coronal',
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
},
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
options: {
|
||||
// ToDo: choose appropriate preset
|
||||
displayPreset: 'CT-Bone',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
export const primary3D = {
|
||||
id: 'primary3D',
|
||||
locked: true,
|
||||
name: '3D primary',
|
||||
icon: 'layout-advanced-3d-primary',
|
||||
isPreset: true,
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'interleaveCenter',
|
||||
displaySetSelectors: {
|
||||
mprDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
id: 'primary3DStage',
|
||||
name: 'primary3D',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 3,
|
||||
columns: 3,
|
||||
layoutOptions: [
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 2 / 3,
|
||||
height: 1,
|
||||
},
|
||||
{
|
||||
x: 2 / 3,
|
||||
y: 0,
|
||||
width: 1 / 3,
|
||||
height: 1 / 3,
|
||||
},
|
||||
{
|
||||
x: 2 / 3,
|
||||
y: 1 / 3,
|
||||
width: 1 / 3,
|
||||
height: 1 / 3,
|
||||
},
|
||||
{
|
||||
x: 2 / 3,
|
||||
y: 2 / 3,
|
||||
width: 1 / 3,
|
||||
height: 1 / 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'volume3d',
|
||||
viewportType: 'volume3d',
|
||||
orientation: 'coronal',
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
},
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
options: {
|
||||
// ToDo: choose appropriate preset
|
||||
displayPreset: 'CT-Bone',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'axial',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'coronal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'sagittal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
export const primaryAxial = {
|
||||
id: 'primaryAxial',
|
||||
locked: true,
|
||||
name: 'Axial Primary',
|
||||
icon: 'layout-advanced-axial-primary',
|
||||
isPreset: true,
|
||||
createdDate: '2023-03-15T10:29:44.894Z',
|
||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [],
|
||||
imageLoadStrategy: 'interleaveCenter',
|
||||
displaySetSelectors: {
|
||||
mprDisplaySet: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
weight: 1,
|
||||
attribute: 'isReconstructable',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
id: 'primaryAxialStage',
|
||||
name: 'primaryAxial',
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 3,
|
||||
layoutOptions: [
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 2 / 3,
|
||||
height: 1,
|
||||
},
|
||||
{
|
||||
x: 2 / 3,
|
||||
y: 0,
|
||||
width: 1 / 3,
|
||||
height: 1 / 2,
|
||||
},
|
||||
{
|
||||
x: 2 / 3,
|
||||
y: 1 / 2,
|
||||
width: 1 / 3,
|
||||
height: 1 / 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'axial',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'sagittal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'mpr',
|
||||
viewportType: 'volume',
|
||||
orientation: 'coronal',
|
||||
initialImageOptions: {
|
||||
preset: 'middle',
|
||||
},
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'mpr',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'mprDisplaySet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -13,6 +13,7 @@ import init from './init';
|
||||
import getCustomizationModule from './getCustomizationModule';
|
||||
import getCommandsModule from './commandsModule';
|
||||
import getHangingProtocolModule from './getHangingProtocolModule';
|
||||
import getToolbarModule from './getToolbarModule';
|
||||
import ToolGroupService from './services/ToolGroupService';
|
||||
import SyncGroupService from './services/SyncGroupService';
|
||||
import SegmentationService from './services/SegmentationService';
|
||||
@@ -26,7 +27,6 @@ import dicomLoaderService from './utils/dicomLoaderService';
|
||||
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
||||
|
||||
import { id } from './id';
|
||||
import * as csWADOImageLoader from './initWADOImageLoader.js';
|
||||
import { measurementMappingUtils } from './utils/measurementServiceMappings';
|
||||
import type { PublicViewportOptions } from './services/ViewportService/Viewport';
|
||||
import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
|
||||
@@ -79,6 +79,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
return init.call(this, props);
|
||||
},
|
||||
|
||||
getToolbarModule,
|
||||
getHangingProtocolModule,
|
||||
getViewportModule({ servicesManager, commandsManager }) {
|
||||
const ExtendedOHIFCornerstoneViewport = props => {
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
metaData,
|
||||
volumeLoader,
|
||||
imageLoadPoolManager,
|
||||
getEnabledElement,
|
||||
Settings,
|
||||
utilities as csUtilities,
|
||||
Enums as csEnums,
|
||||
} from '@cornerstonejs/core';
|
||||
import { Enums } from '@cornerstonejs/tools';
|
||||
import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader';
|
||||
|
||||
import initWADOImageLoader from './initWADOImageLoader';
|
||||
@@ -40,7 +40,6 @@ export default async function init({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
configuration,
|
||||
appConfig,
|
||||
}: Types.Extensions.ExtensionParams): Promise<void> {
|
||||
// Note: this should run first before initializing the cornerstone
|
||||
@@ -93,12 +92,26 @@ export default async function init({
|
||||
cineService,
|
||||
cornerstoneViewportService,
|
||||
hangingProtocolService,
|
||||
toolGroupService,
|
||||
toolbarService,
|
||||
viewportGridService,
|
||||
stateSyncService,
|
||||
segmentationService,
|
||||
} = servicesManager.services as CornerstoneServices;
|
||||
|
||||
toolbarService.registerEventForToolbarUpdate(cornerstoneViewportService, [
|
||||
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
|
||||
]);
|
||||
|
||||
toolbarService.registerEventForToolbarUpdate(segmentationService, [
|
||||
segmentationService.EVENTS.SEGMENTATION_ADDED,
|
||||
segmentationService.EVENTS.SEGMENTATION_REMOVED,
|
||||
segmentationService.EVENTS.SEGMENTATION_UPDATED,
|
||||
]);
|
||||
|
||||
toolbarService.registerEventForToolbarUpdate(eventTarget, [
|
||||
cornerstoneTools.Enums.Events.TOOL_ACTIVATED,
|
||||
]);
|
||||
|
||||
window.services = servicesManager.services;
|
||||
window.extensionManager = extensionManager;
|
||||
window.commandsManager = commandsManager;
|
||||
@@ -124,6 +137,9 @@ export default async function init({
|
||||
// an OHIFCornerstoneViewport can be redisplayed with the same LUT
|
||||
stateSyncService.register('lutPresentationStore', { clearOnModeExit: true });
|
||||
|
||||
// Stores synchronizers state to be restored
|
||||
stateSyncService.register('synchronizersStore', { clearOnModeExit: true });
|
||||
|
||||
// Stores a map from `positionPresentationId` to a Presentation object so that
|
||||
// an OHIFCornerstoneViewport can be redisplayed with the same position
|
||||
stateSyncService.register('positionPresentationStore', {
|
||||
@@ -199,6 +215,16 @@ export default async function init({
|
||||
}
|
||||
);
|
||||
|
||||
// resize the cornerstone viewport service when the grid size changes
|
||||
// IMPORTANT: this should happen outside of the OHIFCornerstoneViewport
|
||||
// since it will trigger a rerender of each viewport and each resizing
|
||||
// the offscreen canvas which would result in a performance hit, this should
|
||||
// done only once per grid resize here. Doing it once here, allows us to reduce
|
||||
// the refreshRage(in ms) to 10 from 50. I tried with even 1 or 5 ms it worked fine
|
||||
viewportGridService.subscribe(viewportGridService.EVENTS.GRID_SIZE_CHANGED, () => {
|
||||
cornerstoneViewportService.resize(true);
|
||||
});
|
||||
|
||||
initContextMenu({
|
||||
cornerstoneViewportService,
|
||||
customizationService,
|
||||
@@ -210,101 +236,40 @@ export default async function init({
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
/**
|
||||
* When a viewport gets a new display set, this call will go through all the
|
||||
* active tools in the toolbar, and call any commands registered in the
|
||||
* toolbar service with a callback to re-enable on displaying the viewport.
|
||||
*/
|
||||
const toolbarEventListener = evt => {
|
||||
const { element } = evt.detail;
|
||||
const activeTools = toolbarService.getActiveTools();
|
||||
|
||||
activeTools.forEach(tool => {
|
||||
const toolData = toolbarService.getNestedButton(tool);
|
||||
const commands = toolData?.listeners?.[evt.type];
|
||||
commandsManager.run(commands, { element, evt });
|
||||
});
|
||||
};
|
||||
|
||||
/** Listens for active viewport events and fires the toolbar listeners */
|
||||
const activeViewportEventListener = evt => {
|
||||
const { viewportId } = evt;
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
const activeTools = toolbarService.getActiveTools();
|
||||
|
||||
activeTools.forEach(tool => {
|
||||
if (!toolGroup?._toolInstances?.[tool]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if tool is active on the new viewport
|
||||
const toolEnabled = toolGroup._toolInstances[tool].mode === Enums.ToolModes.Enabled;
|
||||
|
||||
if (!toolEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const button = toolbarService.getNestedButton(tool);
|
||||
const commands = button?.listeners?.[evt.type];
|
||||
commandsManager.run(commands, { viewportId, evt });
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs error handler for failed requests.
|
||||
* @param event
|
||||
* @param event
|
||||
*/
|
||||
const imageLoadFailedHandler = ({ detail }) => {
|
||||
const handler = errorHandler.getHTTPErrorHandler()
|
||||
const handler = errorHandler.getHTTPErrorHandler();
|
||||
handler(detail.error);
|
||||
};
|
||||
|
||||
const resetCrosshairs = evt => {
|
||||
const { element } = evt.detail;
|
||||
const { viewportId, renderingEngineId } = cornerstone.getEnabledElement(element);
|
||||
|
||||
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroupForViewport(
|
||||
viewportId,
|
||||
renderingEngineId
|
||||
);
|
||||
|
||||
if (!toolGroup || !toolGroup._toolInstances?.['Crosshairs']) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mode = toolGroup._toolInstances['Crosshairs'].mode;
|
||||
|
||||
if (mode === Enums.ToolModes.Active) {
|
||||
toolGroup.setToolActive('Crosshairs');
|
||||
} else if (mode === Enums.ToolModes.Passive) {
|
||||
toolGroup.setToolPassive('Crosshairs');
|
||||
} else if (mode === Enums.ToolModes.Enabled) {
|
||||
toolGroup.setToolEnabled('Crosshairs');
|
||||
}
|
||||
};
|
||||
|
||||
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, evt => {
|
||||
const { element } = evt.detail;
|
||||
cornerstoneTools.utilities.stackContextPrefetch.enable(element);
|
||||
});
|
||||
eventTarget.addEventListener(EVENTS.IMAGE_LOAD_FAILED, imageLoadFailedHandler);
|
||||
eventTarget.addEventListener(EVENTS.IMAGE_LOAD_ERROR, imageLoadFailedHandler);
|
||||
|
||||
|
||||
function elementEnabledHandler(evt) {
|
||||
const { element } = evt.detail;
|
||||
|
||||
element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
||||
element.addEventListener(EVENTS.CAMERA_RESET, evt => {
|
||||
const { element } = evt.detail;
|
||||
const { viewportId } = getEnabledElement(element);
|
||||
commandsManager.runCommand('resetCrosshairs', { viewportId });
|
||||
});
|
||||
|
||||
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener);
|
||||
// eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener);
|
||||
|
||||
initViewTiming({ element, eventTarget });
|
||||
initViewTiming({ element });
|
||||
}
|
||||
|
||||
function elementDisabledHandler(evt) {
|
||||
const { element } = evt.detail;
|
||||
|
||||
element.removeEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
||||
// element.removeEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
||||
|
||||
// TODO - consider removing the callback when all elements are gone
|
||||
// eventTarget.removeEventListener(
|
||||
@@ -317,10 +282,10 @@ export default async function init({
|
||||
|
||||
eventTarget.addEventListener(EVENTS.ELEMENT_DISABLED, elementDisabledHandler.bind(null));
|
||||
|
||||
viewportGridService.subscribe(
|
||||
viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
|
||||
activeViewportEventListener
|
||||
);
|
||||
// viewportGridService.subscribe(
|
||||
// viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
|
||||
// activeViewportEventListener
|
||||
// );
|
||||
}
|
||||
|
||||
function CPUModal() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
import { Types as OhifTypes, ServicesManager, PubSubService } from '@ohif/core';
|
||||
import {
|
||||
cache,
|
||||
@@ -11,7 +9,6 @@ import {
|
||||
volumeLoader,
|
||||
} from '@cornerstonejs/core';
|
||||
import {
|
||||
CONSTANTS as cstConstants,
|
||||
Enums as csToolsEnums,
|
||||
segmentation as cstSegmentation,
|
||||
Types as cstTypes,
|
||||
@@ -23,7 +20,6 @@ import { easeInOutBell, reverseEaseInOutBell } from '../../utils/transitions';
|
||||
import { Segment, Segmentation, SegmentationConfig } from './SegmentationServiceTypes';
|
||||
import { mapROIContoursToRTStructData } from './RTSTRUCT/mapROIContoursToRTStructData';
|
||||
|
||||
const { COLOR_LUT } = cstConstants;
|
||||
const LABELMAP = csToolsEnums.SegmentationRepresentations.Labelmap;
|
||||
const CONTOUR = csToolsEnums.SegmentationRepresentations.Contour;
|
||||
|
||||
@@ -465,15 +461,6 @@ class SegmentationService extends PubSubService {
|
||||
},
|
||||
]);
|
||||
|
||||
// if first segmentation, we can use the default colorLUT, otherwise
|
||||
// we need to generate a new one and use a new colorLUT
|
||||
const colorLUTIndex = 0;
|
||||
if (Object.keys(this.segmentations).length !== 0) {
|
||||
const newColorLUT = this.generateNewColorLUT();
|
||||
const colorLUTIndex = this.getNextColorLUTIndex();
|
||||
cstSegmentation.config.color.addColorLUT(newColorLUT, colorLUTIndex);
|
||||
}
|
||||
|
||||
this.segmentations[segmentationId] = {
|
||||
...segmentation,
|
||||
label: segmentation.label || '',
|
||||
@@ -482,7 +469,6 @@ class SegmentationService extends PubSubService {
|
||||
segmentCount: segmentation.segmentCount ?? 0,
|
||||
isActive: false,
|
||||
isVisible: true,
|
||||
colorLUTIndex,
|
||||
};
|
||||
|
||||
cachedSegmentation = this.segmentations[segmentationId];
|
||||
@@ -976,7 +962,7 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
// Force use of a Uint8Array SharedArrayBuffer for the segmentation to save space and so
|
||||
// it is easily compressible in worker thread.
|
||||
await volumeLoader.createAndCacheDerivedVolume(volumeId, {
|
||||
await volumeLoader.createAndCacheDerivedSegmentationVolume(volumeId, {
|
||||
volumeId: segmentationId,
|
||||
targetBuffer: {
|
||||
type: 'Uint8Array',
|
||||
@@ -1037,8 +1023,6 @@ class SegmentationService extends PubSubService {
|
||||
segmentation.hydrated = true;
|
||||
}
|
||||
|
||||
const { colorLUTIndex } = segmentation;
|
||||
|
||||
// Based on the segmentationId, set the colorLUTIndex.
|
||||
const segmentationRepresentationUIDs = await cstSegmentation.addSegmentationRepresentations(
|
||||
toolGroupId,
|
||||
@@ -1057,12 +1041,6 @@ class SegmentationService extends PubSubService {
|
||||
segmentationRepresentationUIDs[0]
|
||||
);
|
||||
|
||||
cstSegmentation.config.color.setColorLUT(
|
||||
toolGroupId,
|
||||
segmentationRepresentationUIDs[0],
|
||||
colorLUTIndex
|
||||
);
|
||||
|
||||
// add the segmentation segments properly
|
||||
for (const segment of segmentation.segments) {
|
||||
if (segment === null || segment === undefined) {
|
||||
@@ -1390,8 +1368,6 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
public setConfiguration = (configuration: SegmentationConfig): void => {
|
||||
const {
|
||||
brushSize,
|
||||
brushThresholdGate,
|
||||
fillAlpha,
|
||||
fillAlphaInactive,
|
||||
outlineWidthActive,
|
||||
@@ -1483,7 +1459,6 @@ class SegmentationService extends PubSubService {
|
||||
segmentInfo.label = label;
|
||||
|
||||
if (suppressEvents === false) {
|
||||
// this._setSegmentationModified(segmentationId);
|
||||
this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, {
|
||||
segmentation,
|
||||
});
|
||||
@@ -1530,7 +1505,6 @@ class SegmentationService extends PubSubService {
|
||||
segments: [],
|
||||
isVisible: true,
|
||||
isActive: false,
|
||||
colorLUTIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2124,23 +2098,6 @@ class SegmentationService extends PubSubService {
|
||||
return viewportInfo.getToolGroupId();
|
||||
};
|
||||
|
||||
private getNextColorLUTIndex = (): number => {
|
||||
let i = 0;
|
||||
while (true) {
|
||||
if (cstSegmentation.state.getColorLUT(i) === undefined) {
|
||||
return i;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
};
|
||||
|
||||
private generateNewColorLUT() {
|
||||
const newColorLUT = cloneDeep(COLOR_LUT);
|
||||
|
||||
return newColorLUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts object of objects to array.
|
||||
*
|
||||
|
||||
@@ -67,4 +67,4 @@ type SegmentationRepresentationData = {
|
||||
LABELMAP?: LabelmapSegmentationData;
|
||||
};
|
||||
|
||||
export { SegmentationConfig, Segment, Segmentation };
|
||||
export type { SegmentationConfig, Segment, Segmentation };
|
||||
@@ -1,4 +1,5 @@
|
||||
import { synchronizers, SynchronizerManager, Synchronizer } from '@cornerstonejs/tools';
|
||||
import { getRenderingEngines } from '@cornerstonejs/core';
|
||||
|
||||
import { pubSubServiceInterface, Types, ServicesManager } from '@ohif/core';
|
||||
|
||||
@@ -25,6 +26,7 @@ const POSITION = 'cameraposition';
|
||||
const VOI = 'voi';
|
||||
const ZOOMPAN = 'zoompan';
|
||||
const STACKIMAGE = 'stackimage';
|
||||
const IMAGE_SLICE = 'imageslice';
|
||||
|
||||
const asSyncGroup = (syncGroup: string | SyncGroup): SyncGroup =>
|
||||
typeof syncGroup === 'string' ? { type: syncGroup } : syncGroup;
|
||||
@@ -45,9 +47,14 @@ export default class SyncGroupService {
|
||||
[POSITION]: synchronizers.createCameraPositionSynchronizer,
|
||||
[VOI]: synchronizers.createVOISynchronizer,
|
||||
[ZOOMPAN]: synchronizers.createZoomPanSynchronizer,
|
||||
[STACKIMAGE]: synchronizers.createStackImageSynchronizer,
|
||||
// todo: remove stack image since it is legacy now and the image_slice
|
||||
// handles both stack and volume viewports
|
||||
[STACKIMAGE]: synchronizers.createImageSliceSynchronizer,
|
||||
[IMAGE_SLICE]: synchronizers.createImageSliceSynchronizer,
|
||||
};
|
||||
|
||||
synchronizersByType: { [key: string]: Synchronizer[] } = {};
|
||||
|
||||
constructor(serviceManager: ServicesManager) {
|
||||
this.servicesManager = serviceManager;
|
||||
this.listeners = {};
|
||||
@@ -57,14 +64,27 @@ export default class SyncGroupService {
|
||||
}
|
||||
|
||||
private _createSynchronizer(type: string, id: string, options): Synchronizer | undefined {
|
||||
// Initialize if not already done
|
||||
this.synchronizersByType[type] = this.synchronizersByType[type] || [];
|
||||
|
||||
const syncCreator = this.synchronizerCreators[type.toLowerCase()];
|
||||
|
||||
if (syncCreator) {
|
||||
return syncCreator(id, options);
|
||||
const synchronizer = syncCreator(id, options);
|
||||
|
||||
if (synchronizer) {
|
||||
this.synchronizersByType[type].push(synchronizer);
|
||||
return synchronizer;
|
||||
}
|
||||
} else {
|
||||
console.warn('Unknown synchronizer type', type, id);
|
||||
console.warn(`Unknown synchronizer type: ${type}, id: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getSyncCreatorForType(type: string): SyncCreator {
|
||||
return this.synchronizerCreators[type.toLowerCase()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a synchronizer type.
|
||||
* @param type is the type of the synchronizer to create
|
||||
@@ -74,6 +94,19 @@ export default class SyncGroupService {
|
||||
this.synchronizerCreators[type.toLowerCase()] = creator;
|
||||
}
|
||||
|
||||
public getSynchronizer(id: string): Synchronizer | void {
|
||||
return SynchronizerManager.getSynchronizer(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of synchronizers of a specific type.
|
||||
* @param type - The type of synchronizers to retrieve.
|
||||
* @returns An array of synchronizers of the specified type.
|
||||
*/
|
||||
public getSynchronizersOfType(type: string): Synchronizer[] {
|
||||
return this.synchronizersByType[type];
|
||||
}
|
||||
|
||||
protected _getOrCreateSynchronizer(
|
||||
type: string,
|
||||
id: string,
|
||||
@@ -121,6 +154,20 @@ export default class SyncGroupService {
|
||||
SynchronizerManager.destroy();
|
||||
}
|
||||
|
||||
public getSynchronizersForViewport(viewportId: string): Synchronizer[] {
|
||||
const renderingEngine =
|
||||
getRenderingEngines().find(re => {
|
||||
return re.getViewports().find(vp => vp.id === viewportId);
|
||||
}) || getRenderingEngines()[0];
|
||||
|
||||
const synchronizers = SynchronizerManager.getAllSynchronizers();
|
||||
return synchronizers.filter(
|
||||
s =>
|
||||
s.hasSourceViewport(renderingEngine.id, viewportId) ||
|
||||
s.hasTargetViewport(renderingEngine.id, viewportId)
|
||||
);
|
||||
}
|
||||
|
||||
public removeViewportFromSyncGroup(
|
||||
viewportId: string,
|
||||
renderingEngineId: string,
|
||||
|
||||
@@ -189,19 +189,6 @@ export default class ToolGroupService {
|
||||
this.addToolsToToolGroup(toolGroupId, tools);
|
||||
return toolGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
private changeConfigurationIfNecessary(toolGroup, volumeUID) {
|
||||
// handle specific assignment for volumeUID (e.g., fusion)
|
||||
const toolInstances = toolGroup._toolInstances;
|
||||
// Object.values(toolInstances).forEach(toolInstance => {
|
||||
// if (toolInstance.configuration) {
|
||||
// toolInstance.configuration.volumeUID = volumeUID;
|
||||
// }
|
||||
// });
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the tool's configuration based on the tool name and tool group id
|
||||
* @param toolGroupId - The id of the tool group that the tool instance belongs to.
|
||||
|
||||
+358
-103
@@ -10,6 +10,7 @@ import {
|
||||
VolumeViewport3D,
|
||||
cache,
|
||||
Enums as csEnums,
|
||||
BaseVolumeViewport,
|
||||
} from '@cornerstonejs/core';
|
||||
|
||||
import { utilities as csToolsUtils, Enums as csToolsEnums } from '@cornerstonejs/tools';
|
||||
@@ -17,7 +18,7 @@ import { IViewportService } from './IViewportService';
|
||||
import { RENDERING_ENGINE_ID } from './constants';
|
||||
import ViewportInfo, { DisplaySetOptions, PublicViewportOptions } from './Viewport';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
import { Presentation, Presentations } from '../../types/Presentation';
|
||||
import { LutPresentation, PositionPresentation, Presentations } from '../../types/Presentation';
|
||||
|
||||
import JumpPresets from '../../utils/JumpPresets';
|
||||
|
||||
@@ -44,6 +45,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
viewportsById: Map<string, ViewportInfo> = new Map();
|
||||
viewportGridResizeObserver: ResizeObserver | null;
|
||||
viewportsDisplaySets: Map<string, string[]> = new Map();
|
||||
beforeResizePositionPresentations: Map<string, PositionPresentation> = new Map();
|
||||
|
||||
// Some configs
|
||||
enableResizeDetector: true;
|
||||
@@ -51,6 +53,11 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
resizeRefreshMode: 'debounce';
|
||||
servicesManager = null;
|
||||
|
||||
resizeQueue = [];
|
||||
viewportResizeTimer = null;
|
||||
gridResizeDelay = 50;
|
||||
gridResizeTimeOut = null;
|
||||
|
||||
constructor(servicesManager: ServicesManager) {
|
||||
super(EVENTS);
|
||||
this.renderingEngine = null;
|
||||
@@ -94,14 +101,28 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
/**
|
||||
* It triggers the resize on the rendering engine.
|
||||
* It triggers the resize on the rendering engine, and renders the viewports
|
||||
*
|
||||
* @param isGridResize - if the resize is triggered by a grid resize
|
||||
* this is used to avoid double resize of the viewports since if the
|
||||
* grid is resized, all viewports will be resized so there is no need
|
||||
* to resize them individually which will get triggered by their
|
||||
* individual resize observers
|
||||
*/
|
||||
public resize() {
|
||||
const immediate = true;
|
||||
const keepCamera = true;
|
||||
|
||||
this.renderingEngine.resize(immediate, keepCamera);
|
||||
this.renderingEngine.render();
|
||||
public resize(isGridResize = false) {
|
||||
// if there is a grid resize happening, it means the viewport grid
|
||||
// has been manipulated (e.g., panels closed, added, etc.) and we need
|
||||
// to resize all viewports, so we will add a timeout here to make sure
|
||||
// we don't double resize the viewports when viewports in the grid are
|
||||
// resized individually
|
||||
if (isGridResize) {
|
||||
this.performResize();
|
||||
this.resetGridResizeTimeout();
|
||||
this.resizeQueue = [];
|
||||
clearTimeout(this.viewportResizeTimer);
|
||||
} else {
|
||||
this.enqueueViewportResizeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,74 +159,236 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
this.viewportsDisplaySets.delete(viewportId);
|
||||
}
|
||||
|
||||
public setPresentations(viewport, presentations?: Presentations): void {
|
||||
const properties = presentations?.lutPresentation?.properties;
|
||||
if (properties) {
|
||||
viewport.setProperties(properties);
|
||||
/**
|
||||
* Sets the presentations for a given viewport. Presentations is an object
|
||||
* that can define the lut or position for a viewport.
|
||||
*
|
||||
* @param viewportId - The ID of the viewport.
|
||||
* @param presentations - The presentations to apply to the viewport.
|
||||
*/
|
||||
public setPresentations(viewportId: string, presentations?: Presentations): void {
|
||||
const viewport = this.getCornerstoneViewport(viewportId) as
|
||||
| Types.IStackViewport
|
||||
| Types.IVolumeViewport;
|
||||
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
const camera = presentations?.positionPresentation?.camera;
|
||||
if (camera) {
|
||||
viewport.setCamera(camera);
|
||||
|
||||
if (!presentations) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lutPresentation, positionPresentation } = presentations;
|
||||
if (lutPresentation) {
|
||||
const { presentation } = lutPresentation;
|
||||
|
||||
if (viewport instanceof BaseVolumeViewport) {
|
||||
Object.entries(presentation).forEach(
|
||||
([volumeId, properties]: [string, Types.ViewportProperties]) => {
|
||||
viewport.setProperties(properties, volumeId);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
viewport.setProperties(presentation);
|
||||
}
|
||||
}
|
||||
|
||||
if (positionPresentation) {
|
||||
const { viewPlaneNormal, viewUp, zoom, pan } = positionPresentation.presentation;
|
||||
viewport.setCamera({ viewPlaneNormal, viewUp });
|
||||
|
||||
if (zoom !== undefined) {
|
||||
viewport.setZoom(zoom);
|
||||
}
|
||||
|
||||
if (pan !== undefined) {
|
||||
viewport.setPan(pan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getPresentation(viewportId: string): Presentation {
|
||||
/**
|
||||
* Retrieves the position presentation information for a given viewport.
|
||||
* @param viewportId The ID of the viewport.
|
||||
* @returns The position presentation object containing various properties
|
||||
* such as ID, viewport type, initial image index, view plane normal, view up, zoom, and pan.
|
||||
*/
|
||||
public getPositionPresentation(viewportId: string): PositionPresentation {
|
||||
const viewportInfo = this.viewportsById.get(viewportId);
|
||||
if (!viewportInfo) {
|
||||
return;
|
||||
}
|
||||
const { viewportType, presentationIds } = viewportInfo.getViewportOptions();
|
||||
|
||||
const presentationIds = viewportInfo.getPresentationIds();
|
||||
|
||||
if (!presentationIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { positionPresentationId } = presentationIds;
|
||||
|
||||
const csViewport = this.getCornerstoneViewport(viewportId);
|
||||
if (!csViewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
const properties = csViewport.getProperties();
|
||||
if (properties.isComputedVOI) {
|
||||
delete properties.voiRange;
|
||||
delete properties.VOILUTFunction;
|
||||
}
|
||||
const initialImageIndex = csViewport.getCurrentImageIdIndex();
|
||||
const camera = csViewport.getCamera();
|
||||
const { viewPlaneNormal, viewUp } = csViewport.getCamera();
|
||||
const initialImageIndex = csViewport.getCurrentImageIdIndex() || 0;
|
||||
const zoom = csViewport.getZoom();
|
||||
const pan = csViewport.getPan();
|
||||
|
||||
return {
|
||||
presentationIds,
|
||||
viewportType: !viewportType || viewportType === 'stack' ? 'stack' : 'volume',
|
||||
properties,
|
||||
initialImageIndex,
|
||||
camera,
|
||||
id: positionPresentationId,
|
||||
viewportType: viewportInfo.getViewportType(),
|
||||
presentation: {
|
||||
initialImageIndex,
|
||||
viewUp,
|
||||
viewPlaneNormal,
|
||||
zoom,
|
||||
pan,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public storePresentation({ viewportId }) {
|
||||
const stateSyncService = this.servicesManager.services.stateSyncService;
|
||||
let presentation;
|
||||
try {
|
||||
presentation = this.getPresentation(viewportId);
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
|
||||
if (!presentation || !presentation.presentationIds) {
|
||||
/**
|
||||
* Retrieves the LUT (Lookup Table) presentation for a given viewport.
|
||||
* @param viewportId The ID of the viewport.
|
||||
* @returns The LUT presentation object, or undefined if the viewport does not exist.
|
||||
*/
|
||||
public getLutPresentation(viewportId: string): LutPresentation {
|
||||
const viewportInfo = this.viewportsById.get(viewportId);
|
||||
if (!viewportInfo) {
|
||||
return;
|
||||
}
|
||||
const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState();
|
||||
const { presentationIds } = presentation;
|
||||
const { lutPresentationId, positionPresentationId } = presentationIds || {};
|
||||
const storeState = {};
|
||||
|
||||
const presentationIds = viewportInfo.getPresentationIds();
|
||||
|
||||
if (!presentationIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lutPresentationId } = presentationIds;
|
||||
|
||||
const csViewport = this.getCornerstoneViewport(viewportId) as
|
||||
| Types.IStackViewport
|
||||
| Types.IVolumeViewport;
|
||||
|
||||
if (!csViewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanProperties = properties => {
|
||||
if (properties.isComputedVOI) {
|
||||
delete properties.voiRange;
|
||||
delete properties.VOILUTFunction;
|
||||
}
|
||||
return properties;
|
||||
};
|
||||
|
||||
const presentation =
|
||||
csViewport instanceof BaseVolumeViewport
|
||||
? new Map()
|
||||
: cleanProperties(csViewport.getProperties());
|
||||
|
||||
if (presentation instanceof Map) {
|
||||
csViewport.getActors().forEach(({ uid: volumeId }) => {
|
||||
const properties = cleanProperties(csViewport.getProperties(volumeId));
|
||||
presentation.set(volumeId, properties);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: lutPresentationId,
|
||||
viewportType: viewportInfo.getViewportType(),
|
||||
presentation,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the presentations for a given viewport.
|
||||
* @param viewportId - The ID of the viewport.
|
||||
* @returns The presentations for the viewport.
|
||||
*/
|
||||
public getPresentations(viewportId: string): Presentations {
|
||||
const viewportInfo = this.viewportsById.get(viewportId);
|
||||
if (!viewportInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const positionPresentation = this.getPositionPresentation(viewportId);
|
||||
const lutPresentation = this.getLutPresentation(viewportId);
|
||||
|
||||
return {
|
||||
positionPresentation,
|
||||
lutPresentation,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the presentation state for a given viewport inside the
|
||||
* stateSyncService. This is used to persist the presentation state
|
||||
* across different scenarios e.g., when the viewport is changing the
|
||||
* display set, or when the viewport is moving to a different layout.
|
||||
*
|
||||
* @param viewportId The ID of the viewport.
|
||||
*/
|
||||
public storePresentation({ viewportId }) {
|
||||
let presentations = null as Presentations;
|
||||
try {
|
||||
presentations = this.getPresentations(viewportId);
|
||||
if (!presentations?.positionPresentation && !presentations?.lutPresentation) {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const { stateSyncService, syncGroupService } = this.servicesManager.services;
|
||||
|
||||
const synchronizers = syncGroupService.getSynchronizersForViewport(viewportId);
|
||||
|
||||
const { positionPresentationStore, synchronizersStore, lutPresentationStore } =
|
||||
stateSyncService.getState();
|
||||
|
||||
const { lutPresentation, positionPresentation } = presentations;
|
||||
const { id: positionPresentationId } = positionPresentation;
|
||||
const { id: lutPresentationId } = lutPresentation;
|
||||
|
||||
const updateStore = (store, id, value) => ({ ...store, [id]: value });
|
||||
|
||||
const newState = {} as { [key: string]: any };
|
||||
|
||||
if (lutPresentationId) {
|
||||
storeState.lutPresentationStore = {
|
||||
...lutPresentationStore,
|
||||
[lutPresentationId]: presentation,
|
||||
};
|
||||
newState.lutPresentationStore = updateStore(
|
||||
lutPresentationStore,
|
||||
lutPresentationId,
|
||||
lutPresentation
|
||||
);
|
||||
}
|
||||
|
||||
if (positionPresentationId) {
|
||||
storeState.positionPresentationStore = {
|
||||
...positionPresentationStore,
|
||||
[positionPresentationId]: presentation,
|
||||
};
|
||||
newState.positionPresentationStore = updateStore(
|
||||
positionPresentationStore,
|
||||
positionPresentationId,
|
||||
positionPresentation
|
||||
);
|
||||
}
|
||||
stateSyncService.store(storeState);
|
||||
|
||||
if (synchronizers?.length) {
|
||||
newState.synchronizersStore = updateStore(
|
||||
synchronizersStore,
|
||||
viewportId,
|
||||
synchronizers.map(synchronizer => ({
|
||||
id: synchronizer.id,
|
||||
sourceViewports: [...synchronizer.getSourceViewports()],
|
||||
targetViewports: [...synchronizer.getTargetViewports()],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
stateSyncService.store(newState);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,9 +484,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
});
|
||||
}
|
||||
|
||||
public getCornerstoneViewport(
|
||||
viewportId: string
|
||||
): Types.IStackViewport | Types.IVolumeViewport | null {
|
||||
/**
|
||||
* Retrieves the Cornerstone viewport with the specified ID.
|
||||
*
|
||||
* @param viewportId - The ID of the viewport.
|
||||
* @returns The Cornerstone viewport object if found, otherwise null.
|
||||
*/
|
||||
public getCornerstoneViewport(viewportId: string): Types.IViewport | null {
|
||||
const viewportInfo = this.getViewportInfo(viewportId);
|
||||
|
||||
if (!viewportInfo || !this.renderingEngine || this.renderingEngine.hasBeenDestroyed) {
|
||||
@@ -315,15 +502,51 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return viewport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the viewport information for a given viewport ID. The viewport information
|
||||
* is the OHIF construct that holds different options and data for a given viewport and
|
||||
* is different from the cornerstone viewport.
|
||||
*
|
||||
* @param viewportId The ID of the viewport.
|
||||
* @returns The viewport information.
|
||||
*/
|
||||
public getViewportInfo(viewportId: string): ViewportInfo {
|
||||
return this.viewportsById.get(viewportId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks through the viewports to see if the specified measurement can be
|
||||
* displayed in one of the viewports.
|
||||
*
|
||||
* @param measurement
|
||||
* The measurement that is desired to view.
|
||||
* @param activeViewportId - the index that was active at the time the jump
|
||||
* was initiated.
|
||||
* @return the viewportId that the measurement should be displayed in.
|
||||
*/
|
||||
public getViewportIdToJump(
|
||||
activeViewportId: string,
|
||||
displaySetInstanceUID: string,
|
||||
cameraProps: unknown
|
||||
): string {
|
||||
const viewportInfo = this.getViewportInfo(activeViewportId);
|
||||
const { referencedImageId } = cameraProps;
|
||||
if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) {
|
||||
return activeViewportId;
|
||||
}
|
||||
|
||||
return (
|
||||
[...this.viewportsById.values()].find(viewportInfo =>
|
||||
viewportInfo.contains(displaySetInstanceUID, referencedImageId)
|
||||
)?.viewportId ?? null
|
||||
);
|
||||
}
|
||||
|
||||
private async _setStackViewport(
|
||||
viewport: Types.IStackViewport,
|
||||
viewportData: StackViewportData,
|
||||
viewportInfo: ViewportInfo,
|
||||
presentations: Presentations
|
||||
presentations: Presentations = {}
|
||||
): Promise<void> {
|
||||
const displaySetOptions = viewportInfo.getDisplaySetOptions();
|
||||
|
||||
@@ -340,7 +563,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
|
||||
const properties = { ...presentations.lutPresentation?.properties };
|
||||
if (!presentations.lutPresentation?.properties) {
|
||||
const { voi, voiInverted } = displaySetOptions[0];
|
||||
const { voi, voiInverted, colormap } = displaySetOptions[0];
|
||||
if (voi && (voi.windowWidth || voi.windowCenter)) {
|
||||
const { lower, upper } = csUtils.windowLevel.toLowHighRange(
|
||||
voi.windowWidth,
|
||||
@@ -352,14 +575,15 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
if (voiInverted !== undefined) {
|
||||
properties.invert = voiInverted;
|
||||
}
|
||||
|
||||
if (colormap !== undefined) {
|
||||
properties.colormap = colormap;
|
||||
}
|
||||
}
|
||||
|
||||
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
|
||||
viewport.setProperties({ ...properties });
|
||||
const camera = presentations.positionPresentation?.camera;
|
||||
if (camera) {
|
||||
viewport.setCamera(camera);
|
||||
}
|
||||
this.setPresentations(viewport.id, presentations);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -427,7 +651,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
viewport: Types.IVolumeViewport,
|
||||
viewportData: VolumeViewportData,
|
||||
viewportInfo: ViewportInfo,
|
||||
presentations: Presentations
|
||||
presentations: Presentations = {}
|
||||
): Promise<void> {
|
||||
// TODO: We need to overhaul the way data sources work so requests can be made
|
||||
// async. I think we should follow the image loader pattern which is async and
|
||||
@@ -472,20 +696,24 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
|
||||
this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs);
|
||||
|
||||
if (hangingProtocolService.getShouldPerformCustomImageLoad()) {
|
||||
// delegate the volume loading to the hanging protocol service if it has a custom image load strategy
|
||||
return hangingProtocolService.runImageLoadStrategy({
|
||||
viewportId: viewport.id,
|
||||
volumeInputArray,
|
||||
const volumesNotLoaded = volumeToLoad.filter(volume => !volume.loadStatus.loaded);
|
||||
|
||||
if (volumesNotLoaded.length) {
|
||||
if (hangingProtocolService.getShouldPerformCustomImageLoad()) {
|
||||
// delegate the volume loading to the hanging protocol service if it has a custom image load strategy
|
||||
return hangingProtocolService.runImageLoadStrategy({
|
||||
viewportId: viewport.id,
|
||||
volumeInputArray,
|
||||
});
|
||||
}
|
||||
|
||||
volumesNotLoaded.forEach(volume => {
|
||||
if (!volume.loadStatus.loading) {
|
||||
volume.load();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
volumeToLoad.forEach(volume => {
|
||||
if (!volume.loadStatus.loaded && !volume.loadStatus.loading) {
|
||||
volume.load();
|
||||
}
|
||||
});
|
||||
|
||||
// This returns the async continuation only
|
||||
return this.setVolumesForViewport(viewport, volumeInputArray, presentations);
|
||||
}
|
||||
@@ -531,7 +759,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
viewport.setProperties(properties, volumeId);
|
||||
});
|
||||
|
||||
this.setPresentations(viewport, presentations);
|
||||
this.setPresentations(viewport.id, presentations);
|
||||
|
||||
// load any secondary displaySets
|
||||
const displaySetInstanceUIDs = this.viewportsDisplaySets.get(viewport.id);
|
||||
@@ -685,7 +913,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
_setDisplaySets(
|
||||
viewport: StackViewport | VolumeViewport,
|
||||
viewport: Types.IViewport,
|
||||
viewportData: StackViewportData | VolumeViewportData,
|
||||
viewportInfo: ViewportInfo,
|
||||
presentations: Presentations = {}
|
||||
@@ -697,16 +925,18 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
viewportInfo,
|
||||
presentations
|
||||
);
|
||||
} else if (viewport instanceof VolumeViewport || viewport instanceof VolumeViewport3D) {
|
||||
}
|
||||
|
||||
if ([VolumeViewport, VolumeViewport3D].some(type => viewport instanceof type)) {
|
||||
return this._setVolumeViewport(
|
||||
viewport,
|
||||
viewport as Types.IVolumeViewport,
|
||||
viewportData as VolumeViewportData,
|
||||
viewportInfo,
|
||||
presentations
|
||||
);
|
||||
} else {
|
||||
throw new Error('Unknown viewport type');
|
||||
}
|
||||
|
||||
throw new Error('Unknown viewport type');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -772,32 +1002,57 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks through the viewports to see if the specified measurement can be
|
||||
* displayed in one of the viewports.
|
||||
*
|
||||
* @param measurement
|
||||
* The measurement that is desired to view.
|
||||
* @param activeViewportId - the index that was active at the time the jump
|
||||
* was initiated.
|
||||
* @return the viewportId that the measurement should be displayed in.
|
||||
*/
|
||||
public getViewportIdToJump(
|
||||
activeViewportId: string,
|
||||
displaySetInstanceUID: string,
|
||||
cameraProps: unknown
|
||||
): string {
|
||||
const viewportInfo = this.getViewportInfo(activeViewportId);
|
||||
const { referencedImageId } = cameraProps;
|
||||
if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) {
|
||||
return activeViewportId;
|
||||
private enqueueViewportResizeRequest() {
|
||||
this.resizeQueue.push(false); // false indicates viewport resize
|
||||
|
||||
clearTimeout(this.viewportResizeTimer);
|
||||
this.viewportResizeTimer = setTimeout(() => {
|
||||
this.processViewportResizeQueue();
|
||||
}, this.gridResizeDelay);
|
||||
}
|
||||
|
||||
private processViewportResizeQueue() {
|
||||
const isGridResizeInQueue = this.resizeQueue.some(isGridResize => isGridResize);
|
||||
if (this.resizeQueue.length > 0 && !isGridResizeInQueue && !this.gridResizeTimeOut) {
|
||||
this.performResize();
|
||||
}
|
||||
|
||||
return (
|
||||
[...this.viewportsById.values()].find(viewportInfo =>
|
||||
viewportInfo.contains(displaySetInstanceUID, referencedImageId)
|
||||
)?.viewportId ?? null
|
||||
);
|
||||
// Clear the queue after processing viewport resizes
|
||||
this.resizeQueue = [];
|
||||
}
|
||||
|
||||
private performResize() {
|
||||
const isImmediate = false;
|
||||
|
||||
const viewports = this.getRenderingEngine().getViewports();
|
||||
|
||||
// Store the current position presentations for each viewport.
|
||||
viewports.forEach(({ id }) => {
|
||||
const presentation = this.getPositionPresentation(id);
|
||||
this.beforeResizePositionPresentations.set(id, presentation);
|
||||
});
|
||||
|
||||
// Resize the rendering engine and render.
|
||||
const renderingEngine = this.renderingEngine;
|
||||
renderingEngine.resize(isImmediate);
|
||||
renderingEngine.render();
|
||||
|
||||
// Reset the camera for viewports that should reset their camera on resize,
|
||||
// which means only those viewports that have a zoom level of 1.
|
||||
this.beforeResizePositionPresentations.forEach((positionPresentation, viewportId) => {
|
||||
this.setPresentations(viewportId, { positionPresentation });
|
||||
});
|
||||
|
||||
// Resize and render the rendering engine again.
|
||||
renderingEngine.resize(isImmediate);
|
||||
renderingEngine.render();
|
||||
}
|
||||
|
||||
private resetGridResizeTimeout() {
|
||||
clearTimeout(this.gridResizeTimeOut);
|
||||
this.gridResizeTimeOut = setTimeout(() => {
|
||||
this.gridResizeTimeOut = null;
|
||||
}, this.gridResizeDelay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Types } from '@cornerstonejs/core';
|
||||
import { StackData, VolumeData } from '../../types/CornerstoneCacheService';
|
||||
import { DisplaySetOptions, PublicViewportOptions, ViewportOptions } from './Viewport';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
import { DisplaySetOptions, PublicViewportOptions } from './Viewport';
|
||||
import { Presentations } from '../../types/Presentation';
|
||||
|
||||
/**
|
||||
* Handles cornerstone viewport logic including enabling, disabling, and
|
||||
@@ -36,7 +37,7 @@ export interface IViewportService {
|
||||
* the element for resizing events
|
||||
* @param {*} elementRef
|
||||
*/
|
||||
resize(element: HTMLDivElement): void;
|
||||
resize(isGridResize: boolean): void;
|
||||
/**
|
||||
* Removes the viewport from cornerstone, and destroys the rendering engine
|
||||
*/
|
||||
@@ -55,8 +56,10 @@ export interface IViewportService {
|
||||
* @returns
|
||||
*/
|
||||
setViewportData(
|
||||
viewportData: StackData | VolumeData,
|
||||
viewportId: string,
|
||||
viewportData: StackViewportData | VolumeViewportData,
|
||||
publicViewportOptions: PublicViewportOptions,
|
||||
publicDisplaySetOptions: DisplaySetOptions[]
|
||||
publicDisplaySetOptions: DisplaySetOptions[],
|
||||
presentations?: Presentations
|
||||
): void;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Types, Enums } from '@cornerstonejs/core';
|
||||
import { Types as UITypes } from '@ohif/ui';
|
||||
import { Types as CoreTypes } from '@ohif/core';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
import getCornerstoneBlendMode from '../../utils/getCornerstoneBlendMode';
|
||||
import getCornerstoneOrientation from '../../utils/getCornerstoneOrientation';
|
||||
@@ -18,7 +18,7 @@ export type ViewportOptions = {
|
||||
toolGroupId: string;
|
||||
viewportId: string;
|
||||
// Presentation ID to store/load presentation state from
|
||||
presentationIds?: UITypes.PresentationIds;
|
||||
presentationIds?: CoreTypes.PresentationIds;
|
||||
orientation?: Enums.OrientationAxis;
|
||||
background?: Types.Point3;
|
||||
displayArea?: Types.DisplayArea;
|
||||
@@ -36,7 +36,7 @@ export type PublicViewportOptions = {
|
||||
id?: string;
|
||||
viewportType?: string;
|
||||
toolGroupId?: string;
|
||||
presentationIds?: UITypes.PresentationIds;
|
||||
presentationIds?: CoreTypes.PresentationIds;
|
||||
viewportId?: string;
|
||||
orientation?: Enums.OrientationAxis;
|
||||
background?: Types.Point3;
|
||||
@@ -71,7 +71,7 @@ export type DisplaySetOptions = {
|
||||
voiInverted: boolean;
|
||||
blendMode?: Enums.BlendModes;
|
||||
slabThickness?: number;
|
||||
colormap?: string;
|
||||
colormap?: { name: string; opacity?: number };
|
||||
displayPreset?: string;
|
||||
};
|
||||
|
||||
@@ -238,6 +238,11 @@ class ViewportInfo {
|
||||
return this.viewportOptions;
|
||||
}
|
||||
|
||||
public getPresentationIds(): CoreTypes.PresentationIds {
|
||||
const { presentationIds } = this.viewportOptions;
|
||||
return presentationIds;
|
||||
}
|
||||
|
||||
public setDisplaySetOptions(displaySetOptions: Array<DisplaySetOptions>): void {
|
||||
this.displaySetOptions = displaySetOptions;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { VolumeViewport, metaData, utilities } from '@cornerstonejs/core';
|
||||
import { IStackViewport, IVolumeViewport, Point3 } from '@cornerstonejs/core/dist/esm/types';
|
||||
import { AnnotationDisplayTool, drawing } from '@cornerstonejs/tools';
|
||||
import { guid } from '@ohif/core/src/utils';
|
||||
import { guid, b64toBlob } from '@ohif/core/src/utils';
|
||||
import OverlayPlaneModuleProvider from './OverlayPlaneModuleProvider';
|
||||
|
||||
interface CachedStat {
|
||||
@@ -46,7 +46,7 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
||||
super(toolProps, defaultToolProps);
|
||||
}
|
||||
|
||||
onSetToolDisabled = (): void => { };
|
||||
onSetToolDisabled = (): void => {};
|
||||
|
||||
protected getReferencedImageId(viewport: IStackViewport | IVolumeViewport): string {
|
||||
if (viewport instanceof VolumeViewport) {
|
||||
@@ -174,6 +174,10 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
||||
pixelData = overlay.pixelData[0];
|
||||
} else if (overlay.pixelData.retrieveBulkData) {
|
||||
pixelData = await overlay.pixelData.retrieveBulkData();
|
||||
} else if (overlay.pixelData.InlineBinary) {
|
||||
const blob = b64toBlob(overlay.pixelData.InlineBinary);
|
||||
const arrayBuffer = await blob.arrayBuffer();
|
||||
pixelData = arrayBuffer;
|
||||
}
|
||||
|
||||
if (!pixelData) {
|
||||
|
||||
@@ -1,23 +1,45 @@
|
||||
/** Store presentation data for either stack viewports or volume viewports */
|
||||
import { Types } from '@cornerstonejs/core';
|
||||
import { Types as UITypes } from '@ohif/ui';
|
||||
import type { Types } from '@cornerstonejs/core';
|
||||
|
||||
/**
|
||||
* Has information on the presentation of the viewport.
|
||||
* Represents a position presentation in a viewport. This is basically
|
||||
* viewport specific camera position and zoom, and not the display set
|
||||
*/
|
||||
export interface Presentation extends Types.StackViewportProperties {
|
||||
presentationIds: UITypes.PresentationIds;
|
||||
export type PositionPresentation = {
|
||||
id: string;
|
||||
viewportType: string;
|
||||
initialImageIndex: number;
|
||||
camera: Types.ICamera;
|
||||
properties: Types.StackViewportProperties | Types.VolumeViewportProperties;
|
||||
zoom?: number;
|
||||
pan?: [number, number];
|
||||
presentation: {
|
||||
initialImageIndex: number;
|
||||
viewUp: Types.Point3;
|
||||
viewPlaneNormal: Types.Point3;
|
||||
zoom?: number;
|
||||
pan?: Types.Point2;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a LUT presentation in a viewport, and is really related
|
||||
* to displaySets and not the viewport itself. So that is why it can
|
||||
* be an object with volumeId keys, or a single object with the properties
|
||||
* itself
|
||||
*/
|
||||
export interface LutPresentation {
|
||||
id: string;
|
||||
viewportType: string;
|
||||
presentation: Record<string, Types.ViewportProperties> | Types.ViewportProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presentation can be a PositionPresentation or a LutPresentation.
|
||||
*/
|
||||
type Presentation = PositionPresentation | LutPresentation;
|
||||
|
||||
/**
|
||||
* Viewport presentations object that can contain a positionPresentation
|
||||
* and or a lutPresentation.
|
||||
*/
|
||||
export type Presentations = {
|
||||
positionPresentation?: Presentation;
|
||||
lutPresentation?: Presentation;
|
||||
positionPresentation?: PositionPresentation;
|
||||
lutPresentation?: LutPresentation;
|
||||
};
|
||||
|
||||
export default Presentation;
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import html2canvas from 'html2canvas';
|
||||
import {
|
||||
Enums,
|
||||
getEnabledElement,
|
||||
getOrCreateCanvas,
|
||||
StackViewport,
|
||||
VolumeViewport,
|
||||
BaseVolumeViewport,
|
||||
} from '@cornerstonejs/core';
|
||||
import { ToolGroupManager } from '@cornerstonejs/tools';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -93,6 +93,7 @@ const CornerstoneViewportDownloadForm = ({
|
||||
renderingEngine.resize();
|
||||
|
||||
// Trigger the render on the viewport to update the on screen
|
||||
downloadViewport.resetCamera();
|
||||
downloadViewport.render();
|
||||
|
||||
downloadViewportElement.addEventListener(
|
||||
@@ -153,7 +154,7 @@ const CornerstoneViewportDownloadForm = ({
|
||||
console.warn('Unable to set properties', e);
|
||||
}
|
||||
});
|
||||
} else if (downloadViewport instanceof VolumeViewport) {
|
||||
} else if (downloadViewport instanceof BaseVolumeViewport) {
|
||||
const actors = viewport.getActors();
|
||||
// downloadViewport.setActors(actors);
|
||||
actors.forEach(actor => {
|
||||
@@ -188,7 +189,7 @@ const CornerstoneViewportDownloadForm = ({
|
||||
// add the viewport to the toolGroup
|
||||
toolGroup.addViewport(downloadViewportId, renderingEngineId);
|
||||
|
||||
Object.keys(toolGroup._toolInstances).forEach(toolName => {
|
||||
Object.keys(toolGroup.getToolInstances()).forEach(toolName => {
|
||||
// make all tools Enabled so that they can not be interacted with
|
||||
// in the download viewport
|
||||
if (toggle && toolName !== 'Crosshairs') {
|
||||
@@ -223,7 +224,6 @@ const CornerstoneViewportDownloadForm = ({
|
||||
minimumSize={MINIMUM_SIZE}
|
||||
maximumSize={MAX_TEXTURE_SIZE}
|
||||
defaultSize={DEFAULT_SIZE}
|
||||
canvasClass={'cornerstone-canvas'}
|
||||
activeViewportElement={activeViewportElement}
|
||||
enableViewport={enableViewport}
|
||||
disableViewport={disableViewport}
|
||||
|
||||
File renamed without changes.
+24
-9
@@ -1,20 +1,35 @@
|
||||
const STACK_SYNC_NAME = 'stackImageSync';
|
||||
const IMAGE_SLICE_SYNC_NAME = 'IMAGE_SLICE_SYNC';
|
||||
|
||||
export default function toggleStackImageSync({
|
||||
toggledState,
|
||||
export default function toggleImageSliceSync({
|
||||
servicesManager,
|
||||
viewports: providedViewports,
|
||||
syncId,
|
||||
}) {
|
||||
if (!toggledState) {
|
||||
return disableSync(STACK_SYNC_NAME, servicesManager);
|
||||
}
|
||||
|
||||
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
|
||||
servicesManager.services;
|
||||
|
||||
syncId ||= IMAGE_SLICE_SYNC_NAME;
|
||||
|
||||
const viewports =
|
||||
providedViewports || getReconstructableStackViewports(viewportGridService, displaySetService);
|
||||
|
||||
// Todo: right now we don't have a proper way to define specific
|
||||
// viewports to add to synchronizers, and right now it is global or not
|
||||
// after we do that, we should do fine grained control of the synchronizers
|
||||
const someViewportHasSync = viewports.some(viewport => {
|
||||
const syncStates = syncGroupService.getSynchronizersForViewport(
|
||||
viewport.viewportOptions.viewportId
|
||||
);
|
||||
|
||||
const imageSync = syncStates.find(syncState => syncState.id === syncId);
|
||||
|
||||
return !!imageSync;
|
||||
});
|
||||
|
||||
if (someViewportHasSync) {
|
||||
return disableSync(syncId, servicesManager);
|
||||
}
|
||||
|
||||
// create synchronization group and add the viewports to it.
|
||||
viewports.forEach(gridViewport => {
|
||||
const { viewportId } = gridViewport.viewportOptions;
|
||||
@@ -23,8 +38,8 @@ export default function toggleStackImageSync({
|
||||
return;
|
||||
}
|
||||
syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, {
|
||||
type: 'stackimage',
|
||||
id: STACK_SYNC_NAME,
|
||||
type: 'imageSlice',
|
||||
id: syncId,
|
||||
source: true,
|
||||
target: true,
|
||||
});
|
||||
@@ -1,13 +1,7 @@
|
||||
import { log, Types } from '@ohif/core';
|
||||
import { log, Enums } from '@ohif/core';
|
||||
import { EVENTS } from '@cornerstonejs/core';
|
||||
|
||||
const { TimingEnum } = Types;
|
||||
|
||||
const IMAGE_TIMING_KEYS = [
|
||||
TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES,
|
||||
TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE,
|
||||
TimingEnum.STUDY_TO_FIRST_IMAGE,
|
||||
];
|
||||
const IMAGE_TIMING_KEYS = [];
|
||||
|
||||
const imageTiming = {
|
||||
viewportsWaiting: 0,
|
||||
@@ -23,6 +17,18 @@ const imageTiming = {
|
||||
*/
|
||||
|
||||
export default function initViewTiming({ element }) {
|
||||
if (!IMAGE_TIMING_KEYS.length) {
|
||||
// Work around a bug in WebPack that doesn't getting the enums initialized
|
||||
// quite fast enough to be declared statically.
|
||||
const { TimingEnum } = Enums;
|
||||
|
||||
IMAGE_TIMING_KEYS.push(
|
||||
TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES,
|
||||
TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE,
|
||||
TimingEnum.STUDY_TO_FIRST_IMAGE,
|
||||
);
|
||||
}
|
||||
|
||||
if (!IMAGE_TIMING_KEYS.find(key => log.timingKeys[key])) {
|
||||
return;
|
||||
}
|
||||
@@ -34,6 +40,7 @@ function imageRenderedListener(evt) {
|
||||
if (evt.detail.viewportStatus === 'preRender') {
|
||||
return;
|
||||
}
|
||||
const { TimingEnum } = Enums;
|
||||
log.timeEnd(TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
|
||||
log.timeEnd(TimingEnum.STUDY_TO_FIRST_IMAGE);
|
||||
log.timeEnd(TimingEnum.SCRIPT_TO_VIEW);
|
||||
|
||||
@@ -50,7 +50,7 @@ const Angle = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@@ -62,6 +62,7 @@ const Angle = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -48,7 +48,7 @@ const Length = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@@ -59,6 +59,7 @@ const Length = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -45,7 +45,7 @@ const Bidirectional = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@@ -57,6 +57,7 @@ const Bidirectional = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -43,7 +43,7 @@ const CircleROI = {
|
||||
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService);
|
||||
|
||||
@@ -55,6 +55,7 @@ const CircleROI = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -50,7 +50,7 @@ const CobbAngle = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@@ -62,6 +62,7 @@ const CobbAngle = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -43,7 +43,7 @@ const EllipticalROI = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@@ -55,6 +55,7 @@ const EllipticalROI = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -53,7 +53,7 @@ const Length = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@@ -65,6 +65,7 @@ const Length = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -48,7 +48,7 @@ const PlanarFreehandROI = {
|
||||
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService);
|
||||
|
||||
@@ -60,6 +60,7 @@ const PlanarFreehandROI = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -43,7 +43,7 @@ const RectangleROI = {
|
||||
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService);
|
||||
|
||||
@@ -55,6 +55,7 @@ const RectangleROI = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
const VOI_SYNC_NAME = 'VOI_SYNC';
|
||||
|
||||
const getSyncId = modality => `${VOI_SYNC_NAME}_${modality}`;
|
||||
|
||||
export default function toggleVOISliceSync({
|
||||
servicesManager,
|
||||
viewports: providedViewports,
|
||||
syncId,
|
||||
}) {
|
||||
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
|
||||
servicesManager.services;
|
||||
|
||||
const viewports =
|
||||
providedViewports || groupViewportsByModality(viewportGridService, displaySetService);
|
||||
|
||||
// Todo: right now we don't have a proper way to define specific
|
||||
// viewports to add to synchronizers, and right now it is global or not
|
||||
// after we do that, we should do fine grained control of the synchronizers
|
||||
|
||||
// we can apply voi sync within each modality group
|
||||
for (const [modality, modalityViewports] of Object.entries(viewports)) {
|
||||
const syncIdToUse = syncId || getSyncId(modality);
|
||||
|
||||
const someViewportHasSync = modalityViewports.some(viewport => {
|
||||
const syncStates = syncGroupService.getSynchronizersForViewport(
|
||||
viewport.viewportOptions.viewportId
|
||||
);
|
||||
|
||||
const imageSync = syncStates.find(syncState => syncState.id === syncIdToUse);
|
||||
|
||||
return !!imageSync;
|
||||
});
|
||||
|
||||
if (someViewportHasSync) {
|
||||
return disableSync(modalityViewports, syncIdToUse, servicesManager);
|
||||
}
|
||||
|
||||
// create synchronization group and add the modalityViewports to it.
|
||||
modalityViewports.forEach(gridViewport => {
|
||||
const { viewportId } = gridViewport.viewportOptions;
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, {
|
||||
type: 'voi',
|
||||
id: syncIdToUse,
|
||||
source: true,
|
||||
target: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function disableSync(modalityViewports, syncId, servicesManager) {
|
||||
const { syncGroupService, cornerstoneViewportService } = servicesManager.services;
|
||||
|
||||
const viewports = modalityViewports;
|
||||
viewports.forEach(gridViewport => {
|
||||
const { viewportId } = gridViewport.viewportOptions;
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
syncGroupService.removeViewportFromSyncGroup(
|
||||
viewport.id,
|
||||
viewport.getRenderingEngine().id,
|
||||
syncId
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function groupViewportsByModality(viewportGridService, displaySetService) {
|
||||
let { viewports } = viewportGridService.getState();
|
||||
|
||||
viewports = [...viewports.values()];
|
||||
|
||||
// group the viewports by modality
|
||||
return viewports.reduce((acc, viewport) => {
|
||||
const { displaySetInstanceUIDs } = viewport;
|
||||
// Todo: add proper fusion support
|
||||
const displaySetInstanceUID = displaySetInstanceUIDs[0];
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
const modality = displaySet.Modality;
|
||||
if (!acc[modality]) {
|
||||
acc[modality] = [];
|
||||
}
|
||||
|
||||
acc[modality].push(viewport);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
@@ -3,6 +3,417 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **layout:** new layout selector with 3D volume rendering ([#3923](https://github.com/OHIF/Viewers/issues/3923)) ([617043f](https://github.com/OHIF/Viewers/commit/617043fe0da5de91fbea4ac33a27f1df16ae1ca6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **worklist:** new investigational use text ([#3999](https://github.com/OHIF/Viewers/issues/3999)) ([45b68e8](https://github.com/OHIF/Viewers/commit/45b68e841dcb9e28a2ea991c37ee7ac4a8c5b71e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Sort merge results based on default data source (input) ([#3903](https://github.com/OHIF/Viewers/issues/3903)) ([5bba98e](https://github.com/OHIF/Viewers/commit/5bba98ed848bdf46b5ba4fc4708527cced3308b5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* catch errors in getPTImageIdInstanceMetadata ([#3897](https://github.com/OHIF/Viewers/issues/3897)) ([a47aeb8](https://github.com/OHIF/Viewers/commit/a47aeb8bd729dcb8d2cfc13b27a31b0dd88f11ad))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Check merge key for merge data source ([#3901](https://github.com/OHIF/Viewers/issues/3901)) ([911d672](https://github.com/OHIF/Viewers/commit/911d67283536b2fe7930948f2819ea0ad66e2a32))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **hp:** enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol ([#3804](https://github.com/OHIF/Viewers/issues/3804)) ([0049f4c](https://github.com/OHIF/Viewers/commit/0049f4c0303f0b6ea995972326fc8784259f5a47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ui:** sidePanel expandedWidth ([#3728](https://github.com/OHIF/Viewers/issues/3728)) ([61bf22c](https://github.com/OHIF/Viewers/commit/61bf22c6f80e764bdf5c3b56bb0124a95aa0f793))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve disableEditing flag ([#3875](https://github.com/OHIF/Viewers/issues/3875)) ([2049c09](https://github.com/OHIF/Viewers/commit/2049c0936c86f819604c243d3dc7b3fe971b5b2c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* PDF display request in v3 ([#3878](https://github.com/OHIF/Viewers/issues/3878)) ([9865030](https://github.com/OHIF/Viewers/commit/98650302c7575f0aea386e32cfc4112c378035e6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** enhanced i18n support ([#3761](https://github.com/OHIF/Viewers/issues/3761)) ([d14a8f0](https://github.com/OHIF/Viewers/commit/d14a8f0199db95cd9e85866a011b64d6bf830d57))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SM:** drag and drop is now fixed for SM ([#3813](https://github.com/OHIF/Viewers/issues/3813)) ([f1a6764](https://github.com/OHIF/Viewers/commit/f1a67647aed635437b188cea7cf5d5a8fb974bbe))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Merge Data Source ([#3788](https://github.com/OHIF/Viewers/issues/3788)) ([c4ff2c2](https://github.com/OHIF/Viewers/commit/c4ff2c2f09546ce8b72eab9c5e7beed611e3cab0))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('../../babel.config.js');
|
||||
@@ -0,0 +1,17 @@
|
||||
const base = require('../../jest.config.base.js');
|
||||
const pkg = require('./package');
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
name: pkg.name,
|
||||
displayName: pkg.name,
|
||||
moduleNameMapper: {
|
||||
...base.moduleNameMapper,
|
||||
'@ohif/(.*)': '<rootDir>/../../platform/$1/src',
|
||||
},
|
||||
// rootDir: "../.."
|
||||
// testMatch: [
|
||||
// //`<rootDir>/platform/${pack.name}/**/*.spec.js`
|
||||
// "<rootDir>/platform/app/**/*.test.js"
|
||||
// ]
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-default",
|
||||
"version": "3.8.0-beta.21",
|
||||
"version": "3.8.0-beta.66",
|
||||
"description": "Common/default features and functionality for basic image viewing",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
@@ -23,6 +23,8 @@
|
||||
"ohif-extension"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||
"dev:dicom-pdf": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
@@ -30,10 +32,10 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.8.0-beta.21",
|
||||
"@ohif/i18n": "3.8.0-beta.21",
|
||||
"@ohif/core": "3.8.0-beta.66",
|
||||
"@ohif/i18n": "3.8.0-beta.66",
|
||||
"dcmjs": "^0.29.12",
|
||||
"dicomweb-client": "^0.10.2",
|
||||
"dicomweb-client": "^0.10.4",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
||||
@@ -8,6 +8,7 @@ export type SidePanelWithServicesProps = {
|
||||
className: string;
|
||||
activeTabIndex: number;
|
||||
tabs: any;
|
||||
expandedWidth?: number;
|
||||
};
|
||||
|
||||
const SidePanelWithServices = ({
|
||||
@@ -16,7 +17,8 @@ const SidePanelWithServices = ({
|
||||
className,
|
||||
activeTabIndex: activeTabIndexProp,
|
||||
tabs,
|
||||
}) => {
|
||||
expandedWidth,
|
||||
}: SidePanelWithServicesProps) => {
|
||||
const panelService: PanelService = servicesManager?.services?.panelService;
|
||||
|
||||
// Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM.
|
||||
@@ -53,6 +55,7 @@ const SidePanelWithServices = ({
|
||||
onOpen={() => {
|
||||
setHasBeenOpened(true);
|
||||
}}
|
||||
expandedWidth={expandedWidth}
|
||||
></SidePanel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder';
|
||||
import ContextMenu from '../../../../platform/ui/src/components/ContextMenu/ContextMenu';
|
||||
import { CommandsManager, ServicesManager, Types } from '@ohif/core';
|
||||
import { annotation as CsAnnotation } from '@cornerstonejs/tools';
|
||||
import { Menu, MenuItem, Point, ContextMenuProps } from './types';
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,18 @@ export default class ContextMenuController {
|
||||
|
||||
const { event, subMenu, menuId, menus, selectorProps } = contextMenuProps;
|
||||
|
||||
console.log('Getting items from', menus);
|
||||
const annotationManager = CsAnnotation.state.getAnnotationManager();
|
||||
const { locking } = CsAnnotation;
|
||||
const targetAnnotationId = selectorProps?.nearbyToolData?.annotationUID as string;
|
||||
const isLocked = locking.isAnnotationLocked(
|
||||
annotationManager.getAnnotation(targetAnnotationId)
|
||||
);
|
||||
|
||||
if (isLocked) {
|
||||
console.warn('Annotation is locked.');
|
||||
return;
|
||||
}
|
||||
|
||||
const items = ContextMenuItemsBuilder.getMenuItems(
|
||||
selectorProps || contextMenuProps,
|
||||
event,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import ContextMenuItemsBuilder from './ContextMenuItemsBuilder';
|
||||
import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder';
|
||||
|
||||
const menus = [
|
||||
{
|
||||
id: 'one',
|
||||
selector: ({ value }) => value === 'one',
|
||||
selector: ({ value } = {}) => value === 'one',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
id: 'two',
|
||||
selector: ({ value }) => value === 'two',
|
||||
selector: ({ value } = {}) => value === 'two',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
@@ -17,13 +17,13 @@ const menus = [
|
||||
},
|
||||
];
|
||||
|
||||
const menuBuilder = new ContextMenuItemsBuilder();
|
||||
|
||||
describe('ContextMenuItemsBuilder', () => {
|
||||
test('findMenuDefault', () => {
|
||||
expect(menuBuilder.findMenuDefault(menus, {})).toBe(menus[2]);
|
||||
expect(menuBuilder.findMenuDefault(menus, { value: 'two' })).toBe(menus[1]);
|
||||
expect(menuBuilder.findMenuDefault([], {})).toBeUndefined();
|
||||
expect(menuBuilder.findMenuDefault(undefined, undefined)).toBeNull();
|
||||
expect(ContextMenuItemsBuilder.findMenuDefault(menus, {})).toBe(menus[2]);
|
||||
expect(
|
||||
ContextMenuItemsBuilder.findMenuDefault(menus, { selectorProps: { value: 'two' } })
|
||||
).toBe(menus[1]);
|
||||
expect(ContextMenuItemsBuilder.findMenuDefault([], {})).toBeUndefined();
|
||||
expect(ContextMenuItemsBuilder.findMenuDefault(undefined, undefined)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -70,8 +70,6 @@ export function findMenu(menus: Menu[], props?: Types.IProps, menuIdFilter?: str
|
||||
current = findIt.next();
|
||||
}
|
||||
|
||||
console.log('Menu chosen', menu?.id || 'NONE');
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,19 +28,38 @@ const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1';
|
||||
const metadataProvider = classes.MetadataProvider;
|
||||
|
||||
/**
|
||||
* Creates a DICOM Web API based on the provided configuration.
|
||||
*
|
||||
* @param {string} name - Data source name
|
||||
* @param {string} wadoUriRoot - Legacy? (potentially unused/replaced)
|
||||
* @param {string} qidoRoot - Base URL to use for QIDO requests
|
||||
* @param {string} wadoRoot - Base URL to use for WADO requests
|
||||
* @param {boolean} qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response
|
||||
* @param {string} imageRengering - wadors | ? (unsure of where/how this is used)
|
||||
* @param {string} thumbnailRendering - wadors | ? (unsure of where/how this is used)
|
||||
* @param {bool} supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE)
|
||||
* @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
|
||||
* @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true
|
||||
* @param {object} dicomWebConfig - Configuration for the DICOM Web API
|
||||
* @param {string} dicomWebConfig.name - Data source name
|
||||
* @param {string} dicomWebConfig.wadoUriRoot - Legacy? (potentially unused/replaced)
|
||||
* @param {string} dicomWebConfig.qidoRoot - Base URL to use for QIDO requests
|
||||
* @param {string} dicomWebConfig.wadoRoot - Base URL to use for WADO requests
|
||||
* @param {string} dicomWebConfig.wadoUri - Base URL to use for WADO URI requests
|
||||
* @param {boolean} dicomWebConfig.qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response
|
||||
* @param {string} dicomWebConfig.imageRendering - wadors | ? (unsure of where/how this is used)
|
||||
* @param {string} dicomWebConfig.thumbnailRendering - wadors | ? (unsure of where/how this is used)
|
||||
* @param {boolean} dicomWebConfig.supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE)
|
||||
* @param {boolean} dicomWebConfig.lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
|
||||
* @param {string|boolean} dicomWebConfig.singlepart - indicates if the retrieves can fetch singlepart. Options are bulkdata, video, image, or boolean true
|
||||
* @param {string} dicomWebConfig.requestTransferSyntaxUID - Transfer syntax to request from the server
|
||||
* @param {object} dicomWebConfig.acceptHeader - Accept header to use for requests
|
||||
* @param {boolean} dicomWebConfig.omitQuotationForMultipartRequest - Whether to omit quotation marks for multipart requests
|
||||
* @param {boolean} dicomWebConfig.supportsFuzzyMatching - Whether the server supports fuzzy matching
|
||||
* @param {boolean} dicomWebConfig.supportsWildcard - Whether the server supports wildcard matching
|
||||
* @param {boolean} dicomWebConfig.supportsNativeDICOMModel - Whether the server supports the native DICOM model
|
||||
* @param {boolean} dicomWebConfig.enableStudyLazyLoad - Whether to enable study lazy loading
|
||||
* @param {boolean} dicomWebConfig.enableRequestTag - Whether to enable request tag
|
||||
* @param {boolean} dicomWebConfig.enableStudyLazyLoad - Whether to enable study lazy loading
|
||||
* @param {boolean} dicomWebConfig.bulkDataURI - Whether to enable bulkDataURI
|
||||
* @param {function} dicomWebConfig.onConfiguration - Function that is called after the configuration is initialized
|
||||
* @param {boolean} dicomWebConfig.staticWado - Whether to use the static WADO client
|
||||
* @param {object} userAuthenticationService - User authentication service
|
||||
* @param {object} userAuthenticationService.getAuthorizationHeader - Function that returns the authorization header
|
||||
* @returns {object} - DICOM Web API object
|
||||
*/
|
||||
function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
function createDicomWebApi(dicomWebConfig, servicesManager) {
|
||||
const { userAuthenticationService, customizationService } = servicesManager.services;
|
||||
let dicomWebConfigCopy,
|
||||
qidoConfig,
|
||||
wadoConfig,
|
||||
@@ -140,7 +159,13 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
instances: {
|
||||
search: (studyInstanceUid, queryParameters) => {
|
||||
qidoDicomWebClient.headers = getAuthrorizationHeader();
|
||||
qidoSearch.call(undefined, qidoDicomWebClient, studyInstanceUid, null, queryParameters);
|
||||
return qidoSearch.call(
|
||||
undefined,
|
||||
qidoDicomWebClient,
|
||||
studyInstanceUid,
|
||||
null,
|
||||
queryParameters
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -184,6 +209,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
sortCriteria,
|
||||
sortFunction,
|
||||
madeInClient = false,
|
||||
returnPromises = false,
|
||||
} = {}) => {
|
||||
if (!StudyInstanceUID) {
|
||||
throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID');
|
||||
@@ -195,7 +221,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
filters,
|
||||
sortCriteria,
|
||||
sortFunction,
|
||||
madeInClient
|
||||
madeInClient,
|
||||
returnPromises
|
||||
);
|
||||
}
|
||||
|
||||
@@ -211,7 +238,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
},
|
||||
|
||||
store: {
|
||||
dicom: async (dataset, request) => {
|
||||
dicom: async (dataset, request, dicomDict) => {
|
||||
wadoDicomWebClient.headers = getAuthrorizationHeader();
|
||||
if (dataset instanceof ArrayBuffer) {
|
||||
const options = {
|
||||
@@ -220,21 +247,26 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
};
|
||||
await wadoDicomWebClient.storeInstances(options);
|
||||
} else {
|
||||
const meta = {
|
||||
FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value,
|
||||
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
||||
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
||||
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
|
||||
ImplementationClassUID,
|
||||
ImplementationVersionName,
|
||||
};
|
||||
let effectiveDicomDict = dicomDict;
|
||||
|
||||
const denaturalized = denaturalizeDataset(meta);
|
||||
const dicomDict = new DicomDict(denaturalized);
|
||||
if (!dicomDict) {
|
||||
const meta = {
|
||||
FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value,
|
||||
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
||||
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
||||
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
|
||||
ImplementationClassUID,
|
||||
ImplementationVersionName,
|
||||
};
|
||||
|
||||
dicomDict.dict = denaturalizeDataset(dataset);
|
||||
const denaturalized = denaturalizeDataset(meta);
|
||||
const defaultDicomDict = new DicomDict(denaturalized);
|
||||
defaultDicomDict.dict = denaturalizeDataset(dataset);
|
||||
|
||||
const part10Buffer = dicomDict.write();
|
||||
effectiveDicomDict = defaultDicomDict;
|
||||
}
|
||||
|
||||
const part10Buffer = effectiveDicomDict.write();
|
||||
|
||||
const options = {
|
||||
datasets: [part10Buffer],
|
||||
@@ -262,7 +294,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
enableStudyLazyLoad,
|
||||
filters,
|
||||
sortCriteria,
|
||||
sortFunction
|
||||
sortFunction,
|
||||
dicomWebConfig
|
||||
);
|
||||
|
||||
// first naturalize the data
|
||||
@@ -314,6 +347,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
Object.keys(instancesPerSeries).forEach(seriesInstanceUID =>
|
||||
DicomMetadataStore.addInstances(instancesPerSeries[seriesInstanceUID], madeInClient)
|
||||
);
|
||||
|
||||
return seriesSummaryMetadata;
|
||||
},
|
||||
|
||||
_retrieveSeriesMetadataAsync: async (
|
||||
@@ -321,7 +356,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
filters,
|
||||
sortCriteria,
|
||||
sortFunction,
|
||||
madeInClient = false
|
||||
madeInClient = false,
|
||||
returnPromises = false
|
||||
) => {
|
||||
const enableStudyLazyLoad = true;
|
||||
wadoDicomWebClient.headers = generateWadoHeader();
|
||||
@@ -333,7 +369,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
enableStudyLazyLoad,
|
||||
filters,
|
||||
sortCriteria,
|
||||
sortFunction
|
||||
sortFunction,
|
||||
dicomWebConfig
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -357,11 +394,12 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
// in which case it isn't necessary to re-read this.
|
||||
if (value && value.BulkDataURI && !value.Value) {
|
||||
// Provide a method to fetch bulkdata
|
||||
value.retrieveBulkData = () => {
|
||||
value.retrieveBulkData = (options = {}) => {
|
||||
// handle the scenarios where bulkDataURI is relative path
|
||||
fixBulkDataURI(value, naturalized, dicomWebConfig);
|
||||
|
||||
const options = {
|
||||
const { mediaType } = options;
|
||||
const useOptions = {
|
||||
// The bulkdata fetches work with either multipart or
|
||||
// singlepart, so set multipart to false to let the server
|
||||
// decide which type to respond with.
|
||||
@@ -372,9 +410,13 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
// isn't well specified in the standard, but is needed in
|
||||
// any implementation that stores static copies of the metadata
|
||||
StudyInstanceUID: naturalized.StudyInstanceUID,
|
||||
mediaTypes: mediaType
|
||||
? [{ mediaType }, { mediaType: 'application/octet-stream' }]
|
||||
: undefined,
|
||||
...options,
|
||||
};
|
||||
// Todo: this needs to be from wado dicom web client
|
||||
return qidoDicomWebClient.retrieveBulkData(options).then(val => {
|
||||
return qidoDicomWebClient.retrieveBulkData(useOptions).then(val => {
|
||||
// There are DICOM PDF cases where the first ArrayBuffer in the array is
|
||||
// the bulk data and DICOM video cases where the second ArrayBuffer is
|
||||
// the bulk data. Here we play it safe and do a find.
|
||||
@@ -395,7 +437,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
const naturalizedInstances = instances.map(addRetrieveBulkData);
|
||||
|
||||
// Adding instanceMetadata to OHIF MetadataProvider
|
||||
naturalizedInstances.forEach((instance, index) => {
|
||||
naturalizedInstances.forEach(instance => {
|
||||
instance.wadoRoot = dicomWebConfig.wadoRoot;
|
||||
instance.wadoUri = dicomWebConfig.wadoUri;
|
||||
|
||||
@@ -422,7 +464,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
}
|
||||
|
||||
function setSuccessFlag() {
|
||||
const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient);
|
||||
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
if (!study) {
|
||||
return;
|
||||
}
|
||||
@@ -437,13 +479,24 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
|
||||
DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient);
|
||||
|
||||
const seriesDeliveredPromises = seriesPromises.map(promise =>
|
||||
promise.then(instances => {
|
||||
const seriesDeliveredPromises = seriesPromises.map(promise => {
|
||||
if (!returnPromises) {
|
||||
promise?.start();
|
||||
}
|
||||
return promise.then(instances => {
|
||||
storeInstances(instances);
|
||||
})
|
||||
);
|
||||
await Promise.all(seriesDeliveredPromises);
|
||||
setSuccessFlag();
|
||||
});
|
||||
});
|
||||
|
||||
if (returnPromises) {
|
||||
Promise.all(seriesDeliveredPromises).then(() => setSuccessFlag());
|
||||
return seriesPromises;
|
||||
} else {
|
||||
await Promise.all(seriesDeliveredPromises);
|
||||
setSuccessFlag();
|
||||
}
|
||||
|
||||
return seriesSummaryMetadata;
|
||||
},
|
||||
deleteStudyMetadataPromise,
|
||||
getImageIdsForDisplaySet(displaySet) {
|
||||
@@ -473,7 +526,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
|
||||
return imageIds;
|
||||
},
|
||||
getImageIdsForInstance({ instance, frame }) {
|
||||
getImageIdsForInstance({ instance, frame = undefined }) {
|
||||
const imageIds = getImageId({
|
||||
instance,
|
||||
frame,
|
||||
|
||||
@@ -25,7 +25,8 @@ export function retrieveStudyMetadata(
|
||||
enableStudyLazyLoad,
|
||||
filters,
|
||||
sortCriteria,
|
||||
sortFunction
|
||||
sortFunction,
|
||||
dicomWebConfig = {}
|
||||
) {
|
||||
// @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all
|
||||
// and further requests for that metadata will always fail. On failure, we probably need to remove the
|
||||
@@ -38,9 +39,11 @@ export function retrieveStudyMetadata(
|
||||
throw new Error(`${moduleName}: Required 'StudyInstanceUID' parameter not provided.`);
|
||||
}
|
||||
|
||||
const promiseId = `${dicomWebConfig.name}:${StudyInstanceUID}`;
|
||||
|
||||
// Already waiting on result? Return cached promise
|
||||
if (StudyMetaDataPromises.has(StudyInstanceUID)) {
|
||||
return StudyMetaDataPromises.get(StudyInstanceUID);
|
||||
if (StudyMetaDataPromises.has(promiseId)) {
|
||||
return StudyMetaDataPromises.get(promiseId);
|
||||
}
|
||||
|
||||
let promise;
|
||||
@@ -71,7 +74,7 @@ export function retrieveStudyMetadata(
|
||||
}
|
||||
|
||||
// Store the promise in cache
|
||||
StudyMetaDataPromises.set(StudyInstanceUID, promise);
|
||||
StudyMetaDataPromises.set(promiseId, promise);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { api } from 'dicomweb-client';
|
||||
* performing searches doesn't work. This version fixes the query issue
|
||||
* by manually implementing a query option.
|
||||
*/
|
||||
|
||||
export default class StaticWadoClient extends api.DICOMwebClient {
|
||||
static studyFilterKeys = {
|
||||
studyinstanceuid: '0020000D',
|
||||
|
||||
@@ -11,10 +11,17 @@ export default class RetrieveMetadataLoader {
|
||||
* @param {Object} client The dicomweb-client.
|
||||
* @param {Array} studyInstanceUID Study instance ui to be retrieved
|
||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @param {Function} [sortSeries] - Custom sort function for series
|
||||
* @param {string} [filters.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @param {Object} [sortCriteria] - Custom sort criteria used for series
|
||||
* @param {Function} [sortFunction] - Custom sort function for series
|
||||
*/
|
||||
constructor(client, studyInstanceUID, filters = {}, sortCriteria, sortFunction) {
|
||||
constructor(
|
||||
client,
|
||||
studyInstanceUID,
|
||||
filters = {},
|
||||
sortCriteria = undefined,
|
||||
sortFunction = undefined
|
||||
) {
|
||||
this.client = client;
|
||||
this.studyInstanceUID = studyInstanceUID;
|
||||
this.filters = filters;
|
||||
@@ -26,7 +33,6 @@ export default class RetrieveMetadataLoader {
|
||||
const preLoadData = await this.preLoad();
|
||||
const loadData = await this.load(preLoadData);
|
||||
const postLoadData = await this.posLoad(loadData);
|
||||
|
||||
return postLoadData;
|
||||
}
|
||||
|
||||
@@ -37,13 +43,9 @@ export default class RetrieveMetadataLoader {
|
||||
async runLoaders(loaders) {
|
||||
let result;
|
||||
for (const loader of loaders) {
|
||||
try {
|
||||
result = await loader();
|
||||
if (result && result.length) {
|
||||
break; // closes iterator in case data is retrieved successfully
|
||||
}
|
||||
} catch (e) {
|
||||
throw e;
|
||||
result = await loader();
|
||||
if (result && result.length) {
|
||||
break; // closes iterator in case data is retrieved successfully
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,58 @@
|
||||
import dcmjs from 'dcmjs';
|
||||
import { sortStudySeries, sortingCriteria } from '@ohif/core/src/utils/sortStudy';
|
||||
import { sortStudySeries } from '@ohif/core/src/utils/sortStudy';
|
||||
import RetrieveMetadataLoader from './retrieveMetadataLoader';
|
||||
|
||||
// Series Date, Series Time, Series Description and Series Number to be included
|
||||
// in the series metadata query result
|
||||
const includeField = ['00080021', '00080031', '0008103E', '00200011'].join(',');
|
||||
|
||||
export class DeferredPromise {
|
||||
metadata = undefined;
|
||||
processFunction = undefined;
|
||||
internalPromise = undefined;
|
||||
thenFunction = undefined;
|
||||
rejectFunction = undefined;
|
||||
|
||||
setMetadata(metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
setProcessFunction(func) {
|
||||
this.processFunction = func;
|
||||
}
|
||||
getPromise() {
|
||||
return this.start();
|
||||
}
|
||||
start() {
|
||||
if (this.internalPromise) {
|
||||
return this.internalPromise;
|
||||
}
|
||||
this.internalPromise = this.processFunction();
|
||||
// in case then and reject functions called before start
|
||||
if (this.thenFunction) {
|
||||
this.then(this.thenFunction);
|
||||
this.thenFunction = undefined;
|
||||
}
|
||||
if (this.rejectFunction) {
|
||||
this.reject(this.rejectFunction);
|
||||
this.rejectFunction = undefined;
|
||||
}
|
||||
return this.internalPromise;
|
||||
}
|
||||
then(func) {
|
||||
if (this.internalPromise) {
|
||||
return this.internalPromise.then(func);
|
||||
} else {
|
||||
this.thenFunction = func;
|
||||
}
|
||||
}
|
||||
reject(func) {
|
||||
if (this.internalPromise) {
|
||||
return this.internalPromise.reject(func);
|
||||
} else {
|
||||
this.rejectFunction = func;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates an immutable series loader object which loads each series sequentially using the iterator interface.
|
||||
*
|
||||
@@ -16,12 +67,17 @@ function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList)
|
||||
hasNext() {
|
||||
return seriesInstanceUIDList.length > 0;
|
||||
},
|
||||
async next() {
|
||||
const seriesInstanceUID = seriesInstanceUIDList.shift();
|
||||
return client.retrieveSeriesMetadata({
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
next() {
|
||||
const { seriesInstanceUID, metadata } = seriesInstanceUIDList.shift();
|
||||
const promise = new DeferredPromise();
|
||||
promise.setMetadata(metadata);
|
||||
promise.setProcessFunction(() => {
|
||||
return client.retrieveSeriesMetadata({
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -40,15 +96,22 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
const preLoaders = [];
|
||||
const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this;
|
||||
|
||||
// asking to include Series Date, Series Time, Series Description
|
||||
// and Series Number in the series metadata returned to better sort series
|
||||
// in preLoad function
|
||||
let options = {
|
||||
studyInstanceUID,
|
||||
queryParams: {
|
||||
includefield: includeField,
|
||||
},
|
||||
};
|
||||
|
||||
if (seriesInstanceUID) {
|
||||
const options = {
|
||||
studyInstanceUID,
|
||||
queryParams: { SeriesInstanceUID: seriesInstanceUID },
|
||||
};
|
||||
options.queryParams.SeriesInstanceUID = seriesInstanceUID;
|
||||
preLoaders.push(client.searchForSeries.bind(client, options));
|
||||
}
|
||||
// Fallback preloader
|
||||
preLoaders.push(client.searchForSeries.bind(client, { studyInstanceUID }));
|
||||
preLoaders.push(client.searchForSeries.bind(client, options));
|
||||
|
||||
yield* preLoaders;
|
||||
}
|
||||
@@ -62,24 +125,23 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
|
||||
const naturalized = result.map(naturalizeDataset);
|
||||
|
||||
return sortStudySeries(
|
||||
naturalized,
|
||||
sortCriteria || sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria,
|
||||
sortFunction
|
||||
);
|
||||
return sortStudySeries(naturalized, sortCriteria, sortFunction);
|
||||
}
|
||||
|
||||
async load(preLoadData) {
|
||||
const { client, studyInstanceUID } = this;
|
||||
|
||||
const seriesInstanceUIDs = preLoadData.map(s => s.SeriesInstanceUID);
|
||||
const seriesInstanceUIDs = preLoadData.map(seriesMetadata => {
|
||||
return { seriesInstanceUID: seriesMetadata.SeriesInstanceUID, metadata: seriesMetadata };
|
||||
});
|
||||
|
||||
const seriesAsyncLoader = makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDs);
|
||||
|
||||
const promises = [];
|
||||
|
||||
while (seriesAsyncLoader.hasNext()) {
|
||||
promises.push(seriesAsyncLoader.next());
|
||||
const promise = seriesAsyncLoader.next();
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { createDicomWebApi } from '../DicomWebDataSource/index';
|
||||
* dicomWeb configuration array
|
||||
*
|
||||
*/
|
||||
function createDicomWebProxyApi(dicomWebProxyConfig, UserAuthenticationService) {
|
||||
function createDicomWebProxyApi(dicomWebProxyConfig, servicesManager) {
|
||||
const { name } = dicomWebProxyConfig;
|
||||
let dicomWebDelegate = undefined;
|
||||
|
||||
@@ -28,7 +28,7 @@ function createDicomWebProxyApi(dicomWebProxyConfig, UserAuthenticationService)
|
||||
|
||||
dicomWebDelegate = createDicomWebApi(
|
||||
data.servers.dicomWeb[0].configuration,
|
||||
UserAuthenticationService
|
||||
servicesManager
|
||||
);
|
||||
dicomWebDelegate.initialize({ params, query });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core';
|
||||
import {
|
||||
mergeMap,
|
||||
callForAllDataSourcesAsync,
|
||||
callForAllDataSources,
|
||||
callForDefaultDataSource,
|
||||
callByRetrieveAETitle,
|
||||
createMergeDataSourceApi,
|
||||
} from './index';
|
||||
|
||||
jest.mock('@ohif/core');
|
||||
|
||||
describe('MergeDataSource', () => {
|
||||
let path,
|
||||
sourceName,
|
||||
mergeConfig,
|
||||
extensionManager,
|
||||
series1,
|
||||
series2,
|
||||
series3,
|
||||
series4,
|
||||
mergeKey,
|
||||
tagFunc,
|
||||
dataSourceAndSeriesMap,
|
||||
dataSourceAndUIDsMap,
|
||||
dataSourceAndDSMap,
|
||||
pathSync;
|
||||
|
||||
beforeAll(() => {
|
||||
path = 'query.series.search';
|
||||
pathSync = 'getImageIdsForInstance';
|
||||
tagFunc = jest.fn((data, sourceName) =>
|
||||
data.map(item => ({ ...item, RetrieveAETitle: sourceName }))
|
||||
);
|
||||
sourceName = 'dicomweb1';
|
||||
mergeKey = 'seriesInstanceUid';
|
||||
series1 = { [mergeKey]: '123' };
|
||||
series2 = { [mergeKey]: '234' };
|
||||
series3 = { [mergeKey]: '345' };
|
||||
series4 = { [mergeKey]: '456' };
|
||||
mergeConfig = {
|
||||
seriesMerge: {
|
||||
dataSourceNames: ['dicomweb1', 'dicomweb2'],
|
||||
defaultDataSourceName: 'dicomweb1',
|
||||
},
|
||||
};
|
||||
dataSourceAndSeriesMap = {
|
||||
dataSource1: series1,
|
||||
dataSource2: series2,
|
||||
dataSource3: series3,
|
||||
};
|
||||
dataSourceAndUIDsMap = {
|
||||
dataSource1: ['123'],
|
||||
dataSource2: ['234'],
|
||||
dataSource3: ['345'],
|
||||
};
|
||||
dataSourceAndDSMap = {
|
||||
dataSource1: {
|
||||
displaySet: {
|
||||
StudyInstanceUID: '123',
|
||||
SeriesInstanceUID: '123',
|
||||
},
|
||||
},
|
||||
dataSource2: {
|
||||
displaySet: {
|
||||
StudyInstanceUID: '234',
|
||||
SeriesInstanceUID: '234',
|
||||
},
|
||||
},
|
||||
dataSource3: {
|
||||
displaySet: {
|
||||
StudyInstanceUID: '345',
|
||||
SeriesInstanceUID: '345',
|
||||
},
|
||||
},
|
||||
};
|
||||
extensionManager = {
|
||||
dataSourceDefs: {
|
||||
dataSource1: {
|
||||
sourceName: 'dataSource1',
|
||||
configuration: {},
|
||||
},
|
||||
dataSource2: {
|
||||
sourceName: 'dataSource2',
|
||||
configuration: {},
|
||||
},
|
||||
dataSource3: {
|
||||
sourceName: 'dataSource3',
|
||||
configuration: {},
|
||||
},
|
||||
},
|
||||
getDataSources: jest.fn(dataSourceName => [
|
||||
{
|
||||
[path]: jest.fn().mockResolvedValue([dataSourceAndSeriesMap[dataSourceName]]),
|
||||
},
|
||||
]),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('callForAllDataSourcesAsync', () => {
|
||||
it('should call the correct functions and return the merged data', async () => {
|
||||
/** Arrange */
|
||||
extensionManager.getDataSources = jest.fn(dataSourceName => [
|
||||
{
|
||||
[path]: jest.fn().mockResolvedValue([dataSourceAndSeriesMap[dataSourceName]]),
|
||||
},
|
||||
]);
|
||||
|
||||
/** Act */
|
||||
const data = await callForAllDataSourcesAsync({
|
||||
mergeMap,
|
||||
path,
|
||||
args: [],
|
||||
extensionManager,
|
||||
dataSourceNames: ['dataSource1', 'dataSource2'],
|
||||
});
|
||||
|
||||
/** Assert */
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledTimes(2);
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource1');
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2');
|
||||
expect(data).toEqual([series1, series2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('callForAllDataSources', () => {
|
||||
it('should call the correct functions and return the merged data', () => {
|
||||
/** Arrange */
|
||||
extensionManager.getDataSources = jest.fn(dataSourceName => [
|
||||
{
|
||||
[pathSync]: () => dataSourceAndUIDsMap[dataSourceName],
|
||||
},
|
||||
]);
|
||||
|
||||
/** Act */
|
||||
const data = callForAllDataSources({
|
||||
path: pathSync,
|
||||
args: [],
|
||||
extensionManager,
|
||||
dataSourceNames: ['dataSource2', 'dataSource3'],
|
||||
});
|
||||
|
||||
/** Assert */
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledTimes(2);
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2');
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource3');
|
||||
expect(data).toEqual(['234', '345']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('callForDefaultDataSource', () => {
|
||||
it('should call the correct function and return the data', () => {
|
||||
/** Arrange */
|
||||
extensionManager.getDataSources = jest.fn(dataSourceName => [
|
||||
{
|
||||
[pathSync]: () => dataSourceAndUIDsMap[dataSourceName],
|
||||
},
|
||||
]);
|
||||
|
||||
/** Act */
|
||||
const data = callForDefaultDataSource({
|
||||
path: pathSync,
|
||||
args: [],
|
||||
extensionManager,
|
||||
defaultDataSourceName: 'dataSource2',
|
||||
});
|
||||
|
||||
/** Assert */
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledTimes(1);
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2');
|
||||
expect(data).toEqual(['234']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('callByRetrieveAETitle', () => {
|
||||
it('should call the correct function and return the data', () => {
|
||||
/** Arrange */
|
||||
DicomMetadataStore.getSeries.mockImplementationOnce(() => [series2]);
|
||||
extensionManager.getDataSources = jest.fn(dataSourceName => [
|
||||
{
|
||||
[pathSync]: () => dataSourceAndUIDsMap[dataSourceName],
|
||||
},
|
||||
]);
|
||||
|
||||
/** Act */
|
||||
const data = callByRetrieveAETitle({
|
||||
path: pathSync,
|
||||
args: [dataSourceAndDSMap['dataSource2']],
|
||||
extensionManager,
|
||||
defaultDataSourceName: 'dataSource2',
|
||||
});
|
||||
|
||||
/** Assert */
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledTimes(1);
|
||||
expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2');
|
||||
expect(data).toEqual(['234']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,293 @@
|
||||
import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core';
|
||||
import { get, uniqBy } from 'lodash';
|
||||
import {
|
||||
MergeConfig,
|
||||
CallForAllDataSourcesAsyncOptions,
|
||||
CallForAllDataSourcesOptions,
|
||||
CallForDefaultDataSourceOptions,
|
||||
CallByRetrieveAETitleOptions,
|
||||
MergeMap,
|
||||
} from './types';
|
||||
|
||||
export const mergeMap: MergeMap = {
|
||||
'query.studies.search': {
|
||||
mergeKey: 'studyInstanceUid',
|
||||
tagFunc: x => x,
|
||||
},
|
||||
'query.series.search': {
|
||||
mergeKey: 'seriesInstanceUid',
|
||||
tagFunc: (series, sourceName) => {
|
||||
series.forEach(series => {
|
||||
series.RetrieveAETitle = sourceName;
|
||||
DicomMetadataStore.updateSeriesMetadata(series);
|
||||
});
|
||||
return series;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls all data sources asynchronously and merges the results.
|
||||
* @param {CallForAllDataSourcesAsyncOptions} options - The options for calling all data sources.
|
||||
* @param {string} options.path - The path to the function to be called on each data source.
|
||||
* @param {unknown[]} options.args - The arguments to be passed to the function.
|
||||
* @param {ExtensionManager} options.extensionManager - The extension manager.
|
||||
* @param {string[]} options.dataSourceNames - The names of the data sources to be called.
|
||||
* @param {string} options.defaultDataSourceName - The name of the default data source.
|
||||
* @returns {Promise<unknown[]>} - A promise that resolves to the merged data from all data sources.
|
||||
*/
|
||||
export const callForAllDataSourcesAsync = async ({
|
||||
mergeMap,
|
||||
path,
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}: CallForAllDataSourcesAsyncOptions) => {
|
||||
const { mergeKey, tagFunc } = mergeMap[path] || { tagFunc: x => x };
|
||||
|
||||
/** Sort by default data source */
|
||||
const defs = Object.values(extensionManager.dataSourceDefs);
|
||||
const defaultDataSourceDef = defs.find(def => def.sourceName === defaultDataSourceName);
|
||||
const dataSourceDefs = defs.filter(def => def.sourceName !== defaultDataSourceName);
|
||||
if (defaultDataSourceDef) {
|
||||
dataSourceDefs.unshift(defaultDataSourceDef);
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
const sourceNames = [];
|
||||
|
||||
for (const dataSourceDef of dataSourceDefs) {
|
||||
const { configuration, sourceName } = dataSourceDef;
|
||||
if (!!configuration && dataSourceNames.includes(sourceName)) {
|
||||
const [dataSource] = extensionManager.getDataSources(sourceName);
|
||||
const func = get(dataSource, path);
|
||||
const promise = func.apply(dataSource, args);
|
||||
promises.push(promise);
|
||||
sourceNames.push(sourceName);
|
||||
}
|
||||
}
|
||||
|
||||
const data = await Promise.allSettled(promises);
|
||||
const mergedData = data.map((data, i) => tagFunc(data.value, sourceNames[i]));
|
||||
|
||||
let results = [];
|
||||
if (mergeKey) {
|
||||
results = uniqBy(mergedData.flat(), obj => get(obj, mergeKey));
|
||||
} else {
|
||||
results = mergedData.flat();
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls all data sources that match the provided names and merges their data.
|
||||
* @param options - The options for calling all data sources.
|
||||
* @param options.path - The path to the function to be called on each data source.
|
||||
* @param options.args - The arguments to be passed to the function.
|
||||
* @param options.extensionManager - The extension manager instance.
|
||||
* @param options.dataSourceNames - The names of the data sources to be called.
|
||||
* @param options.defaultDataSourceName - The name of the default data source.
|
||||
* @returns The merged data from all the matching data sources.
|
||||
*/
|
||||
export const callForAllDataSources = ({
|
||||
path,
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}: CallForAllDataSourcesOptions) => {
|
||||
/** Sort by default data source */
|
||||
const defs = Object.values(extensionManager.dataSourceDefs);
|
||||
const defaultDataSourceDef = defs.find(def => def.sourceName === defaultDataSourceName);
|
||||
const dataSourceDefs = defs.filter(def => def.sourceName !== defaultDataSourceName);
|
||||
if (defaultDataSourceDef) {
|
||||
dataSourceDefs.unshift(defaultDataSourceDef);
|
||||
}
|
||||
|
||||
const mergedData = [];
|
||||
for (const dataSourceDef of dataSourceDefs) {
|
||||
const { configuration, sourceName } = dataSourceDef;
|
||||
if (!!configuration && dataSourceNames.includes(sourceName)) {
|
||||
const [dataSource] = extensionManager.getDataSources(sourceName);
|
||||
const func = get(dataSource, path);
|
||||
const data = func.apply(dataSource, args);
|
||||
mergedData.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
return mergedData.flat();
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls the default data source function specified by the given path with the provided arguments.
|
||||
* @param {CallForDefaultDataSourceOptions} options - The options for calling the default data source.
|
||||
* @param {string} options.path - The path to the function within the default data source.
|
||||
* @param {unknown[]} options.args - The arguments to pass to the function.
|
||||
* @param {string} options.defaultDataSourceName - The name of the default data source.
|
||||
* @param {ExtensionManager} options.extensionManager - The extension manager instance.
|
||||
* @returns {unknown} - The result of calling the default data source function.
|
||||
*/
|
||||
export const callForDefaultDataSource = ({
|
||||
path,
|
||||
args,
|
||||
defaultDataSourceName,
|
||||
extensionManager,
|
||||
}: CallForDefaultDataSourceOptions) => {
|
||||
const [dataSource] = extensionManager.getDataSources(defaultDataSourceName);
|
||||
const func = get(dataSource, path);
|
||||
return func.apply(dataSource, args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls the data source specified by the RetrieveAETitle of the given display set.
|
||||
* @typedef {Object} CallByRetrieveAETitleOptions
|
||||
* @property {string} path - The path of the method to call on the data source.
|
||||
* @property {any[]} args - The arguments to pass to the method.
|
||||
* @property {string} defaultDataSourceName - The name of the default data source.
|
||||
* @property {ExtensionManager} extensionManager - The extension manager.
|
||||
*/
|
||||
export const callByRetrieveAETitle = ({
|
||||
path,
|
||||
args,
|
||||
defaultDataSourceName,
|
||||
extensionManager,
|
||||
}: CallByRetrieveAETitleOptions) => {
|
||||
const [displaySet] = args;
|
||||
const seriesMetadata = DicomMetadataStore.getSeries(
|
||||
displaySet.StudyInstanceUID,
|
||||
displaySet.SeriesInstanceUID
|
||||
);
|
||||
const [dataSource] = extensionManager.getDataSources(
|
||||
seriesMetadata.RetrieveAETitle || defaultDataSourceName
|
||||
);
|
||||
return dataSource[path](...args);
|
||||
};
|
||||
|
||||
function createMergeDataSourceApi(
|
||||
mergeConfig: MergeConfig,
|
||||
servicesManager: unknown,
|
||||
extensionManager
|
||||
) {
|
||||
const { seriesMerge } = mergeConfig;
|
||||
const { dataSourceNames, defaultDataSourceName } = seriesMerge;
|
||||
|
||||
const implementation = {
|
||||
initialize: (...args: unknown[]) =>
|
||||
callForAllDataSources({
|
||||
path: 'initialize',
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}),
|
||||
query: {
|
||||
studies: {
|
||||
search: (...args: unknown[]) =>
|
||||
callForAllDataSourcesAsync({
|
||||
mergeMap,
|
||||
path: 'query.studies.search',
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}),
|
||||
},
|
||||
series: {
|
||||
search: (...args: unknown[]) =>
|
||||
callForAllDataSourcesAsync({
|
||||
mergeMap,
|
||||
path: 'query.series.search',
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}),
|
||||
},
|
||||
instances: {
|
||||
search: (...args: unknown[]) =>
|
||||
callForAllDataSourcesAsync({
|
||||
mergeMap,
|
||||
path: 'query.instances.search',
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}),
|
||||
},
|
||||
},
|
||||
retrieve: {
|
||||
bulkDataURI: (...args: unknown[]) =>
|
||||
callForAllDataSourcesAsync({
|
||||
mergeMap,
|
||||
path: 'retrieve.bulkDataURI',
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}),
|
||||
directURL: (...args: unknown[]) =>
|
||||
callForDefaultDataSource({
|
||||
path: 'retrieve.directURL',
|
||||
args,
|
||||
defaultDataSourceName,
|
||||
extensionManager,
|
||||
}),
|
||||
series: {
|
||||
metadata: (...args: unknown[]) =>
|
||||
callForAllDataSourcesAsync({
|
||||
mergeMap,
|
||||
path: 'retrieve.series.metadata',
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}),
|
||||
},
|
||||
},
|
||||
store: {
|
||||
dicom: (...args: unknown[]) =>
|
||||
callForDefaultDataSource({
|
||||
path: 'store.dicom',
|
||||
args,
|
||||
defaultDataSourceName,
|
||||
extensionManager,
|
||||
}),
|
||||
},
|
||||
deleteStudyMetadataPromise: (...args: unknown[]) =>
|
||||
callForAllDataSources({
|
||||
path: 'deleteStudyMetadataPromise',
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}),
|
||||
getImageIdsForDisplaySet: (...args: unknown[]) =>
|
||||
callByRetrieveAETitle({
|
||||
path: 'getImageIdsForDisplaySet',
|
||||
args,
|
||||
defaultDataSourceName,
|
||||
extensionManager,
|
||||
}),
|
||||
getImageIdsForInstance: (...args: unknown[]) =>
|
||||
callByRetrieveAETitle({
|
||||
path: 'getImageIdsForDisplaySet',
|
||||
args,
|
||||
defaultDataSourceName,
|
||||
extensionManager,
|
||||
}),
|
||||
getStudyInstanceUIDs: (...args: unknown[]) =>
|
||||
callForAllDataSources({
|
||||
path: 'getStudyInstanceUIDs',
|
||||
args,
|
||||
extensionManager,
|
||||
dataSourceNames,
|
||||
defaultDataSourceName,
|
||||
}),
|
||||
};
|
||||
|
||||
return IWebApiDataSource.create(implementation);
|
||||
}
|
||||
|
||||
export { createMergeDataSourceApi };
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ExtensionManager } from '@ohif/core';
|
||||
|
||||
export type MergeMap = {
|
||||
[key: string]: {
|
||||
mergeKey: string;
|
||||
tagFunc: (data: unknown[], sourceName: string) => unknown[];
|
||||
};
|
||||
};
|
||||
|
||||
export type CallForAllDataSourcesAsyncOptions = {
|
||||
mergeMap: object;
|
||||
path: string;
|
||||
args: unknown[];
|
||||
dataSourceNames: string[];
|
||||
extensionManager: ExtensionManager;
|
||||
defaultDataSourceName: string;
|
||||
};
|
||||
|
||||
export type CallForAllDataSourcesOptions = {
|
||||
path: string;
|
||||
args: unknown[];
|
||||
dataSourceNames: string[];
|
||||
extensionManager: ExtensionManager;
|
||||
defaultDataSourceName: string;
|
||||
};
|
||||
|
||||
export type CallForDefaultDataSourceOptions = {
|
||||
path: string;
|
||||
args: unknown[];
|
||||
defaultDataSourceName: string;
|
||||
extensionManager: ExtensionManager;
|
||||
};
|
||||
|
||||
export type CallByRetrieveAETitleOptions = {
|
||||
path: string;
|
||||
args: unknown[];
|
||||
defaultDataSourceName: string;
|
||||
extensionManager: ExtensionManager;
|
||||
};
|
||||
|
||||
export type MergeConfig = {
|
||||
seriesMerge: {
|
||||
dataSourceNames: string[];
|
||||
defaultDataSourceName: string;
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { utils, ServicesManager } from '@ohif/core';
|
||||
import { MeasurementTable, Dialog, Input, useViewportGrid, ButtonEnums } from '@ohif/ui';
|
||||
import ActionButtons from './ActionButtons';
|
||||
@@ -18,6 +19,8 @@ export default function PanelMeasurementTable({
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}): React.FunctionComponent {
|
||||
const { t } = useTranslation('MeasurementTable');
|
||||
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
const { activeViewportId, viewports } = viewportGrid;
|
||||
const { measurementService, uiDialogService, uiNotificationService, displaySetService } = (
|
||||
@@ -209,7 +212,7 @@ export default function PanelMeasurementTable({
|
||||
data-cy={'measurements-panel'}
|
||||
>
|
||||
<MeasurementTable
|
||||
title="Measurements"
|
||||
title={t("Measurements")}
|
||||
servicesManager={servicesManager}
|
||||
data={displayMeasurements}
|
||||
onClick={jumpToImage}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
function getImageSrcFromImageId(cornerstone, imageId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
// Note: the default width and height of the canvas is 300x150
|
||||
// but we need to set the width and height to the same number since
|
||||
// the thumbnails are usually square and we want to maintain the aspect ratio
|
||||
canvas.width = 128 / window.devicePixelRatio;
|
||||
canvas.height = 128 / window.devicePixelRatio;
|
||||
cornerstone.utilities
|
||||
.loadImageToCanvas({ canvas, imageId })
|
||||
.then(imageId => {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui';
|
||||
import { ServicesManager } from '@ohif/core';
|
||||
|
||||
function LegacyLayoutSelectorWithServices({ servicesManager, ...props }) {
|
||||
const { toolbarService } = servicesManager.services;
|
||||
|
||||
const onSelection = useCallback(
|
||||
props => {
|
||||
toolbarService.recordInteraction({
|
||||
interactionType: 'action',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'setViewportGridLayout',
|
||||
commandOptions: { ...props },
|
||||
context: 'DEFAULT',
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
[toolbarService]
|
||||
);
|
||||
|
||||
return (
|
||||
<LayoutSelector
|
||||
{...props}
|
||||
onSelection={onSelection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function LayoutSelector({ rows, columns, className, onSelection, ...rest }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const closeOnOutsideClick = () => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('click', closeOnOutsideClick);
|
||||
return () => {
|
||||
window.removeEventListener('click', closeOnOutsideClick);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const onInteractionHandler = () => setIsOpen(!isOpen);
|
||||
const DropdownContent = isOpen ? OHIFLayoutSelector : null;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
id="Layout"
|
||||
label="Grid Layout"
|
||||
icon="tool-layout"
|
||||
onInteraction={onInteractionHandler}
|
||||
className={className}
|
||||
rounded={rest.rounded}
|
||||
dropdownContent={
|
||||
DropdownContent !== null && (
|
||||
<DropdownContent
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
onSelection={onSelection}
|
||||
/>
|
||||
)
|
||||
}
|
||||
isActive={isOpen}
|
||||
type="toggle"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
LayoutSelector.propTypes = {
|
||||
rows: PropTypes.number,
|
||||
columns: PropTypes.number,
|
||||
onLayoutChange: PropTypes.func,
|
||||
servicesManager: PropTypes.instanceOf(ServicesManager),
|
||||
};
|
||||
|
||||
LayoutSelector.defaultProps = {
|
||||
rows: 3,
|
||||
columns: 3,
|
||||
onLayoutChange: () => {},
|
||||
};
|
||||
|
||||
export default LegacyLayoutSelectorWithServices;
|
||||
@@ -1,57 +1,53 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Tooltip } from '@ohif/ui';
|
||||
import classnames from 'classnames';
|
||||
import { useViewportGrid } from '@ohif/ui';
|
||||
import { useToolbar } from '@ohif/core';
|
||||
|
||||
export default function Toolbar({
|
||||
servicesManager,
|
||||
}: Types.Extensions.ExtensionParams): React.ReactElement {
|
||||
const { toolbarService } = servicesManager.services;
|
||||
export function Toolbar({ servicesManager }) {
|
||||
const { toolbarButtons, onInteraction } = useToolbar({
|
||||
servicesManager,
|
||||
buttonSection: 'primary',
|
||||
});
|
||||
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
const [toolbarButtons, setToolbarButtons] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const updateToolbar = () => {
|
||||
const toolGroupId =
|
||||
viewportGridService.getActiveViewportOptionByKey('toolGroupId') ?? 'default';
|
||||
setToolbarButtons(toolbarService.getButtonSection(toolGroupId));
|
||||
};
|
||||
|
||||
const { unsubscribe } = toolbarService.subscribe(
|
||||
toolbarService.EVENTS.TOOL_BAR_MODIFIED,
|
||||
updateToolbar
|
||||
);
|
||||
|
||||
updateToolbar();
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [toolbarService, viewportGrid]);
|
||||
|
||||
const onInteraction = useCallback(
|
||||
args => toolbarService.recordInteraction(args),
|
||||
[toolbarService]
|
||||
);
|
||||
if (!toolbarButtons.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{toolbarButtons.map(toolDef => {
|
||||
if (!toolDef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { id, Component, componentProps } = toolDef;
|
||||
return (
|
||||
// The margin for separating the tools on the toolbar should go here and NOT in each individual component (button) item.
|
||||
// This allows for the individual items to be included in other UI components where perhaps alternative margins are desired.
|
||||
const { disabled } = componentProps;
|
||||
|
||||
const tool = (
|
||||
<Component
|
||||
key={id}
|
||||
id={id}
|
||||
onInteraction={onInteraction}
|
||||
servicesManager={servicesManager}
|
||||
{...componentProps}
|
||||
/>
|
||||
);
|
||||
|
||||
return disabled ? (
|
||||
<Tooltip
|
||||
key={id}
|
||||
position="bottom"
|
||||
content={componentProps.label}
|
||||
secondaryContent={'Not available on the current viewport'}
|
||||
>
|
||||
<div className={classnames('mr-1')}>{tool}</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div
|
||||
key={id}
|
||||
className={classnames('mr-1')}
|
||||
className="mr-1"
|
||||
>
|
||||
<Component
|
||||
id={id}
|
||||
{...componentProps}
|
||||
onInteraction={onInteraction}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
{tool}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ToolbarButton, ButtonGroup } from '@ohif/ui';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
function ToolbarButtonGroupWithServices({ groupId, items, onInteraction, size }) {
|
||||
const getSplitButtonItems = useCallback(
|
||||
items =>
|
||||
items.map((item, index) => (
|
||||
<ToolbarButton
|
||||
key={item.id}
|
||||
icon={item.icon}
|
||||
label={item.label}
|
||||
disabled={item.disabled}
|
||||
className={item.className}
|
||||
id={item.id}
|
||||
size={size}
|
||||
onClick={() => {
|
||||
onInteraction({
|
||||
groupId,
|
||||
itemId: item.id,
|
||||
commands: item.commands,
|
||||
});
|
||||
}}
|
||||
// Note: this is necessary since tooltip will add
|
||||
// default styles to the tooltip container which
|
||||
// we don't want for groups
|
||||
toolTipClassName=""
|
||||
/>
|
||||
)),
|
||||
[onInteraction, groupId]
|
||||
);
|
||||
|
||||
return <ButtonGroup>{getSplitButtonItems(items)}</ButtonGroup>;
|
||||
}
|
||||
|
||||
export default ToolbarButtonGroupWithServices;
|
||||
@@ -1,75 +0,0 @@
|
||||
import { ToolbarButton } from '@ohif/ui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function ToolbarButtonWithServices({
|
||||
id,
|
||||
type,
|
||||
commands,
|
||||
onInteraction,
|
||||
servicesManager,
|
||||
...props
|
||||
}) {
|
||||
const { toolbarService } = servicesManager?.services || {};
|
||||
|
||||
const [buttonsState, setButtonState] = useState({
|
||||
primaryToolId: '',
|
||||
toggles: {},
|
||||
groups: {},
|
||||
});
|
||||
const { primaryToolId } = buttonsState;
|
||||
|
||||
const isActive =
|
||||
(type === 'tool' && id === primaryToolId) ||
|
||||
(type === 'toggle' && buttonsState.toggles[id] === true);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = toolbarService.subscribe(
|
||||
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
|
||||
state => {
|
||||
setButtonState({ ...state });
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [toolbarService]);
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
commands={commands}
|
||||
id={id}
|
||||
type={type}
|
||||
isActive={isActive}
|
||||
onInteraction={onInteraction}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
ToolbarButtonWithServices.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
|
||||
commands: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
commandName: PropTypes.string.isRequired,
|
||||
context: PropTypes.string,
|
||||
})
|
||||
),
|
||||
onInteraction: PropTypes.func.isRequired,
|
||||
servicesManager: PropTypes.shape({
|
||||
services: PropTypes.shape({
|
||||
toolbarService: PropTypes.shape({
|
||||
subscribe: PropTypes.func.isRequired,
|
||||
state: PropTypes.shape({
|
||||
primaryToolId: PropTypes.string,
|
||||
toggles: PropTypes.objectOf(PropTypes.bool),
|
||||
groups: PropTypes.objectOf(PropTypes.any),
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default ToolbarButtonWithServices;
|
||||
@@ -1,38 +1,110 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui';
|
||||
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton, LayoutPreset } from '@ohif/ui';
|
||||
import { ServicesManager } from '@ohif/core';
|
||||
|
||||
function ToolbarLayoutSelectorWithServices({ servicesManager, ...props }) {
|
||||
const { toolbarService } = servicesManager.services;
|
||||
|
||||
const onSelection = useCallback(
|
||||
props => {
|
||||
toolbarService.recordInteraction({
|
||||
interactionType: 'action',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'setViewportGridLayout',
|
||||
commandOptions: { ...props },
|
||||
context: 'DEFAULT',
|
||||
},
|
||||
],
|
||||
});
|
||||
const defaultCommonPresets = [
|
||||
{
|
||||
icon: 'layout-common-1x1',
|
||||
commandOptions: {
|
||||
numRows: 1,
|
||||
numCols: 1,
|
||||
},
|
||||
[toolbarService]
|
||||
);
|
||||
},
|
||||
{
|
||||
icon: 'layout-common-1x2',
|
||||
commandOptions: {
|
||||
numRows: 1,
|
||||
numCols: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: 'layout-common-2x2',
|
||||
commandOptions: {
|
||||
numRows: 2,
|
||||
numCols: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: 'layout-common-2x3',
|
||||
commandOptions: {
|
||||
numRows: 2,
|
||||
numCols: 3,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const generateAdvancedPresets = hangingProtocolService => {
|
||||
const hangingProtocols = Array.from(hangingProtocolService.protocols.values());
|
||||
return hangingProtocols
|
||||
.map(hp => {
|
||||
if (!hp.isPreset) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
icon: hp.icon,
|
||||
title: hp.name,
|
||||
commandOptions: {
|
||||
protocolId: hp.id,
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter(preset => preset !== null);
|
||||
};
|
||||
|
||||
function ToolbarLayoutSelectorWithServices({ commandsManager, servicesManager, ...props }) {
|
||||
const [isDisabled, setIsDisabled] = useState(false);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsDisabled(false);
|
||||
};
|
||||
|
||||
const onSelection = useCallback(props => {
|
||||
commandsManager.run({
|
||||
commandName: 'setViewportGridLayout',
|
||||
commandOptions: { ...props },
|
||||
});
|
||||
setIsDisabled(true);
|
||||
}, []);
|
||||
|
||||
const onSelectionPreset = useCallback(props => {
|
||||
commandsManager.run({
|
||||
commandName: 'setHangingProtocol',
|
||||
commandOptions: { ...props },
|
||||
});
|
||||
setIsDisabled(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<LayoutSelector
|
||||
{...props}
|
||||
onSelection={onSelection}
|
||||
/>
|
||||
<div onMouseEnter={handleMouseEnter}>
|
||||
<LayoutSelector
|
||||
{...props}
|
||||
onSelection={onSelection}
|
||||
onSelectionPreset={onSelectionPreset}
|
||||
servicesManager={servicesManager}
|
||||
tooltipDisabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LayoutSelector({ rows, columns, className, onSelection, ...rest }) {
|
||||
function LayoutSelector({
|
||||
rows,
|
||||
columns,
|
||||
className,
|
||||
onSelection,
|
||||
onSelectionPreset,
|
||||
servicesManager,
|
||||
tooltipDisabled,
|
||||
...rest
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { customizationService, hangingProtocolService } = servicesManager.services;
|
||||
const commonPresets = customizationService.get('commonPresets') || defaultCommonPresets;
|
||||
const advancedPresets =
|
||||
customizationService.get('advancedPresets') || generateAdvancedPresets(hangingProtocolService);
|
||||
|
||||
const closeOnOutsideClick = () => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false);
|
||||
@@ -46,24 +118,68 @@ function LayoutSelector({ rows, columns, className, onSelection, ...rest }) {
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const onInteractionHandler = () => setIsOpen(!isOpen);
|
||||
const onInteractionHandler = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
const DropdownContent = isOpen ? OHIFLayoutSelector : null;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
id="Layout"
|
||||
label="Grid Layout"
|
||||
label="Layout"
|
||||
icon="tool-layout"
|
||||
onInteraction={onInteractionHandler}
|
||||
className={className}
|
||||
rounded={rest.rounded}
|
||||
disableToolTip={tooltipDisabled}
|
||||
dropdownContent={
|
||||
DropdownContent !== null && (
|
||||
<DropdownContent
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
onSelection={onSelection}
|
||||
/>
|
||||
<div className="flex ">
|
||||
<div className="bg-secondary-dark flex flex-col gap-2.5 p-2">
|
||||
<div className="text-aqua-pale text-xs">Common</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
{commonPresets.map((preset, index) => (
|
||||
<LayoutPreset
|
||||
key={index}
|
||||
classNames="hover:bg-primary-dark group p-1 cursor-pointer"
|
||||
icon={preset.icon}
|
||||
commandOptions={preset.commandOptions}
|
||||
onSelection={onSelection}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="h-[2px] bg-black"></div>
|
||||
|
||||
<div className="text-aqua-pale text-xs">Advanced</div>
|
||||
|
||||
<div className="flex flex-col gap-2.5">
|
||||
{advancedPresets.map((preset, index) => (
|
||||
<LayoutPreset
|
||||
key={index + commonPresets.length}
|
||||
classNames="hover:bg-primary-dark group flex gap-2 p-1 cursor-pointer"
|
||||
icon={preset.icon}
|
||||
title={preset.title}
|
||||
commandOptions={preset.commandOptions}
|
||||
onSelection={onSelectionPreset}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-primary-dark flex flex-col gap-2.5 border-l-2 border-solid border-black p-2">
|
||||
<div className="text-aqua-pale text-xs">Custom</div>
|
||||
<DropdownContent
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
onSelection={onSelection}
|
||||
/>
|
||||
<p className="text-aqua-pale text-xs leading-tight">
|
||||
Hover to select <br></br>rows and columns <br></br> Click to apply
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
isActive={isOpen}
|
||||
@@ -80,8 +196,8 @@ LayoutSelector.propTypes = {
|
||||
};
|
||||
|
||||
LayoutSelector.defaultProps = {
|
||||
columns: 4,
|
||||
rows: 3,
|
||||
columns: 3,
|
||||
onLayoutChange: () => {},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { SplitButton, Icon, ToolbarButton } from '@ohif/ui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { SplitButton, ToolbarButton } from '@ohif/ui';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
function ToolbarSplitButtonWithServices({
|
||||
isRadio,
|
||||
isAction,
|
||||
groupId,
|
||||
primary,
|
||||
secondary,
|
||||
@@ -16,123 +13,35 @@ function ToolbarSplitButtonWithServices({
|
||||
}) {
|
||||
const { toolbarService } = servicesManager?.services;
|
||||
|
||||
const handleItemClick = (item, index) => {
|
||||
const { id, type, commands } = item;
|
||||
onInteraction({
|
||||
groupId,
|
||||
itemId: id,
|
||||
interactionType: type,
|
||||
commands,
|
||||
});
|
||||
|
||||
setState(state => ({
|
||||
...state,
|
||||
primary: !isAction && isRadio ? { ...item, index } : state.primary,
|
||||
isExpanded: false,
|
||||
items: getSplitButtonItems(items).filter(item =>
|
||||
isRadio && !isAction ? item.index !== index : true
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
/* Bubbles up individual item clicks */
|
||||
const getSplitButtonItems = items =>
|
||||
items.map((item, index) => ({
|
||||
...item,
|
||||
index,
|
||||
onClick: () => handleItemClick(item, index),
|
||||
}));
|
||||
|
||||
const [buttonsState, setButtonState] = useState({
|
||||
primaryToolId: '',
|
||||
toggles: {},
|
||||
groups: {},
|
||||
});
|
||||
|
||||
const [state, setState] = useState({
|
||||
primary,
|
||||
items: getSplitButtonItems(items).filter(item =>
|
||||
isRadio && !isAction ? item.id !== primary.id : true
|
||||
),
|
||||
});
|
||||
|
||||
const { primaryToolId, toggles } = buttonsState;
|
||||
|
||||
const isPrimaryToggle = state.primary.type === 'toggle';
|
||||
|
||||
const isPrimaryActive =
|
||||
(state.primary.type === 'tool' && primaryToolId === state.primary.id) ||
|
||||
(isPrimaryToggle && toggles[state.primary.id] === true);
|
||||
const getSplitButtonItems = useCallback(
|
||||
items =>
|
||||
items.map((item, index) => ({
|
||||
...item,
|
||||
index,
|
||||
onClick: () => {
|
||||
onInteraction({
|
||||
groupId,
|
||||
itemId: item.id,
|
||||
commands: item.commands,
|
||||
});
|
||||
},
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
const PrimaryButtonComponent =
|
||||
toolbarService?.getButtonComponentForUIType(state.primary.uiType) ?? ToolbarButton;
|
||||
toolbarService?.getButtonComponentForUIType(primary.uiType) ?? ToolbarButton;
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = toolbarService.subscribe(
|
||||
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
|
||||
state => {
|
||||
setButtonState({ ...state });
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [toolbarService]);
|
||||
|
||||
const updatedItems = state.items.map(item => {
|
||||
const isActive = item.type === 'tool' && primaryToolId === item.id;
|
||||
|
||||
// We could have added the
|
||||
// item.type === 'toggle' && toggles[item.id] === true
|
||||
// too but that makes the button active when the toggle is active under it
|
||||
// which feels weird
|
||||
return {
|
||||
...item,
|
||||
isActive,
|
||||
};
|
||||
});
|
||||
|
||||
const DefaultListItemRenderer = ({ type, icon, label, t, id }) => {
|
||||
const isActive = type === 'toggle' && toggles[id] === true;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'hover:bg-primary-dark flex h-8 w-full flex-row items-center p-3',
|
||||
'whitespace-pre text-base',
|
||||
isActive && 'bg-primary-dark',
|
||||
isActive
|
||||
? 'text-[#348CFD]'
|
||||
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light'
|
||||
)}
|
||||
>
|
||||
{icon && (
|
||||
<span className="mr-4">
|
||||
<Icon
|
||||
name={icon}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
<span className="mr-5">{t(label)}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const listItemRenderer = renderer || DefaultListItemRenderer;
|
||||
const listItemRenderer = renderer;
|
||||
|
||||
return (
|
||||
<SplitButton
|
||||
isRadio={isRadio}
|
||||
isAction={isAction}
|
||||
primary={state.primary}
|
||||
primary={primary}
|
||||
secondary={secondary}
|
||||
items={updatedItems}
|
||||
items={getSplitButtonItems(items)}
|
||||
groupId={groupId}
|
||||
renderer={listItemRenderer}
|
||||
isActive={isPrimaryActive || updatedItems.some(item => item.isActive)}
|
||||
isToggle={isPrimaryToggle}
|
||||
onInteraction={onInteraction}
|
||||
Component={props => (
|
||||
<PrimaryButtonComponent
|
||||
@@ -146,11 +55,9 @@ function ToolbarSplitButtonWithServices({
|
||||
|
||||
ToolbarSplitButtonWithServices.propTypes = {
|
||||
isRadio: PropTypes.bool,
|
||||
isAction: PropTypes.bool,
|
||||
groupId: PropTypes.string,
|
||||
primary: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
|
||||
uiType: PropTypes.string,
|
||||
}),
|
||||
secondary: PropTypes.shape({
|
||||
@@ -158,15 +65,17 @@ ToolbarSplitButtonWithServices.propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
tooltip: PropTypes.string.isRequired,
|
||||
isActive: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
items: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
|
||||
icon: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
})
|
||||
),
|
||||
renderer: PropTypes.func,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ErrorBoundary, UserPreferences, AboutModal, Header, useModal } from '@o
|
||||
import i18n from '@ohif/i18n';
|
||||
import { hotkeys } from '@ohif/core';
|
||||
import { useAppConfig } from '@state';
|
||||
import Toolbar from '../Toolbar/Toolbar';
|
||||
import { Toolbar } from '../Toolbar/Toolbar';
|
||||
|
||||
const { availableLanguages, defaultLanguage, currentLanguage } = i18n;
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { SidePanel, ErrorBoundary, LoadingIndicatorProgress } from '@ohif/ui';
|
||||
import {
|
||||
SidePanel,
|
||||
ErrorBoundary,
|
||||
LoadingIndicatorProgress,
|
||||
InvestigationalUseDialog,
|
||||
} from '@ohif/ui';
|
||||
import { ServicesManager, HangingProtocolService, CommandsManager } from '@ohif/core';
|
||||
import { useAppConfig } from '@state';
|
||||
import ViewerHeader from './ViewerHeader';
|
||||
@@ -43,22 +48,13 @@ function ViewerLayout({
|
||||
const getComponent = id => {
|
||||
const entry = extensionManager.getModuleEntry(id);
|
||||
|
||||
if (!entry) {
|
||||
if (!entry || !entry.component) {
|
||||
throw new Error(
|
||||
`${id} is not valid for an extension module. Please verify your configuration or ensure that the extension is properly registered. It's also possible that your mode is utilizing a module from an extension that hasn't been included in its dependencies (add the extension to the "extensionDependencies" array in your mode's index.js file)`
|
||||
`${id} is not valid for an extension module or no component found from extension ${id}. Please verify your configuration or ensure that the extension is properly registered. It's also possible that your mode is utilizing a module from an extension that hasn't been included in its dependencies (add the extension to the "extensionDependencies" array in your mode's index.js file). Check the reference string to the extension in your Mode configuration`
|
||||
);
|
||||
}
|
||||
|
||||
let content;
|
||||
if (entry && entry.component) {
|
||||
content = entry.component;
|
||||
} else {
|
||||
throw new Error(
|
||||
`No component found from extension ${id}. Check the reference string to the extension in your Mode configuration`
|
||||
);
|
||||
}
|
||||
|
||||
return { entry, content };
|
||||
return { entry, content: entry.component };
|
||||
};
|
||||
|
||||
const getPanelData = id => {
|
||||
@@ -71,6 +67,7 @@ function ViewerLayout({
|
||||
label: entry.label,
|
||||
name: entry.name,
|
||||
content,
|
||||
contexts: entry.contexts,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -152,6 +149,8 @@ function ViewerLayout({
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
</div>
|
||||
|
||||
<InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export type HangingProtocolParams = {
|
||||
stageIndex?: number;
|
||||
activeStudyUID?: string;
|
||||
stageId?: string;
|
||||
reset?: false;
|
||||
};
|
||||
|
||||
export type UpdateViewportDisplaySetParams = {
|
||||
@@ -107,38 +108,6 @@ const commandsModule = ({
|
||||
measurementService.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles off all tools which contain a commandName of setHangingProtocol
|
||||
* or toggleHangingProtocol, and which match/don't match the protocol id/stage
|
||||
*/
|
||||
toggleHpTools: () => {
|
||||
const {
|
||||
protocol,
|
||||
stageIndex: toggleStageIndex,
|
||||
stage,
|
||||
} = hangingProtocolService.getActiveProtocol();
|
||||
const enableListener = button => {
|
||||
if (!button.id) {
|
||||
return;
|
||||
}
|
||||
const { commands, items } = button.props || button;
|
||||
if (items) {
|
||||
items.forEach(enableListener);
|
||||
}
|
||||
const hpCommand = commands?.find?.(isHangingProtocolCommand);
|
||||
if (!hpCommand) {
|
||||
return;
|
||||
}
|
||||
const { protocolId, stageIndex, stageId } = hpCommand.commandOptions;
|
||||
const isActive =
|
||||
(!protocolId || protocolId === protocol.id) &&
|
||||
(stageIndex === undefined || stageIndex === toggleStageIndex) &&
|
||||
(!stageId || stageId === stage.id);
|
||||
toolbarService.setToggled(button.id, isActive);
|
||||
};
|
||||
Object.values(toolbarService.getButtons()).forEach(enableListener);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the specified protocol
|
||||
* 1. Records any existing state using the viewport grid service
|
||||
@@ -170,14 +139,12 @@ const commandsModule = ({
|
||||
stageIndex,
|
||||
reset = false,
|
||||
}: HangingProtocolParams): boolean => {
|
||||
const primaryToolBeforeHPChange = toolbarService.getActivePrimaryTool();
|
||||
try {
|
||||
// Stores in the state the display set selector id to displaySetUID mapping
|
||||
// Pass in viewportId for the active viewport. This item will get set as
|
||||
// the activeViewportId
|
||||
const state = viewportGridService.getState();
|
||||
const hpInfo = hangingProtocolService.getState();
|
||||
const { protocol: oldProtocol } = hangingProtocolService.getActiveProtocol();
|
||||
const stateSyncReduce = reuseCachedLayouts(state, hangingProtocolService, stateSyncService);
|
||||
const { hangingProtocolStageIndexMap, viewportGridStore, displaySetSelectorMap } =
|
||||
stateSyncReduce;
|
||||
@@ -240,33 +207,9 @@ const commandsModule = ({
|
||||
`${activeStudyUID || hpInfo.activeStudyUID}:activeDisplaySet:0`
|
||||
];
|
||||
stateSyncService.store(stateSyncReduce);
|
||||
// This is a default action applied
|
||||
actions.toggleHpTools();
|
||||
|
||||
// try to use the same tool in the new hanging protocol stage
|
||||
const primaryButton = toolbarService.getButton(primaryToolBeforeHPChange);
|
||||
if (primaryButton) {
|
||||
// is there any type of interaction on this button, if not it might be in the
|
||||
// items. This is a bit of a hack, but it works for now.
|
||||
|
||||
let interactionType = primaryButton.props?.interactionType;
|
||||
|
||||
if (!interactionType && primaryButton.props?.items) {
|
||||
const firstItem = primaryButton.props.items[0];
|
||||
interactionType = firstItem.props?.interactionType || firstItem.props?.type;
|
||||
}
|
||||
|
||||
if (interactionType) {
|
||||
toolbarService.recordInteraction({
|
||||
interactionType,
|
||||
...primaryButton.props,
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.toggleHpTools();
|
||||
uiNotificationService.show({
|
||||
title: 'Apply Hanging Protocol',
|
||||
message: 'The hanging protocol could not be applied.',
|
||||
@@ -636,56 +579,38 @@ const commandsModule = ({
|
||||
},
|
||||
clearMeasurements: {
|
||||
commandFn: actions.clearMeasurements,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
displayNotification: {
|
||||
commandFn: actions.displayNotification,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
setHangingProtocol: {
|
||||
commandFn: actions.setHangingProtocol,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
toggleHangingProtocol: {
|
||||
commandFn: actions.toggleHangingProtocol,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
navigateHistory: {
|
||||
commandFn: actions.navigateHistory,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
nextStage: {
|
||||
commandFn: actions.deltaStage,
|
||||
storeContexts: [],
|
||||
options: { direction: 1 },
|
||||
},
|
||||
previousStage: {
|
||||
commandFn: actions.deltaStage,
|
||||
storeContexts: [],
|
||||
options: { direction: -1 },
|
||||
},
|
||||
setViewportGridLayout: {
|
||||
commandFn: actions.setViewportGridLayout,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
toggleOneUp: {
|
||||
commandFn: actions.toggleOneUp,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
openDICOMTagViewer: {
|
||||
commandFn: actions.openDICOMTagViewer,
|
||||
},
|
||||
updateViewportDisplaySet: {
|
||||
commandFn: actions.updateViewportDisplaySet,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,13 @@ export const findOrCreateViewport = (
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// and lastly if there is no default viewport, then we see if we can grab the
|
||||
// viewportsByPosition at the position index and use that
|
||||
// const candidate = Object.values(viewportsByPosition)[position];
|
||||
|
||||
// // if it has something to display, then we can use it
|
||||
// return candidate?.displaySetInstanceUIDs ? candidate : {};
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
@@ -93,8 +93,8 @@ export default function getCustomizationModule({ servicesManager, extensionManag
|
||||
instance && this.attribute
|
||||
? instance[this.attribute]
|
||||
: this.contentF && typeof this.contentF === 'function'
|
||||
? this.contentF(props)
|
||||
: null;
|
||||
? this.contentF(props)
|
||||
: null;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createDicomWebApi } from './DicomWebDataSource/index.js';
|
||||
import { createDicomJSONApi } from './DicomJSONDataSource/index.js';
|
||||
import { createDicomLocalApi } from './DicomLocalDataSource/index.js';
|
||||
import { createDicomWebProxyApi } from './DicomWebProxyDataSource/index.js';
|
||||
import { createMergeDataSourceApi } from './MergeDataSource/index';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -32,6 +33,11 @@ function getDataSourcesModule() {
|
||||
type: 'localApi',
|
||||
createDataSource: createDicomLocalApi,
|
||||
},
|
||||
{
|
||||
name: 'merge',
|
||||
type: 'mergeApi',
|
||||
createDataSource: createMergeDataSourceApi,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 375 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user